001    /*
002     * Copyright (c) 2005-2006 Stephen J. McConnell
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.metro.runtime;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.net.URI;
024    import java.net.URL;
025    
026    import net.dpml.component.Controller;
027    import net.dpml.component.ControlException;
028    import net.dpml.component.ControllerContext;
029    import net.dpml.component.ControllerContextListener;
030    import net.dpml.component.ControllerContextEvent;
031    import net.dpml.component.Model;
032    import net.dpml.component.Component;
033    import net.dpml.component.Composition;
034    
035    import net.dpml.lang.Part;
036    import net.dpml.lang.Builder;
037    import net.dpml.lang.StandardClassLoader;
038    import net.dpml.lang.Info;
039    import net.dpml.lang.Category;
040    import net.dpml.lang.Classpath;
041    
042    import net.dpml.metro.ComponentModel;
043    import net.dpml.metro.data.ComponentDirective;
044    import net.dpml.metro.data.DefaultComposition;
045    import net.dpml.metro.builder.ComponentDecoder;
046    
047    import net.dpml.util.Logger;
048    import net.dpml.util.DefaultLogger;
049    import net.dpml.util.EventQueue;
050    import net.dpml.util.Resolver;
051    
052    import org.w3c.dom.Element;
053    
054    /**
055     * The composition controller is the controller used to establish remotely accessible
056     * component controls.
057     *
058     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
059     * @version 1.2.0
060     */
061    public class CompositionController implements Controller, Builder
062    {
063        //--------------------------------------------------------------------
064        // static
065        //--------------------------------------------------------------------
066            
067       /**
068        * Static URI of this controller.
069        */
070        public static final URI CONTROLLER_URI = createStaticURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.2.0" );
071        
072        private static final String BASEPATH = setupBasePathSpec();
073        
074        static final URI ROOT_URI = createStaticURI( "metro:/" );
075    
076        //--------------------------------------------------------------------
077        // immutable state
078        //--------------------------------------------------------------------
079    
080        private final Logger m_logger;
081        private final ControllerContext m_context;
082        private final ComponentController m_controller;
083        private final InternalControllerContextListener m_listener;
084        private final String m_partition;
085        private final EventQueue m_events;
086        
087        //--------------------------------------------------------------------
088        // mutable state
089        //--------------------------------------------------------------------
090        
091        private boolean m_disposed = false;
092        
093        //--------------------------------------------------------------------
094        // constructor
095        //--------------------------------------------------------------------
096    
097       /**
098        * Creation of a new controller.
099        * @param context the control context
100        * @exception ControlException if an error occurs during controller creation
101        */
102        public CompositionController( ControllerContext context )
103           throws ControlException
104        {
105            super();
106            
107            m_context = context;
108            m_partition = context.getPartition();
109            Logger root = new DefaultLogger( m_partition );
110            m_logger = root.getChildLogger( "dpml.metro" );
111            m_listener = new InternalControllerContextListener();
112            m_context.addControllerContextListener( m_listener );
113            m_controller = new ComponentController( m_logger, this );
114            m_events = new EventQueue( m_logger, "DPML Metro Event Queue" );
115        }
116        
117        EventQueue getEventQueue()
118        {
119            return m_events;
120        }
121        
122       /**
123        * Controller finalization.
124        * @exception Throwable if a finalization error occurs
125        */
126        protected void finalize() throws Throwable
127        {
128            if( getLogger().isTraceEnabled() )
129            {
130                getLogger().trace( 
131                  "finalizing controller [" 
132                  + getClass().getName() 
133                  + "#"
134                  + System.identityHashCode( this ) 
135                  + "]" );
136            }
137            dispose();
138        }
139        
140        //--------------------------------------------------------------------
141        // Builder
142        //--------------------------------------------------------------------
143        
144       /**
145        * Construct the deployment information from a part definition.
146        * @param logger the logging channel
147        * @param info the part info definition
148        * @param classpath the part classpath definition
149        * @param strategy the DOM element definining the deplyment streategy
150        * @param resolver build-time uri resolver
151        * @return the part definition
152        * @exception IOException if an I/O error occurs
153        */
154        public Part build( 
155          Logger logger, Info info, Classpath classpath, Element strategy, Resolver resolver ) throws IOException
156        {
157            ClassLoader context = Thread.currentThread().getContextClassLoader();
158            try
159            {
160                ClassLoader anchor = Component.class.getClassLoader();
161                Thread.currentThread().setContextClassLoader( anchor );
162                ComponentDecoder decoder = new ComponentDecoder();
163                ComponentDirective directive = decoder.buildComponent( strategy, resolver );
164                return new DefaultComposition( logger, info, classpath, this, directive );
165            }
166            finally
167            {
168                Thread.currentThread().setContextClassLoader( context );
169            }
170        }
171    
172        //--------------------------------------------------------------------
173        // PartHandler
174        //--------------------------------------------------------------------
175        
176       /**
177        * Build a classloader stack.
178        * @param name the name to assign to the classloader
179        * @param anchor the anchor classloader to server as the classloader chain root
180        * @param classpath the part classpath definition
181        * @return the new classloader
182        * @exception IOException if an IO error occurs during classpath evaluation
183        */
184        public ClassLoader getClassLoader( String name, ClassLoader anchor, Classpath classpath ) throws IOException
185        {
186            return getClassLoader( name, anchor, classpath, true );
187        }
188        
189       /**
190        * Build a classloader stack.
191        * @param anchor the anchor classloader to server as the classloader chain root
192        * @param classpath the part classpath definition
193        * @return the new classloader
194        * @exception IOException if an IO error occurs during classpath evaluation
195        */
196        private ClassLoader getClassLoader( String name, ClassLoader anchor, Classpath classpath, boolean expand ) throws IOException
197        {
198            Logger logger = getLogger();
199            
200            if( expand )
201            {
202                Classpath cp = classpath.getBaseClasspath();
203                if( null != cp )
204                {
205                    ClassLoader cl = getClassLoader( name + " (super)", anchor, cp );
206                    return getClassLoader( name, cl, classpath, false );
207                }
208            }
209            
210            Class root = ComponentDirective.class;
211            String classname = root.getName();
212            ClassLoader base = anchor;
213            
214            if( null == classpath )
215            {
216                ClassLoader management = root.getClassLoader();
217                return new CompositionClassLoader( logger, name, Category.PROTECTED, management, anchor );
218            }
219            
220            try
221            {
222                anchor.loadClass( classname );
223            }
224            catch( ClassNotFoundException e )
225            {
226                ClassLoader management = root.getClassLoader();
227                base = new CompositionClassLoader( logger, name, Category.PROTECTED, management, anchor );
228            }
229            
230            URI[] apis = classpath.getDependencies( Category.PUBLIC );
231            ClassLoader api = StandardClassLoader.buildClassLoader( logger, name, Category.PUBLIC, base, apis );
232            URI[] spis = classpath.getDependencies( Category.PROTECTED );
233            ClassLoader spi = StandardClassLoader.buildClassLoader( logger, name, Category.PROTECTED, api, spis );
234            URI[] imps = classpath.getDependencies( Category.PRIVATE );
235            ClassLoader impl = StandardClassLoader.buildClassLoader( logger, name, Category.PRIVATE, spi, imps );
236            return impl;
237        }
238        
239        //--------------------------------------------------------------------
240        // Controller
241        //--------------------------------------------------------------------
242        
243       /**
244        * Returns the uri of this controller.
245        * @return the controller uri
246        */
247        public URI getURI()
248        {
249            return CONTROLLER_URI;
250        }
251        
252       /**
253        * Create and return a new management context using the supplied directive uri.
254        *
255        * @param uri a uri identifying a deployment directive
256        * @return the management context model
257        * @exception ControlException if an error occurs
258        * @exception IOException if an error occurs reading the identified resource
259        */
260        public Model createModel( URI uri ) throws ControlException, IOException
261        {
262            if( getLogger().isTraceEnabled() )
263            {
264                getLogger().trace( 
265                  "creating new model from URI" 
266                  + "\n  URI: " + uri );
267            }
268            Part part = Part.load( uri, false );
269            if( part instanceof Composition )
270            {
271                Composition composition = (Composition) part;
272                return createModel( composition );
273            }
274            else
275            {
276                final String error = 
277                  "Part class [" 
278                  + part.getClass().getName() 
279                  + "] not recognized.";
280                throw new ControllerException( error );
281            }
282        }
283    
284       /**
285        * Create and return a new management context using the supplied directive uri.
286        *
287        * @param composition a composition directive
288        * @return the management model
289        * @exception ControlException if an error occurs
290        * @exception IOException if an I/O error occurs
291        */
292        public Model createModel( Composition composition ) throws ControlException, IOException
293        {
294            if( getLogger().isTraceEnabled() )
295            {
296                getLogger().trace( 
297                  "creating new model from part" 
298                    + "\n  URI: " + composition.getInfo().getURI() );
299            }
300            if( composition instanceof DefaultComposition )
301            {
302                DefaultComposition data = (DefaultComposition) composition;
303                return m_controller.createComponentModel( data );
304            }
305            else
306            {
307                final String error = 
308                  "Composition class [" 
309                  + composition.getClass().getName() 
310                  + "] not recognized.";
311                throw new ControllerException( error );
312            }
313        }
314        
315       /**
316        * Create and return a remote reference to a component handler.
317        * @param uri a uri identifying a deployment directive
318        * @return the component handler
319        * @exception Exception if an error occurs
320        */
321        public Component createComponent( URI uri ) throws Exception
322        {
323            if( getLogger().isTraceEnabled() )
324            {
325                getLogger().trace( 
326                  "creating new component from URI"
327                   + "\nURI: " + uri );
328            }
329            Part part = Part.load( uri, false );
330            if( part instanceof DefaultComposition )
331            {
332                DefaultComposition composition = (DefaultComposition) part;
333                Model model = m_controller.createComponentModel( composition );
334                return createComponent( model, true );
335            }
336            else
337            {
338                final String error = 
339                  "Part class [" 
340                  + part.getClass().getName() 
341                  + "] is not recognized.";
342                throw new ControllerException( error );
343            }
344        }
345    
346       /**
347        * Create and return a remote reference to a component handler.
348        * @param model the component model
349        * @return the component handler
350        * @exception Exception if an error occurs during component creation
351        */
352        public Component createComponent( Model model ) throws Exception
353        {
354            return createComponent( model, false );
355        }
356        
357       /**
358        * Create and return a remote reference to a component handler.
359        * @param model the component model
360        * @param flag if true the component is responsible for model retraction
361        * @return the component handler
362        * @exception Exception if an error occurs during component creation
363        */
364        private Component createComponent( Model model, boolean flag ) throws Exception
365        {
366            if( model instanceof ComponentModel )
367            {
368                ClassLoader anchor = Logger.class.getClassLoader();
369                ComponentModel componentModel = (ComponentModel) model;
370                return m_controller.createDefaultComponentHandler( anchor, componentModel, flag );
371            }
372            else
373            {
374                //
375                // TODO delegate to foreign controller
376                //
377                
378                final String error =
379                  "Construction of a handler for the context model class ["
380                  + model.getClass().getName() 
381                  + "] is not supported.";
382                throw new ControllerException( error );
383            }
384        }
385        
386       /**
387        * Return the controllers runtime context. The runtime context holds infromation 
388        * about the working and temporary directories and a uri identifying the execution 
389        * domain.
390        * 
391        * @return the runtime context
392        */
393        public ControllerContext getControllerContext()
394        {
395            return m_context;
396        }
397    
398       /**
399        * Return the assigned logging channel.
400        * @return the logging channel
401        */
402        Logger getLogger()
403        {
404            return m_logger;
405        }
406    
407        String getPartition()
408        {
409            return m_partition;
410        }
411        
412        void dispose()
413        {
414            if( !m_disposed )
415            { 
416                getLogger().debug( "initating shutdown" );
417                m_context.removeControllerContextListener( m_listener );
418                m_events.terminateDispatchThread();
419                m_disposed = true;
420                getLogger().debug( "shutdown complete" );
421            }
422        }
423        
424        private static URI createStaticURI( String path )
425        {
426            try
427            {
428                return new URI( path );
429            }
430            catch( Throwable e )
431            {
432                return null;
433            }
434        }
435    
436        private URI getURIFromURL( URL url )
437        {
438            try
439            {
440                return new URI( url.toExternalForm() );
441            }
442            catch( Throwable e )
443            {
444                throw new RuntimeException( e );
445            }
446        }
447        
448       /**
449        * Controller context listener.
450        */
451        private class InternalControllerContextListener implements ControllerContextListener
452        {
453           /**
454            * Notify the listener that the working directory has changed.
455            *
456            * @param event the change event
457            */
458            public void workingDirectoryChanged( ControllerContextEvent event )
459            {
460            }
461    
462           /**
463            * Notify the listener that the temporary directory has changed.
464            *
465            * @param event the change event
466            */
467            public void tempDirectoryChanged( ControllerContextEvent event )
468            {
469            }
470        
471           /**
472            * Notify listeners of the disposal of the controller.
473            * @param event the context event
474            */
475            public void controllerDisposal( ControllerContextEvent event )
476            {
477                dispose();
478            }
479        }
480        
481        static String setupBasePathSpec()
482        {
483            try
484            {
485                String path = System.getProperty( "user.dir" );
486                File file = new File( path );
487                URI uri = file.toURI();
488                URL url = uri.toURL();
489                return url.toString();
490            }
491            catch( Exception e )
492            {   
493                return e.toString();
494            }
495        }
496        
497        private static String getPartSpec( URI uri )
498        {
499            String path = uri.toASCIIString();
500            if( path.startsWith( BASEPATH ) )
501            {
502                return "./" + path.substring( BASEPATH.length() );
503            }
504            else
505            {
506                return path;
507            }
508        }
509    }