blob: bef2957a49adef0b5172bd495f6d8b0fdcf4f519 [file] [log] [blame]
/*
* Copyright 2002-2005 The Apache Software 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.apache.cocoon.core.container;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.avalon.excalibur.logger.LoggerManager;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.container.ContainerUtil;
import org.apache.avalon.framework.context.Context;
import org.apache.avalon.framework.context.Contextualizable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.ServiceSelector;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.components.ComponentInfo;
import org.apache.cocoon.components.Preloadable;
import org.apache.cocoon.core.container.handler.AbstractComponentHandler;
import org.apache.cocoon.core.container.handler.ComponentHandler;
import org.apache.cocoon.util.JMXUtils;
/**
* Default component selector for Cocoon's components.
*
* @version $Id$
* @since 2.2
*/
public class StandaloneServiceSelector
extends AbstractLogEnabled
implements Preloadable,
ServiceSelector,
Serviceable,
Configurable,
Disposable,
Initializable,
Contextualizable {
/** The application context for components
*/
protected ServiceManager serviceManager;
/** The parent selector, if any */
protected StandaloneServiceSelector parentSelector;
/** The parent locator, if any */
protected ServiceManager parentLocator;
/** The role of this selector. Set in <code>configure()</code>. */
protected String roleName;
/** The default key */
protected String defaultKey;
/** The application context for components */
protected Context context;
/** Static component mapping handlers. */
protected final Map componentMapping = Collections.synchronizedMap(new HashMap());
/** Used to map roles to ComponentHandlers. */
protected final Map componentHandlers = Collections.synchronizedMap(new HashMap());
/** Is the Manager disposed or not? */
protected boolean disposed;
/** Is the Manager initialized? */
protected boolean initialized;
/** RoleInfos. */
protected RoleManager roleManager;
/** LoggerManager. */
protected LoggerManager loggerManager;
protected ComponentEnvironment componentEnv;
/**
* @see org.apache.avalon.framework.context.Contextualizable#contextualize(org.apache.avalon.framework.context.Context)
*/
public void contextualize( final Context context ) {
this.context = context;
}
public void setRoleManager( final RoleManager roles ) {
this.roleManager = roles;
}
/**
* Configure the LoggerManager.
*/
public void setLoggerManager( final LoggerManager manager ) {
this.loggerManager = manager;
}
/**
* Obtain a new ComponentHandler for the specified component.
*
* @param role the component's role.
* @param componentClass Class of the component for which the handle is
* being requested.
* @param configuration The configuration for this component.
* @param serviceManager The service manager which will be managing the Component.
*
* @throws Exception If there were any problems obtaining a ComponentHandler
*/
protected ComponentHandler getComponentHandler( final String role,
final Class componentClass,
final Configuration configuration,
final ServiceManager serviceManager,
final ComponentInfo baseInfo)
throws Exception {
if (this.componentEnv == null) {
this.componentEnv = new ComponentEnvironment(null, getLogger(), this.roleManager,
this.loggerManager, this.context, serviceManager);
}
// FIXME - we should always get an info here
ComponentInfo info;
if ( baseInfo != null ) {
info = baseInfo.duplicate();
} else {
info = new ComponentInfo();
info.fill(configuration);
info.setRole(role);
info.setJmxDomain(JMXUtils.findJmxDomain(info.getJmxDomain(), serviceManager));
info.setJmxName(JMXUtils.findJmxName(info.getJmxName(), componentClass.getName()));
}
info.setConfiguration(configuration);
info.setServiceClassName(componentClass.getName());
return AbstractComponentHandler.getComponentHandler(role,
this.componentEnv,
info);
}
protected void addComponent(String className,
String role,
Configuration configuration,
ComponentInfo info)
throws ConfigurationException {
// check for old excalibur class names - we only test against the selector
// implementation
if ( "org.apache.cocoon.components.ExtendedComponentSelector".equals(className)) {
className = DefaultServiceSelector.class.getName();
}
try {
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( "Adding component (" + role + " = " + className + ")" );
}
// FIXME - use different classloader
final Class clazz = this.getClass().getClassLoader().loadClass( className );
this.addComponent( role, clazz, configuration, info );
} catch( final ClassNotFoundException cnfe ) {
final String message = "Could not get class (" + className + ") for role "
+ role + " at " + configuration.getLocation();
if( this.getLogger().isErrorEnabled() ) {
this.getLogger().error( message, cnfe );
}
throw new ConfigurationException( message, cnfe );
} catch( final ServiceException ce ) {
final String message = "Cannot setup class "+ className + " for role " + role
+ " at " + configuration.getLocation();
if( this.getLogger().isErrorEnabled() ) {
this.getLogger().error( message, ce );
}
throw new ConfigurationException( message, ce );
} catch( final Exception e ) {
final String message = "Unexpected exception when setting up role " + role + " at " + configuration.getLocation();
if( this.getLogger().isErrorEnabled() ) {
this.getLogger().error( message, e );
}
throw new ConfigurationException( message, e );
}
}
/**
* @see org.apache.avalon.framework.service.ServiceSelector#select(java.lang.Object)
*/
public Object select( Object hint )
throws ServiceException {
final String key;
if (hint == null) {
key = this.defaultKey;
} else {
key = hint.toString();
}
if( !this.initialized ) {
if( this.getLogger().isWarnEnabled() ) {
this.getLogger().warn( "Selecting a component on an uninitialized service selector "
+ "with key [" + key + "]" );
}
}
if( this.disposed ) {
throw new IllegalStateException(
"You cannot select a component from a disposed service selector." );
}
ComponentHandler handler = (ComponentHandler)this.componentHandlers.get( key );
// Retrieve the instance of the requested component
if( null == handler ) {
// Doesn't exist here : try in parent selector
if ( this.parentSelector != null ) {
return this.parentSelector.select(key);
}
final String message = this.roleName
+ ": service selector could not find the component for key [" + key + "]";
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( message );
}
throw new ServiceException( key, message );
}
Object component = null;
try {
component = handler.get();
} catch( final ServiceException ce ) {
//rethrow
throw ce;
} catch( final Exception e ) {
final String message = this.roleName
+ ": service selector could not access the component for key [" + key + "]";
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( message, e );
}
throw new ServiceException( key, message, e );
}
if( null == component ) {
// Doesn't exist here : try in parent selector
if ( this.parentSelector != null ) {
component = this.parentSelector.select(key);
} else {
final String message = this.roleName
+ ": service selector could not find the component for key [" + key + "]";
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( message );
}
throw new ServiceException( key, message );
}
}
this.componentMapping.put( component, handler );
return component;
}
/**
* @see org.apache.avalon.framework.service.ServiceSelector#isSelectable(java.lang.Object)
*/
public boolean isSelectable( Object hint ) {
final String key;
if (hint == null) {
key = this.defaultKey;
} else {
key = hint.toString();
}
if( !this.initialized ) return false;
if( this.disposed ) return false;
boolean exists = false;
try {
ComponentHandler handler = (ComponentHandler)this.componentHandlers.get( key );
exists = (handler != null);
} catch( Throwable t ) {
// We can safely ignore all exceptions
}
if ( !exists && this.parentSelector != null ) {
exists = this.parentSelector.isSelectable( key );
}
return exists;
}
/**
* @see org.apache.avalon.framework.service.ServiceSelector#release(java.lang.Object)
*/
public void release( final Object component ) {
if( null == component ) {
return;
}
// Was it selected on the parent ?
if ( this.parentSelector != null &&
this.parentSelector.canRelease(component) ) {
this.parentSelector.release(component);
} else {
final ComponentHandler handler =
(ComponentHandler)this.componentMapping.get( component );
if( null == handler ) {
this.getLogger().warn( "Attempted to release a " + component.getClass().getName()
+ " but its handler could not be located." );
return;
}
// ThreadSafe components will always be using a ThreadSafeComponentHandler,
// they will only have a single entry in the m_componentMapping map which
// should not be removed until the ComponentLocator is disposed. All
// other components have an entry for each instance which should be
// removed.
if( !handler.isSingleton() ) {
// Remove the component before calling put. This is critical to avoid the
// problem where another thread calls put on the same component before
// remove can be called.
this.componentMapping.remove( component );
}
try {
handler.put( component );
} catch( Exception e ) {
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( "Error trying to release component", e );
}
}
}
}
/**
* @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
*/
public void service( final ServiceManager componentManager )
throws ServiceException {
this.serviceManager = componentManager;
}
/**
* @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
*/
public void configure( final Configuration config )
throws ConfigurationException {
this.roleName = getRoleName(config);
// Get default key
this.defaultKey = config.getAttribute(this.getDefaultKeyAttributeName(), null);
// Add components
String compInstanceName = getComponentInstanceName();
Configuration[] instances = config.getChildren();
for (int i = 0; i < instances.length; i++) {
Configuration instance = instances[i];
ComponentInfo info = null;
String key = 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) {
info = this.roleManager.getDefaultServiceInfoForKey(roleName, instance.getName());
className = info.getServiceClassName();
} else {
className = classAttr.trim();
}
} else {
// component-instances names explicitly defined
if (compInstanceName.equals(instance.getName())) {
className = (classAttr == null) ? null : classAttr.trim();
} else {
info = this.roleManager.getDefaultServiceInfoForKey(roleName, instance.getName());
className = info.getServiceClassName();
}
}
if (className == null) {
String message = "Unable to determine class name for component named '" + key +
"' at " + instance.getLocation();
getLogger().error(message);
throw new ConfigurationException(message);
}
this.addComponent( className, key, instance, info );
}
}
/**
* @see org.apache.avalon.framework.activity.Initializable#initialize()
*/
public void initialize()
throws Exception {
this.initialized = true;
List keys = new ArrayList( this.componentHandlers.keySet() );
for( int i = 0; i < keys.size(); i++ ) {
final Object key = keys.get( i );
final ComponentHandler handler =
(ComponentHandler)this.componentHandlers.get( key );
try {
handler.initialize();
} catch( Exception e ) {
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug( "Caught an exception trying to initialize "
+ "of the component handler.", e );
}
}
}
}
/**
* @see org.apache.avalon.framework.activity.Disposable#dispose()
*/
public void dispose() {
Iterator keys = this.componentHandlers.keySet().iterator();
List keyList = new ArrayList();
while( keys.hasNext() ) {
Object key = keys.next();
ComponentHandler handler =
(ComponentHandler)this.componentHandlers.get( key );
handler.dispose();
keyList.add( key );
}
keys = keyList.iterator();
while( keys.hasNext() ) {
this.componentHandlers.remove( keys.next() );
}
keyList.clear();
if ( this.parentLocator != null ) {
this.parentLocator.release( this.parentSelector );
this.parentLocator = null;
this.parentSelector = null;
}
this.disposed = true;
}
/** Add a new component to the manager.
* @param key the key for the new component.
* @param component the class of this component.
* @param configuration the configuration for this component.
*/
public void addComponent( final String key,
final Class component,
final Configuration configuration,
final ComponentInfo info)
throws ServiceException {
if( this.initialized ) {
throw new ServiceException( key,
"Cannot add components to an initialized service selector" );
}
try {
final ComponentHandler handler = getComponentHandler( null,
component,
configuration,
this.serviceManager,
info);
handler.initialize();
this.componentHandlers.put( key, handler );
if( this.getLogger().isDebugEnabled() ) {
this.getLogger().debug(
"Adding " + component.getName() + " for key [" + key + "]" );
}
} catch (ServiceException se) {
throw se;
} catch( final Exception e ) {
final String message =
"Could not set up component for key [ " + key + "]";
if( this.getLogger().isErrorEnabled() ) {
this.getLogger().error( message, e );
}
throw new ServiceException(key, message, e );
}
}
/**
* 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 key 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 key can be specified.
*
* @return "<code>default</code>", but can be changed by subclasses
*/
protected String getDefaultKeyAttributeName() {
return "default";
}
/**
* 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 name = config.getAttribute("role", null);
if (name == null && this.roleManager != null) {
name = this.roleManager.getRoleForName(config.getName());
}
return name;
}
/**
* Set the ComponentLocatorImpl that allows access to a possible
* parent of this selector
* @param locator
* @throws ServiceException
*/
void setParentLocator(ServiceManager locator, String role)
throws ServiceException {
if (this.parentSelector != null) {
throw new ServiceException(null, "Parent selector is already set");
}
this.parentLocator = locator;
if (locator != null && locator.hasService(role)) {
// Get the parent, unwrapping it as far as needed
Object parent = locator.lookup(role);
if (parent instanceof StandaloneServiceSelector) {
this.parentSelector = (StandaloneServiceSelector)parent;
} else {
throw new IllegalArgumentException("Parent selector is not an extended component selector (" + parent + ")");
}
}
}
protected boolean canRelease(Object component) {
if ( this.parentSelector != null &&
this.parentSelector.canRelease(component) ) {
return true;
}
return this.componentMapping.containsKey( component );
}
/**
* A special factory that sets the RoleManager and LoggerManager after service()
*/
public static class Factory extends ComponentFactory {
private final String role;
public Factory(ComponentEnvironment env, ComponentInfo info, String role)
throws Exception {
super(env, info);
this.role = role;
}
protected void setupObject(Object obj)
throws Exception {
final StandaloneServiceSelector component = (StandaloneServiceSelector)obj;
ContainerUtil.enableLogging(component, this.environment.logger);
ContainerUtil.contextualize(component, this.environment.context);
ContainerUtil.service(component, this.environment.serviceManager);
component.setLoggerManager(this.environment.loggerManager);
component.setRoleManager(this.environment.roleManager);
ServiceManager manager = this.environment.serviceManager;
if (manager instanceof CoreServiceManager) {
// Can it be something else?
component.setParentLocator( ((CoreServiceManager)manager).parentManager, this.role);
}
ContainerUtil.configure(component, this.serviceInfo.getConfiguration());
ContainerUtil.initialize(component);
ContainerUtil.start(component);
}
}
}