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