001    /*
002    /*
003     * Copyright (c) 2005 Stephen J. McConnell
004     *
005     * Licensed  under the  Apache License,  Version 2.0  (the "License");
006     * you may not use  this file  except in  compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *   http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed  under the  License is distributed on an "AS IS" BASIS,
013     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
014     * implied.
015     *
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    package net.dpml.component;
021    
022    import java.io.File;
023    import java.lang.reflect.Constructor;
024    import java.lang.reflect.InvocationHandler;
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Proxy;
027    import java.net.URI;
028    import java.util.EventObject;
029    import java.util.EventListener;
030    
031    import net.dpml.lang.Part;
032    import net.dpml.lang.Plugin;
033    
034    import net.dpml.util.DefaultLogger;
035    
036    /**
037     * The CompositionControllerContext class wraps a ContentModel and supplies convinience
038     * operations that translate ContentModel properties and events to type-safe values used 
039     * in the conposition controller.
040     *
041     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
042     * @version 1.2.0
043     */
044    public final class InitialContext extends LocalEventProducer 
045      implements ControllerContext, Disposable
046    {
047        //----------------------------------------------------------------------------
048        // static
049        //----------------------------------------------------------------------------
050        
051       /**
052        * Create the default controller using the default initial context.
053        * The default context and associated controller disposal will be triggered 
054        * on JVM shutdown.
055        *
056        * @return the default controller
057        */
058        public static Controller createController()
059        {
060            return createController( null );
061        }
062        
063       /**
064        * Create the default controller.  Controller disposal is the responsiblity
065        * of the client application.
066        *
067        * @param context the controller context
068        * @return the controller
069        */
070        public static Controller createController( final InitialContext context )
071        {
072            ControllerInvocationHandler handler = 
073              new ControllerInvocationHandler( context );
074            return (Controller) Proxy.newProxyInstance( 
075              Controller.class.getClassLoader(), new Class[]{Controller.class}, handler );
076        }
077        
078       /**
079        * Internal invocation handler that delays controller instantiation
080        * until a request against the controller is made by a client.
081        */
082        private static final class ControllerInvocationHandler implements InvocationHandler
083        {
084            private InitialContext m_context;
085            private Controller m_controller;
086            
087            private ControllerInvocationHandler( InitialContext context )
088            {
089                m_context = context;
090            }
091            
092            /**
093            * Invoke the specified method on underlying object.
094            * This is called by the proxy object.
095            *
096            * @param proxy the proxy object
097            * @param method the method invoked on proxy object
098            * @param args the arguments supplied to method
099            * @return the return value of method
100            * @throws Throwable if an error occurs
101            */
102            public Object invoke( 
103              final Object proxy, final Method method, final Object[] args ) throws Throwable
104            {
105                Controller controller = getController();
106                return method.invoke( controller, args );
107            }
108            
109            private synchronized Controller getController()
110            {
111                if( null != m_controller )
112                {
113                    return m_controller;
114                }
115                else
116                {
117                    m_controller = InitialContext.newController( m_context );
118                    return m_controller;
119                }
120            }
121        }
122        
123        private static URI getControllerURI() throws Exception
124        {
125            String spec = 
126              System.getProperty( 
127                "dpml.part.controller.uri", 
128                "artifact:part:dpml/metro/dpml-metro-runtime#1.2.0" );
129            return new URI( spec );
130        }
131        
132       /**
133        * Construct a controller.
134        * @param context the controller context
135        * @return the controller
136        */
137        private static Controller newController( final InitialContext context )
138        {
139            InitialContext control = context;
140            if( null == control )
141            {
142                control = new InitialContext();
143                Runtime.getRuntime().addShutdownHook( new ContextShutdownHook( control ) );
144            }
145            
146            ClassLoader classloader = Thread.currentThread().getContextClassLoader();
147            try
148            {
149                Thread.currentThread().setContextClassLoader( InitialContext.class.getClassLoader() );
150                URI uri = getControllerURI();
151                Part part = Part.load( uri );
152                if( part instanceof Plugin )
153                {
154                    Plugin plugin = (Plugin) part;
155                    Class c = plugin.getPluginClass();
156                    Constructor constructor = c.getConstructor( new Class[]{ControllerContext.class} );
157                    return (Controller) constructor.newInstance( new Object[]{control} );
158                }
159                else
160                {
161                    return (Controller) part.instantiate( new Object[]{control} );
162                }
163            }
164            catch( Throwable e )
165            {
166                final String error =
167                  "Internal error while attempting to establish the standard controller.";
168                throw new RuntimeException( error, e );
169            }
170            finally
171            {
172                Thread.currentThread().setContextClassLoader( classloader );
173            }
174        }
175        
176        //----------------------------------------------------------------------------
177        // state
178        //----------------------------------------------------------------------------
179        
180       /**
181        * Working directory.
182        */
183        private File m_work;
184      
185       /**
186        * Temp directory.
187        */
188        private File m_temp;
189    
190       /**
191        * The assigned partition name.
192        */
193        private String m_partition;
194    
195        //----------------------------------------------------------------------------
196        // constructor
197        //----------------------------------------------------------------------------
198        
199       /**
200        * Creation of a new <tt>InitialContext</tt>.
201        */
202        public InitialContext()
203        {
204            this( "", null, null );
205        }
206    
207       /**
208        * Creation of a new <tt>InitialContext</tt>.
209        * @param partition the assigned partition name
210        */
211        public InitialContext( String partition )
212        {
213            this( partition, null, null );
214        }
215        
216       /**
217        * Creation of a new <tt>InitialContext</tt>.
218        * @param partition the partition name
219        * @param work the working directory
220        * @param temp the temporary directory
221        */
222        public InitialContext( String partition, File work, File temp )
223        {
224            super( new DefaultLogger( partition ) );
225    
226            m_partition = partition;
227    
228            if( null == work )
229            {
230                String path = System.getProperty( "user.dir" );
231                m_work = new File( path );
232            }
233            else
234            {
235                m_work = work;
236            }
237            
238            if( null == temp )
239            {
240                String path = System.getProperty( "java.io.tmpdir" );
241                m_temp = new File( path );
242            }
243            else
244            {
245                m_temp = temp;
246            }
247        }
248    
249        //----------------------------------------------------------------------------
250        // Disposable
251        //----------------------------------------------------------------------------
252        
253       /**
254        * Initiate disposal.
255        */
256        public void dispose()
257        {
258            ControllerDisposalEvent event = new ControllerDisposalEvent( this );
259            enqueueEvent( event, false );
260            super.dispose();
261        }
262    
263        //----------------------------------------------------------------------------
264        // ControllerContext
265        //----------------------------------------------------------------------------
266    
267       /**
268        * Return the partition name
269        * @return the partion name
270        */
271        public String getPartition()
272        {
273            return m_partition;
274        }
275    
276       /**
277        * Return the root working directory.
278        *
279        * @return directory representing the root of the working directory hierachy
280        */
281        public File getWorkingDirectory()
282        {
283            synchronized( getLock() )
284            {
285                return m_work;
286            }
287        }
288    
289       /**
290        * Set the root working directory.
291        *
292        * @param dir the root of the working directory hierachy
293        */
294        public void setWorkingDirectory( File dir )
295        {
296            synchronized( getLock() )
297            {
298                m_work = dir;
299            }
300        }
301    
302       /**
303        * Return the root temporary directory.
304        *
305        * @return directory representing the root of the temporary directory hierachy.
306        */
307        public File getTempDirectory()
308        {
309            synchronized( getLock() )
310            {
311                return m_temp;
312            }
313        }
314    
315       /**
316        * Add the supplied controller context listener to the controller context.  A 
317        * controller implementation should not maintain strong references to supplied 
318        * listeners.
319        *
320        * @param listener the controller context listener to add
321        */
322        public void addControllerContextListener( ControllerContextListener listener ) 
323        {
324            addListener( listener );
325        }
326    
327       /**
328        * Remove the supplied controller context listener from the controller context.
329        *
330        * @param listener the controller context listener to remove
331        */
332        public void removeControllerContextListener( ControllerContextListener listener )
333        {
334            removeListener( listener );
335        }
336    
337        //----------------------------------------------------------------------------
338        // impl
339        //----------------------------------------------------------------------------
340    
341       /**
342        * Process a context related event.
343        * @param event the event to process
344        */
345        protected void processEvent( EventObject event )
346        {
347            if( event instanceof WorkingDirectoryChangeEvent )
348            {
349                processWorkingDirectoryChangeEvent( (WorkingDirectoryChangeEvent) event );
350            }
351            else if( event instanceof TempDirectoryChangeEvent )
352            {
353                processTempDirectoryChangeEvent( (TempDirectoryChangeEvent) event );
354            }
355            else if( event instanceof ControllerDisposalEvent )
356            {
357                processControllerDisposalEvent( (ControllerDisposalEvent) event );
358            }
359        }
360    
361        private void processWorkingDirectoryChangeEvent( WorkingDirectoryChangeEvent event )
362        {
363            EventListener[] listeners = super.listeners();
364            for( int i=0; i<listeners.length; i++ )
365            {
366                EventListener eventListener = listeners[i];
367                if( eventListener instanceof ControllerContextListener )
368                {
369                    ControllerContextListener listener = (ControllerContextListener) eventListener;
370                    try
371                    {
372                        listener.workingDirectoryChanged( event );
373                    }
374                    catch( Throwable e )
375                    {
376                        final String error =
377                          "ControllerContextListener working dir change notification error.";
378                        getInternalLogger().error( error, e );
379                    }
380                }
381            }
382        }
383    
384        private void processTempDirectoryChangeEvent( TempDirectoryChangeEvent event )
385        {
386            EventListener[] listeners = super.listeners();
387            for( int i=0; i<listeners.length; i++ )
388            {
389                EventListener eventListener = listeners[i];
390                if( eventListener instanceof ControllerContextListener )
391                {
392                    ControllerContextListener listener = (ControllerContextListener) eventListener;
393                    try
394                    {
395                        listener.tempDirectoryChanged( event );
396                    }
397                    catch( Throwable e )
398                    {
399                        final String error =
400                          "ControllerContextListener temp dir change notification error.";
401                        getInternalLogger().error( error, e );
402                    }
403                }
404            }
405        }
406    
407        private void processControllerDisposalEvent( ControllerDisposalEvent event )
408        {
409            EventListener[] listeners = super.listeners();
410            for( int i=0; i<listeners.length; i++ )
411            {
412                EventListener eventListener = listeners[i];
413                if( eventListener instanceof ControllerContextListener )
414                {
415                    ControllerContextListener listener = (ControllerContextListener) eventListener;
416                    try
417                    {
418                        listener.controllerDisposal( event );
419                    }
420                    catch( Throwable e )
421                    {
422                        final String error =
423                          "ControllerContextListener disposal notification error.";
424                        getInternalLogger().error( error, e );
425                    }
426                }
427            }
428        }
429    
430        //----------------------------------------------------------------------------
431        // static (private)
432        //----------------------------------------------------------------------------
433    
434       /**
435        * Working directory change event impl.
436        */
437        private static class WorkingDirectoryChangeEvent extends ControllerContextEvent
438        {
439            public WorkingDirectoryChangeEvent( 
440              ControllerContext source, File oldDir, File newDir )
441            {
442                super( source, oldDir, newDir );
443            }
444        }
445    
446       /**
447        * Temp directory change event impl.
448        */
449        private static class TempDirectoryChangeEvent extends ControllerContextEvent
450        {
451            public TempDirectoryChangeEvent( 
452              ControllerContext source, File oldDir, File newDir )
453            {
454                super( source, oldDir, newDir );
455            }
456        }
457    
458       /**
459        * Disposal event.
460        */
461        private static class ControllerDisposalEvent extends ControllerContextEvent
462        {
463            public ControllerDisposalEvent( ControllerContext source )
464            {
465                super( source, null, null );
466            }
467        }
468    
469       /**
470        * Internal utility class to handle context disposal on JVM shutdown.
471        */
472        private static class ContextShutdownHook extends Thread
473        {
474            private InitialContext m_context;
475            
476           /**
477            * Creation of a new initial context shutdown hook.
478            * @param context the initional context to be shutdown on JVM shutdown
479            */
480            ContextShutdownHook( InitialContext context )
481            {
482                m_context = context;
483            }
484            
485           /**
486            * Execute context disposal.
487            */
488            public void run()
489            {
490                try
491                {
492                    m_context.dispose();
493                }
494                catch( Throwable e )
495                {
496                    boolean ignorable = true;
497                }
498                finally
499                {
500                    System.runFinalization();
501                }
502            }
503        }
504    }