/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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.apache.cocoon.components; import org.apache.avalon.excalibur.component.ExcaliburComponentSelector; import org.apache.avalon.excalibur.component.RoleManager; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.configuration.DefaultConfiguration; /** * An extension of <code>ExcaliburComponentSelector</code> that can have a parent * and accepts a wider variety of configurations. * * @author <a href="mailto:sylvain@apache.org">Sylvain Wallez</a> * @version CVS $Id$ */ public class ExtendedComponentSelector extends ExcaliburComponentSelector implements ParentAware { /** The role manager */ protected RoleManager roles; /** The parent selector, if any */ protected ExtendedComponentSelector parentSelector; /** The parent locator, if any */ protected ComponentLocator parentLocator; /** The class loader to use */ protected ClassLoader classLoader; /** The role of this selector. Set in <code>configure()</code>. */ protected String roleName; /** The default hint */ protected String defaultHint; /** This selector's location (used for debugging purposes) */ private String location; /** Create the ComponentSelector with the Thread context ClassLoader */ public ExtendedComponentSelector() { this.classLoader = Thread.currentThread().getContextClassLoader(); } /** Create the ComponentSelector with a ClassLoader */ public ExtendedComponentSelector(ClassLoader loader) { super(loader); if (loader == null) { this.classLoader = Thread.currentThread().getContextClassLoader(); } else { this.classLoader = loader; } } /** * Get the name for component-instance elements (i.e. components not defined * by their role shortcut. If <code>null</code>, any element having a 'class' * attribute will be considered as a component instance. * <p> * The default here is to return <code>null</code>, and subclasses can redefine * this method to return particular values. * * @return <code>null</code>, but can be changed by subclasses */ protected String getComponentInstanceName() { return null; } /** * Get the name of the attribute giving the class name of a component. * The default here is "class", but this can be overriden in subclasses. * * @return "<code>class</code>", but can be changed by subclasses */ protected String getClassAttributeName() { return "class"; } /** * Get the name of the attribute giving the default hint to use if * none is given. The default here is "default", but this can be * overriden in subclasses. If this method returns <code>null</code>, * no default hint can be specified. * * @return "<code>default</code>", but can be changed by subclasses */ protected String getDefaultHintAttributeName() { return "default"; } /** * Configure the RoleManager. Redeclared only because parent member is private. */ public void setRoleManager(RoleManager roles) { super.setRoleManager(roles); this.roles = roles; } /** * Set the parent of this selector. This can be done after the selector is * initialized, but <em>only once</em>. This allows this selector to be * created by a component manager while still being able to have a parent. * * @param parent the parent selector * @throws IllegalStateException if parent is already set */ /* public void setParentSelector(ComponentSelector parent) { if (this.parentSelector != null) { throw new IllegalStateException("Parent selector is already set"); } this.parentSelector = parent; this.parentComponents = new HashSet(); } */ /** * Get the role name for this selector. This is called by <code>configure()</code> * to set the value of <code>this.roleName</code>. * * @return the role name, or <code>null<code> if it couldn't be determined. */ protected String getRoleName(Configuration config) { // Get the role for this selector String roleName = config.getAttribute("role", null); if (roleName == null && this.roles != null) { roleName = this.roles.getRoleForName(config.getName()); } return roleName; } /** * Configure this selector. This is the main difference with the parent class : * <ul> * <li>if {@link #getComponentInstanceName()} returns <code>null</code>, * any child configurations having a attribute named as the result of * {@link #getClassAttributeName()}, is considered as a component instance. * </li> * <li>if {@link #getComponentInstanceName()} returns a non-null value, * only child configurations having this name are considered as a * component instance. * </li> * <li>if other cases, it's name is considered to be a hint in the role manager. * The behaviour is then the same as <code>ExcaliburComponentSelector</code>. * </li> * * @param config the configuration * @throws ConfigurationException if some hints aren't defined */ public void configure(Configuration config) throws ConfigurationException { // Store location this.location = config.getLocation(); this.roleName = getRoleName(config); // Pass a copy of the top-level object to superclass so that // our name is properly initialized // FIXME : could be avoided if parent m_role was protected or had protected accessors DefaultConfiguration temp = new DefaultConfiguration(config.getName(), this.location); if (config.getAttribute("role", null) != null) { temp.setAttribute("role", this.roleName); } super.configure(temp); // Get default hint this.defaultHint = config.getAttribute(this.getDefaultHintAttributeName(), null); // Add components String compInstanceName = getComponentInstanceName(); Configuration[] instances = config.getChildren(); for (int i = 0; i < instances.length; i++) { Configuration instance = instances[i]; Object hint = instance.getAttribute("name").trim(); String classAttr = instance.getAttribute(getClassAttributeName(), null); String className; if (compInstanceName == null) { // component-instance implicitly defined by the presence of the 'class' attribute if (classAttr == null) { className = this.roles.getDefaultClassNameForHint(roleName, instance.getName()); } else { className = classAttr.trim(); } } else { // component-instances names explicitly defined if (compInstanceName.equals(instance.getName())) { className = (classAttr == null) ? null : classAttr.trim(); } else { className = this.roles.getDefaultClassNameForHint(roleName, instance.getName()); } } if (className == null) { String message = "Unable to determine class name for component named '" + hint + "' at " + instance.getLocation(); getLogger().error(message); throw new ConfigurationException(message); } try { Class clazz = this.classLoader.loadClass(className); addComponent(hint, clazz, instance); } catch (Exception e) { String message = "Could not load class " + className + " for component named '" + hint + "' at " + instance.getLocation(); getLogger().error(message, e); throw new ConfigurationException(message, e); } } } /** * Get the default hint, if any for this selector. */ public String getDefaultHint() { // Inherit parent default hint if have no own if (this.defaultHint == null && this.parentSelector != null) { return this.parentSelector.getDefaultHint(); } return this.defaultHint; } /* (non-Javadoc) * @see org.apache.avalon.framework.component.ComponentSelector#select(java.lang.Object) */ public Component select(Object hint) throws ComponentException { if (hint == null) { hint = this.defaultHint; } if (parentSelector == null) { // No parent: default behaviour return super.select(hint); } try { // Try in this selector first final Component component = super.select(hint); return component; } catch (ComponentException original) { try { // Doesn't exist here: try in parent selector final Component component = this.parentSelector.select(hint); return component; } catch (ComponentException nested) { // Doesn't exist in parent too: throw exception. if (nested.getCause() != null) { // Nested exception has a cause; let's throw it instead of original. throw nested; } // Throw original exception throw original; } } } /* (non-Javadoc) * @see org.apache.avalon.framework.component.ComponentSelector#release(org.apache.avalon.framework.component.Component) */ public void release(Component component) { // Was it selected on the parent ? if (this.parentSelector != null && this.parentSelector.canRelease(component)) { // Yes this.parentSelector.release(component); } else { // No super.release(component); } } /** * Does this selector or its parent have the given hint ? */ public boolean hasComponent(Object hint) { boolean exists = super.hasComponent(hint); if (!exists && this.parentSelector != null) { exists = this.parentSelector.hasComponent(hint); } return exists; } /** * Does this selector declare a given hint? Check is performed on the components declared for this * selector only, and <strong>not</strong> those potentially inherited from the parent selector. * * @param hint the hint to check for * @return <code>true</code> if this selector has the specified hint */ protected boolean hasDeclaredComponent(Object hint) { return super.hasComponent(hint); } /* (non-Javadoc) * @see org.apache.cocoon.components.ParentAware#setParentInformation(org.apache.avalon.framework.component.ComponentManager, java.lang.String) */ public void setParentLocator(ComponentLocator locator) throws ComponentException { if (this.parentSelector != null) { throw new ComponentException(null, "Parent selector is already set"); } this.parentLocator = locator; this.parentSelector = (ExtendedComponentSelector) locator.lookup(); } /* (non-Javadoc) * @see org.apache.avalon.framework.activity.Disposable#dispose() */ public void dispose() { super.dispose(); if (this.parentLocator != null) { this.parentLocator.release(this.parentSelector); this.parentLocator = null; this.parentSelector = null; } } /* (non-Javadoc) * @see org.apache.avalon.excalibur.component.ExcaliburComponentSelector#canRelease(org.apache.avalon.framework.component.Component) */ protected boolean canRelease(Component component) { if (this.parentSelector != null && this.parentSelector.canRelease(component)) { return true; } return super.canRelease(component); } }