blob: ae4dbbff07f7be0b5e1c2988bce319a524bd4b73 [file] [log] [blame]
/*
* 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.core.container.spring.avalon;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.avalon.excalibur.pool.Poolable;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.cocoon.acting.Action;
import org.apache.cocoon.components.pipeline.ProcessingPipeline;
import org.apache.cocoon.components.pipeline.impl.PipelineComponentInfo;
import org.apache.cocoon.configuration.Settings;
import org.apache.cocoon.core.container.spring.logger.LoggerFactoryBean;
import org.apache.cocoon.core.container.spring.logger.LoggerUtils;
import org.apache.cocoon.core.container.spring.pipeline.PipelineComponentInfoFactoryBean;
import org.apache.cocoon.core.container.spring.pipeline.PipelineComponentInfoInitializer;
import org.apache.cocoon.generation.Generator;
import org.apache.cocoon.matching.Matcher;
import org.apache.cocoon.reading.Reader;
import org.apache.cocoon.selection.Selector;
import org.apache.cocoon.serialization.Serializer;
import org.apache.cocoon.spring.configurator.impl.AbstractElementParser;
import org.apache.cocoon.transformation.Transformer;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.ResourceLoader;
import org.w3c.dom.Element;
/**
* This is the main implementation of the Avalon-Spring-bridge.
* It creates the environment for Avalon components: a logger bean and a context
* bean, reads the Avalon style configurations and registers the components
* as beans in the Spring bean definition registry.
*
* @since 2.2
* @version $Id$
*/
public class BridgeElementParser extends AbstractElementParser {
public static final String DEFAULT_COCOON_XCONF_LOCATION = "resource://org/apache/cocoon/cocoon.xconf";
/**
* @see org.springframework.beans.factory.xml.BeanDefinitionParser#parse(Element, ParserContext)
*/
@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
final ResourceLoader resourceLoader = parserContext.getReaderContext().getReader().getResourceLoader();
// read avalon style configuration - it's optional for this element.
// the schema for the sitemap element ensures that location is never null.
final String location = getAttributeValue(element, "location", DEFAULT_COCOON_XCONF_LOCATION);
try {
final ConfigurationInfo info = readConfiguration(location, resourceLoader);
createComponents(element,
info,
parserContext.getRegistry(),
parserContext.getDelegate().getReaderContext().getReader(),
resourceLoader);
} catch (Exception e) {
throw new BeanDefinitionStoreException("Unable to read Avalon configuration from '" + location + "'.",e);
}
return null;
}
/**
*
* @param element Can be null.
* @param info ConfigurationInfo.
* @param registry BeanDefinitionRegistry.
* @param reader Can be null.
* @param resourceLoader ResourceLoader.
* @throws Exception from called components.
*/
public void createComponents(Element element,
ConfigurationInfo info,
BeanDefinitionRegistry registry,
BeanDefinitionReader reader,
ResourceLoader resourceLoader)
throws Exception {
// add context
addContext(element, registry);
// add service manager
addComponent(AvalonServiceManager.class,
AvalonUtils.SERVICE_MANAGER_ROLE,
null,
false,
registry);
// add logger
addLogger(registry, info.getRootLogger());
// handle includes of spring configurations
final Iterator<String> includeIter = info.getImports().iterator();
while ( includeIter.hasNext() ) {
if ( reader == null ) {
throw new Exception("Import of spring configuration files not supported. (Reader is null)");
}
final String uri = includeIter.next();
reader.loadBeanDefinitions(resourceLoader.getResource(uri));
}
// then create components
this.createConfig(info, registry);
// register component infos for child factories
this.registerComponentInfo(info, registry);
// and finally add avalon bean post processor
final RootBeanDefinition beanDef = createBeanDefinition(AvalonBeanPostProcessor.class, "init", true);
beanDef.getPropertyValues().addPropertyValue(
"context", new RuntimeBeanReference(AvalonUtils.CONTEXT_ROLE));
beanDef.getPropertyValues().addPropertyValue(
"configurationInfo", new RuntimeBeanReference(ConfigurationInfo.class.getName()));
beanDef.getPropertyValues().addPropertyValue(
"resourceLoader", resourceLoader);
beanDef.getPropertyValues().addPropertyValue(
"location", this.getConfigurationLocation());
this.register(beanDef, AvalonBeanPostProcessor.class.getName(), registry);
final RootBeanDefinition resolverDef = new RootBeanDefinition();
resolverDef.setBeanClassName("org.apache.cocoon.components.treeprocessor.variables.PreparedVariableResolver");
resolverDef.setLazyInit(false);
resolverDef.setScope("prototype");
resolverDef.getPropertyValues().addPropertyValue(
"manager", new RuntimeBeanReference("org.apache.avalon.framework.service.ServiceManager"));
this.register(
resolverDef, "org.apache.cocoon.components.treeprocessor.variables.VariableResolver", null, registry);
}
protected ConfigurationInfo readConfiguration(String location, ResourceLoader resourceLoader)
throws Exception {
return ConfigurationReader.readConfiguration(location, resourceLoader);
}
protected void addContext(Element element, BeanDefinitionRegistry registry) {
this.addComponent(AvalonContextFactoryBean.class,
AvalonUtils.CONTEXT_ROLE,
"init",
true,
registry);
}
/**
* Add the logger bean.
*
* @param registry The bean registry.
* @param loggerCategory The optional category for the logger.
*/
protected void addLogger(BeanDefinitionRegistry registry,
String loggerCategory) {
final RootBeanDefinition beanDef = createBeanDefinition(LoggerFactoryBean.class, "init", false);
if (loggerCategory != null) {
beanDef.getPropertyValues().addPropertyValue("category", loggerCategory);
}
register(beanDef, LoggerUtils.LOGGER_ROLE, registry);
}
public void createConfig(ConfigurationInfo info,
BeanDefinitionRegistry registry)
throws Exception {
final Map<String, ComponentInfo> components = info.getComponents();
final List<String> pooledRoles = new ArrayList<String>();
// Iterate over all definitions
final Iterator<Map.Entry<String, ComponentInfo>> i = components.entrySet().iterator();
while ( i.hasNext() ) {
Map.Entry<String, ComponentInfo> entry = i.next();
final ComponentInfo current = entry.getValue();
final String role = current.getRole();
String className = current.getComponentClassName();
boolean isSelector = false;
boolean singleton = true;
boolean poolable = false;
// Test for Selector - we just create a wrapper for them to flatten the hierarchy
if ( current.isSelector() ) {
// Add selector
className = AvalonServiceSelector.class.getName();
isSelector = true;
} else {
// test for unknown model
if ( current.getModel() == ComponentInfo.MODEL_UNKNOWN ) {
try {
final Class serviceClass = Class.forName(className);
if ( ThreadSafe.class.isAssignableFrom(serviceClass) ) {
current.setModel(ComponentInfo.MODEL_SINGLETON);
} else if ( Poolable.class.isAssignableFrom(serviceClass) ) {
current.setModel(ComponentInfo.MODEL_POOLED);
} else {
current.setModel(ComponentInfo.MODEL_PRIMITIVE);
}
} catch (NoClassDefFoundError ncdfe) {
throw new ConfigurationException(
"Unable to create class for component with role " + current.getRole()
+ " with class: " + className, ncdfe);
} catch (ClassNotFoundException cnfe) {
throw new ConfigurationException(
"Unable to create class for component with role " + current.getRole()
+ " with class: " + className, cnfe);
}
}
if ( current.getModel() == ComponentInfo.MODEL_POOLED ) {
poolable = true;
singleton = false;
} else if ( current.getModel() != ComponentInfo.MODEL_SINGLETON ) {
singleton = false;
}
}
final String beanName;
if ( !poolable ) {
beanName = role;
} else {
beanName = role + "Pooled";
}
final RootBeanDefinition beanDef = new RootBeanDefinition();
beanDef.setBeanClassName(className);
if ( current.getInitMethodName() != null ) {
beanDef.setInitMethodName(current.getInitMethodName());
}
if ( current.getDestroyMethodName() != null ) {
beanDef.setDestroyMethodName(current.getDestroyMethodName());
}
beanDef.setScope( singleton ? BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE );
beanDef.setLazyInit(singleton && current.isLazyInit());
if ( isSelector ) {
beanDef.getConstructorArgumentValues().
addGenericArgumentValue(role.substring(0, role.length()-8), "java.lang.String");
if ( current.getDefaultValue() != null ) {
beanDef.getPropertyValues().addPropertyValue("default", current.getDefaultValue());
}
}
this.register(beanDef, beanName, current.getAlias(), registry);
if ( poolable ) {
// add the factory for poolables
final RootBeanDefinition poolableBeanDef = new RootBeanDefinition();
poolableBeanDef.setBeanClass(PoolableFactoryBean.class);
poolableBeanDef.setScope(BeanDefinition.SCOPE_SINGLETON);
poolableBeanDef.setLazyInit(false);
poolableBeanDef.setInitMethodName("initialize");
poolableBeanDef.setDestroyMethodName("dispose");
poolableBeanDef.getConstructorArgumentValues().
addIndexedArgumentValue(0, beanName, "java.lang.String");
poolableBeanDef.getConstructorArgumentValues().
addIndexedArgumentValue(1, className, "java.lang.String");
if ( current.getConfiguration() != null ) {
// we treat poolMax as a string to allow property replacements
final String poolMax = current.getConfiguration().getAttribute("pool-max", null);
if ( poolMax != null ) {
poolableBeanDef.getConstructorArgumentValues().addIndexedArgumentValue(2, poolMax);
poolableBeanDef.getConstructorArgumentValues().addIndexedArgumentValue(3, new RuntimeBeanReference(Settings.ROLE));
}
}
if ( current.getPoolInMethodName() != null ) {
poolableBeanDef.getPropertyValues().
addPropertyValue("poolInMethodName", current.getPoolInMethodName());
}
if ( current.getPoolOutMethodName() != null ) {
poolableBeanDef.getPropertyValues().
addPropertyValue("poolOutMethodName", current.getPoolOutMethodName());
}
this.register(poolableBeanDef, role, registry);
pooledRoles.add(role);
}
}
// now change roles for pooled components (from {role} to {role}Pooled
final Iterator<String> prI = pooledRoles.iterator();
while ( prI.hasNext() ) {
final String role = prI.next();
final ComponentInfo pooledInfo = components.remove(role);
components.put(role + "Pooled", pooledInfo);
}
}
protected void registerComponentInfo(ConfigurationInfo configInfo,
BeanDefinitionRegistry registry) {
PipelineComponentInfo info = new PipelineComponentInfo(null);
final Iterator i = configInfo.getComponents().values().iterator();
while (i.hasNext()) {
final ComponentInfo current = (ComponentInfo) i.next();
info.componentAdded(current.getRole(), current.getComponentClassName(), current.getConfiguration());
}
prepareSelector(info, configInfo, Generator.ROLE);
prepareSelector(info, configInfo, Transformer.ROLE);
prepareSelector(info, configInfo, Serializer.ROLE);
prepareSelector(info, configInfo, ProcessingPipeline.ROLE);
prepareSelector(info, configInfo, Action.ROLE);
prepareSelector(info, configInfo, Selector.ROLE);
prepareSelector(info, configInfo, Matcher.ROLE);
prepareSelector(info, configInfo, Reader.ROLE);
info.lock();
if (!registry.containsBeanDefinition(PipelineComponentInfo.ROLE)) {
final RootBeanDefinition beanDef = new RootBeanDefinition();
beanDef.setBeanClass(PipelineComponentInfoFactoryBean.class);
beanDef.setScope(BeanDefinition.SCOPE_SINGLETON);
beanDef.setLazyInit(false);
beanDef.setInitMethodName("init");
this.register(beanDef, PipelineComponentInfo.ROLE, registry);
}
BeanDefinitionBuilder initDefBuilder =
BeanDefinitionBuilder.rootBeanDefinition(PipelineComponentInfoInitializer.class);
initDefBuilder.addPropertyReference("info", PipelineComponentInfo.ROLE);
initDefBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
initDefBuilder.setLazyInit(false);
initDefBuilder.setInitMethodName("init");
initDefBuilder.addPropertyValue("data", info.getData());
final String beanName = this.getClass().getName() + "/init";
this.register(initDefBuilder.getBeanDefinition(), beanName, registry);
final RootBeanDefinition ciBeanDef = new RootBeanDefinition();
ciBeanDef.setBeanClass(ConfigurationInfoFactoryBean.class);
ciBeanDef.setScope(BeanDefinition.SCOPE_SINGLETON);
ciBeanDef.setLazyInit(false);
ciBeanDef.getPropertyValues().addPropertyValue("configurationInfo", configInfo);
this.register(ciBeanDef, ConfigurationInfo.class.getName(), registry);
}
protected static void prepareSelector(PipelineComponentInfo info,
ConfigurationInfo configInfo,
String category) {
final ComponentInfo component = configInfo.getComponents().get(category + "Selector");
if (component != null) {
info.setDefaultType(category, component.getDefaultValue());
}
}
protected String getConfigurationLocation() {
return "WEB-INF/cocoon/xconf";
}
}