Java tutorial
/*** * * Copyright 2014 Andrew Hall * * 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.statefulj.framework.core; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javassist.CannotCompileException; import javassist.NotFoundException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.support.ManagedList; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.MethodParameter; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.statefulj.common.utils.ReflectionUtils; import org.statefulj.framework.core.actions.DomainEntityMethodInvocationAction; import org.statefulj.framework.core.actions.MethodInvocationAction; import org.statefulj.framework.core.annotations.StatefulController; import org.statefulj.framework.core.annotations.Transition; import org.statefulj.framework.core.annotations.Transitions; import org.statefulj.framework.core.fsm.FSM; import org.statefulj.framework.core.fsm.TransitionImpl; import org.statefulj.framework.core.model.EndpointBinder; import org.statefulj.framework.core.model.PersistenceSupportBeanFactory; import org.statefulj.framework.core.model.ReferenceFactory; import org.statefulj.framework.core.model.StatefulFSM; import org.statefulj.framework.core.model.impl.MemoryPersistenceSupportBeanFactoryImpl; import org.statefulj.framework.core.model.impl.ReferenceFactoryImpl; import org.statefulj.framework.core.model.impl.StatefulFSMImpl; import org.statefulj.fsm.model.impl.StateImpl; /** * StatefulFactory is responsible for inspecting all StatefulControllers and building out * the StatefulJ framework. The factory is invoked at post processing of the beans but before * the beans are instantiated * * @author Andrew Hall * */ public class StatefulFactory implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware { private ApplicationContext appContext; private static final String DEFAULT_PACKAGE = "org.statefulj"; private static final Logger logger = LoggerFactory.getLogger(StatefulFactory.class); private final Pattern binder = Pattern.compile("(([^:]*):)?(.*)"); private Map<Class<?>, Set<String>> entityToControllerMappings = new HashMap<Class<?>, Set<String>>(); private MemoryPersistenceSupportBeanFactoryImpl memoryPersistenceFactory = new MemoryPersistenceSupportBeanFactoryImpl(); private String[] packages; /** * The default constructor will build the StatefulJ Framework using the binders and persisters from the * "org.stateful" package * */ public StatefulFactory() { this(DEFAULT_PACKAGE); } /** * Will build the StatefulJ Framework using the binders and persisters from the packages specified * in the parameter list. This constructor will not scan the "org.stateful" packages unless * explicitly provided in the parameter list * * @param packages This list of packages to scan for the binders and persisters */ public StatefulFactory(String... packages) { this.packages = packages; } // Resolver that injects the FSM for a given controller. It is inferred by the ClassType or will use the bean Id specified by the value of the // FSM Annotation // class FSMAnnotationResolver extends QualifierAnnotationAutowireCandidateResolver { @Override public Object getSuggestedValue(DependencyDescriptor descriptor) { Object suggested = null; Field field = descriptor.getField(); MethodParameter methodParameter = descriptor.getMethodParameter(); boolean isStatefulFSM = false; String controllerId = null; Type genericFieldType = null; String fieldName = null; org.statefulj.framework.core.annotations.FSM fsmAnnotation = null; // If this is a StatefulFSM, parse out the Annotation and Type information // if (field != null) { if (isStatefulFSM(field)) { fsmAnnotation = field.getAnnotation(org.statefulj.framework.core.annotations.FSM.class); genericFieldType = field.getGenericType(); fieldName = field.getName(); isStatefulFSM = true; } } else if (methodParameter != null) { if (isStatefulFSM(methodParameter)) { fsmAnnotation = methodParameter .getParameterAnnotation(org.statefulj.framework.core.annotations.FSM.class); genericFieldType = methodParameter.getGenericParameterType(); fieldName = methodParameter.getParameterName(); isStatefulFSM = true; } } // If this is a StatefulFSM field, then resolve bean reference // if (isStatefulFSM) { // Determine the controllerId - either explicit or derived // controllerId = getControllerId(fsmAnnotation); if (StringUtils.isEmpty(controllerId)) { // Get the Managed Class // Class<?> managedClass = getManagedClass(fieldName, genericFieldType); // Fetch the Controller from the mapping // controllerId = deriveControllerId(fieldName, managedClass); } ReferenceFactory refFactory = new ReferenceFactoryImpl(controllerId); suggested = appContext.getBean(refFactory.getStatefulFSMId()); } return (suggested != null) ? suggested : super.getSuggestedValue(descriptor); } private String getControllerId(org.statefulj.framework.core.annotations.FSM fsmAnnotation) { String controllerId = (fsmAnnotation != null) ? fsmAnnotation.value() : null; return controllerId; } /** * @param field * @return */ private boolean isStatefulFSM(Field field) { return field != null && field.getType().isAssignableFrom(StatefulFSM.class); } /** * @param methodParameter * @return */ private boolean isStatefulFSM(MethodParameter methodParameter) { return methodParameter != null && methodParameter.getParameterType().isAssignableFrom(StatefulFSM.class); } /** * @param fieldName * @param managedClass * @return */ private String deriveControllerId(String fieldName, Class<?> managedClass) { String controllerId; Set<String> controllers = entityToControllerMappings.get(managedClass); if (controllers == null) { throw new RuntimeException("Unable to resolve FSM for field " + fieldName); } if (controllers.size() > 1) { throw new RuntimeException("Ambiguous fsm for " + fieldName); } controllerId = controllers.iterator().next(); return controllerId; } /** * @param fieldName * @param genericFieldType * @return */ private Class<?> getManagedClass(String fieldName, Type genericFieldType) { Class<?> managedClass = null; if (genericFieldType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericFieldType; Type[] fieldArgTypes = aType.getActualTypeArguments(); for (Type fieldArgType : fieldArgTypes) { managedClass = (Class<?>) fieldArgType; break; } } if (managedClass == null) { logger.error("Field {} isn't parametrized", fieldName); throw new RuntimeException("Field " + fieldName + " isn't paramertized"); } return managedClass; } } /* Set the FSMAnnotationResolver to resolve all FSM annotations * * (non-Javadoc) * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory) */ public void postProcessBeanFactory(final ConfigurableListableBeanFactory reg) throws BeansException { DefaultListableBeanFactory bf = (DefaultListableBeanFactory) reg; bf.setAutowireCandidateResolver(new FSMAnnotationResolver()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.appContext = applicationContext; } /* * Override postProcessBeanDefinitionRegistry to dynamically generate all the StatefulJ beans for each StatefulController * * (non-Javadoc) * @see org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry(org.springframework.beans.factory.support.BeanDefinitionRegistry) */ public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry reg) throws BeansException { logger.debug("postProcessBeanDefinitionRegistry : enter"); try { // Reflect over StatefulJ // Reflections reflections = new Reflections((Object[]) this.packages); // Load up all Endpoint Binders // Map<String, EndpointBinder> binders = new HashMap<String, EndpointBinder>(); loadEndpointBinders(reflections, binders); // Load up all PersistenceSupportBeanFactories // Map<Class<?>, PersistenceSupportBeanFactory> persistenceFactories = new HashMap<Class<?>, PersistenceSupportBeanFactory>(); loadPersistenceSupportBeanFactories(reflections, persistenceFactories); // Map Controllers and Entities // Map<String, Class<?>> controllerToEntityMapping = new HashMap<String, Class<?>>(); Map<Class<?>, String> entityToRepositoryMappings = new HashMap<Class<?>, String>(); mapControllerAndEntityClasses(reg, controllerToEntityMapping, entityToRepositoryMappings, this.entityToControllerMappings); // Iterate thru all StatefulControllers and build the framework // for (Entry<String, Class<?>> entry : controllerToEntityMapping.entrySet()) { buildFramework(entry.getKey(), entry.getValue(), reg, entityToRepositoryMappings, binders, persistenceFactories); } } catch (Exception e) { throw new BeanCreationException("Unable to create bean", e); } logger.debug("postProcessBeanDefinitionRegistry : exit"); } /** * Iterate thru all beans and fetch the StatefulControllers * * @param reg * @return * @throws ClassNotFoundException */ private void mapControllerAndEntityClasses(BeanDefinitionRegistry reg, Map<String, Class<?>> controllerToEntityMapping, Map<Class<?>, String> entityToRepositoryMapping, Map<Class<?>, Set<String>> entityToControllerMappings) throws ClassNotFoundException { // Loop thru the bean registry // for (String bfName : reg.getBeanDefinitionNames()) { BeanDefinition bf = reg.getBeanDefinition(bfName); if (bf.isAbstract()) { logger.debug("Skipping abstract bean " + bfName); continue; } Class<?> clazz = getClassFromBeanDefinition(bf, reg); if (clazz == null) { logger.debug("Unable to resolve class for bean " + bfName); continue; } // If it's a StatefulController, map controller to the entity and the entity to the controller // if (ReflectionUtils.isAnnotationPresent(clazz, StatefulController.class)) { mapEntityWithController(controllerToEntityMapping, entityToControllerMappings, bfName, clazz); } // Else, if the Bean is a Repository, then map the // Entity associated with the Repo to the PersistenceSupport object // else if (RepositoryFactoryBeanSupport.class.isAssignableFrom(clazz)) { mapEntityToRepository(entityToRepositoryMapping, bfName, bf); } } } /** * @param entityToRepositoryMapping * @param bfName * @param bf * @throws ClassNotFoundException */ private void mapEntityToRepository(Map<Class<?>, String> entityToRepositoryMapping, String bfName, BeanDefinition bf) throws ClassNotFoundException { // Determine the Entity Class associated with the Repo // String value = (String) bf.getAttribute("factoryBeanObjectType"); Class<?> repoInterface = Class.forName(value); Class<?> entityType = null; for (Type type : repoInterface.getGenericInterfaces()) { if (type instanceof ParameterizedType) { ParameterizedType parmType = (ParameterizedType) type; if (Repository.class.isAssignableFrom((Class<?>) parmType.getRawType()) && parmType.getActualTypeArguments() != null && parmType.getActualTypeArguments().length > 0) { entityType = (Class<?>) parmType.getActualTypeArguments()[0]; break; } } } if (entityType == null) { throw new RuntimeException("Unable to determine Entity type for class " + repoInterface.getName()); } // Map Entity to the RepositoryFactoryBeanSupport bean // logger.debug("Mapped \"{}\" to repo \"{}\", beanId=\"{}\"", entityType.getName(), value, bfName); entityToRepositoryMapping.put(entityType, bfName); } /** * @param controllerToEntityMapping * @param entityToControllerMappings * @param bfName * @param clazz */ private void mapEntityWithController(Map<String, Class<?>> controllerToEntityMapping, Map<Class<?>, Set<String>> entityToControllerMappings, String bfName, Class<?> clazz) { logger.debug("Found StatefulController, class = \"{}\", beanName = \"{}\"", clazz.getName(), bfName); // Ctrl -> Entity // controllerToEntityMapping.put(bfName, clazz); // Entity -> Ctrls // Class<?> managedEntity = ReflectionUtils.getFirstClassAnnotation(clazz, StatefulController.class).clazz(); Set<String> controllers = entityToControllerMappings.get(managedEntity); if (controllers == null) { controllers = new HashSet<String>(); entityToControllerMappings.put(managedEntity, controllers); } controllers.add(bfName); } private void buildFramework(String statefulControllerBeanId, Class<?> statefulControllerClass, BeanDefinitionRegistry reg, Map<Class<?>, String> entityToRepositoryMappings, Map<String, EndpointBinder> binders, Map<Class<?>, PersistenceSupportBeanFactory> persistenceFactories) throws CannotCompileException, IllegalArgumentException, NotFoundException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { // Determine the managed class // StatefulController scAnnotation = ReflectionUtils.getFirstClassAnnotation(statefulControllerClass, StatefulController.class); Class<?> managedClass = scAnnotation.clazz(); // Is the the Controller and ManagedClass the same? (DomainEntity) // boolean isDomainEntity = managedClass.equals(statefulControllerClass); // ReferenceFactory will generate all the necessary bean ids // ReferenceFactory referenceFactory = new ReferenceFactoryImpl(statefulControllerBeanId); // We need to map Transitions across all Methods // Map<String, Map<String, Method>> providersMappings = new HashMap<String, Map<String, Method>>(); Map<Transition, Method> transitionMapping = new HashMap<Transition, Method>(); Map<Transition, Method> anyMapping = new HashMap<Transition, Method>(); Set<String> states = new HashSet<String>(); Set<String> blockingStates = new HashSet<String>(); // Fetch Repo info // String repoBeanId = getRepoId(entityToRepositoryMappings, managedClass); // Get the Persistence Factory // PersistenceSupportBeanFactory factory = null; BeanDefinition repoBeanDefinitionFactory = null; // If we don't have a repo mapped to this entity, fall back to memory persister // if (repoBeanId == null) { logger.warn("Unable to find Spring Data Repository for {}, using an in-memory persister", managedClass.getName()); factory = this.memoryPersistenceFactory; } else { repoBeanDefinitionFactory = reg.getBeanDefinition(repoBeanId); Class<?> repoClassName = getClassFromBeanClassName(repoBeanDefinitionFactory); // Fetch the PersistenceFactory // factory = persistenceFactories.get(repoClassName); } // Map the Events and Transitions for the Controller // mapEventsTransitionsAndStates(statefulControllerClass, providersMappings, transitionMapping, anyMapping, states, blockingStates); // Do we have binders? // boolean hasBinders = (providersMappings.size() > 0); // Iterate thru the providers - building and registering each Binder // if (hasBinders) { for (Entry<String, Map<String, Method>> entry : providersMappings.entrySet()) { // Fetch the binder // EndpointBinder binder = binders.get(entry.getKey()); // Check if we found the binder // if (binder == null) { logger.error("Unable to locate binder: {}", entry.getKey()); throw new RuntimeException("Unable to locate binder: " + entry.getKey()); } // Build out the Binder Class // Class<?> binderClass = binder.bindEndpoints(statefulControllerBeanId, statefulControllerClass, factory.getIdType(), isDomainEntity, entry.getValue(), referenceFactory); // Add the new Binder Class to the Bean Registry // registerBinderBean(entry.getKey(), referenceFactory, binderClass, reg); } } // -- Build the FSM infrastructure -- // Build out a set of States // List<RuntimeBeanReference> stateBeans = new ManagedList<RuntimeBeanReference>(); for (String state : states) { logger.debug("Registering state \"{}\"", state); String stateId = registerState(referenceFactory, statefulControllerClass, state, blockingStates.contains(state), reg); stateBeans.add(new RuntimeBeanReference(stateId)); } // Build out the Action classes and the Transitions // RuntimeBeanReference controllerRef = new RuntimeBeanReference(statefulControllerBeanId); int cnt = 1; List<String> transitionIds = new LinkedList<String>(); for (Entry<Transition, Method> entry : anyMapping.entrySet()) { for (String state : states) { Transition t = entry.getKey(); String from = state; String to = (t.to().equals(Transition.ANY_STATE)) ? state : entry.getKey().to(); String transitionId = referenceFactory.getTransitionId(cnt++); boolean reload = t.reload(); registerActionAndTransition(referenceFactory, statefulControllerClass, from, to, reload, entry.getKey(), entry.getValue(), isDomainEntity, controllerRef, transitionId, reg); transitionIds.add(transitionId); } } for (Entry<Transition, Method> entry : transitionMapping.entrySet()) { Transition t = entry.getKey(); boolean reload = t.reload(); String transitionId = referenceFactory.getTransitionId(cnt++); registerActionAndTransition(referenceFactory, statefulControllerClass, entry.getKey().from(), entry.getKey().to(), reload, entry.getKey(), entry.getValue(), isDomainEntity, controllerRef, transitionId, reg); transitionIds.add(transitionId); } // Build out the Managed Entity Factory Bean // String factoryId = registerFactoryBean(referenceFactory, factory, scAnnotation, reg); // Build out the Managed Entity Finder Bean if we have endpoint binders; otherwise, it's not needed // String finderId = null; if (hasFinder(scAnnotation, repoBeanId)) { finderId = registerFinderBean(referenceFactory, factory, scAnnotation, repoBeanId, reg); } // Build out the Managed Entity State Persister Bean // String persisterId = registerPersisterBean(referenceFactory, factory, scAnnotation, managedClass, repoBeanId, repoBeanDefinitionFactory, stateBeans, reg); // Build out the FSM Bean // String fsmBeanId = registerFSM(referenceFactory, statefulControllerClass, scAnnotation, persisterId, managedClass, finderId, factory.getIdAnnotationType(), reg); // Build out the StatefulFSM Bean // String statefulFSMBeanId = registerStatefulFSMBean(referenceFactory, managedClass, fsmBeanId, factoryId, transitionIds, reg); // Build out the FSMHarness Bean if we have binders; otherwise, it's not needed // if (hasBinders) { registerFSMHarness(referenceFactory, factory, managedClass, statefulFSMBeanId, factoryId, finderId, repoBeanDefinitionFactory, reg); } } private void mapEventsTransitionsAndStates(Class<?> statefulControllerClass, Map<String, Map<String, Method>> providerMappings, Map<Transition, Method> transitionMapping, Map<Transition, Method> anyMapping, Set<String> states, Set<String> blockingStates) throws IllegalArgumentException, NotFoundException, IllegalAccessException, InvocationTargetException, CannotCompileException { // Walk up the Class hierarchy building out the FSM // if (statefulControllerClass == null) { return; } else { mapEventsTransitionsAndStates(statefulControllerClass.getSuperclass(), providerMappings, transitionMapping, anyMapping, states, blockingStates); } logger.debug("Mapping events and transitions for {}", statefulControllerClass); // Pull StateController Annotation // StatefulController ctrlAnnotation = statefulControllerClass.getAnnotation(StatefulController.class); if (ctrlAnnotation != null) { // Add Start State // states.add(ctrlAnnotation.startState()); // Add the list of BlockingStates // blockingStates.addAll(Arrays.asList(ctrlAnnotation.blockingStates())); // Map the NOOP Transitions // for (Transition transition : ctrlAnnotation.noops()) { mapTransition(transition, null, providerMappings, transitionMapping, anyMapping, states); } } // TODO : As we map the events, we need to make sure that the method signature and return // types of all the handlers for the event are the same for (Method method : statefulControllerClass.getDeclaredMethods()) { // Map the set of transitions as defined by the Transitions annotation // Transitions transitions = method.getAnnotation(Transitions.class); if (transitions != null) { for (Transition transition : transitions.value()) { mapTransition(transition, method, providerMappings, transitionMapping, anyMapping, states); } } // Map the Transition annotation // Transition transition = method.getAnnotation(Transition.class); if (transition != null) { mapTransition(transition, method, providerMappings, transitionMapping, anyMapping, states); } } } private void mapTransition(Transition transition, Method method, Map<String, Map<String, Method>> providerMappings, Map<Transition, Method> transitionMapping, Map<Transition, Method> anyMapping, Set<String> states) { logger.debug("Mapping {}:{}->{}", transition.from(), transition.event(), transition.to()); Pair<String, String> providerEvent = parseEvent(transition.event()); String provider = providerEvent.getLeft(); if (provider != null) { Map<String, Method> eventMapping = providerMappings.get(provider); if (eventMapping == null) { eventMapping = new HashMap<String, Method>(); providerMappings.put(provider, eventMapping); } // Add to the event mapping if this the first occurrence of an event, or the method // has more parameters than the existing mapping // Method existing = eventMapping.get(providerEvent.getRight()); if (existing == null || method.getParameterTypes().length > existing.getParameterTypes().length) { eventMapping.put(providerEvent.getRight(), method); } } if (!transition.from().equals(Transition.ANY_STATE)) { states.add(transition.from()); transitionMapping.put(transition, method); } else { anyMapping.put(transition, method); } if (!transition.to().equals(Transition.ANY_STATE)) { states.add(transition.to()); } } private Pair<String, String> parseEvent(String event) { Matcher matcher = this.binder.matcher(event); if (!matcher.matches()) { throw new RuntimeException("Unable to parse event=" + event); } return new ImmutablePair<String, String>(matcher.group(2), matcher.group(3)); } private void registerActionAndTransition(ReferenceFactory referenceFactory, Class<?> clazz, String from, String to, boolean reload, Transition transition, Method method, boolean isDomainEntity, RuntimeBeanReference controllerRef, String transitionId, BeanDefinitionRegistry reg) { // Remap to="Any" to to=from // to = (Transition.ANY_STATE.equals(to)) ? from : to; logger.debug("Registered: {}({})->{}/{}", from, transition.event(), to, (method == null) ? "noop" : method.getName()); // Build the Action Bean // RuntimeBeanReference actionRef = null; if (method != null) { String actionId = referenceFactory.getActionId(method); if (!reg.isBeanNameInUse(actionId)) { registerMethodInvocationAction(referenceFactory, method, isDomainEntity, controllerRef, reg, actionId); } actionRef = new RuntimeBeanReference(actionId); } registerTransition(referenceFactory, from, to, reload, transition, transitionId, reg, actionRef); } /** * @param referenceFactory * @param from * @param to * @param reload * @param transition * @param transitionId * @param reg * @param actionRef */ private void registerTransition(ReferenceFactory referenceFactory, String from, String to, boolean reload, Transition transition, String transitionId, BeanDefinitionRegistry reg, RuntimeBeanReference actionRef) { // Build the Transition Bean // BeanDefinition transitionBean = BeanDefinitionBuilder.genericBeanDefinition(TransitionImpl.class) .getBeanDefinition(); String fromId = referenceFactory.getStateId(from); String toId = referenceFactory.getStateId(to); Pair<String, String> providerEvent = parseEvent(transition.event()); ConstructorArgumentValues args = transitionBean.getConstructorArgumentValues(); args.addIndexedArgumentValue(0, new RuntimeBeanReference(fromId)); args.addIndexedArgumentValue(1, new RuntimeBeanReference(toId)); args.addIndexedArgumentValue(2, providerEvent.getRight()); args.addIndexedArgumentValue(3, actionRef); args.addIndexedArgumentValue(4, (transition.from().equals(Transition.ANY_STATE) && transition.to().equals(Transition.ANY_STATE))); args.addIndexedArgumentValue(5, reload); reg.registerBeanDefinition(transitionId, transitionBean); } /** * @param referenceFactory * @param method * @param isDomainEntity * @param controllerRef * @param reg * @param actionId */ private void registerMethodInvocationAction(ReferenceFactory referenceFactory, Method method, boolean isDomainEntity, RuntimeBeanReference controllerRef, BeanDefinitionRegistry reg, String actionId) { // Choose the type of invocationAction based off of // whether the controller is a DomainEntity // Class<?> methodInvocationAction = (isDomainEntity) ? DomainEntityMethodInvocationAction.class : MethodInvocationAction.class; BeanDefinition actionBean = BeanDefinitionBuilder.genericBeanDefinition(methodInvocationAction) .getBeanDefinition(); ConstructorArgumentValues args = actionBean.getConstructorArgumentValues(); args.addIndexedArgumentValue(0, method.getName()); args.addIndexedArgumentValue(1, method.getParameterTypes()); args.addIndexedArgumentValue(2, new RuntimeBeanReference(referenceFactory.getFSMId())); if (!isDomainEntity) { args.addIndexedArgumentValue(3, controllerRef); } reg.registerBeanDefinition(actionId, actionBean); } private String registerState(ReferenceFactory referenceFactory, Class<?> statefulControllerClass, String state, boolean isBlocking, BeanDefinitionRegistry reg) { String stateId = referenceFactory.getStateId(state); BeanDefinition stateBean = BeanDefinitionBuilder.genericBeanDefinition(StateImpl.class).getBeanDefinition(); ConstructorArgumentValues args = stateBean.getConstructorArgumentValues(); args.addIndexedArgumentValue(0, state); args.addIndexedArgumentValue(1, false); args.addIndexedArgumentValue(2, isBlocking); reg.registerBeanDefinition(stateId, stateBean); return stateId; } private String registerFSM(ReferenceFactory referenceFactory, Class<?> statefulControllerClass, StatefulController scAnnotation, String persisterId, Class<?> managedClass, String finderId, Class<? extends Annotation> idAnnotationType, BeanDefinitionRegistry reg) { int retryAttempts = scAnnotation.retryAttempts(); int retryInterval = scAnnotation.retryInterval(); String fsmBeanId = referenceFactory.getFSMId(); BeanDefinition fsmBean = BeanDefinitionBuilder.genericBeanDefinition(FSM.class).getBeanDefinition(); ConstructorArgumentValues args = fsmBean.getConstructorArgumentValues(); args.addIndexedArgumentValue(0, fsmBeanId); args.addIndexedArgumentValue(1, new RuntimeBeanReference(persisterId)); args.addIndexedArgumentValue(2, retryAttempts); args.addIndexedArgumentValue(3, retryInterval); args.addIndexedArgumentValue(4, managedClass); args.addIndexedArgumentValue(5, idAnnotationType); args.addIndexedArgumentValue(6, this.appContext); if (finderId != null) { args.addIndexedArgumentValue(7, new RuntimeBeanReference(finderId)); } reg.registerBeanDefinition(fsmBeanId, fsmBean); return fsmBeanId; } private String registerStatefulFSMBean(ReferenceFactory referenceFactory, Class<?> statefulClass, String fsmBeanId, String factoryId, List<String> transitionIds, BeanDefinitionRegistry reg) { String statefulFSMBeanId = referenceFactory.getStatefulFSMId(); BeanDefinition statefulFSMBean = BeanDefinitionBuilder.genericBeanDefinition(StatefulFSMImpl.class) .getBeanDefinition(); ConstructorArgumentValues args = statefulFSMBean.getConstructorArgumentValues(); args.addIndexedArgumentValue(0, new RuntimeBeanReference(fsmBeanId)); args.addIndexedArgumentValue(1, statefulClass); args.addIndexedArgumentValue(2, new RuntimeBeanReference(factoryId)); reg.registerBeanDefinition(statefulFSMBeanId, statefulFSMBean); statefulFSMBean.setDependsOn(transitionIds.toArray(new String[] {})); return statefulFSMBeanId; } private String registerBinderBean(String key, ReferenceFactory referenceFactory, Class<?> binderClass, BeanDefinitionRegistry reg) { BeanDefinition def = BeanDefinitionBuilder.genericBeanDefinition(binderClass).getBeanDefinition(); String binderId = referenceFactory.getBinderId(key); reg.registerBeanDefinition(binderId, def); return binderId; } private String registerFactoryBean(ReferenceFactory referenceFactory, PersistenceSupportBeanFactory persistenceFactory, StatefulController statefulContollerAnnotation, BeanDefinitionRegistry reg) { String factoryId = statefulContollerAnnotation.factoryId(); if (StringUtils.isEmpty(factoryId)) { if (persistenceFactory == null) { throw new RuntimeException( "PersistenceFactory is undefined and no factory bean was specified in the StatefulController Annotation for " + statefulContollerAnnotation.clazz()); } factoryId = referenceFactory.getFactoryId(); reg.registerBeanDefinition(factoryId, persistenceFactory.buildFactoryBean(statefulContollerAnnotation.clazz())); } return factoryId; } private String registerFinderBean(ReferenceFactory referenceFactory, PersistenceSupportBeanFactory persistenceFactory, StatefulController statefulContollerAnnotation, String repoBeanId, BeanDefinitionRegistry reg) { String finderId = statefulContollerAnnotation.finderId(); if (StringUtils.isEmpty(finderId)) { if (persistenceFactory == null) { throw new RuntimeException( "PersistenceFactory is undefined and no finder bean was specified in the StatefulController Annotation for " + statefulContollerAnnotation.clazz()); } if (StringUtils.isEmpty(repoBeanId)) { throw new RuntimeException("No Repository is defined for " + statefulContollerAnnotation.clazz()); } finderId = referenceFactory.getFinderId(); reg.registerBeanDefinition(finderId, persistenceFactory.buildFinderBean(repoBeanId)); } return finderId; } private String registerPersisterBean(ReferenceFactory referenceFactory, PersistenceSupportBeanFactory persistenceFactory, StatefulController statefulContollerAnnotation, Class<?> statefulClass, String repoBeanId, BeanDefinition repoBeanDefinitionFactory, List<RuntimeBeanReference> stateBeans, BeanDefinitionRegistry reg) { String persisterId = statefulContollerAnnotation.persisterId(); if (StringUtils.isEmpty(persisterId)) { if (persistenceFactory == null) { throw new RuntimeException( "PersistenceFactory is undefined and no persister bean was specified in the StatefulController Annotation for " + statefulContollerAnnotation.clazz()); } String startStateId = referenceFactory.getStateId(statefulContollerAnnotation.startState()); persisterId = referenceFactory.getPersisterId(); reg.registerBeanDefinition(persisterId, persistenceFactory.buildPersisterBean(statefulClass, repoBeanId, repoBeanDefinitionFactory, statefulContollerAnnotation.stateField(), startStateId, stateBeans)); } return persisterId; } private String registerFSMHarness(ReferenceFactory referenceFactory, PersistenceSupportBeanFactory persistenceFactory, Class<?> statefulClass, String fsmBeanId, String factoryId, String finderId, BeanDefinition repoBeanFactory, BeanDefinitionRegistry reg) { String fsmHarnessId = referenceFactory.getFSMHarnessId(); reg.registerBeanDefinition(fsmHarnessId, persistenceFactory.buildFSMHarnessBean(statefulClass, fsmBeanId, factoryId, finderId, repoBeanFactory)); return fsmHarnessId; } private String getRepoId(Map<Class<?>, String> entityToRepositoryMappings, Class<?> clazz) { if (clazz != null) { String id = entityToRepositoryMappings.get(clazz); if (id != null) { return id; } id = getRepoId(entityToRepositoryMappings, clazz.getSuperclass()); if (id != null) { return id; } for (Class<?> interfaze : clazz.getInterfaces()) { id = getRepoId(entityToRepositoryMappings, interfaze); if (id != null) { return id; } } } return null; } private Class<?> getClassFromBeanDefinition(BeanDefinition bf, BeanDefinitionRegistry reg) throws ClassNotFoundException { Class<?> clazz = null; if (bf.getBeanClassName() == null) { clazz = getClassFromFactoryMethod(bf, reg); } else { clazz = getClassFromBeanClassName(bf); } if (clazz == null) { clazz = getClassFromParentBean(bf, reg); } return clazz; } /** * @param bf * @return * @throws ClassNotFoundException */ private Class<?> getClassFromBeanClassName(BeanDefinition bf) throws ClassNotFoundException { return Class.forName(bf.getBeanClassName()); } /** * @param bf * @param reg * @param clazz * @return * @throws ClassNotFoundException */ private Class<?> getClassFromParentBean(BeanDefinition bf, BeanDefinitionRegistry reg) throws ClassNotFoundException { Class<?> clazz = null; String parentBeanName = bf.getParentName(); if (parentBeanName != null) { BeanDefinition parent = reg.getBeanDefinition(parentBeanName); if (parent != null) { clazz = this.getClassFromBeanDefinition(parent, reg); } } return clazz; } /** * @param bf * @param reg * @param clazz * @return * @throws ClassNotFoundException */ private Class<?> getClassFromFactoryMethod(BeanDefinition bf, BeanDefinitionRegistry reg) throws ClassNotFoundException { Class<?> clazz = null; String factoryBeanName = bf.getFactoryBeanName(); if (factoryBeanName != null) { BeanDefinition factory = reg.getBeanDefinition(factoryBeanName); if (factory != null) { String factoryClassName = factory.getBeanClassName(); Class<?> factoryClass = Class.forName(factoryClassName); List<Method> methods = new LinkedList<Method>(); methods.addAll(Arrays.asList(factoryClass.getMethods())); methods.addAll(Arrays.asList(factoryClass.getDeclaredMethods())); for (Method method : methods) { method.setAccessible(true); if (method.getName().equals(bf.getFactoryMethodName())) { clazz = method.getReturnType(); break; } } } } return clazz; } /** * @param reflections * @throws InstantiationException * @throws IllegalAccessException */ private void loadPersistenceSupportBeanFactories(Reflections reflections, Map<Class<?>, PersistenceSupportBeanFactory> persistenceFactories) throws InstantiationException, IllegalAccessException { Set<Class<? extends PersistenceSupportBeanFactory>> persistenceFactoryTypes = reflections .getSubTypesOf(PersistenceSupportBeanFactory.class); for (Class<?> persistenceFactoryType : persistenceFactoryTypes) { if (!Modifier.isAbstract(persistenceFactoryType.getModifiers())) { PersistenceSupportBeanFactory factory = (PersistenceSupportBeanFactory) persistenceFactoryType .newInstance(); Class<?> key = factory.getKey(); if (key != null) { persistenceFactories.put(key, factory); } } } } /** * @return * @throws InstantiationException * @throws IllegalAccessException */ private void loadEndpointBinders(Reflections reflections, Map<String, EndpointBinder> binders) throws InstantiationException, IllegalAccessException { Set<Class<? extends EndpointBinder>> endpointBinders = reflections.getSubTypesOf(EndpointBinder.class); for (Class<?> binderClass : endpointBinders) { if (!Modifier.isAbstract(binderClass.getModifiers())) { EndpointBinder binder = (EndpointBinder) binderClass.newInstance(); binders.put(binder.getKey(), binder); } } } /** * @param scAnnotation * @param repoBeanId * @return */ private boolean hasFinder(StatefulController scAnnotation, String repoBeanId) { return !"".equals(scAnnotation.finderId()) || (repoBeanId != null && !repoBeanId.trim().equals("")); } }