/* * Copyright 2001-2006 Codehaus Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.codehaus.plexus.component.builder; import static org.apache.xbean.recipe.RecipeHelper.toClass; import org.apache.xbean.recipe.AbstractRecipe; import org.apache.xbean.recipe.ConstructionException; import org.apache.xbean.recipe.ObjectRecipe; import org.apache.xbean.recipe.Option; import org.apache.xbean.recipe.RecipeHelper; import org.codehaus.plexus.MutablePlexusContainer; import org.codehaus.plexus.PlexusConstants; import org.codehaus.plexus.PlexusContainer; import static org.codehaus.plexus.PlexusConstants.PLEXUS_DEFAULT_HINT; import org.codehaus.plexus.classworlds.realm.ClassRealm; import org.codehaus.plexus.component.MapOrientedComponent; import static org.codehaus.plexus.component.ComponentStack.setComponentStackProperty; import static org.codehaus.plexus.component.CastUtils.isAssignableFrom; import org.codehaus.plexus.component.collections.LiveMap; import org.codehaus.plexus.component.collections.LiveList; import org.codehaus.plexus.component.configurator.BasicComponentConfigurator; import org.codehaus.plexus.component.configurator.ComponentConfigurationException; import org.codehaus.plexus.component.configurator.ComponentConfigurator; import org.codehaus.plexus.component.configurator.converters.ConfigurationConverter; import org.codehaus.plexus.component.configurator.converters.composite.MapConverter; import org.codehaus.plexus.component.configurator.converters.lookup.ConverterLookup; import org.codehaus.plexus.component.configurator.converters.lookup.DefaultConverterLookup; import org.codehaus.plexus.component.configurator.converters.special.ClassRealmConverter; import org.codehaus.plexus.component.configurator.expression.DefaultExpressionEvaluator; import org.codehaus.plexus.component.factory.ComponentFactory; import org.codehaus.plexus.component.factory.ComponentInstantiationException; import org.codehaus.plexus.component.factory.UndefinedComponentFactoryException; import org.codehaus.plexus.component.factory.java.JavaComponentFactory; import org.codehaus.plexus.component.manager.ComponentManager; import org.codehaus.plexus.component.repository.ComponentDescriptor; import org.codehaus.plexus.component.repository.ComponentRequirement; import org.codehaus.plexus.component.repository.ComponentRequirementList; import org.codehaus.plexus.component.repository.exception.ComponentLifecycleException; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.codehaus.plexus.configuration.PlexusConfiguration; import org.codehaus.plexus.configuration.PlexusConfigurationException; import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.personality.plexus.lifecycle.phase.PhaseExecutionException; import org.codehaus.plexus.util.StringUtils; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class XBeanComponentBuilder<T> implements ComponentBuilder<T> { private ComponentManager<T> componentManager; public XBeanComponentBuilder() { } public XBeanComponentBuilder(ComponentManager<T> componentManager) { setComponentManager(componentManager); } public ComponentManager<T> getComponentManager() { return componentManager; } public void setComponentManager(ComponentManager<T> componentManager) { this.componentManager = componentManager; } protected MutablePlexusContainer getContainer() { return componentManager.getContainer(); } public T build(ComponentDescriptor<T> descriptor, ClassRealm realm, ComponentBuildListener listener) throws ComponentInstantiationException, ComponentLifecycleException { if (listener != null) { listener.beforeComponentCreate(descriptor, realm); } T component = createComponentInstance(descriptor, realm); if (listener != null) { listener.componentCreated(descriptor, component, realm); } startComponentLifecycle(component, realm); if (listener != null) { listener.componentConfigured(descriptor, component, realm); } return component; } protected T createComponentInstance(ComponentDescriptor<T> descriptor, ClassRealm realm) throws ComponentInstantiationException { MutablePlexusContainer container = getContainer(); if (realm == null) { realm = descriptor.getRealm(); } ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(realm); try { ObjectRecipe recipe = createObjectRecipe(descriptor, realm); T instance; ComponentFactory componentFactory = container.getComponentFactoryManager().findComponentFactory(descriptor.getComponentFactory()); if (JavaComponentFactory.class.equals(componentFactory.getClass())) { // xbean-reflect will create object and do injection instance = (T) recipe.create(); } else { // todo figure out how to easily let xbean use the factory to construct the component // use object factory to construct component and then inject into that object instance = (T) componentFactory.newInstance(descriptor, realm, container); recipe.setProperties( instance ); } // todo figure out how to easily let xbean do this map oriented stuff (if it is actually used in plexus) if ( instance instanceof MapOrientedComponent) { MapOrientedComponent mapOrientedComponent = (MapOrientedComponent) instance; processMapOrientedComponent(descriptor, mapOrientedComponent, realm); } return instance; } catch ( ConstructionException e ) { Throwable cause = unwrapConstructionException( e ); // do not rewrap if (cause instanceof ComponentInstantiationException) { throw (ComponentInstantiationException) cause; } // reuse original exception message.. the ones from XBean contain a lot of information if ( cause != null ) { // wrap real cause if we got one throw new ComponentInstantiationException( e.getMessage(), cause ); } else { throw new ComponentInstantiationException( e.getMessage() ); } } catch ( UndefinedComponentFactoryException e ) { throw new ComponentInstantiationException( e ); } finally { Thread.currentThread().setContextClassLoader( oldClassLoader ); } } public ObjectRecipe createObjectRecipe(ComponentDescriptor<T> descriptor, ClassRealm realm) throws ComponentInstantiationException { String factoryMethod = null; String[] constructorArgNames = null; Class[] constructorArgTypes = null; ObjectRecipe recipe = new ObjectRecipe(descriptor.getImplementationClass(), factoryMethod, constructorArgNames, constructorArgTypes); recipe.allow(Option.FIELD_INJECTION); recipe.allow(Option.PRIVATE_PROPERTIES); // MapOrientedComponents don't get normal injection if (!MapOrientedComponent.class.isAssignableFrom(descriptor.getImplementationClass())) { for (ComponentRequirement requirement : descriptor.getRequirements() ) { String name = requirement.getFieldName(); RequirementRecipe requirementRecipe = new RequirementRecipe(descriptor, requirement, getContainer(), name == null); if (name != null) { recipe.setProperty(name, requirementRecipe); } else { recipe.setAutoMatchProperty(requirement.getRole(), requirementRecipe); } } // add configuration data if (shouldConfigure(descriptor )) { PlexusConfiguration configuration = descriptor.getConfiguration(); if (configuration != null) { for (String name : configuration.getAttributeNames()) { String value; try { value = configuration.getAttribute(name); } catch (PlexusConfigurationException e) { throw new ComponentInstantiationException("Error getting value for attribute " + name, e); } name = fromXML(name); recipe.setProperty(name, value); } for (PlexusConfiguration child : configuration.getChildren()) { String name = child.getName(); name = fromXML(name); if ( child.getChildCount() > 0 ) { recipe.setProperty( name, new PlexusConfigurationRecipe( child ) ); } else if ( StringUtils.isNotEmpty( child.getValue() ) ) { recipe.setProperty( name, child.getValue() ); } } } } } return recipe; } protected boolean shouldConfigure( ComponentDescriptor<T> descriptor ) { String configuratorId = descriptor.getComponentConfigurator(); if (StringUtils.isEmpty(configuratorId)) { return true; } try { ComponentConfigurator componentConfigurator = getContainer().lookup(ComponentConfigurator.class, configuratorId); return componentConfigurator == null || componentConfigurator.getClass().equals(BasicComponentConfigurator.class); } catch (ComponentLookupException e) { } return true; } protected String fromXML(String elementName) { return StringUtils.lowercaseFirstLetter(StringUtils.removeAndHump(elementName, "-")); } protected void startComponentLifecycle(Object component, ClassRealm realm) throws ComponentLifecycleException { try { componentManager.start(component); } catch (Exception e) { Throwable cause = e; // if we got a PhaseExecutionException, unwrap it if ( e instanceof PhaseExecutionException && e.getCause() != null ) { cause = e.getCause(); } throw new ComponentLifecycleException("Error invoking start method", cause); } } public static class RequirementRecipe<T> extends AbstractRecipe { private ComponentDescriptor<T> componentDescriptor; private ComponentRequirement requirement; private MutablePlexusContainer container; private boolean autoMatch; public RequirementRecipe(ComponentDescriptor<T> componentDescriptor, ComponentRequirement requirement, MutablePlexusContainer container, boolean autoMatch) { this.componentDescriptor = componentDescriptor; this.requirement = requirement; this.container = container; this.autoMatch = autoMatch; } public boolean canCreate(Type expectedType) { if (!autoMatch) { return true; } Class<?> propertyType = toClass(expectedType); // Never auto match array, map or collection if (propertyType.isArray() || Map.class.isAssignableFrom(propertyType) || Collection.class.isAssignableFrom(propertyType) || requirement instanceof ComponentRequirementList) { return false; } // if the type to be created is an instance of the expected type, return true try { String roleHint = requirement.getRoleHint(); Class<?> roleType = getInterfaceClass( container, requirement.getRole(), roleHint ); for ( ComponentDescriptor<?> descriptor : container.getComponentDescriptorList( roleType ) ) { if ( descriptor.getRoleHint().equals( roleHint ) && isAssignableFrom( propertyType, descriptor.getImplementationClass() ) ) { return true; } } } catch (Exception e) { } return false; } @Override protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException { Class<?> propertyType = toClass(expectedType); // push requirement property name on the stack setComponentStackProperty( requirement.getFieldName() ); try { Class<?> roleType = getInterfaceClass( container, requirement.getRole(), requirement.getRoleHint() ); List<String> roleHints = null; if ( requirement instanceof ComponentRequirementList ) { roleHints = ( (ComponentRequirementList) requirement ).getRoleHints(); } Object assignment; if ( propertyType.isArray() ) { assignment = new ArrayList<Object>( container.lookupList( roleType, roleHints ) ); } // Map.class.isAssignableFrom( clazz ) doesn't make sense, since Map.class doesn't really // have a meaningful superclass. else { if ( Map.class.equals( propertyType ) ) { // todo this is a lazy map // get component type Type keyType = Object.class; Type valueType = Object.class; Type[] typeParameters = RecipeHelper.getTypeParameters( Collection.class, expectedType ); if ( typeParameters != null && typeParameters.length == 2 ) { if ( typeParameters[0] instanceof Class ) { keyType = typeParameters[0]; } if ( typeParameters[1] instanceof Class ) { valueType = typeParameters[1]; } } // if no generic type, load the roll as a class Class<?> valueClass = toClass( valueType ); if ( valueClass.equals( Object.class ) ) { valueClass = roleType; } // todo verify key type is String assignment = new LiveMap( container, roleType, roleHints, componentDescriptor.getHumanReadableKey() ); } // List.class.isAssignableFrom( clazz ) doesn't make sense, since List.class doesn't really // have a meaningful superclass other than Collection.class, which we'll handle next. else if ( List.class.equals( propertyType ) ) { // get component type Type[] typeParameters = RecipeHelper.getTypeParameters( Collection.class, expectedType ); Type componentType = Object.class; if ( typeParameters != null && typeParameters.length == 1 && typeParameters[0] instanceof Class ) { componentType = typeParameters[0]; } // if no generic type, load the roll as a class Class<?> componentClass = toClass( componentType ); if ( componentClass.equals( Object.class ) ) { componentClass = roleType; } assignment = new LiveList( container, roleType, roleHints, componentDescriptor.getHumanReadableKey() ); } // Set.class.isAssignableFrom( clazz ) doesn't make sense, since Set.class doesn't really // have a meaningful superclass other than Collection.class, and that would make this // if-else cascade unpredictable (both List and Set extend Collection, so we'll put another // check in for Collection.class. else if ( Set.class.equals( propertyType ) || Collection.class.isAssignableFrom( propertyType ) ) { // todo why isn't this lazy as above? assignment = container.lookupMap( roleType, roleHints ); } else if ( Logger.class.equals( propertyType ) ) { // todo magic reference types should not be handled here assignment = container.getLoggerManager().getLoggerForComponent( componentDescriptor.getRole() ); } else if ( PlexusContainer.class.equals( propertyType ) ) { // todo magic reference types should not be handled here assignment = container; } else { String roleHint = requirement.getRoleHint(); assignment = container.lookup( roleType, roleHint ); } } return assignment; } catch ( ComponentLookupException e ) { // simply wrap exception, so it can be unwrapped and rethrown throw new ConstructionException( e ); } finally { setComponentStackProperty( null ); } } @Override public String toString() { return "RequirementRecipe[fieldName=" + requirement.getFieldName() + ", role=" + componentDescriptor.getRole() + "]"; } } private class PlexusConfigurationRecipe extends AbstractRecipe { private final PlexusConfiguration child; public PlexusConfigurationRecipe( PlexusConfiguration child ) { this.child = child; } public boolean canCreate( Type type ) { try { ConverterLookup lookup = createConverterLookup(); lookup.lookupConverterForType( toClass( type ) ); return true; } catch ( ComponentConfigurationException e ) { return false; } } @Override protected Object internalCreate( Type expectedType, boolean lazyRefAllowed ) throws ConstructionException { setComponentStackProperty( child.getName() ); try { ConverterLookup lookup = createConverterLookup(); ConfigurationConverter converter = lookup.lookupConverterForType( toClass( expectedType ) ); // todo this will not work for static factories ObjectRecipe caller = (ObjectRecipe) RecipeHelper.getCaller(); Class parentClass = toClass( caller.getType() ); Object value = converter.fromConfiguration( lookup, child, toClass( expectedType ), parentClass, Thread.currentThread().getContextClassLoader(), new DefaultExpressionEvaluator() ); return value; } catch ( ComponentConfigurationException e ) { // simply wrap exception, so it can be unwrapped and rethrown throw new ConstructionException( e ); } finally { setComponentStackProperty( null ); } } private ConverterLookup createConverterLookup() { ClassRealm realm = (ClassRealm) Thread.currentThread().getContextClassLoader(); ConverterLookup lookup = new DefaultConverterLookup(); lookup.registerConverter( new ClassRealmConverter( realm ) ); return lookup; } } private void processMapOrientedComponent(ComponentDescriptor<?> descriptor, MapOrientedComponent mapOrientedComponent, ClassRealm realm) throws ComponentInstantiationException { MutablePlexusContainer container = getContainer(); for (ComponentRequirement requirement : descriptor.getRequirements()) { String role = requirement.getRole(); String hint = requirement.getRoleHint(); String mappingType = requirement.getFieldMappingType(); try { // if the hint is not empty (and not default), we don't care about mapping type... // it's a single-value, not a collection. Object value; if (StringUtils.isNotEmpty(hint) && !hint.equals(PlexusConstants.PLEXUS_DEFAULT_HINT)) { value = container.lookup(role, hint); } else if ("single".equals(mappingType)) { value = container.lookup(role, hint); } else if ("map".equals(mappingType)) { value = container.lookupMap(role); } else if ("set".equals(mappingType)) { value = new HashSet<Object>(container.lookupList(role)); } else { value = container.lookup(role, hint); } mapOrientedComponent.addComponentRequirement(requirement, value); } catch ( ComponentLookupException e ) { throw new ComponentInstantiationException( "Error looking up requirement of MapOrientedComponent ", e ); } catch ( ComponentConfigurationException e ) { throw new ComponentInstantiationException( "Error adding requirement to MapOrientedComponent ", e ); } } MapConverter converter = new MapConverter(); ConverterLookup converterLookup = new DefaultConverterLookup(); DefaultExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator(); PlexusConfiguration configuration = container.getConfigurationSource().getConfiguration( descriptor ); if ( configuration != null ) { try { Map context = (Map) converter.fromConfiguration(converterLookup, configuration, null, null, realm, expressionEvaluator, null ); mapOrientedComponent.setComponentConfiguration( context ); } catch ( ComponentConfigurationException e ) { throw new ComponentInstantiationException( "Error adding configuration to MapOrientedComponent ", e ); } } } private static Class<?> getInterfaceClass( PlexusContainer container, String role, String hint ) { if ( hint == null ) hint = PLEXUS_DEFAULT_HINT; try { ClassRealm realm = container.getLookupRealm(); if ( realm != null ) { return realm.loadClass( role ); } } catch ( Throwable e ) { } try { ClassLoader loader = Thread.currentThread().getContextClassLoader(); if ( loader != null ) { return loader.loadClass( role ); } } catch ( Throwable e ) { } try { ComponentDescriptor<?> cd = container.getComponentDescriptor( role, hint ); if ( cd != null ) { ClassLoader loader = cd.getImplementationClass().getClassLoader(); if ( loader != null ) { return loader.loadClass( role ); } } } catch ( Throwable ignored ) { } return Object.class; } /** * There are a few bugs in XBean reflect where a constuction exception is wrapped with another constuction exception. */ private static Throwable unwrapConstructionException( ConstructionException e ) { Throwable cause = e; while ( cause instanceof ConstructionException && e.getCause() != null) { cause = cause.getCause(); } return cause; } }