/*
* Copyright 2004-2012 the original author or authors.
*
* 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.springframework.webflow.config;
import java.io.IOException;
import java.util.Collections;
import java.util.Set;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.binding.convert.ConversionExecutor;
import org.springframework.core.io.Resource;
import org.springframework.util.ClassUtils;
import org.springframework.webflow.core.collection.AttributeMap;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.registry.FlowDefinitionConstructionException;
import org.springframework.webflow.definition.registry.FlowDefinitionHolder;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.DefaultFlowHolder;
import org.springframework.webflow.engine.builder.FlowAssembler;
import org.springframework.webflow.engine.builder.FlowBuilder;
import org.springframework.webflow.engine.builder.FlowBuilderContext;
import org.springframework.webflow.engine.builder.model.FlowModelFlowBuilder;
import org.springframework.webflow.engine.builder.support.FlowBuilderContextImpl;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import org.springframework.webflow.engine.model.builder.DefaultFlowModelHolder;
import org.springframework.webflow.engine.model.builder.FlowModelBuilder;
import org.springframework.webflow.engine.model.builder.xml.XmlFlowModelBuilder;
import org.springframework.webflow.engine.model.registry.FlowModelHolder;
/**
* A factory for a flow definition registry. Is a Spring FactoryBean, for provision by the flow definition registry bean
* definition parser. Is package-private, as people should not be using this class directly, but rather through the
* higher-level webflow-config Spring 2.x configuration namespace.
*
* @author Keith Donald
* @author Jeremy Grelle
* @author Scott Andrews
*/
class FlowRegistryFactoryBean implements FactoryBean<FlowDefinitionRegistry>, BeanClassLoaderAware, InitializingBean,
DisposableBean {
private FlowLocation[] flowLocations;
private String[] flowLocationPatterns;
private FlowBuilderInfo[] flowBuilders;
private FlowBuilderServices flowBuilderServices;
private FlowDefinitionRegistry parent;
private String basePath;
private ClassLoader classLoader;
/**
* The definition registry produced by this factory bean.
*/
private DefaultFlowRegistry flowRegistry;
/**
* A helper for creating abstract representation of externalized flow definition resources.
*/
private FlowDefinitionResourceFactory flowResourceFactory;
/**
* Flow definitions defined in external files that should be registered in the registry produced by this factory
* bean.
*/
public void setFlowLocations(FlowLocation... flowLocations) {
this.flowLocations = flowLocations;
}
/**
* Resolvable path patterns to flows to register in the registry produced by this factory bean.
*/
public void setFlowLocationPatterns(String... flowLocationPatterns) {
this.flowLocationPatterns = flowLocationPatterns;
}
/**
* Java {@link FlowBuilder flow builder} classes that should be registered in the registry produced by this factory
* bean.
*/
public void setFlowBuilders(FlowBuilderInfo... flowBuilders) {
this.flowBuilders = flowBuilders;
}
/**
* The holder for services needed to build flow definitions registered in this registry.
*/
public void setFlowBuilderServices(FlowBuilderServices flowBuilderServices) {
this.flowBuilderServices = flowBuilderServices;
}
/**
* Base path used when determining the default flow id
*/
public void setBasePath(String basePath) {
this.basePath = basePath;
}
/**
* The parent of the registry created by this factory bean.
*/
public void setParent(FlowDefinitionRegistry parent) {
this.parent = parent;
}
// implement BeanClassLoaderAware
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void afterPropertiesSet() throws Exception {
flowResourceFactory = new FlowDefinitionResourceFactory(flowBuilderServices.getApplicationContext());
if (basePath != null) {
flowResourceFactory.setBasePath(basePath);
}
flowRegistry = new DefaultFlowRegistry();
flowRegistry.setParent(parent);
registerFlowLocations();
registerFlowLocationPatterns();
registerFlowBuilders();
}
public FlowDefinitionRegistry getObject() throws Exception {
return flowRegistry;
}
public Class<?> getObjectType() {
return FlowDefinitionRegistry.class;
}
public boolean isSingleton() {
return true;
}
// implement DisposableBean
public void destroy() throws Exception {
flowRegistry.destroy();
}
private void registerFlowLocations() {
if (flowLocations != null) {
for (FlowLocation location : flowLocations) {
flowRegistry.registerFlowDefinition(createFlowDefinitionHolder(createResource(location)));
}
}
}
private void registerFlowLocationPatterns() {
if (flowLocationPatterns != null) {
for (String pattern : flowLocationPatterns) {
FlowDefinitionResource[] resources;
AttributeMap<Object> attributes = getFlowAttributes(Collections.<FlowElementAttribute> emptySet());
try {
resources = flowResourceFactory.createResources(pattern, attributes);
} catch (IOException e) {
IllegalStateException ise = new IllegalStateException(
"An I/O Exception occurred resolving the flow location pattern '" + pattern + "'");
ise.initCause(e);
throw ise;
}
for (FlowDefinitionResource resource : resources) {
flowRegistry.registerFlowDefinition(createFlowDefinitionHolder(resource));
}
}
}
}
private void registerFlowBuilders() {
if (flowBuilders != null) {
for (FlowBuilderInfo builderInfo : flowBuilders) {
flowRegistry.registerFlowDefinition(buildFlowDefinition(builderInfo));
}
}
}
private FlowDefinitionHolder createFlowDefinitionHolder(FlowDefinitionResource flowResource) {
FlowBuilder builder = createFlowBuilder(flowResource);
FlowBuilderContext builderContext = new FlowBuilderContextImpl(flowResource.getId(),
flowResource.getAttributes(), flowRegistry, flowBuilderServices);
FlowAssembler assembler = new FlowAssembler(builder, builderContext);
return new DefaultFlowHolder(assembler);
}
private FlowDefinitionResource createResource(FlowLocation location) {
AttributeMap<Object> flowAttributes = getFlowAttributes(location.getAttributes());
return flowResourceFactory.createResource(location.getPath(), flowAttributes, location.getId());
}
private AttributeMap<Object> getFlowAttributes(Set<FlowElementAttribute> attributes) {
MutableAttributeMap<Object> flowAttributes = null;
if (flowBuilderServices.getDevelopment()) {
flowAttributes = new LocalAttributeMap<Object>(1 + attributes.size(), 1);
flowAttributes.put("development", true);
}
if (!attributes.isEmpty()) {
if (flowAttributes == null) {
flowAttributes = new LocalAttributeMap<Object>(attributes.size(), 1);
}
for (FlowElementAttribute attribute : attributes) {
flowAttributes.put(attribute.getName(), getConvertedValue(attribute));
}
}
return flowAttributes;
}
private FlowBuilder createFlowBuilder(FlowDefinitionResource resource) {
return new FlowModelFlowBuilder(createFlowModelHolder(resource));
}
private FlowModelHolder createFlowModelHolder(FlowDefinitionResource resource) {
FlowModelHolder modelHolder = new DefaultFlowModelHolder(createFlowModelBuilder(resource));
// register the flow model holder with the backing flow model registry - this is needed to support flow model
// merging during the flow build process
flowRegistry.getFlowModelRegistry().registerFlowModel(resource.getId(), modelHolder);
return modelHolder;
}
private FlowModelBuilder createFlowModelBuilder(FlowDefinitionResource resource) {
if (isXml(resource.getPath())) {
return new XmlFlowModelBuilder(resource.getPath(), flowRegistry.getFlowModelRegistry());
} else {
throw new IllegalArgumentException(resource
+ " is not a supported resource type; supported types are [.xml]");
}
}
private boolean isXml(Resource flowResource) {
return flowResource.getFilename().endsWith(".xml");
}
private Object getConvertedValue(FlowElementAttribute attribute) {
if (attribute.needsTypeConversion()) {
Class<?> targetType = fromStringToClass(attribute.getType());
ConversionExecutor converter = flowBuilderServices.getConversionService().getConversionExecutor(
String.class, targetType);
return converter.execute(attribute.getValue());
} else {
return attribute.getValue();
}
}
private Class<?> fromStringToClass(String name) {
Class<?> clazz = flowBuilderServices.getConversionService().getClassForAlias(name);
if (clazz != null) {
return clazz;
} else {
return loadClass(name);
}
}
private Class<?> loadClass(String name) {
try {
return ClassUtils.forName(name, classLoader);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Unable to load class '" + name + "'");
}
}
private FlowDefinition buildFlowDefinition(FlowBuilderInfo builderInfo) {
try {
Class<?> flowBuilderClass = loadClass(builderInfo.getClassName());
FlowBuilder builder = (FlowBuilder) flowBuilderClass.newInstance();
AttributeMap<Object> flowAttributes = getFlowAttributes(builderInfo.getAttributes());
FlowBuilderContext builderContext = new FlowBuilderContextImpl(builderInfo.getId(), flowAttributes,
flowRegistry, flowBuilderServices);
FlowAssembler assembler = new FlowAssembler(builder, builderContext);
return assembler.assembleFlow();
} catch (IllegalArgumentException e) {
throw new FlowDefinitionConstructionException(builderInfo.getId(), e);
} catch (InstantiationException e) {
throw new FlowDefinitionConstructionException(builderInfo.getId(), e);
} catch (IllegalAccessException e) {
throw new FlowDefinitionConstructionException(builderInfo.getId(), e);
}
}
}