001    /*
002     * Copyright 2004-2007 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.runtime;
020    
021    import dpml.appliance.StandardAppliance;
022    import dpml.lang.Value;
023    import dpml.lang.Disposable;
024    import dpml.state.StateDecoder;
025    import dpml.util.DefaultLogger;
026    
027    import java.io.IOException;
028    import java.io.Writer;
029    import java.io.File;
030    import java.lang.ref.Reference;
031    import java.lang.ref.SoftReference;
032    import java.lang.ref.WeakReference;
033    import java.lang.reflect.Constructor;
034    import java.lang.reflect.InvocationHandler;
035    import java.lang.reflect.Method;
036    import java.lang.reflect.Proxy;
037    import java.lang.reflect.InvocationTargetException;
038    import java.net.URI;
039    import java.net.URL;
040    import java.util.ServiceLoader;
041    import java.util.WeakHashMap;
042    import java.util.Hashtable;
043    import java.util.Map;
044    import java.util.Set;
045    import java.util.concurrent.Executors;
046    import java.util.concurrent.ExecutorService;
047    import java.util.concurrent.CopyOnWriteArraySet;
048    import java.util.concurrent.TimeUnit;
049    
050    import net.dpml.annotation.Activation;
051    import net.dpml.annotation.CollectionPolicy;
052    import net.dpml.annotation.LifestylePolicy;
053    import net.dpml.annotation.ActivationPolicy;
054    
055    import net.dpml.appliance.Appliance;
056    
057    import net.dpml.lang.Buffer;
058    import net.dpml.lang.ServiceRegistry;
059    import net.dpml.lang.StandardServiceRegistry;
060    import net.dpml.lang.Strategy;
061    import net.dpml.lang.TypeCastException;
062    
063    import net.dpml.state.State;
064    import net.dpml.state.StateMachine;
065    
066    import net.dpml.transit.Artifact;
067    
068    import net.dpml.util.Logger;
069    
070    import static dpml.state.DefaultState.NULL_STATE;
071    
072    /**
073     * Component strategy.
074     *
075     * @author <a href="http://www.dpml.net">Digital Product Management Laboratory</a>
076     * @version 2.0.0
077     */
078    public class ComponentStrategy extends Strategy implements Component, ServiceRegistry
079    {
080        private static final Logger LOGGER = new DefaultLogger( "dpml.lang.component" );
081        private static final ComponentStrategyHandler HANDLER = new ComponentStrategyHandler();
082        
083        private final String m_name;
084        private final int m_priority;
085        private final Class<?> m_class;
086        private final String m_path;
087        private final LifestylePolicy m_lifestyle;
088        private final CollectionPolicy m_collection;
089        private final State m_graph;
090        private final LifestyleHandler m_handler;
091        private final ContextModel m_context;
092        private final ActivationPolicy m_activation;
093        private final PartsDirective m_parts;
094        private final Logger m_logger;
095        private final Map<String,Object> m_map = new Hashtable<String,Object>();
096        
097        private final Set<ComponentListener> m_listeners = new CopyOnWriteArraySet<ComponentListener>();
098        private final ExecutorService m_queue = Executors.newSingleThreadExecutor();
099        
100        private ServiceRegistry m_registry;
101        
102       /**
103        * Creation of a new component strategy.
104        * @param partition the enclosing partition
105        * @param name the component name relative to the enclosing partition
106        * @param priority the component priority
107        * @param type the component class
108        * @param activation the activation policy
109        * @param lifestyle the lifestyle policy
110        * @param collection the collection policy
111        * @param context the context model
112        * @param parts the internal part structure
113        * @exception IOException if an IO error occurs
114        */
115        ComponentStrategy( 
116          final String partition, final String name, int priority, final Class type, 
117          ActivationPolicy activation, LifestylePolicy lifestyle, CollectionPolicy collection, 
118          ContextModel context, PartsDirective parts ) 
119          throws IOException
120        {
121            super( type.getClassLoader() );
122            
123            m_class = type;
124            m_priority = priority;
125            m_activation = activation;
126            m_lifestyle = lifestyle;
127            m_collection = collection;
128            m_context = context;
129            
130            m_name = getComponentName( name, m_class );
131            m_path = getComponentPath( partition, m_name, m_class );
132            m_logger = getComponentLogger( m_path );
133            m_graph = getLifecycleGraph( m_class );
134            m_parts = getPartsDirective( parts );
135            
136            m_parts.initialize( this );
137            
138            m_map.put( "name", m_name );
139            m_map.put( "path", m_path );
140            m_map.put( "work", new File( System.getProperty( "user.dir" ) ).getCanonicalFile() );
141            m_map.put( "temp", new File( System.getProperty( "java.io.tmpdir" ) ).getCanonicalFile() );
142            m_map.put( "uri", URI.create( "component:" + m_path ) ); 
143            
144            if( m_logger.isTraceEnabled() )
145            {
146                final String message = 
147                  "new "
148                  + m_collection.toString().toLowerCase()
149                  + " "
150                  + m_lifestyle.toString().toLowerCase()
151                  + " ["
152                  + m_class.getName()
153                  + "]";
154                m_logger.trace( message );
155            }
156            
157            m_handler = getLifestyleHandler( m_lifestyle );
158        }
159        
160        public String getName()
161        {
162            return m_name;
163        }
164        
165        public int getPriority()
166        {
167            return m_priority;
168        }
169        
170       /**
171        * Add a listener to the component.
172        * @param listener the component listener
173        */
174        public void addComponentListener( ComponentListener listener )
175        {
176            m_listeners.add( listener );
177        }
178        
179       /**
180        * Remove a listener from the component.
181        * @param listener the component listener
182        */
183        public void removeComponentListener( ComponentListener listener )
184        {
185            m_listeners.remove( listener );
186        }
187    
188        void processEvent( ComponentEvent event )
189        {
190            Logger logger= getLogger();
191            for( ComponentListener listener : m_listeners )
192            {
193                m_queue.execute( new ComponentEventDistatcher( logger, listener, event ) );
194            }
195        }
196        
197        Map<String,Object> getContextMap()
198        {
199            return m_map;
200        }
201        
202        public Provider getProvider()
203        {
204            synchronized( m_handler )
205            {
206                return m_handler.getProvider();
207            }
208        }
209        
210        public void release( Provider provider )
211        {
212            synchronized( m_handler )
213            {
214                m_handler.release( provider );
215            }
216        }
217        
218        public void initialize( ServiceRegistry registry ) // TODO: parts initialization should occur here?
219        {
220            if( m_logger.isTraceEnabled() )
221            {
222                m_logger.trace( "initialization" );
223            }
224            m_registry = registry;
225        }
226    
227        public boolean isaCandidate( Class<?> type )
228        {
229            return type.isAssignableFrom( m_class );
230        }
231        
232        public <T>T lookup( Class<T> service )
233        {
234            if( m_logger.isTraceEnabled() )
235            {
236                m_logger.trace( "lookup: " + service.getName() );
237            }
238            
239            for( String key : m_parts.getKeys() )
240            {
241                Strategy strategy = m_parts.getStrategy( key );
242                if( strategy.isaCandidate( service ) )
243                {
244                    try
245                    {
246                        return strategy.getInstance( service );
247                    }
248                    catch( Exception e )
249                    {
250                        if( strategy instanceof ComponentStrategy )
251                        {
252                            ComponentStrategy s = (ComponentStrategy) strategy;
253                            String path = s.getComponentPath();
254                            
255                            final String error = 
256                              "Lookup aquisition in ["
257                              + getComponentPath()
258                              + "] failed while aquiring the service ["
259                              + service.getName()
260                              + "] from the provider ["
261                              + path 
262                              + "].";
263                            throw new ComponentError( error, e );
264                        }
265                        else
266                        {
267                            final String error = 
268                              "Lookup aquisition in ["
269                              + getComponentPath()
270                              + "] failed while aquiring the service ["
271                              + service.getName()
272                              + "].";
273                            throw new ComponentError( error, e );
274                        }
275                    }
276                }
277            }
278            
279            if( null != m_registry )
280            {
281                return m_registry.lookup( service );
282            }
283            else
284            {
285                ServiceRegistry registry = new StandardServiceRegistry();
286                return registry.lookup( service );
287            }
288        }
289        
290        public void terminate()
291        {
292            terminate( 10, TimeUnit.SECONDS );
293        }
294        
295        void terminate( long timeout, TimeUnit units )
296        {
297            synchronized( this )
298            {
299                if( getLogger().isTraceEnabled() )
300                {
301                    getLogger().trace( "termination" );
302                }
303                m_handler.terminate();
304                m_queue.shutdown();
305                try
306                {
307                    boolean ok = m_queue.awaitTermination( timeout, units );
308                    if( !ok )
309                    {
310                        final String message = 
311                          "Component termination timeout in ["
312                          + getName()
313                          + "] (some events may not have been processed).";
314                        getLogger().warn( message );
315                    }
316                }
317                catch( Exception e )
318                {
319                    e.printStackTrace();
320                }
321            }
322        }
323        
324       /**
325        * Internal support for the resolution of a context service lookup request.
326        * The service classname comes from a context entry in this component and 
327        * is resolved by the parent component.  The parent evaluates off of its 
328        * internal parts for a component implementing the service and if found, 
329        * the instance is returned.
330        */
331        <T>T getService( Class<?> clazz, Class<T> type ) throws Exception // TODO: ensure we don't evaluate the requestor
332        {
333            if( getLogger().isTraceEnabled() )
334            {
335                getLogger().trace( "invoking lookup in " + getComponentPath() + " for " + clazz.getName() );
336            }
337            if( null != m_registry )
338            {
339                try
340                {
341                    Object value = m_registry.lookup( clazz );
342                    return type.cast( value );
343                }
344                catch( Exception e )
345                {
346                    final String error = 
347                      "Service lookup in component ["
348                      + getComponentPath()
349                      + "] failed.";
350                    throw new ComponentException( error, e );
351                }
352            }
353            else
354            {
355                return null;
356            }
357        }
358        
359        Class getComponentClass()
360        {
361            return m_class;
362        }
363        
364        String getComponentName()
365        {
366            return m_name;
367        }
368        
369        String getComponentPath()
370        {
371            return m_path;
372        }
373        
374        ContextModel getContextModel()
375        {
376            return m_context;
377        }
378        
379        PartsDirective getPartsDirective()
380        {
381            return m_parts;
382        }
383        
384        State getStateGraph()
385        {
386            return m_graph;
387        }
388        
389        Logger getComponentLogger()
390        {
391            return m_logger;
392        }
393        
394        Logger getLogger()
395        {
396            return m_logger;
397        }
398        
399       /**
400        * Return the assigned collection policy.
401        * @return the collection policy
402        */
403        public CollectionPolicy getCollectionPolicy()
404        {
405            return m_collection;
406        }
407        
408       /**
409        * Return the assigned lifestyle policy.
410        * @return the lifestyle policy
411        */
412        public LifestylePolicy getLifestylePolicy()
413        {
414            return m_lifestyle;
415        }
416        
417       /**
418        * Return the assigned activation policy.
419        * @return the activation policy
420        */
421        public ActivationPolicy getActivationPolicy()
422        {
423            return m_activation;
424        }
425        
426        private Logger getComponentLogger( String path )
427        {
428            return new DefaultLogger( path );
429        }
430        
431        public <T>T getInstance( Class<T> clazz )
432        {
433            if( clazz.equals( Component.class ) )
434            {
435                return clazz.cast( this );
436            }
437            synchronized( m_handler )
438            {
439                Provider provider = getProvider();
440                if( clazz.equals( Provider.class ) )
441                {
442                    return clazz.cast( provider );
443                }
444                Object instance = provider.getInstance( clazz );
445                return clazz.cast( instance );
446            }
447        }
448        
449        public <T>T getContentForClass( Class<T> c ) throws IOException
450        {
451            if( c.isAssignableFrom( m_class ) )
452            {
453                return getInstance( c );
454            }
455            else if( c == Provider.class )
456            {
457                synchronized( m_handler )
458                {
459                    Provider provider = m_handler.getProvider();
460                    return c.cast( provider );
461                }
462            }
463            else if( c.isAssignableFrom( getClass() ) )
464            {
465                return c.cast( this );
466            }
467            else if( c == Appliance.class ) 
468            {
469                Logger logger = getComponentLogger();
470                Appliance appliance = new StandardAppliance( logger, this );
471                return c.cast( appliance );
472            }
473            else
474            {
475                return null;
476            }
477        }
478        
479        public void encode(  Buffer buffer, String key ) throws IOException
480        {
481            String name = getComponentName();
482            String classname = getComponentClass().getName();
483            ContextDirective context = getContextModel().getDirective();
484            PartsDirective parts = getPartsDirective();
485            
486            boolean flag = buffer.isNamespace( NAMESPACE );
487            if( flag )
488            {
489                buffer.nl( "<component" );
490            }
491            else
492            {
493                buffer.nl( "<component xmlns=\"" + NAMESPACE + "\"" );
494                buffer.nl( "    " );
495            }
496            if( null != key )
497            {
498                buffer.write( " key=\"" + key + "\"" );
499            }
500            if( null != name )
501            {
502                buffer.write( " name=\"" + name + "\"" );
503            }
504            if( m_priority != 0 )
505            {
506                buffer.write( " priority=\"" + m_priority + "\"" );
507            }
508            buffer.write( " class=\"" + classname + "\"" );
509            if( ( context.size() == 0 ) && ( parts.size() == 0 ) )
510            {
511                buffer.write( "/>" ); 
512            }
513            else
514            {
515                buffer.write( ">" ); 
516                Buffer b = buffer.namespace( NAMESPACE );
517                context.encode( b.indent(), null );
518                parts.encode( b.indent() );
519                buffer.nl( "</component>" );
520            }
521        }
522        
523        private PartsDirective getPartsDirective( PartsDirective directive )
524        {
525            if( null == directive )
526            {
527                return new PartsDirective();
528            }
529            else
530            {
531                return directive;
532            }
533        }
534        
535        private String getComponentPath( String partition, String name, Class c )
536        {
537            if( null == partition )
538            {
539                return "/" + name;
540            }
541            else
542            {
543                return "/" + partition.replace( ".", "/" ) + "/" + name;
544            }
545        }
546        
547        private String getComponentName( String name, Class<?> c )
548        {
549            if( null != name )
550            {
551                return name;
552            }
553            else
554            {
555                return getComponentName( c );
556            }
557        }
558        
559        //-----------------------------------------------------------------------
560        // Lifestyle handlers
561        //-----------------------------------------------------------------------
562        
563        private LifestyleHandler getLifestyleHandler( LifestylePolicy policy )
564        {
565            if( policy.equals( LifestylePolicy.SINGLETON ) )
566            {
567                return new SingletonLifestyleHandler( this );
568            }
569            else if( policy.equals( LifestylePolicy.THREAD ) )
570            {
571                return new ThreadLifestyleHandler( this );
572            }
573            else
574            {
575                return new TransientLifestyleHandler( this );
576            }
577        }
578        
579       /**
580        * Singleton holder class.  The singleton holder mains a single 
581        * <tt>LifestyleHandler</tt> of a component relative to the component model 
582        * identity within the scope of the controller.  References to the 
583        * singleton instance will be shared across mutliple threads.
584        */
585        private class SingletonLifestyleHandler extends LifestyleHandler
586        {
587            private Reference<Provider> m_reference;
588            
589            SingletonLifestyleHandler( ComponentStrategy strategy )
590            {
591                super( strategy );
592                Provider provider = new StandardProvider( strategy );
593                m_reference = createReference( null );
594            }
595            
596            Provider getProvider()
597            {
598                Provider provider = m_reference.get();
599                if( null == provider )
600                {
601                    ComponentStrategy strategy = getComponentStrategy();
602                    provider = new StandardProvider( strategy );
603                    m_reference = createReference( provider );
604                }
605                return provider;
606            }
607            
608            void release( Provider provider )
609            {
610            }
611            
612            void terminate()
613            {
614                synchronized( this )
615                {
616                    Provider provider = m_reference.get();
617                    if( null != provider )
618                    {
619                        if( provider instanceof Disposable )
620                        {
621                            Disposable disposable = (Disposable) provider;
622                            disposable.dispose();
623                        }
624                        m_reference = createReference( null ); 
625                    }
626                }
627            }
628        }
629        
630       /**
631        * Transient holder class.  The transient holder provides support for 
632        * the transient lifestyle ensuing the creation of a new <tt>LifestyleHandler</tt>
633        * per request.
634        */
635        private class TransientLifestyleHandler extends LifestyleHandler
636        {
637            private final WeakHashMap<Provider,Void> m_providers = new WeakHashMap<Provider,Void>(); // transients
638            
639            TransientLifestyleHandler( ComponentStrategy strategy )
640            {
641                super( strategy );
642            }
643            
644            Provider getProvider()
645            {
646                ComponentStrategy strategy = getComponentStrategy();
647                Provider provider = new StandardProvider( strategy );
648                m_providers.put( provider, null );
649                return provider;
650            }
651            
652            void release( Provider provider )
653            {
654                if( null == provider )
655                {
656                    return;
657                }
658                if( m_providers.containsKey( provider ) )
659                {
660                    m_providers.remove( provider );
661                    if( provider instanceof Disposable )
662                    {
663                        Disposable disposable = (Disposable) provider;
664                        disposable.dispose();
665                    }
666                }
667            }
668            
669            void terminate()
670            {
671                Provider[] providers = m_providers.keySet().toArray( new Provider[0] );
672                for( Provider provider : providers )
673                {
674                    release( provider );
675                }
676            }
677        }
678    
679       /**
680        * The ThreadHolder class provides support for the per-thread lifestyle
681        * policy within which new <tt>LifestyleHandler</tt> creation is based on a single
682        * <tt>LifestyleHandler</tt> per thread.
683        */
684        private class ThreadLifestyleHandler extends LifestyleHandler
685        {
686            private final ThreadLocalHolder m_threadLocalHolder = new ThreadLocalHolder();
687            
688            ThreadLifestyleHandler( ComponentStrategy strategy )
689            {
690                super( strategy );
691            }
692            
693            Provider getProvider()
694            {
695                return (Provider) m_threadLocalHolder.get();
696            }
697            
698            void release( Provider provider )
699            {
700                m_threadLocalHolder.release( provider );
701            }
702    
703            void terminate()
704            {
705                m_threadLocalHolder.terminate();
706            }
707            
708           /**
709            * Internal thread local holder for the per-thread lifestyle holder.
710            */
711            private class ThreadLocalHolder extends ThreadLocal
712            {
713                private final WeakHashMap<Provider,Void> m_providers = 
714                  new WeakHashMap<Provider,Void>(); // per thread instances
715                
716                synchronized protected Provider initialValue()
717                {
718                    ComponentStrategy strategy = getComponentStrategy();
719                    Provider provider = new StandardProvider( strategy );
720                    m_providers.put( provider, null );
721                    return provider;
722                }
723                
724                synchronized void release( Provider provider )
725                {
726                    if( m_providers.containsKey( provider ) )
727                    {
728                        m_providers.remove( provider );
729                        if( provider instanceof Disposable )
730                        {
731                            Disposable disposable = (Disposable) provider;
732                            disposable.dispose();
733                        }
734                    }
735                }
736                
737                synchronized void terminate()
738                {
739                    Provider[] providers = m_providers.keySet().toArray( new Provider[0] );
740                    for( Provider provider : providers )
741                    {
742                        release( provider );
743                    }
744                }
745            }
746        }
747        
748        //-----------------------------------------------------------------------
749        // utilities
750        //-----------------------------------------------------------------------
751    
752        private static String getComponentName( Class<?> c )
753        {
754            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
755            {
756                net.dpml.annotation.Component annotation = 
757                  c.getAnnotation( net.dpml.annotation.Component.class );
758                String name = annotation.name();
759                if( !"".equals( name ) )
760                {
761                    return name;
762                }
763            }
764            return c.getName();
765        }
766        
767        private static String getComponentNameFromClass( Class<?> c )
768        {
769            String classname = c.getName();
770            int n = classname.lastIndexOf( "." );
771            if( n > -1 )
772            {
773                return classname.substring( n+1 );
774            }
775            else
776            {
777                return classname;
778            }
779        }
780        
781        private static CollectionPolicy getCollectionPolicy( Class<?> c )
782        {
783            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
784            {
785                net.dpml.annotation.Component annotation = 
786                  c.getAnnotation( net.dpml.annotation.Component.class );
787                return annotation.collection();
788            }
789            return CollectionPolicy.HARD;
790        }
791        
792        private static State getLifecycleGraph( Class<?> c ) throws IOException
793        {
794            String name = getComponentNameFromClass( c );
795            String spec = name + ".xgraph";
796            URL url = getLifecycleURL( c, spec );
797            if( null != url )
798            {
799                StateDecoder decoder = new StateDecoder();
800                return decoder.loadState( url );
801            }
802            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
803            {
804                net.dpml.annotation.Component annotation = 
805                  c.getAnnotation( net.dpml.annotation.Component.class );
806                String path = annotation.lifecycle();
807                return getLifecycleGraph( c, path );
808            }
809            else
810            {
811                return NULL_STATE;
812            }
813        }
814        
815        private static State getLifecycleGraph( Class<?> c, String path ) throws IOException
816        {
817            if( ( null == path ) || "".equals( path ) )
818            {
819                return NULL_STATE;
820            }
821            else
822            {
823                URL url = getLifecycleURL( c, path );
824                StateDecoder decoder = new StateDecoder();
825                return decoder.loadState( url );
826            }
827        }
828        
829        private static URL getLifecycleURL( Class<?> c, String path ) throws IOException
830        {
831            int n = path.indexOf( ":" );
832            if( n > -1 )
833            {
834                try
835                {
836                    return Artifact.toURL( new URI( path ) );
837                }
838                catch( Exception e )
839                {
840                    final String error = 
841                      "Bad url: " + path;
842                    IOException ioe = new IOException( error );
843                    ioe.initCause( e );
844                    throw ioe;
845                }
846            }
847            else
848            {
849                return c.getResource( path );
850            }
851        }
852    
853        private static final String NAMESPACE = ComponentStrategyHandler.NAMESPACE;
854        
855       /**
856        * Returns the string representing of the component.
857        * @return the component as a string
858        */
859        public String toString()
860        {
861            return getComponentPath();
862        }
863    
864        private static class ComponentEventDistatcher implements Runnable
865        {
866            private Logger m_logger;
867            private ComponentListener m_listener;
868            private ComponentEvent m_event;
869            
870            ComponentEventDistatcher( Logger logger, ComponentListener listener, ComponentEvent event )
871            {
872                m_logger = logger;
873                m_listener = listener;
874                m_event = event;
875            }
876            
877            public void run()
878            {
879                try
880                {
881                    m_listener.componentChanged( m_event );
882                }
883                catch( Throwable e )
884                {
885                    final String error = 
886                      "Event distatch error.";
887                    m_logger.error( error, e );
888                }
889            }
890        }
891    }