001    /*
002     * Copyright (c) 2005 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.tools;
020    
021    import java.beans.IntrospectionException;
022    import java.beans.Encoder;
023    import java.beans.Expression;
024    import java.beans.DefaultPersistenceDelegate;
025    import java.io.File;
026    import java.io.IOException;
027    import java.net.URI;
028    import java.net.URISyntaxException;
029    
030    import net.dpml.component.Directive;
031    import net.dpml.component.ActivationPolicy;
032    
033    import net.dpml.library.info.Scope;
034    import net.dpml.library.Resource;
035    
036    import net.dpml.metro.data.ComponentDirective;
037    import net.dpml.metro.data.ContextDirective;
038    import net.dpml.metro.data.CategoriesDirective;
039    import net.dpml.metro.info.LifestylePolicy;
040    import net.dpml.metro.info.CollectionPolicy;
041    import net.dpml.metro.info.PartReference;
042    import net.dpml.metro.info.Type;
043    import net.dpml.metro.info.EntryDescriptor;
044    import net.dpml.metro.builder.ComponentTypeDecoder;
045    import net.dpml.metro.data.DefaultComposition;
046    
047    import net.dpml.lang.Classpath;
048    import net.dpml.lang.Part;
049    import net.dpml.lang.Info;
050    
051    import net.dpml.transit.monitor.LoggingAdapter;
052    
053    import net.dpml.tools.tasks.PartTask;
054    
055    import org.apache.tools.ant.BuildException;
056    import org.apache.tools.ant.Project;
057    import org.apache.tools.ant.AntClassLoader;
058    import org.apache.tools.ant.types.Path;
059    
060    /**
061     * Task that handles the construction of a serialized container part.
062     *
063     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
064     * @version 1.2.0
065     */
066    public class ComponentBuilderTask extends PartTask implements PartReferenceBuilder
067    {
068        private static final String NAMESPACE = "link:xsd:dpml/lang/dpml-component#1.0";
069        
070        private static final ComponentTypeDecoder COMPONENT_TYPE_DECODER = 
071          new ComponentTypeDecoder();
072          
073        private URI m_uri;
074        private String m_key;
075        private boolean m_embedded = false;
076        private String m_name;
077        private String m_classname;
078        private LifestylePolicy m_lifestyle;
079        private CollectionPolicy m_collection;
080        private ActivationPolicy m_activation = ActivationPolicy.SYSTEM;
081        private CategoriesDataType m_categories;
082        private ContextDataType m_context;
083        private PartsDataType m_parts;
084        private File m_output;
085        private Type m_type;
086        private URI m_extends;
087        private boolean m_alias = false;
088        
089       /**
090        * Set the part key.
091        * @param key the key
092        */
093        public void setKey( String key )
094        {
095            m_key = key;
096        }
097    
098       /**
099        * Set the alias production flag value.
100        * @param alias true if alias production is requested
101        */
102        public void setAlias( boolean alias )
103        {
104            m_alias = alias;
105        }
106        
107       /**
108        * Set the extends uri feature.
109        * @param uri the uri from which the component extends
110        */
111        public void setExtends( URI uri )
112        {
113            m_extends = uri;
114        }
115    
116       /**
117        * Set the embedded component flag.
118        * @param flag true if embedded
119        */
120        //public void setEmbedded( boolean flag )
121        //{
122        //    m_embedded = flag;
123        //}
124    
125       /**
126        * Set the component name.
127        * @param name the component name
128        */
129        public void setName( String name )
130        {
131            m_name = name;
132        }
133      
134       /**
135        * Set the component classname.
136        * @param classname the component type classname
137        */
138        public void setType( String classname )
139        {
140            m_classname = classname;
141        }
142    
143       /**
144        * Set the lifestyle policy vlaue.
145        * @param policy the lifestyle policy
146        */
147        public void setLifestyle( String policy )
148        {
149            m_lifestyle = LifestylePolicy.parse( policy );
150        }
151    
152       /**
153        * Set the gabage collection policy value.
154        * @param policy the collection policy
155        */
156        public void setCollection( String policy )
157        {
158            m_collection = CollectionPolicy.parse( policy );
159        }
160    
161       /**
162        * Set the activation policy value.
163        * @param policy the activation policy
164        */
165        public void setActivation( String policy )
166        {
167            m_activation = ActivationPolicy.parse( policy );
168        }
169    
170       /**
171        * Create a new categories data type.
172        * @return the categories datatype
173        */
174        public CategoriesDataType createCategories()
175        {
176            if( m_categories == null )
177            {
178                m_categories = new CategoriesDataType();
179                return m_categories;
180            }
181            else
182            {
183                 final String error =
184                  "Illegal attempt to create a duplicate categories declaration.";
185                 throw new BuildException( error, getLocation() );
186            }
187        }
188    
189       /**
190        * Create a new context data type.
191        * @return the context datatype
192        */
193        public ContextDataType createContext()
194        {
195            if( null == m_context )
196            {
197                 m_context = new ContextDataType();
198                 return m_context;
199            }
200            else
201            {
202                 final String error =
203                  "Illegal attempt to create a duplicate context declaration.";
204                 throw new BuildException( error, getLocation() );
205            }
206        }
207    
208       /**
209        * Create a new part datatype.
210        * @return a new part datatype
211        */
212        public PartsDataType createParts()
213        {
214            if( m_parts == null )
215            {
216                m_parts = new PartsDataType( this );
217                return m_parts;
218            }
219            else
220            {
221                 final String error =
222                  "Illegal attempt to create a duplicate parts element.";
223                 throw new BuildException( error, getLocation() );
224            }
225        }
226        
227       /**
228        * Build the plugin definition.
229        * @param resource the project resource definition
230        * @return the part definition
231        */
232        protected Part build( Resource resource )
233        {
234            try
235            {
236                Info info = getInfo( resource );
237                Classpath classpath = getClasspath( resource );
238                ClassLoader classloader = createClassLoader();
239                ComponentDirective profile = buildComponentDirective( classloader );
240                return new DefaultComposition( 
241                    new LoggingAdapter( "depot" ),
242                    info, classpath, null, profile );
243            }
244            catch( Throwable e )
245            {
246                final String error = 
247                  "Internal error while attempting to build an external part defintion."
248                  + "\nResource: " + resource;
249                throw new BuildException( error, e, getLocation() );
250            }
251        }
252        
253       /**
254        * Return the runtime classloader.
255        * @return the classloader
256        */
257        protected ClassLoader createClassLoader()
258        {
259            Project project = getProject();
260            Path path = getContext().getPath( Scope.RUNTIME );
261            File classes = getContext().getTargetClassesMainDirectory();
262            path.createPathElement().setLocation( classes );
263            ClassLoader parentClassLoader = getClass().getClassLoader();
264            return new AntClassLoader( parentClassLoader, project, path, true );
265        }
266        
267        //---------------------------------------------------------------------
268        // Builder
269        //---------------------------------------------------------------------
270    
271       /**
272        * Return a uri identitifying the builder.
273        *
274        * @return the builder uri
275        */
276        public URI getBuilderURI()
277        {
278            return PART_BUILDER_URI;
279        }
280    
281        //---------------------------------------------------------------------
282        // PartBuilder
283        //---------------------------------------------------------------------
284    
285       /**
286        * Return a urn identitifying the part handler for this builder.
287        *
288        * @return a strategy uri
289        */
290        public URI getPartHandlerURI()
291        {
292            return PART_HANDLER_URI;
293        }
294    
295       /**
296        * Build the part.
297        * @param classloader the classloader
298        * @return the part
299        * @exception IntrospectionException if an error occurs while introspecting the component class
300        * @exception IOException if an I/O error occurs
301        * @exception ClassNotFoundException if the component class cannot be found
302        */
303        public Directive buildDirective( ClassLoader classloader )
304          throws IntrospectionException, IOException, ClassNotFoundException
305        {
306            String classname = getClassname();
307            Type type = loadType( classloader, classname );
308            return buildComponentDirective( type, classloader );
309        }
310        
311        //---------------------------------------------------------------------
312        // PartReferenceBuilder
313        //---------------------------------------------------------------------
314    
315       /**
316        * Return the part key.
317        *
318        * @return the key
319        */
320        public String getKey()
321        {
322            if( null == m_key )
323            {
324                final String error = 
325                  "Missing key attribute for nested part reference.";
326                throw new BuildException( error );
327            }
328            else
329            {
330                return m_key;
331            }
332        }
333    
334       /**
335        * Build a part reference.
336        * @param classloader the classloader
337        * @param type the component type
338        * @return the part reference
339        * @exception IntrospectionException if an error occurs while introspecting the component class
340        * @exception IOException if an I/O error occurs
341        * @exception ClassNotFoundException if the component class cannot be found
342        */
343        public PartReference buildPartReference( ClassLoader classloader, Type type )
344          throws IntrospectionException, IOException, ClassNotFoundException
345        {
346            String key = getKey();
347            Directive directive = buildComponentDirective( type, classloader );
348            return new PartReference( key, directive );
349        }
350    
351        //---------------------------------------------------------------------
352        // impl
353        //---------------------------------------------------------------------
354    
355        private ComponentDirective buildComponentDirective( Type type, ClassLoader classloader ) 
356          throws IntrospectionException, IOException, ClassNotFoundException
357        {
358            return buildComponentDirective( classloader );
359        }
360    
361        private ComponentDirective buildComponentDirective( ClassLoader classloader ) 
362          throws IntrospectionException, IOException, ClassNotFoundException
363        {
364            String classname = getClassname();
365            Type type = loadType( classloader, classname );
366            String id = getName( type.getInfo().getName() );
367            log( "creating [" + id + "] using [" + classname + "]" );
368            
369            LifestylePolicy lifestyle = getLifestylePolicy(); 
370            CollectionPolicy collection = getCollectionPolicy( type );
371            ActivationPolicy activation = getActivationPolicy();
372            CategoriesDirective categories = getCategoriesDirective();
373            ContextDirective context = getContextDirective( classloader, type );
374            PartReference[] parts = getParts( classloader );
375            URI base = getBaseURI();
376            
377            //
378            // return the component profile
379            //
380    
381            return new ComponentDirective( 
382              id, activation, collection, lifestyle, classname, categories, 
383              context, parts, base );
384        }
385        
386        private URI getBaseURI()
387        {
388            return m_extends;
389        }
390    
391        private Type loadType( ClassLoader classloader, String classname )
392        {
393            if( null == classloader )
394            {
395                 throw new NullPointerException( "classloader" );
396            }
397            if( null == classname )
398            {
399                 throw new NullPointerException( "classname" );
400            }
401            try
402            {
403                Resource resource = getResource();
404                Class c = classloader.loadClass( classname );
405                return COMPONENT_TYPE_DECODER.loadType( c, resource );
406            }
407            catch( Throwable e )
408            {
409                final String error = 
410                  "Unexpected error occured while attempting to load type ["
411                  + classname
412                  + "]";
413                throw new BuildException( error, e, getLocation() );
414            }
415        }
416    
417       /**
418        * Return the component name.
419        * @param typeName the component type name (used as a default)
420        * @return the name
421        */
422        protected String getName( String typeName )
423        {
424            if( null == m_name )
425            {
426                if( null != m_key )
427                {
428                    return m_key;
429                }
430                else
431                {
432                    return typeName;
433                }
434            }
435            else
436            {
437                return m_name;
438            }
439        }
440    
441       /**
442        * Return the component classname.
443        * @return the classname
444        */
445        protected String getClassname()
446        {
447            if( null == m_classname )
448            {
449                return Object.class.getName();
450            }
451            else
452            {
453                return m_classname;
454            }
455        }
456    
457       /**
458        * Return the lifestyle policy declared relative to usage.
459        * If undefined then default to the lifestyle declared by the component type.
460        * Lifestyle policies that may be declared under the 'lifestyle' attribute of 
461        * a component are 'transient', 'thread' or 'singleton'.  If 'transient' is supplied
462        * the assigned lefestyle policy is InfoDescriptor.TRANSIENT resulting in the 
463        * creation of a new instance per request.  If 'thread' is declared the assigned
464        * lifestyle policy shall be InfoDescriptor.THREAD in which case a supplied 
465        * instance will be reused for all requests within the same thread of execution.
466        * If the supplied policy is 'singleton' then the established instance will be 
467        * shared across consumers referencing the component.
468        *
469        * @return the lifestyle policy
470        */
471        public LifestylePolicy getLifestylePolicy()
472        {
473            return m_lifestyle;
474        }
475    
476       /**
477        * Return the collection policy.
478        * @param type the component type from which the default collection 
479        *   policy can be resolved if needed
480        * @return the collection policy
481        */
482        public CollectionPolicy getCollectionPolicy( Type type )
483        {
484            if( null == m_collection )
485            {
486                 return type.getInfo().getCollectionPolicy();
487            }
488            else
489            {
490                return m_collection;
491            }
492        }
493    
494       /**
495        * Return the activation policy.
496        * @return the component activation policy
497        */
498        public ActivationPolicy getActivationPolicy()
499        {
500            return m_activation;
501        }
502    
503       /**
504        * Return the context directive.
505        * @param classloader the classloader to use
506        * @param type the component type
507        * @return the context directive
508        * @exception IntrospectionException if a class introspection error occurs
509        * @exception IOException if an I/O error occurs
510        * @exception ClassNotFoundException if a component context class cannont be found
511        */
512        private ContextDirective getContextDirective( ClassLoader classloader, Type type ) 
513          throws IntrospectionException, IOException, ClassNotFoundException
514        {
515            String name = getName( type.getInfo().getName() );
516            String classname = type.getInfo().getClassname();
517            ContextDirective context = createContextDirective( classloader, type );
518            if( null == context )
519            {
520               // return m_profile.getContextDirective();
521               return null;
522            }
523    
524            //
525            // validate that the context directives are declared
526            // and if not - throw an exception
527            //
528    
529            EntryDescriptor[] entries = type.getContextDescriptor().getEntryDescriptors();
530            for( int i=0; i<entries.length; i++ )
531            {
532                EntryDescriptor entry = entries[i];
533                String key = entry.getKey();
534    
535                Directive part = context.getPartDirective( key );
536                if( entry.isRequired() && ( null == part ) )
537                {
538                    final String error = 
539                      "The component model ["
540                      + name 
541                      + "] referencing the component type ["
542                      + classname 
543                      + "] does not declare a context entry for the non-optional entry ["
544                      + key 
545                      + "].";
546                    throw new ConstructionException( error, getLocation() );
547                }
548            }
549    
550            //
551            // we are ship-shape
552            // 
553    
554            return context;
555        }
556    
557        private ContextDirective createContextDirective( ClassLoader classloader, Type type ) 
558          throws IntrospectionException, IOException, ClassNotFoundException
559        {
560            if( null == m_context )
561            {
562                return null;
563            }
564            else
565            {
566                return m_context.getContextDirective( classloader, type );
567            }
568        }
569    
570        private CategoriesDirective getCategoriesDirective()
571        {
572            if( null == m_categories )
573            {
574                //return m_profile.getCategoriesDirective();
575                return null;
576            }
577            else
578            {
579                return m_categories.getCategoriesDirective();
580            }
581        }
582    
583        private PartReference[] getParts( ClassLoader classloader ) 
584          throws IntrospectionException, IOException
585        {
586            if( null != m_parts )
587            {
588                try
589                {
590                    return m_parts.getParts( classloader, null );
591                }
592                catch( ClassNotFoundException cnfe )
593                {
594                    final String error = 
595                      "Unable to load a class referenced by a nested part within a component type.";
596                    throw new BuildException( error, cnfe );
597                }
598            }
599            else
600            {
601                return new PartReference[0];
602            }
603        }
604    
605       /**
606        * Utility class used to handle uri persistence.
607        */
608        public static class URIPersistenceDelegate extends DefaultPersistenceDelegate
609        {
610           /**
611            * Return an expressio to create a uri.
612            * @param old the old value
613            * @param encoder the encoder
614            * @return the expression
615            */
616            public Expression instantiate( Object old, Encoder encoder )
617            {
618                URI uri = (URI) old;
619                String spec = uri.toString();
620                Object[] args = new Object[]{spec};
621                return new Expression( old, old.getClass(), "new", args );
622            }
623        }
624    
625       /**
626        * Constant controller uri.
627        */
628        public static final URI PART_HANDLER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.2.0" );
629    
630       /**
631        * Constant strategy builder uri.
632        */
633        public static final URI STRATEGY_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-runtime#1.2.0" );
634    
635       /**
636        * Constant builder uri.
637        */
638        public static final URI PART_BUILDER_URI = setupURI( "artifact:part:dpml/metro/dpml-metro-tools#1.2.0" );
639    
640       /**
641        * Utility function to create a static uri.
642        * @param spec the uri spec
643        * @return the uri
644        */
645        protected static URI setupURI( String spec )
646        {
647            try
648            {
649                return new URI( spec );
650            }
651            catch( URISyntaxException ioe )
652            {
653                return null;
654            }
655        }
656    }