001    /*
002     * Copyright 2004-2006 Stephen J. McConnell.
003     *
004     * Licensed  under the  Apache License,  Version 2.0  (the "License");
005     * you may not use  this file  except in  compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *   http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed  under the  License is distributed on an "AS IS" BASIS,
012     * WITHOUT  WARRANTIES OR CONDITIONS  OF ANY KIND, either  express  or
013     * implied.
014     *
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package net.dpml.metro.data;
020    
021    import java.io.IOException;
022    import java.net.URI;
023    import java.util.Arrays;
024    
025    import net.dpml.component.ActivationPolicy;
026    import net.dpml.component.Directive;
027    
028    import net.dpml.metro.info.PartReference;
029    import net.dpml.metro.info.LifestylePolicy;
030    import net.dpml.metro.info.CollectionPolicy;
031    
032    import net.dpml.lang.Part;
033    import net.dpml.lang.AbstractDirective;
034    
035    /**
036     * Definition of the criteria for an explicit component profile.  A profile, when
037     * included within the scope of a container declaration will be instantiated in
038     * the model as an EXPLICIT component profile resulting in the initiation of
039     * dependency resolution relative to the component as the target deployment
040     * objective.  Multiple supplementary profiles may be packaged in a .xprofiles
041     * resources and will be assigned to the container automatically.  In the absence
042     * of explicit or packaged profile directives, an implicit profile will be created
043     * for any component types declared under a jar manifest.
044     *
045     * <p><b>XML</b></p>
046     * <p>A component element declares the profile to be applied during the instantiation
047     * of a component type.  It includes a name and class declaration, logging directives
048     * (resolved relative to the component's container), context creation criteria,
049     * together with configuration or parameters information.</p>
050     *
051     * <pre>
052     <font color="gray"><i>&lt;!--
053     Declaration of the services hosted by this container.  Service container here
054     will be managed relative to other provider components at the same level and
055     may be serviced by components declared in parent container.
056     --&gt;</i></font>
057    
058    &lt;component name="<font color="darkred">complex</font>" class="<font color="darkred">org.apache.avalon.playground.ComplexComponent</font>" activation="<font color="darkred">startup</font>"&gt;
059    
060      <font color="gray"><i>&lt;!--
061      Priority and target assignments for component specific logging categrories.
062      --&gt;</i></font>
063    
064      &lt;categories priority="<font color="darkred">DEBUG</font>"&gt;
065        &lt;category name="<font color="darkred">init</font>" priority="<font color="darkred">DEBUG</font>" /&gt;
066      &lt;/categories&gt;
067    
068      <font color="gray"><i>&lt;!--
069      Context entry directives are normally only required in the case where the component
070      type declares a required context type and entry values. Generally speaking, a component
071      will normally qualify it's instantiation criteria through a configuration declaration.
072      Any context values defined at this level will override context values supplied by the
073      container.  The following two context directives for "location" and "home" demonstrate
074      programatics creation of context values.  The first entry declares that the context
075      value to be assigned to the key "location" shall be the String value "Paris".  The second
076      context enty assignes the container's context value for "urn:avalon:home" to the component's
077      context key of "home".
078      --&gt;</i></font>
079    
080      &lt;context&gt;
081        &lt;entry key="<font color="darkred">location</font>"&gt;<font color="darkred">Paris</font>&lt;/entry&gt;
082        &lt;include name="<font color="darkred">urn:avalon:home</font>" key="<font color="darkred">home</font>"/&gt;
083      &lt;/context&gt;
084    
085    &lt;/component&gt;
086    </pre>
087     *
088     * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
089     * @version 1.0.4
090     */
091    public class ComponentDirective extends AbstractDirective  implements Comparable, Directive
092    {
093        //--------------------------------------------------------------------------
094        // static
095        //--------------------------------------------------------------------------
096        
097       /**
098        * Serial version identifier.
099        */
100        static final long serialVersionUID = 1L;
101    
102        private static final CategoriesDirective EMPTY_CATEGORIES = 
103          new CategoriesDirective();
104        
105        //--------------------------------------------------------------------------
106        // state
107        //--------------------------------------------------------------------------
108        
109       /**
110        * The collection policy override.
111        */
112        private final CollectionPolicy m_collection;
113        
114       /**
115        * The component lifestyle policy.
116        */
117        private final LifestylePolicy m_lifestyle;
118    
119       /**
120        * The component classname.
121        */
122        private final String m_classname;
123    
124       /**
125        * The components context directive.
126        */
127        private final ContextDirective m_context;
128    
129       /**
130        * The name of the component profile. This is an
131        * abstract name used during assembly.
132        */
133        private final String m_name;
134    
135       /**
136        * The activation policy.
137        */
138        private final ActivationPolicy m_activation;
139    
140       /**
141        * Logging category directives.
142        */
143        private final CategoriesDirective m_categories;
144    
145       /**
146        * Base directive uri.
147        */
148        private final URI m_uri;
149    
150       /**
151        * Base directive.
152        */
153        private final DefaultComposition m_base;
154    
155       /**
156        * Internal parts.
157        */
158        private final PartReference[] m_parts;
159    
160        //--------------------------------------------------------------------------
161        // constructors
162        //--------------------------------------------------------------------------
163    
164       /**
165        * Creation of a new profile.
166        *
167        * @param name the name to assign to the component deployment scenario
168        * @param classname the classname of the component type
169        * @exception IOException if an IO exception occurs
170        */
171        public ComponentDirective( final String name, final String classname ) throws IOException
172        {
173            this(
174              name, 
175              ActivationPolicy.SYSTEM, 
176              CollectionPolicy.SYSTEM, 
177              LifestylePolicy.SYSTEM,
178              classname, 
179              null, null, null, null );
180        }
181    
182       /**
183        * Creation of a new deployment profile.
184        * @param name the name to assign to the created profile
185        * @param activation the component activation policy
186        * @param collection the component garbage collection policy
187        * @param lifestyle the component lifestyle policy
188        * @param classname the component classname
189        * @param categories logging categories
190        * @param context context directive
191        * @param parts the component internal parts
192        * @param uri URI of the component super-definition
193        * @exception IOException if an IO exception occurs
194        */
195        public ComponentDirective(
196               final String name,
197               final ActivationPolicy activation,
198               final CollectionPolicy collection,
199               final LifestylePolicy lifestyle,
200               final String classname,
201               final CategoriesDirective categories,
202               final ContextDirective context,
203               final PartReference[] parts,
204               final URI uri ) throws IOException
205        {
206            if( null == activation )
207            {
208                if( null != uri )
209                {
210                    m_activation = null;
211                }
212                else
213                {
214                    m_activation = ActivationPolicy.SYSTEM;
215                }
216            }
217            else
218            {
219                m_activation = activation;
220            }
221            
222            if( null == categories )
223            {
224                if( null != uri )
225                {
226                    m_categories = null;
227                }
228                else
229                {
230                    m_categories = EMPTY_CATEGORIES;
231                }
232            }
233            else
234            {
235                m_categories = categories;
236            }
237            
238            if( null == classname )
239            {
240                if( null != uri )
241                {
242                    m_classname = null;
243                }
244                else
245                {
246                    m_classname = Object.class.getName();
247                }
248            }
249            else
250            {
251                m_classname = classname;
252            }
253            
254            if( null == context )
255            {
256                if( null != uri )
257                {
258                    m_context = null;
259                }
260                else
261                {
262                    m_context = new ContextDirective( new PartReference[0] );
263                }
264            }
265            else
266            {
267                m_context = context;
268            }
269            
270            if( null == lifestyle )
271            {
272                if( null != uri )
273                {
274                    m_lifestyle = null;
275                }
276                else
277                {
278                    m_lifestyle = LifestylePolicy.SYSTEM;
279                }
280            }
281            else
282            {
283                m_lifestyle = lifestyle;
284            }
285    
286            if( null == collection )
287            {
288                if( null != uri )
289                {
290                    m_collection = null;
291                }
292                else
293                {
294                    m_collection = CollectionPolicy.SYSTEM;
295                }
296            }
297            else
298            {
299                m_collection = collection;
300            }
301            
302            if( null == parts )
303            {
304                m_parts = new PartReference[0];
305            }
306            else
307            {
308                m_parts = parts;
309            }
310            
311            m_uri = uri;
312            if( null != uri )
313            {
314                Part part = Part.load( uri, false );
315                if( part instanceof DefaultComposition )
316                {
317                    m_base = (DefaultComposition) part;
318                }
319                else
320                {
321                    final String error = 
322                      "Base part class is not recognized."
323                      + "\nClass: " + part.getClass().getName();
324                    throw new IllegalStateException( error );
325                }
326            }
327            else
328            {
329                m_base = null;
330            }
331            
332            if( null == name )
333            {
334                if( null != uri )
335                {
336                    m_name = null;
337                }
338                else
339                {
340                    m_name = toName( m_classname );
341                }
342            }
343            else
344            {
345                validateName( name );
346                m_name = name;
347            }
348        }
349        
350        //--------------------------------------------------------------------------
351        // implementation
352        //--------------------------------------------------------------------------
353    
354        /**
355         * Returns the parts declared by this component type.
356         *
357         * @return the part descriptors
358         */
359        public PartReference[] getPartReferences()
360        {
361            return m_parts;
362        }
363    
364        /**
365         * Retrieve an identified directive.
366         *
367         * @param key the directive key
368         * @return the directive or null if the directive key is unknown
369         */
370        public Directive getDirective( final String key )
371        {
372            for ( int i = 0; i < m_parts.length; i++ )
373            {
374                PartReference reference = m_parts[i];
375                if( reference.getKey().equals( key ) )
376                {
377                    return reference.getDirective();
378                }
379            }
380            return null;
381        }
382    
383        /**
384         * Return the profile name.
385         *
386         * @return the name of the component.
387         */
388        public String getName()
389        {
390            return m_name;
391        }
392    
393        /**
394         * Return the logging categories for the profile.
395         *
396         * @return the categories
397         */
398        public CategoriesDirective getCategoriesDirective()
399        {
400            return m_categories;
401        }
402    
403       /**
404        * Get the activation policy for the profile.
405        *
406        * @return the declared activation policy
407        * @see ActivationPolicy#SYSTEM
408        * @see ActivationPolicy#STARTUP
409        * @see ActivationPolicy#DEMAND
410        */
411        public ActivationPolicy getActivationPolicy()
412        {
413            return m_activation;
414        }
415        
416       /**
417        * Return the component type classname.
418        *
419        * @return classname of the component type
420        */
421        public String getClassname()
422        {
423            return m_classname;
424        }
425    
426       /**
427        * Return the component lifestyle policy.
428        *
429        * @return the lifestyle policy value
430        */
431        public LifestylePolicy getLifestylePolicy()
432        {
433            return m_lifestyle;
434        }
435    
436       /**
437        * Return the component collection policy.  If null, the component
438        * type collection policy will apply.
439        *
440        * @return a HARD, WEAK, SOFT or SYSTEM
441        */
442        public CollectionPolicy getCollectionPolicy()
443        {
444            return m_collection;
445        }
446    
447       /**
448        * Return the context directive for the profile.
449        *
450        * @return the ContextDirective for the profile.
451        */
452        public ContextDirective getContextDirective()
453        {
454            return m_context;
455        }
456    
457       /**
458        * Return the base directive uri.
459        *
460        * @return the uri of the base directive
461        */
462        public URI getBaseURI()
463        {
464            return m_uri;
465        }
466    
467       /**
468        * Return the base directive.
469        *
470        * @return the base directive
471        */
472        public ComponentDirective getBaseDirective()
473        {
474            if( null == m_base )
475            {
476                return null;
477            }
478            else
479            {
480                return m_base.getComponentDirective();
481            }
482        }
483    
484       /**
485        * Return the base part.
486        *
487        * @return the base part
488        */
489        public DefaultComposition getBasePart()
490        {
491            return m_base;
492        }
493    
494       /**
495        * Returns a string representation of the profile.
496        * @return a string representation
497        */
498        public String toString()
499        {
500            if( null != m_name )
501            {
502                return "[" + getName() + "]";
503            }
504            else
505            {
506                return "[unknown]";
507            }
508        }
509    
510       /**
511        * Compare this object with the supplied object.
512        * @param object the object to compare with
513        * @return the result
514        */
515        public int compareTo( Object object )
516        {
517            String name = this.toString();
518            String other = object.toString();
519            return name.compareTo( other );
520        }
521        
522       /**
523        * Test if the supplied object is equal to this object.
524        * @param other the object to compare with this instance
525        * @return TRUE if the supplied object is equal to this object
526        */
527        public boolean equals( Object other )
528        {
529            if( !super.equals( other ) )
530            {
531                return false;
532            }
533            if( !( other instanceof ComponentDirective ) )
534            {
535                return false;
536            }
537            ComponentDirective profile = (ComponentDirective) other;
538            if( !equals( m_name, profile.getName() ) )
539            {
540                return false;
541            }
542            else if( !equals( m_activation, profile.getActivationPolicy() ) )
543            {
544                return false;
545            }
546            else if( !equals( m_categories, profile.getCategoriesDirective() ) )
547            {
548                return false;
549            }
550            else if( !equals( m_classname, profile.getClassname() ) )
551            {
552                return false;
553            }
554            else if( !equals( m_context, profile.getContextDirective() ) )
555            {
556                return false;
557            }
558            else if( !equals( m_collection, profile.getCollectionPolicy() ) )
559            {
560                return false;
561            }
562            else if( !equals( m_lifestyle, profile.getLifestylePolicy() ) )
563            {
564                return false;
565            }
566            else if( !Arrays.equals( m_parts, profile.getPartReferences() ) )
567            {
568                return false;
569            }
570            else
571            {
572                return equals( m_uri, profile.getBaseURI() );
573            }
574        }
575    
576       /**
577        * Return the hashcode for the instance.
578        * @return the instance hashcode
579        */
580        public int hashCode()
581        {
582            int hash = super.hashCode();
583            hash ^= hashValue( m_name );
584            hash ^= hashValue( m_activation );
585            hash ^= hashValue( m_categories );
586            hash ^= hashValue( m_classname );
587            hash ^= hashValue( m_context );
588            hash ^= hashValue( m_collection );
589            hash ^= hashValue( m_lifestyle );
590            hash ^= hashValue( m_uri );
591            hash ^= hashArray( m_parts );
592            return hash;
593        }
594    
595       /**
596        * Internal utility to get the name of the class without the package name. Used
597        * when constructing a default component name.
598        * @param classname the fully qualified classname
599        * @return the short class name without the package name
600        */
601        private String toName( String classname )
602        {
603            int i = classname.lastIndexOf( "." );
604            if( i == -1 )
605            {
606                return classname.toLowerCase();
607            }
608            else
609            {
610                return classname.substring( i + 1, classname.length() ).toLowerCase();
611            }
612        }
613        
614        private void validateName( final String name )
615        {
616            if( name.indexOf( " " ) > 0 || name.indexOf( "." ) > 0 || name.indexOf( "," ) > 0
617              || name.indexOf( "/" ) > 0 )
618            {
619                final String error = 
620                  "Directive name ["
621                  + name
622                  + "] contains an illegal character (' ', ',', '/', or '.')";
623                throw new IllegalArgumentException( error );
624            }
625            else if( name.length() == 0 )
626            {
627                final String error = 
628                  "Directive name [] is insufficient.";
629                throw new IllegalArgumentException( error );
630            }
631        }
632    }