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.runtime;
020    
021    import dpml.lang.DOM3DocumentBuilder;
022    import dpml.util.StandardClassLoader;
023    import dpml.util.ElementHelper;
024    import dpml.util.DefaultLogger;
025    
026    import java.io.IOException;
027    import java.lang.reflect.Constructor;
028    
029    import net.dpml.lang.DecodingException;
030    import net.dpml.lang.Strategy;
031    import net.dpml.lang.StrategyHandler;
032    
033    import net.dpml.annotation.Activation;
034    import net.dpml.annotation.ActivationPolicy;
035    import net.dpml.annotation.LifestylePolicy;
036    import net.dpml.annotation.CollectionPolicy;
037    
038    import net.dpml.util.Resolver;
039    import net.dpml.util.Logger;
040    
041    import org.w3c.dom.Element;
042    
043    /**
044     * Strategy handler for the component model.
045     *
046     * @author <a href="http://www.dpml.net">Digital Product Management Library</a>
047     * @version 2.1.0
048     */
049    public class ComponentStrategyHandler implements StrategyHandler
050    {
051       /**
052        * Constant XSD namespace value.
053        */
054        public static final String NAMESPACE = "dpml:metro";
055        
056        private static final DOM3DocumentBuilder BUILDER = new DOM3DocumentBuilder();
057        
058       /**
059        * Creation of a new component strategy handler for a supplied class.
060        * @param c the implementation class
061        * @return the new strategy handler
062        * @exception IOException if an error occurs in strategy creation
063        */
064        public Strategy newStrategy( Class<?> c ) throws IOException
065        {
066            return newStrategy( c, null );
067        }
068        
069       /**
070        * Creation of a new component strategy handler for a supplied class.
071        * @param c the implementation class
072        * @param name the strategy instance name
073        * @return the new strategy handler
074        * @exception IOException if an error occurs in strategy creation
075        */
076        public Strategy newStrategy( Class<?> c, String name ) throws IOException
077        {
078            String spec = getComponentName( c, name );
079            Profile profile = new Profile( c, spec, null );
080            ContextModel context = getContextModel( null, c, spec, profile, null, null, null, true );
081            PartsDirective parts = profile.getPartsDirective();
082            return new ComponentStrategy( 
083              null, spec, 0, c, ActivationPolicy.SYSTEM, LifestylePolicy.THREAD, 
084              CollectionPolicy.HARD, context, parts );
085        }
086        
087       /**
088        * Creation of a new component strategy handler for a supplied class.
089        * @param c the implementation class
090        * @param name the strategy instance name
091        * @return the new strategy handler
092        * @exception IOException if an error occurs in strategy creation
093        */
094        public Component newComponent( Class<?> c, String name ) throws IOException
095        {
096            return (Component) newStrategy( c, name );
097        }
098        
099       /**
100        * Construct a strategy definition using a supplied element and value resolver.
101        * @param classloader the operational classloader
102        * @param element the DOM element definining the deployment strategy
103        * @param resolver symbolic property resolver
104        * @param partition the enclosing partition
105        * @param query the query fragment to applied to the component context definition
106        * @param validate the validation policy
107        * @return the strategy definition
108        * @exception IOException if an I/O error occurs
109        */
110        public Strategy build( 
111          ClassLoader classloader, Element element, Resolver resolver, String partition, 
112          String query, boolean validate ) throws IOException
113        {
114            Class c = loadComponentClass( classloader, element, resolver );
115            String name = getComponentName( c, element, resolver );
116            int priority = getPriority( element, resolver );
117            String path = getComponentPath( partition, name );
118            Profile profile = new Profile( c, path, resolver );
119            
120            Query q = new Query( query );
121            Element contextElement = ElementHelper.getChild( element, "context" );
122            ContextModel context = 
123              getContextModel( 
124                classloader, c, path, profile, contextElement, resolver, q, validate );
125            ActivationPolicy activation = getActivationPolicy( element, profile, c );
126            LifestylePolicy lifestyle = getLifestylePolicy( element, profile, c );
127            CollectionPolicy collection = getCollectionPolicy( element, profile, c );
128            
129            try
130            {
131                Element partsElement = ElementHelper.getChild( element, "parts" );
132                PartsDirective parts = 
133                  new PartsDirective( 
134                    profile.getPartsDirective(), 
135                    classloader, 
136                    partsElement, 
137                    resolver, 
138                    path );
139                ComponentStrategy strategy = 
140                  new ComponentStrategy( 
141                    partition, 
142                    name, 
143                    priority, 
144                    c, 
145                    activation,
146                    lifestyle,
147                    collection,
148                    context, 
149                    parts );
150                strategy.setElement( element );
151                return strategy;
152            }
153            catch( IOException ioe )
154            {
155                throw ioe;
156            }
157            catch( Exception e )
158            {
159                final String error = 
160                  "Unable to construct component strategy for the class [" 
161                  + c.getName() 
162                  + "] under the partition ["
163                  + partition 
164                  + "]";
165                IOException ioe = new IOException( error );
166                ioe.initCause( e );
167                throw ioe;
168            }
169        }
170        
171       /**
172        * Return the activation policy.
173        * 
174        * @return the resolved activation policy
175        */
176        private static ActivationPolicy getActivationPolicy( Element element, Profile profile, Class<?> c )
177        {
178            if( null != element )
179            {
180                String value = ElementHelper.getAttribute( element, "activation" );
181                if( null != value )
182                {
183                    return ActivationPolicy.valueOf( value.toUpperCase() );
184                }
185            }
186            if( null != profile )
187            {
188                return profile.getActivationPolicy();
189            }
190            if( c.isAnnotationPresent( Activation.class ) )
191            {
192                Activation annotation = 
193                  c.getAnnotation( Activation.class );
194                return annotation.value();
195            }
196            return ActivationPolicy.SYSTEM;
197        }
198        
199       /**
200        * Return the lifestyle policy.
201        * 
202        * @return the resolved lifestyle policy
203        */
204        private static LifestylePolicy getLifestylePolicy( Element element, Profile profile, Class<?> c )
205        {
206            if( null != element )
207            {
208                String value = ElementHelper.getAttribute( element, "lifestyle" );
209                if( null != value )
210                {
211                    return LifestylePolicy.valueOf( value.toUpperCase() );
212                }
213            }
214            if( null != profile )
215            {
216                return profile.getLifestylePolicy();
217            }
218            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
219            {
220                net.dpml.annotation.Component annotation = 
221                  c.getAnnotation( net.dpml.annotation.Component.class );
222                return annotation.lifestyle();
223            }
224            return LifestylePolicy.THREAD;
225        }
226        
227       /**
228        * Return the collection policy.
229        * 
230        * @return the resolved collection policy
231        */
232        private static CollectionPolicy getCollectionPolicy( Element element, Profile profile, Class<?> c )
233        {
234            if( null != element )
235            {
236                String value = ElementHelper.getAttribute( element, "collection" );
237                if( null != value )
238                {
239                    return CollectionPolicy.valueOf( value.toUpperCase() );
240                }
241            }
242            if( null != profile )
243            {
244                return profile.getCollectionPolicy();
245            }
246            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
247            {
248                net.dpml.annotation.Component annotation = 
249                  c.getAnnotation( net.dpml.annotation.Component.class );
250                return annotation.collection();
251            }
252            return CollectionPolicy.HARD;
253        }
254        
255        static int getPriority( Element element, Resolver resolver )
256        {
257            String value = ElementHelper.getAttribute( element, "priority", null, resolver );
258            if( null == value )
259            {
260                return 0;
261            }
262            else
263            {
264                return Integer.parseInt( value );
265            }
266        }
267        
268        static Class loadComponentClass( ClassLoader classloader, Element element, Resolver resolver ) throws DecodingException
269        {
270            String classname = buildComponentClassname( element, resolver );
271            try
272            {
273                return classloader.loadClass( classname );
274            }
275            catch( ClassNotFoundException e )
276            {
277                final String error =
278                  "Class not found [" + classname + "].";
279                Logger logger = new DefaultLogger( "dpml.lang" );
280                String stack = StandardClassLoader.toString( classloader, null );
281                logger.error( error + stack );
282                throw new DecodingException( error, e, element );
283            }
284        }
285        
286        private static String buildComponentClassname( Element element, Resolver resolver )
287        {
288            String classname = ElementHelper.getAttribute( element, "class", null, resolver );
289            if( null != classname )
290            {
291                return classname;
292            }
293            else
294            {
295                return ElementHelper.getAttribute( element, "type", null, resolver );
296            }
297        }
298    
299        static String getComponentName( Class c, Element element, Resolver resolver )
300        {
301            String name = getComponentName( element, resolver );
302            return getComponentName( c, name );
303        }
304        
305        private static String getComponentName( Element element, Resolver resolver )
306        {
307            if( null == element )
308            {
309                return null;
310            }
311            else
312            {
313                String key = ElementHelper.getAttribute( element, "key", null, resolver );
314                if( null != key )
315                {
316                    return key;
317                }
318                else
319                {
320                    return ElementHelper.getAttribute( element, "name", null, resolver );
321                }
322            }
323        }
324    
325        private static String getComponentName( Class<?> c )
326        {
327            if( c.isAnnotationPresent( net.dpml.annotation.Component.class ) )
328            {
329                net.dpml.annotation.Component annotation = 
330                  c.getAnnotation( net.dpml.annotation.Component.class );
331                String name = annotation.name();
332                if( !"".equals( name ) )
333                {
334                    return name;
335                }
336            }
337            return c.getName();
338        }
339        
340        private static String getComponentNameFromClass( Class<?> c )
341        {
342            String classname = c.getName();
343            int n = classname.lastIndexOf( "." );
344            if( n > -1 )
345            {
346                return classname.substring( n+1 );
347            }
348            else
349            {
350                return classname;
351            }
352        }
353        
354        static String getComponentPath( String partition, String name )
355        {
356            if( null == partition )
357            {
358                return name;
359            }
360            else
361            {
362                return partition + "." + name;
363            }
364        }
365        
366        private static String getComponentName( Class c, String name )
367        {
368            if( null != name )
369            {
370                return name;
371            }
372            else
373            {
374                return  getComponentName( c );
375            }
376        }
377    
378        static Constructor getSingleConstructor( Class c )
379        {
380            Constructor[] constructors = c.getConstructors();
381            if( constructors.length < 1 )
382            {
383                final String error =
384                  "The component class ["
385                  + c.getName()
386                  + "] does not declare a public constructor.";
387                throw new ComponentError( error );
388            }
389            else if( constructors.length > 1 )
390            {
391                final String error =
392                  "The component class ["
393                  + c.getName()
394                  + "] declares more than one public constructor.";
395                throw new ComponentError( error );
396            }
397            else
398            {
399                return constructors[0];
400            }
401        }
402    
403        private static Class getContextClass( Constructor constructor, boolean policy )
404        {
405            final Class<?>[] types = constructor.getParameterTypes();
406            for( Class c : types )
407            {
408                if( ContextInvocationHandler.isaContext( c, policy ) )
409                {
410                    return c;
411                }
412            }
413            return null;
414        }
415    
416        private static ContextModel getContextModel( 
417          ClassLoader classloader, Class clazz, String path, 
418          Profile profile, Element element, Resolver resolver, Query query,
419          boolean validate  ) throws IOException
420        {
421            boolean policy = getContextHandlingPolicy( clazz );
422            Constructor constructor = getSingleConstructor( clazz );
423            Class subject = getContextClass( constructor, policy );
424            ContextDirective bundled = profile.getContextDirective();
425            ContextDirective declared = new ContextDirective( classloader, element, resolver );
426            return new ContextModel( clazz, path, subject, policy, bundled, declared, query, validate );
427        }
428        
429        private static boolean getContextHandlingPolicy( Class c )
430        {
431            // TODO: update to get policy as a class annotation
432            return false; // return classic evaluation policy
433        }
434    
435    }