/*
* Copyright 2015-2016 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.statemachine.config.configuration;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.ObjectStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfig;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.statemachine.config.builders.StateMachineConfigBuilder;
import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration;
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
import org.springframework.statemachine.config.model.StatesData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.util.ClassUtils;
/**
* {@link Configuration} which gets imported from {@link EnableStateMachineFactory} and registers
* a {@link StateMachineFactory} build from a {@link StateMachineConfigurerAdapter} via
* a {@link BeanDefinition}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
@Configuration
public class StateMachineFactoryConfiguration<S, E> extends
AbstractImportingAnnotationConfiguration<StateMachineConfigBuilder<S, E>, StateMachineConfig<S, E>> {
private final StateMachineConfigBuilder<S, E> builder = new StateMachineConfigBuilder<S, E>();
@Override
protected BeanDefinition buildBeanDefinition(AnnotationMetadata importingClassMetadata,
Class<? extends Annotation> namedAnnotation) throws Exception {
String enableStateMachineEnclosingClassName = importingClassMetadata.getClassName();
// for below classloader, see gh122
Class<?> enableStateMachineEnclosingClass = ClassUtils.forName(enableStateMachineEnclosingClassName,
ClassUtils.getDefaultClassLoader());
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
.rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class);
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
EnableStateMachineFactory.class.getName(), false));
Boolean contextEvents = attributes.getBoolean("contextEvents");
beanDefinitionBuilder.addConstructorArgValue(builder);
beanDefinitionBuilder.addConstructorArgValue(importingClassMetadata.getClassName());
beanDefinitionBuilder.addConstructorArgValue(contextEvents);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
// try to add more info about generics
ResolvableType type = resolveFactoryObjectType(enableStateMachineEnclosingClass);
if (type != null && beanDefinition instanceof RootBeanDefinition) {
((RootBeanDefinition)beanDefinition).setTargetType(type);
}
return beanDefinition;
}
private ResolvableType resolveFactoryObjectType(Class<?> enableStateMachineEnclosingClass) {
ResolvableType type = null;
try {
Class<?>[] generics = ResolvableType.forClass(enableStateMachineEnclosingClass).getSuperType().resolveGenerics();
if (generics != null && generics.length == 2) {
type = ResolvableType.forClassWithGenerics(StateMachineFactory.class, generics);
}
} catch (Exception e) {
}
return type;
}
@Override
protected List<Class<? extends Annotation>> getAnnotations() {
List<Class<? extends Annotation>> types = new ArrayList<Class<? extends Annotation>>();
types.add(EnableStateMachineFactory.class);
return types;
}
private static class StateMachineFactoryDelegatingFactoryBean<S, E> implements
FactoryBean<StateMachineFactory<S, E>>, BeanFactoryAware, InitializingBean {
private final StateMachineConfigBuilder<S, E> builder;
private List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers;
private BeanFactory beanFactory;
private StateMachineFactory<S, E> stateMachineFactory;
private String clazzName;
private Boolean contextEvents;
@SuppressWarnings("unused")
public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder, String clazzName, Boolean contextEvents) {
this.builder = builder;
this.clazzName = clazzName;
this.contextEvents = contextEvents;
}
@Override
public StateMachineFactory<S, E> getObject() throws Exception {
return stateMachineFactory;
}
@Override
public Class<?> getObjectType() {
return StateMachineFactory.class;
}
@Override
public boolean isSingleton() {
return true;
}
@Override
public void afterPropertiesSet() throws Exception {
// do not continue without configurers, it would not work
if (configurers == null || configurers.size() == 0) {
throw new BeanDefinitionStoreException(
"Cannot configure state machine due to missing configurers. Did you remember to use " +
"@EnableStateMachineFactory with a StateMachineConfigurerAdapter.");
}
for (AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>> configurer : configurers) {
Class<?> clazz = configurer.getClass();
if (ClassUtils.getUserClass(clazz).getName().equals(clazzName)) {
builder.apply(configurer);
}
}
StateMachineConfig<S, E> stateMachineConfig = builder.getOrBuild();
TransitionsData<S, E> stateMachineTransitions = stateMachineConfig.getTransitions();
StatesData<S, E> stateMachineStates = stateMachineConfig.getStates();
ConfigurationData<S, E> stateMachineConfigurationConfig = stateMachineConfig
.getStateMachineConfigurationConfig();
ObjectStateMachineFactory<S, E> objectStateMachineFactory = null;
if (stateMachineConfig.getModel() != null && stateMachineConfig.getModel().getFactory() != null) {
objectStateMachineFactory = new ObjectStateMachineFactory<S, E>(
new DefaultStateMachineModel<S, E>(stateMachineConfigurationConfig, null, null),
stateMachineConfig.getModel().getFactory());
} else {
objectStateMachineFactory = new ObjectStateMachineFactory<S, E>(new DefaultStateMachineModel<S, E>(
stateMachineConfigurationConfig, stateMachineStates, stateMachineTransitions), null);
}
objectStateMachineFactory.setBeanFactory(beanFactory);
objectStateMachineFactory.setContextEventsEnabled(contextEvents);
// explicitly tell factory to handle auto-start because
// machine is not created as a bean so factory need to
// call lifecycle methods manually
objectStateMachineFactory.setHandleAutostartup(true);
this.stateMachineFactory = objectStateMachineFactory;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Autowired(required=false)
protected void onConfigurers(
List<AnnotationConfigurer<StateMachineConfig<S, E>, StateMachineConfigBuilder<S, E>>> configurers)
throws Exception {
this.configurers = configurers;
}
}
}