View Javadoc
1   /*
2    * Copyright 2001-2006 Codehaus Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.codehaus.plexus.component.builder;
17  
18  import static org.apache.xbean.recipe.RecipeHelper.toClass;
19  
20  import org.apache.xbean.recipe.AbstractRecipe;
21  import org.apache.xbean.recipe.ConstructionException;
22  import org.apache.xbean.recipe.ObjectRecipe;
23  import org.apache.xbean.recipe.Option;
24  import org.apache.xbean.recipe.RecipeHelper;
25  import org.codehaus.plexus.MutablePlexusContainer;
26  import org.codehaus.plexus.PlexusConstants;
27  import org.codehaus.plexus.PlexusContainer;
28  import org.codehaus.plexus.ComponentRegistry;
29  import org.codehaus.plexus.classworlds.realm.ClassRealm;
30  import org.codehaus.plexus.component.MapOrientedComponent;
31  import org.codehaus.plexus.component.collections.ComponentList;
32  import org.codehaus.plexus.component.collections.ComponentMap;
33  import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
34  import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
35  import org.codehaus.plexus.component.configurator.ComponentConfigurator;
36  import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter;
37  import org.codehaus.plexus.component.configurator.converters.composite.MapConverter;
38  import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup;
39  import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup;
40  import org.codehaus.plexus.component.configurator.converters.special.ClassRealmConverter;
41  import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator;
42  import org.codehaus.plexus.component.factory.ComponentFactory;
43  import org.codehaus.plexus.component.factory.ComponentInstantiationException;
44  import org.codehaus.plexus.component.factory.java.JavaComponentFactory;
45  import org.codehaus.plexus.component.manager.ComponentManager;
46  import org.codehaus.plexus.component.repository.ComponentDescriptor;
47  import org.codehaus.plexus.component.repository.ComponentRequirement;
48  import org.codehaus.plexus.component.repository.ComponentRequirementList;
49  import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException;
50  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
51  import org.codehaus.plexus.configuration.PlexusConfiguration;
52  import org.codehaus.plexus.configuration.PlexusConfigurationException;
53  import org.codehaus.plexus.logging.Logger;
54  import org.codehaus.plexus.personality.plexus.lifecycle.phase.PhaseExecutionException;
55  import org.codehaus.plexus.util.StringUtils;
56  
57  import java.lang.reflect.Type;
58  import java.util.ArrayList;
59  import java.util.Collection;
60  import java.util.HashSet;
61  import java.util.List;
62  import java.util.Map;
63  import java.util.Set;
64  import java.util.LinkedHashSet;
65  
66  public class XBeanComponentBuilder<T> implements ComponentBuilder<T> {
67      private static final ThreadLocal<LinkedHashSet<ComponentDescriptor<?>>> STACK =
68          new ThreadLocal<LinkedHashSet<ComponentDescriptor<?>>>()
69          {
70              protected LinkedHashSet<ComponentDescriptor<?>> initialValue()
71              {
72                  return new LinkedHashSet<ComponentDescriptor<?>>();
73              }
74          };
75  
76      private ComponentManager<T> componentManager;
77  
78      public XBeanComponentBuilder() {
79      }
80  
81      public XBeanComponentBuilder(ComponentManager<T> componentManager) {
82          setComponentManager(componentManager);
83      }
84  
85      public ComponentManager<T> getComponentManager() {
86          return componentManager;
87      }
88  
89      public void setComponentManager(ComponentManager<T> componentManager) {
90          this.componentManager = componentManager;
91      }
92  
93      protected MutablePlexusContainer getContainer() {
94          return componentManager.getContainer();
95      }
96  
97      public T build( ComponentDescriptor<T> descriptor, ClassRealm realm, ComponentBuildListener listener )
98          throws ComponentInstantiationException, ComponentLifecycleException
99      {
100         LinkedHashSet<ComponentDescriptor<?>> stack = STACK.get();
101         if ( stack.contains( descriptor ) )
102         {
103             // create list of circularity
104             List<ComponentDescriptor<?>> circularity = new ArrayList<ComponentDescriptor<?>>( stack );
105             circularity.subList( circularity.indexOf( descriptor ), circularity.size() );
106             circularity.add( descriptor );
107 
108             // nice circularity message
109             String message = "Creation circularity: ";
110             for ( ComponentDescriptor<?> componentDescriptor : circularity )
111             {
112                 message += "\n\t[" + componentDescriptor.getRole() + ", " + componentDescriptor.getRoleHint() + "]";
113             }
114             throw new ComponentInstantiationException( message );
115         }
116         stack.add( descriptor );
117         try
118         {
119             if (listener != null) {
120                 listener.beforeComponentCreate(descriptor, realm);
121             }
122 
123             T component = createComponentInstance(descriptor, realm);
124 
125             if (listener != null) {
126                 listener.componentCreated(descriptor, component, realm);
127             }
128 
129             startComponentLifecycle(component, realm);
130 
131             if (listener != null) {
132                 listener.componentConfigured(descriptor, component, realm);
133             }
134 
135             return component;
136         }
137         finally
138         {
139             stack.remove( descriptor );
140         }
141     }
142 
143     protected T createComponentInstance(ComponentDescriptor<T> descriptor, ClassRealm realm) throws ComponentInstantiationException, ComponentLifecycleException {
144         MutablePlexusContainer container = getContainer();
145         if (realm == null) {
146             realm = descriptor.getRealm();
147         }
148 
149         ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
150         Thread.currentThread().setContextClassLoader(realm);
151         try {
152             ObjectRecipe recipe;
153 
154             T instance;
155             ComponentFactory componentFactory = container.getComponentFactoryManager().findComponentFactory(descriptor.getComponentFactory());
156             if (JavaComponentFactory.class.equals(componentFactory.getClass())) {
157                 // xbean-reflect will create object and do injection
158                 recipe = createObjectRecipe( null, descriptor, realm );
159                 instance = (T) recipe.create();
160             } else {
161                 // todo figure out how to easily let xbean use the factory to construct the component
162                 // use object factory to construct component and then inject into that object
163                 instance = (T) componentFactory.newInstance(descriptor, realm, container);
164                 recipe = createObjectRecipe( instance, descriptor, realm );
165                 recipe.setProperties( instance );
166             }
167 
168             // todo figure out how to easily let xbean do this map oriented stuff (if it is actually used in plexus)
169             if ( instance instanceof MapOrientedComponent) {
170                 MapOrientedComponent mapOrientedComponent = (MapOrientedComponent) instance;
171                 processMapOrientedComponent(descriptor, mapOrientedComponent, realm);
172             }
173 
174             return instance;
175         } catch (Exception e) {
176             throw new ComponentLifecycleException("Error constructing component " + descriptor.getHumanReadableKey(), e);
177         } catch (LinkageError e) {
178             throw new ComponentLifecycleException("Error constructing component " + descriptor.getHumanReadableKey(), e);
179         } finally {
180             Thread.currentThread().setContextClassLoader(oldClassLoader);
181         }
182     }
183 
184     public ObjectRecipe createObjectRecipe(T instance, ComponentDescriptor<T> descriptor, ClassRealm realm) throws ComponentInstantiationException, PlexusConfigurationException {
185         String factoryMethod = null;
186         String[] constructorArgNames = null;
187         Class[] constructorArgTypes = null;
188 
189         Class<?> implClass = ( instance != null ) ? instance.getClass() : descriptor.getImplementationClass();
190 
191         if ( implClass == null || implClass == Object.class )
192         {
193             // if the descriptor could not load the class, it's time to report this up to the caller now
194             try
195             {
196                 realm.loadClass( descriptor.getImplementation() );
197             }
198             catch ( ClassNotFoundException e )
199             {
200                 throw new ComponentInstantiationException( "Could not load implementation class for component "
201                     + descriptor.getHumanReadableKey() + " from class realm " + realm, e );
202             }
203             catch ( LinkageError e )
204             {
205                 throw new ComponentInstantiationException( "Could not load implementation class for component "
206                     + descriptor.getHumanReadableKey() + " from class realm " + realm, e );
207             }
208         }
209 
210         ObjectRecipe recipe = new ObjectRecipe( implClass,
211                 factoryMethod,
212                 constructorArgNames,
213                 constructorArgTypes);
214         recipe.allow(Option.FIELD_INJECTION);
215         recipe.allow(Option.PRIVATE_PROPERTIES);
216 
217         // MapOrientedComponents don't get normal injection
218         if (!MapOrientedComponent.class.isAssignableFrom( implClass )) {
219             for (ComponentRequirement requirement : descriptor.getRequirements() ) {
220                 String name = requirement.getFieldName();
221                 RequirementRecipe requirementRecipe = new RequirementRecipe(descriptor, requirement, getContainer(), name == null);
222 
223                 if (name != null) {
224                     recipe.setProperty(name, requirementRecipe);
225                 } else {
226                     recipe.setAutoMatchProperty(requirement.getRole(), requirementRecipe);
227                 }
228             }
229 
230             // add configuration data
231             if (shouldConfigure(descriptor )) {
232                 PlexusConfiguration configuration = descriptor.getConfiguration();
233                 if (configuration != null) {
234                     for (String name : configuration.getAttributeNames()) {
235                         String value;
236                         try {
237                             value = configuration.getAttribute(name);
238                         } catch (PlexusConfigurationException e) {
239                             throw new ComponentInstantiationException("Error getting value for attribute " + name, e);
240                         }
241                         name = fromXML(name);
242                         recipe.setProperty(name, value);
243                     }
244                     for (PlexusConfiguration child : configuration.getChildren()) {
245                         String name = child.getName();
246                         name = fromXML(name);
247                         if ( StringUtils.isNotEmpty( child.getValue( null ) ) )
248                         {
249                             recipe.setProperty( name, child.getValue() );
250                         }
251                         else
252                         {
253                             recipe.setProperty( name, new PlexusConfigurationRecipe( child ) );
254                         }
255                     }
256                 }
257             }
258         }
259         return recipe;
260     }
261 
262     protected boolean shouldConfigure( ComponentDescriptor<T> descriptor ) {
263         String configuratorId = descriptor.getComponentConfigurator();
264 
265         if (StringUtils.isEmpty(configuratorId)) {
266             return true;
267         }
268 
269         try {
270             ComponentConfigurator componentConfigurator = getContainer().lookup(ComponentConfigurator.class, configuratorId);
271             return componentConfigurator == null || componentConfigurator.getClass().equals(BasicComponentConfigurator.class);
272         } catch (ComponentLookupException e) {
273         }
274 
275         return true;
276     }
277     protected String fromXML(String elementName) {
278         return StringUtils.lowercaseFirstLetter(StringUtils.removeAndHump(elementName, "-"));
279     }
280 
281     protected void startComponentLifecycle(Object component, ClassRealm realm) throws ComponentLifecycleException {
282         try {
283             componentManager.start(component);
284         } catch (PhaseExecutionException e) {
285             throw new ComponentLifecycleException("Error starting component", e);
286         }
287     }
288 
289     public static class RequirementRecipe<T> extends AbstractRecipe {
290         private ComponentDescriptor<T> componentDescriptor;
291         private ComponentRequirement requirement;
292         private MutablePlexusContainer container;
293         private boolean autoMatch;
294 
295         public RequirementRecipe(ComponentDescriptor<T> componentDescriptor, ComponentRequirement requirement, MutablePlexusContainer container, boolean autoMatch) {
296             this.componentDescriptor = componentDescriptor;
297             this.requirement = requirement;
298             this.container = container;
299             this.autoMatch = autoMatch;
300         }
301 
302         public boolean canCreate(Type expectedType) {
303             if (!autoMatch)
304             {
305                 return true;
306             }
307 
308             Class<?> propertyType = toClass(expectedType);
309 
310             // Never auto match array, map or collection
311             if (propertyType.isArray() || Map.class.isAssignableFrom(propertyType) || Collection.class.isAssignableFrom(propertyType) || requirement instanceof ComponentRequirementList) {
312                 return false;
313             }
314 
315             // if the type to be created is an instance of the expected type, return true
316             try {
317                 ComponentRegistry componentRegistry = container.getComponentRegistry();
318 
319                 return componentRegistry.getComponentDescriptor(propertyType, requirement.getRole(), requirement.getRoleHint()) != null;
320             } catch (Exception e) {
321             }
322 
323             return false;
324         }
325 
326         @Override
327         protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
328             Class<?> propertyType = toClass(expectedType);
329 
330             try {
331                 String role = requirement.getRole();
332                 List<String> roleHints = null;
333                 if (requirement instanceof ComponentRequirementList) {
334                     roleHints = ((ComponentRequirementList) requirement).getRoleHints();
335                 }
336 
337                 Object assignment;
338                 if (propertyType.isArray()) {
339                     assignment = new ArrayList<Object>(container.lookupList(role, roleHints));
340                 }
341 
342                 // Map.class.isAssignableFrom( clazz ) doesn't make sense, since Map.class doesn't really
343                 // have a meaningful superclass.
344                 else {
345                     if (Map.class.equals(propertyType)) {
346                         // todo this is a lazy map
347 
348                         // get component type
349                         Type keyType = Object.class;
350                         Type valueType = Object.class;
351                         Type[] typeParameters = RecipeHelper.getTypeParameters(Collection.class, expectedType);
352                         if (typeParameters != null && typeParameters.length == 2) {
353                             if (typeParameters[0] instanceof Class) {
354                                 keyType = typeParameters[0];
355                             }
356                             if (typeParameters[1] instanceof Class) {
357                                 valueType = typeParameters[1];
358                             }
359                         }
360 
361                         // todo verify key type is String
362 
363                         assignment = new ComponentMap(container,
364                                 toClass(valueType),
365                                 role,
366                                 roleHints,
367                                 componentDescriptor.getHumanReadableKey());
368                     }
369                     // List.class.isAssignableFrom( clazz ) doesn't make sense, since List.class doesn't really
370                     // have a meaningful superclass other than Collection.class, which we'll handle next.
371                     else if (List.class.equals(propertyType)) {
372                         // todo this is a lazy list
373 
374                         // get component type
375                         Type[] typeParameters = RecipeHelper.getTypeParameters(Collection.class, expectedType);
376                         Type componentType = Object.class;
377                         if (typeParameters != null && typeParameters.length == 1 && typeParameters[0] instanceof Class) {
378                             componentType = typeParameters[0];
379                         }
380 
381                         assignment = new ComponentList(container,
382                                 toClass( componentType ),
383                                 role,
384                                 roleHints,
385                                 componentDescriptor.getHumanReadableKey());
386                     }
387                     // Set.class.isAssignableFrom( clazz ) doesn't make sense, since Set.class doesn't really
388                     // have a meaningful superclass other than Collection.class, and that would make this
389                     // if-else cascade unpredictable (both List and Set extend Collection, so we'll put another
390                     // check in for Collection.class.
391                     else if (Set.class.equals(propertyType) || Collection.class.isAssignableFrom(propertyType)) {
392                         // todo why isn't this lazy as above?
393                         assignment = container.lookupMap(role, roleHints);
394                     } else if (Logger.class.equals(propertyType)) {
395                         // todo magic reference
396                         assignment = container.getLoggerManager().getLoggerForComponent(componentDescriptor.getRole());
397                     } else if (PlexusContainer.class.equals(propertyType)) {
398                         // todo magic reference
399                         assignment = container;
400                     } else {
401                         String roleHint = requirement.getRoleHint();
402                         assignment = container.lookup(propertyType, role, roleHint);
403                     }
404                 }
405 
406                 return assignment;
407             } catch (ComponentLookupException e) {
408                 if ( requirement.isOptional() )
409                 {
410                     return null;
411                 }
412 
413                 throw new ConstructionException("Composition failed of field " + requirement.getFieldName() + " "
414                         + "in object of type " + componentDescriptor.getImplementation() + " because the requirement "
415                         + requirement + " was missing)", e);
416             }
417         }
418 
419         @Override
420         public String toString() {
421             return "RequirementRecipe[fieldName=" + requirement.getFieldName() + ", role=" + componentDescriptor.getRole() + "]";
422         }
423     }
424 
425     private class PlexusConfigurationRecipe extends AbstractRecipe {
426         private final PlexusConfiguration child;
427 
428         public PlexusConfigurationRecipe(PlexusConfiguration child) {
429             this.child = child;
430         }
431 
432         public boolean canCreate(Type type) {
433             try {
434                 ConverterLookup lookup = createConverterLookup();
435                 lookup.lookupConverterForType(toClass(type));
436                 return true;
437             } catch (ComponentConfigurationException e) {
438                 return false;
439             }
440         }
441 
442         @Override
443         protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
444             try {
445                 ConverterLookup lookup = createConverterLookup();
446                 ConfigurationConverter converter = lookup.lookupConverterForType(toClass(expectedType));
447 
448                 // todo this will not work for static factories
449                 ObjectRecipe caller = (ObjectRecipe) RecipeHelper.getCaller();
450                 Class parentClass = toClass(caller.getType());
451 
452                 Object value = converter.fromConfiguration(lookup, child, toClass(expectedType), parentClass, Thread.currentThread().getContextClassLoader(), new DefaultExpressionEvaluator());
453                 return value;
454             } catch (ComponentConfigurationException e) {
455                 throw new ConstructionException("Unable to convert configuration for property " + child.getName() + " to " + toClass(expectedType).getName());
456             }
457         }
458 
459         private ConverterLookup createConverterLookup() {
460             ClassRealm realm = (ClassRealm) Thread.currentThread().getContextClassLoader();
461             ConverterLookup lookup = new DefaultConverterLookup();
462             lookup.registerConverter( new ClassRealmConverter(realm) );
463             return lookup;
464         }
465     }
466 
467 
468     private void processMapOrientedComponent(ComponentDescriptor<?> descriptor, MapOrientedComponent mapOrientedComponent, ClassRealm realm) throws ComponentConfigurationException, ComponentLookupException {
469         MutablePlexusContainer container = getContainer();
470 
471         for (ComponentRequirement requirement : descriptor.getRequirements()) {
472             String role = requirement.getRole();
473             String hint = requirement.getRoleHint();
474             String mappingType = requirement.getFieldMappingType();
475 
476             Object value;
477 
478             // if the hint is not empty (and not default), we don't care about mapping type...
479             // it's a single-value, not a collection.
480             if (StringUtils.isNotEmpty(hint) && !hint.equals(PlexusConstants.PLEXUS_DEFAULT_HINT)) {
481                 value = container.lookup(role, hint);
482             } else if ("single".equals(mappingType)) {
483                 value = container.lookup(role, hint);
484             } else if ("map".equals(mappingType)) {
485                 value = container.lookupMap(role);
486             } else if ("set".equals(mappingType)) {
487                 value = new HashSet<Object>(container.lookupList(role));
488             } else {
489                 value = container.lookup(role, hint);
490             }
491 
492             mapOrientedComponent.addComponentRequirement(requirement, value);
493         }
494 
495         MapConverter converter = new MapConverter();
496         ConverterLookup converterLookup = new DefaultConverterLookup();
497         DefaultExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
498         PlexusConfiguration configuration = container.getConfigurationSource().getConfiguration( descriptor );
499 
500         if ( configuration != null )
501         {
502             Map context = (Map) converter.fromConfiguration(converterLookup,
503                                                             configuration,
504                                                             null,
505                                                             null,
506                                                             realm,
507                                                             expressionEvaluator,
508                                                             null );
509 
510             mapOrientedComponent.setComponentConfiguration( context );
511         }
512     }
513 }