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