1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
99 List<ComponentDescriptor<?>> circularity = new ArrayList<ComponentDescriptor<?>>(stack);
100 circularity.subList(circularity.indexOf(descriptor), circularity.size());
101 circularity.add(descriptor);
102
103
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
151 recipe = createObjectRecipe(null, descriptor, realm);
152 instance = (T) recipe.create();
153 } else {
154
155
156 instance = (T) componentFactory.newInstance(descriptor, realm, container);
157 recipe = createObjectRecipe(instance, descriptor, realm);
158 recipe.setProperties(instance);
159 }
160
161
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
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
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
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
306 if (propertyType.isArray()
307 || Map.class.isAssignableFrom(propertyType)
308 || Collection.class.isAssignableFrom(propertyType)
309 || requirement instanceof ComponentRequirementList) {
310 return false;
311 }
312
313
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
343
344 else {
345 if (Map.class.equals(propertyType)) {
346
347
348
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
362
363 assignment = new ComponentMap(
364 container,
365 toClass(valueType),
366 role,
367 roleHints,
368 componentDescriptor.getHumanReadableKey());
369 }
370
371
372 else if (List.class.equals(propertyType)) {
373
374
375
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
392
393
394
395 else if (Set.class.equals(propertyType) || Collection.class.isAssignableFrom(propertyType)) {
396
397 assignment = container.lookupMap(role, roleHints);
398 } else if (Logger.class.equals(propertyType)) {
399
400 assignment = container.getLoggerManager().getLoggerForComponent(componentDescriptor.getRole());
401 } else if (PlexusContainer.class.equals(propertyType)) {
402
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
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
494
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 }