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