[java-idp-plugin-duo] branch main updated: Fix flow tests

Phil Smart philip.smart at jisc.ac.uk
Mon Nov 14 15:03:52 UTC 2022


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-duo.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-duo.git;a=commit;h=4f320bb28996aeeaf893a84b13cbbe6829c61859

The following commit(s) were added to refs/heads/main by this push:
     new 4f320bb  Fix flow tests
4f320bb is described below

commit 4f320bb28996aeeaf893a84b13cbbe6829c61859
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Nov 14 15:03:37 2022 +0000

    Fix flow tests
---
 idp-duo-impl/pom.xml                               |   5 +
 .../impl/AbstractAuthnXmlFlowExecutionTests.java   | 139 ++++++--
 .../plugin/authn/duo/impl/DuoAuthnFlowTest.java    |  31 +-
 .../authn/spring/CustomFlowModelFlowBuilder.java   | 376 +++++++++++----------
 pom.xml                                            |   5 +-
 5 files changed, 343 insertions(+), 213 deletions(-)

diff --git a/idp-duo-impl/pom.xml b/idp-duo-impl/pom.xml
index 83c7454..e64a675 100644
--- a/idp-duo-impl/pom.xml
+++ b/idp-duo-impl/pom.xml
@@ -136,6 +136,11 @@
 			<artifactId>idp-conf-impl</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+		   <groupId>net.shibboleth.ext</groupId>
+           <artifactId>spring-extensions</artifactId>
+           <scope>test</scope>
+		</dependency>
 		<dependency>
 			<groupId>net.shibboleth.idp</groupId>
 			<artifactId>idp-profile-impl</artifactId>
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
index d05ff30..ef939a4 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/AbstractAuthnXmlFlowExecutionTests.java
@@ -17,11 +17,12 @@
 
 package net.shibboleth.idp.plugin.authn.duo.impl;
 
-import java.time.Duration;
 import java.util.Base64;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
 import java.util.Set;
 import java.util.function.Consumer;
 
@@ -36,14 +37,19 @@ import org.springframework.beans.factory.BeanFactory;
 import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.support.BeanDefinitionBuilder;
 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-
+import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.support.ConversionServiceFactoryBean;
+import org.springframework.context.support.GenericApplicationContext;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
 import org.springframework.context.support.StaticApplicationContext;
 import org.springframework.core.convert.converter.Converter;
 import org.springframework.core.env.Environment;
 import org.springframework.core.env.StandardEnvironment;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.EncodedResource;
 import org.springframework.mock.env.MockPropertySource;
 import org.springframework.mock.web.MockHttpServletRequest;
 import org.springframework.mock.web.MockHttpServletResponse;
@@ -57,6 +63,15 @@ import org.springframework.webflow.test.execution.AbstractExternalizedFlowExecut
 
 import com.google.common.net.HttpHeaders;
 
+import net.shibboleth.ext.spring.config.BooleanToPredicateConverter;
+import net.shibboleth.ext.spring.config.FunctionToFunctionConverter;
+import net.shibboleth.ext.spring.config.IdentifiableBeanPostProcessor;
+import net.shibboleth.ext.spring.config.PredicateToPredicateConverter;
+import net.shibboleth.ext.spring.config.StringBooleanToPredicateConverter;
+import net.shibboleth.ext.spring.config.StringToDurationConverter;
+import net.shibboleth.ext.spring.config.StringToIPRangeConverter;
+import net.shibboleth.ext.spring.config.StringToResourceConverter;
+import net.shibboleth.ext.spring.resource.ConditionalResourceResolver;
 import net.shibboleth.ext.spring.util.AnnotationParameterNameDiscoverer;
 import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
@@ -70,7 +85,6 @@ import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
 import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy;
-import net.shibboleth.utilities.java.support.xml.DOMTypeSupport;
 
 
 /**
@@ -146,14 +160,23 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
      * context can be inspected by the test conditions.
      */
     private boolean removeDefaultContextCleanupHook;
+    
+    /** The mocked entityID of this IdP instance.*/
+    @Nonnull private final String entityId;
 
  
-    /** Constructor. */
-    public AbstractAuthnXmlFlowExecutionTests() {        
+    /**
+     * 
+     * Constructor.
+     *
+     * @param idpEntityID The mocked entityID of this IdP instance
+     */
+    public AbstractAuthnXmlFlowExecutionTests(@Nonnull final String idpEntityID) {        
         flowModelResources = Collections.emptyMap();
         subflows = Collections.emptyList();    
         mockProperties = Collections.emptyMap();
         removeDefaultContextCleanupHook = false;
+        entityId = Objects.requireNonNull(idpEntityID);
     }
     
     /**
@@ -275,13 +298,96 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
         subflows.forEach(flow -> builderContext.registerSubflow(flow));        
     }
     
+    /**
+     * Load beans into the builder context from the given resource e.g. a classpath resource. The loader
+     * used here is separate to the main webflow builder and as such some things need setting up specific
+     * to the correct loading of resources in the given xml file.
+     * 
+     * @param builderContext the builder context.
+     * @param xmlFile the xml file.
+     * @param additionalProperties properties that need to be injected into the bean factory.
+     */
+    protected void loadBeanDefinitionsFromXmlFile(@Nonnull final MockFlowBuilderContext builderContext,
+            @Nonnull final Resource xmlFile, @Nullable final Map<String,String> additionalProperties) {
+        assertNotNull(builderContext);
+        assertTrue(xmlFile.exists());
+        
+        assertTrue( builderContext.getApplicationContext() instanceof ConfigurableApplicationContext);
+        final BeanFactory factory = ((ConfigurableApplicationContext) builderContext.
+                getApplicationContext()).getBeanFactory();
+        
+        assertNotNull(factory);
+        assertTrue(factory instanceof DefaultListableBeanFactory);
+        
+        final XmlBeanDefinitionReader beanDefinitionReader = 
+                new XmlBeanDefinitionReader((DefaultListableBeanFactory) factory);
+        //we need to register the IdentifiableBeanPostProcessor as that is not registered by this point if
+        //used in the webflow, and this resource is not loading one.
+        ((DefaultListableBeanFactory)factory).addBeanPostProcessor(new IdentifiableBeanPostProcessor());
+        
+        ((DefaultListableBeanFactory)factory).setParameterNameDiscoverer(new AnnotationParameterNameDiscoverer());
+        
+        //create converter for Durations
+        final ConversionServiceFactoryBean conversationService = new ConversionServiceFactoryBean();
+        //unable to register the spring-ext converter even though it is the same!       
+        conversationService.setConverters(createConverters());
+        conversationService.afterPropertiesSet();
+        
+        ((DefaultListableBeanFactory)factory).setConversionService(conversationService.getObject());   
+        
+        // Add a resource loader to support 'conditional:' resources in things loaded before the webflow config.
+        // TODO this could break things? although does not seem to.
+        final DefaultResourceLoader loader = new DefaultResourceLoader();
+        loader.addProtocolResolver(new ConditionalResourceResolver());
+        ((GenericApplicationContext)builderContext.getApplicationContext()).setResourceLoader(loader);
+        
+        final int beanNumbers = beanDefinitionReader.loadBeanDefinitions(new EncodedResource(xmlFile, "UTF-8"));
+        
+        //process any properties in the file bean expressions
+        final Properties mock = new Properties();        
+        //add the standard mocks to enable basic IdP function if needed.
+        mock.setProperty("idp.home", "classpath:");
+        mock.setProperty("idp.entityID", entityId);
+        mock.setProperty("idp.webflows", "classpath*:/flows");
+        if (additionalProperties != null) {
+            additionalProperties.forEach((k, v) -> {
+                log.debug("Adding property ['{}':'{}']", k, v);
+                mock.setProperty(k, v);
+            });
+        }
+        final PropertySourcesPlaceholderConfigurer ppc = new PropertySourcesPlaceholderConfigurer();
+        ppc.setProperties(mock);
+        ppc.setPlaceholderPrefix("%{");
+        ppc.setPlaceholderSuffix("}");
+        ppc.postProcessBeanFactory((DefaultListableBeanFactory)factory);
+        
+        log.info("Loaded {} beans from {}",beanNumbers, xmlFile);
+    }
+    
+    /**
+    * Create a set of converters similar to those in the IdP's global-system.xml file.
+    * For some reason, it will not register the pre-defined converters, so they are
+    * re-created here. 
+    * 
+    * @return a set of converters.
+    */
+   private Set<Converter<?, ?>> createConverters(){
+
+       //some of these may not work comming from the spring-ext IdP project and may need specifying explicitly
+       //like the above converter. TODO check why.
+       return Set.of(new StringToDurationConverter(), new StringToResourceConverter(), new StringToIPRangeConverter(),
+               new BooleanToPredicateConverter(),new StringBooleanToPredicateConverter(),
+               new StringToResourceConverter(),new FunctionToFunctionConverter<>(),
+               new PredicateToPredicateConverter<>());
+   }
+    
     /**
      * Register mock beans that are not necessary to configure for authentication testing
      * but are required for authentication flows to proceed. 
      * 
      * @param builderContext the context used to register the beans.
      */
-    private void registerMockBeanDefinitions(@Nonnull final MockFlowBuilderContext builderContext) {
+    protected void registerMockBeanDefinitions(@Nonnull final MockFlowBuilderContext builderContext) {
         
         builderContext.registerBean("shibboleth.DefaultIdentifierGenerationStrategy", 
                 new SecureRandomIdentifierGenerationStrategy());
@@ -292,22 +398,9 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
         //create a real JSON parser
         builderContext.registerBean("shibboleth.JSONObjectMapper", new com.fasterxml.jackson.databind.ObjectMapper());
         
-        //create converter for Durations
-        final ConversionServiceFactoryBean conversationService = new ConversionServiceFactoryBean();
-        //unable to register the spring-ext converter even though it is the same!
-        final Converter<String,Duration> durationConverter = new Converter<String,Duration>(){
-
-            @Override
-            public Duration convert(final String source) {
-                if (source.startsWith("P") || source.startsWith("-P")) {
-                    return DOMTypeSupport.stringToDuration(source.trim());
-                }                
-                // Treat as milliseconds.
-                return Duration.ofMillis(Long.valueOf(source));
-            }
-            
-        };        
-        conversationService.setConverters(Set.of(durationConverter));
+        //create converters
+        final ConversionServiceFactoryBean conversationService = new ConversionServiceFactoryBean();            
+        conversationService.setConverters(createConverters());
         conversationService.afterPropertiesSet();
         builderContext.registerBean("conversionService", conversationService.getObject());
         
@@ -331,7 +424,7 @@ public abstract class AbstractAuthnXmlFlowExecutionTests extends CustomAbstractX
                     
                 }});
         }
-                       
+                         
     }
     
     
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
index 5b5b9b9..a71eda1 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAuthnFlowTest.java
@@ -30,10 +30,12 @@ import org.opensaml.profile.context.EventContext;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.io.ClassPathResource;
 import org.springframework.webflow.core.collection.LocalAttributeMap;
 import org.springframework.webflow.engine.Flow;
 import org.springframework.webflow.engine.impl.FlowExecutionImpl;
 import org.springframework.webflow.execution.FlowExecution;
+import org.springframework.webflow.test.MockFlowBuilderContext;
 
 import net.shibboleth.idp.authn.AuthenticationFlowDescriptor;
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -99,8 +101,22 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
             Map.of(
             "classpath:/net/shibboleth/idp/flows/authn/authn-abstract-flow.xml","authn.abstract",
             "classpath:/conf/authn/authn-events-flow.xml","authn.events");
+    
+    
+    /** Constructor.*/
+    public DuoAuthnFlowTest() {
+        super("http://idp.example.org");        
+    }
 
 
+    @Override
+    protected void registerMockBeanDefinitions(@Nonnull final MockFlowBuilderContext builderContext) {
+        super.registerMockBeanDefinitions(builderContext);
+        
+        loadBeanDefinitionsFromXmlFile(builderContext, 
+                new ClassPathResource("META-INF/net.shibboleth.idp/postconfig.xml"),
+                null);               
+    }
 
     /** Test the Duo flow when the health check returns unhealthy.*/
     @Test
@@ -121,7 +137,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
 
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
@@ -153,11 +169,12 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
-        flowExecution.getConversationScope().put("opensamlProfileRequestContext", buildProfileRequestContext(false,true));
+        flowExecution.getConversationScope().put("opensamlProfileRequestContext", 
+                buildProfileRequestContext(false,true));
         updateFlowExecution(flowExecution);         
         flowExecution.start(inputMap, externalContext);         
         assertFlowExecutionActive();
@@ -182,7 +199,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
@@ -227,7 +244,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
@@ -272,7 +289,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         setMockProperties(mockProperties);
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
        
         final FlowExecution flowExecution = getFlowExecutionFactory().createFlowExecution(getFlowDefinition());  
@@ -309,7 +326,7 @@ public class DuoAuthnFlowTest extends AbstractAuthnXmlFlowExecutionTests {
         
         
         
-        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<Object>();
+        final LocalAttributeMap<Object> inputMap = new LocalAttributeMap<>();
         inputMap.put("calledAsSubflow", true);
         
         final FlowExecutionImpl flowExecution = (FlowExecutionImpl)getFlowExecutionFactory()
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/spring/CustomFlowModelFlowBuilder.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/spring/CustomFlowModelFlowBuilder.java
index 0ef4efe..c61423c 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/spring/CustomFlowModelFlowBuilder.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/spring/CustomFlowModelFlowBuilder.java
@@ -133,7 +133,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
     public static final String VALIDATION_HINT_RESOLVER_FLOW_ATTR =
             FlowModelFlowBuilder.class.getSimpleName() + ".validationHintResolver";
 
-    private FlowModelHolder flowModelHolder;
+    private final FlowModelHolder flowModelHolder;
 
     private FlowModel flowModel;
     
@@ -144,7 +144,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * Creates a flow builder that can build a {@link Flow} from a {@link FlowModel}.
      * @param flowModelHolder the flow model holder
      */
-    public CustomFlowModelFlowBuilder(FlowModelHolder flowModelHolder) {
+    public CustomFlowModelFlowBuilder(final FlowModelHolder flowModelHolder) {
         Assert.notNull(flowModelHolder, "The FlowModelHolder is required");
         this.flowModelHolder = flowModelHolder;
     }
@@ -155,11 +155,12 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     protected void doInit() throws FlowBuilderException {
         try {
             flowModel = flowModelHolder.getFlowModel();
             initLocalFlowContext();
-        } catch (FlowModelBuilderException e) {
+        } catch (final FlowModelBuilderException e) {
             throw new FlowBuilderException("Unable to get the model for this flow", e);
         }
         if ("true".equals(flowModel.getAbstract())) {
@@ -167,8 +168,9 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
+    @Override
     protected Flow createFlow() {
-        String flowId = getContext().getFlowId();
+        final String flowId = getContext().getFlowId();
         AttributeMap<Object> flowAttributes = parseFlowMetaAttributes(flowModel);
         flowAttributes = getContext().getFlowAttributes().union(flowAttributes);
         if (IS_SPRING_FACES_PRESENT) {
@@ -176,7 +178,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
             flowAttributes.asMap().put(VALIDATION_HINT_RESOLVER_FLOW_ATTR,
                     getLocalContext().getValidationHintResolver());
         }
-        Flow flow = getLocalContext().getFlowArtifactFactory().createFlow(flowId, flowAttributes);
+        final Flow flow = getLocalContext().getFlowArtifactFactory().createFlow(flowId, flowAttributes);
         flow.setApplicationContext(getLocalContext().getApplicationContext());
         return flow;
     }
@@ -186,9 +188,10 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildVariables() throws FlowBuilderException {
         if (flowModel.getVars() != null) {
-            for (VarModel varModel : flowModel.getVars()) {
+            for (final VarModel varModel : flowModel.getVars()) {
                 getFlow().addVariable(parseFlowVariable(varModel));
             }
         }
@@ -199,6 +202,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildInputMapper() throws FlowBuilderException {
         getFlow().setInputMapper(parseFlowInputMapper(flowModel.getInputs()));
     }
@@ -208,6 +212,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildStartActions() throws FlowBuilderException {
         getFlow().getStartActionList().addAll(parseActions(flowModel.getOnStartActions()));
     }
@@ -217,11 +222,12 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildStates() throws FlowBuilderException {
         if (flowModel.getStates() == null) {
             throw new FlowBuilderException("At least one state is required to build a Flow");
         }
-        for (AbstractStateModel state : flowModel.getStates()) {
+        for (final AbstractStateModel state : flowModel.getStates()) {
             if (state instanceof ActionStateModel) {
                 parseAndAddActionState((ActionStateModel) state, getFlow());
             } else if (state instanceof ViewStateModel) {
@@ -244,6 +250,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildGlobalTransitions() throws FlowBuilderException {
         getFlow().getGlobalTransitionSet().addAll(parseTransitions(flowModel.getGlobalTransitions()));
     }
@@ -253,6 +260,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildEndActions() throws FlowBuilderException {
         getFlow().getEndActionList().addAll(parseActions(flowModel.getOnEndActions()));
     }
@@ -262,6 +270,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building the flow
      */
+    @Override
     public void buildOutputMapper() throws FlowBuilderException {
         if (flowModel.getOutputs() != null) {
             getFlow().setOutputMapper(parseFlowOutputMapper(flowModel.getOutputs()));
@@ -273,15 +282,18 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building this flow
      */
+    @Override
     public void buildExceptionHandlers() throws FlowBuilderException {
         getFlow().getExceptionHandlerSet()
                 .addAll(parseExceptionHandlers(flowModel.getExceptionHandlers(), flowModel.getGlobalTransitions()));
     }
 
+    @Override
     public boolean hasFlowChanged() {
         return flowModelHolder.hasFlowModelChanged();
     }
 
+    @Override
     public String getFlowResourceString() {
         return flowModelHolder.getFlowModelResource().getDescription();
     }
@@ -292,6 +304,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * 
      * @throws FlowBuilderException an exception occurred building this flow
      */
+    @Override
     protected void doDispose() throws FlowBuilderException {
         flowModel = null;
         setLocalContext(null);
@@ -307,7 +320,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         return localFlowBuilderContext;
     }
 
-    protected void setLocalContext(CustomLocalFlowBuilderContext localFlowBuilderContext) {
+    protected void setLocalContext(final CustomLocalFlowBuilderContext localFlowBuilderContext) {
         this.localFlowBuilderContext = localFlowBuilderContext;
     }
 
@@ -320,25 +333,25 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
      * @param beanFactory the bean factory; register local beans with it using
      *            {@link ConfigurableBeanFactory#registerSingleton(String, Object)}
      */
-    protected void registerFlowBeans(ConfigurableBeanFactory beanFactory) {
+    protected void registerFlowBeans(final ConfigurableBeanFactory beanFactory) {
     }
 
     // internal helpers
 
     private void initLocalFlowContext() {
-        Resource[] contextResources = parseContextResources(getFlowModel().getBeanImports());
-        GenericApplicationContext flowContext = createFlowApplicationContext(contextResources);
+        final Resource[] contextResources = parseContextResources(getFlowModel().getBeanImports());
+        final GenericApplicationContext flowContext = createFlowApplicationContext(contextResources);
         setLocalContext(new CustomLocalFlowBuilderContext(getContext(), flowContext));
     }
 
-    private Resource[] parseContextResources(List<BeanImportModel> beanImports) {
+    private Resource[] parseContextResources(final List<BeanImportModel> beanImports) {
         if (beanImports != null && !beanImports.isEmpty()) {
-            Resource flowResource = flowModelHolder.getFlowModelResource();
-            List<Resource> resources = new ArrayList<>(beanImports.size());
-            for (BeanImportModel beanImport : getFlowModel().getBeanImports()) {
+            final Resource flowResource = flowModelHolder.getFlowModelResource();
+            final List<Resource> resources = new ArrayList<>(beanImports.size());
+            for (final BeanImportModel beanImport : getFlowModel().getBeanImports()) {
                 try {
                     resources.add(flowResource.createRelative(beanImport.getResource()));
-                } catch (IOException e) {
+                } catch (final IOException e) {
                     throw new FlowBuilderException(
                             "Could not access flow-relative artifact resource '" + beanImport.getResource() + "'", e);
                 }
@@ -349,11 +362,11 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private GenericApplicationContext createFlowApplicationContext(Resource[] resources) {
-        ApplicationContext parent = getContext().getApplicationContext();
+    private GenericApplicationContext createFlowApplicationContext(final Resource[] resources) {
+        final ApplicationContext parent = getContext().getApplicationContext();
         GenericApplicationContext flowContext;
         if (parent instanceof WebApplicationContext) {
-            GenericWebApplicationContext webContext = new GenericWebApplicationContext();
+            final GenericWebApplicationContext webContext = new GenericWebApplicationContext();
             webContext.setServletContext(((WebApplicationContext) parent).getServletContext());
             flowContext = webContext;
         } else {
@@ -373,11 +386,11 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         flowContext.getDefaultListableBeanFactory().setParameterNameDiscoverer(new AnnotationParameterNameDiscoverer());
 
         // Ensure the current ClassLoader is used, or otherwise setting the ResourceLoader would suppress it
-        ClassLoader classLoaderToUse = flowContext.getClassLoader();
+        final ClassLoader classLoaderToUse = flowContext.getClassLoader();
         flowContext.setClassLoader(classLoaderToUse);
 
-        Resource flowResource = flowModelHolder.getFlowModelResource();
-        CustomFlowRelativeResourceLoader loader = new CustomFlowRelativeResourceLoader(flowResource);
+        final Resource flowResource = flowModelHolder.getFlowModelResource();
+        final CustomFlowRelativeResourceLoader loader = new CustomFlowRelativeResourceLoader(flowResource);
         loader.addProtocolResolver(new ConditionalResourceResolver());
         flowContext.setResourceLoader(loader);
         
@@ -396,18 +409,18 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         return getContext().getFlowAttributes().getBoolean("development", false);
     }
 
-    private void registerMessageSource(GenericApplicationContext flowContext, Resource flowResource) {
-        boolean localMessageSourcePresent =
+    private void registerMessageSource(final GenericApplicationContext flowContext, final Resource flowResource) {
+        final boolean localMessageSourcePresent =
                 flowContext.containsLocalBean(AbstractApplicationContext.MESSAGE_SOURCE_BEAN_NAME);
         if (!localMessageSourcePresent) {
             Resource messageBundle;
             try {
                 messageBundle = flowResource.createRelative("messages.properties");
-            } catch (IOException e) {
+            } catch (final IOException e) {
                 messageBundle = null;
             }
             if (messageBundle != null && messageBundle.exists()) {
-                BeanDefinitionBuilder builder =
+                final BeanDefinitionBuilder builder =
                         BeanDefinitionBuilder.rootBeanDefinition(ReloadableResourceBundleMessageSource.class);
                 builder.addPropertyValue("basename", "messages");
                 if (isFlowInDevelopment()) {
@@ -419,24 +432,24 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private AttributeMap<Object> parseFlowMetaAttributes(FlowModel flow) {
-        MutableAttributeMap<Object> flowAttributes = parseMetaAttributes(flow.getAttributes());
+    private AttributeMap<Object> parseFlowMetaAttributes(final FlowModel flow) {
+        final MutableAttributeMap<Object> flowAttributes = parseMetaAttributes(flow.getAttributes());
         parseAndPutPersistenceContext(flow.getPersistenceContext(), flowAttributes);
         parseAndPutSecured(flow.getSecured(), flowAttributes);
         return flowAttributes;
     }
 
-    private FlowVariable parseFlowVariable(VarModel var) {
-        Class<?> clazz = toClass(var.getClassName());
-        VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz,
+    private FlowVariable parseFlowVariable(final VarModel var) {
+        final Class<?> clazz = toClass(var.getClassName());
+        final VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz,
                 getFlow().getApplicationContext().getAutowireCapableBeanFactory());
         return new FlowVariable(var.getName(), valueFactory);
     }
 
-    private Mapper parseFlowInputMapper(List<InputModel> inputs) {
+    private Mapper parseFlowInputMapper(final List<InputModel> inputs) {
         if (inputs != null && !inputs.isEmpty()) {
-            DefaultMapper inputMapper = new DefaultMapper();
-            for (InputModel inputModel : inputs) {
+            final DefaultMapper inputMapper = new DefaultMapper();
+            for (final InputModel inputModel : inputs) {
                 inputMapper.addMapping(parseFlowInputMapping(inputModel));
             }
             return inputMapper;
@@ -445,27 +458,27 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private DefaultMapping parseFlowInputMapping(InputModel input) {
-        ExpressionParser parser = getLocalContext().getExpressionParser();
-        String name = input.getName();
+    private DefaultMapping parseFlowInputMapping(final InputModel input) {
+        final ExpressionParser parser = getLocalContext().getExpressionParser();
+        final String name = input.getName();
         String value = null;
         if (StringUtils.hasText(input.getValue())) {
             value = input.getValue();
         } else {
             value = "flowScope." + name;
         }
-        Expression source = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
-        Expression target = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
-        DefaultMapping mapping = new DefaultMapping(source, target);
+        final Expression source = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
+        final Expression target = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
+        final DefaultMapping mapping = new DefaultMapping(source, target);
         parseAndSetMappingConversionExecutor(input, mapping);
         parseAndSetMappingRequired(input, mapping);
         return mapping;
     }
 
-    private Mapper parseSubflowInputMapper(List<InputModel> inputs) {
+    private Mapper parseSubflowInputMapper(final List<InputModel> inputs) {
         if (inputs != null && !inputs.isEmpty()) {
-            DefaultMapper inputMapper = new DefaultMapper();
-            for (InputModel inputModel : inputs) {
+            final DefaultMapper inputMapper = new DefaultMapper();
+            for (final InputModel inputModel : inputs) {
                 inputMapper.addMapping(parseSubflowInputMapping(inputModel));
             }
             return inputMapper;
@@ -474,27 +487,27 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private DefaultMapping parseSubflowInputMapping(InputModel input) {
-        ExpressionParser parser = getLocalContext().getExpressionParser();
-        String name = input.getName();
+    private DefaultMapping parseSubflowInputMapping(final InputModel input) {
+        final ExpressionParser parser = getLocalContext().getExpressionParser();
+        final String name = input.getName();
         String value = null;
         if (StringUtils.hasText(input.getValue())) {
             value = input.getValue();
         } else {
             value = name;
         }
-        Expression source = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
-        Expression target = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
-        DefaultMapping mapping = new DefaultMapping(source, target);
+        final Expression source = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
+        final Expression target = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
+        final DefaultMapping mapping = new DefaultMapping(source, target);
         parseAndSetMappingConversionExecutor(input, mapping);
         parseAndSetMappingRequired(input, mapping);
         return mapping;
     }
 
-    private Mapper parseFlowOutputMapper(List<OutputModel> outputs) {
+    private Mapper parseFlowOutputMapper(final List<OutputModel> outputs) {
         if (outputs != null && !outputs.isEmpty()) {
-            DefaultMapper outputMapper = new DefaultMapper();
-            for (OutputModel outputModel : outputs) {
+            final DefaultMapper outputMapper = new DefaultMapper();
+            for (final OutputModel outputModel : outputs) {
                 outputMapper.addMapping(parseFlowOutputMapping(outputModel));
             }
             return outputMapper;
@@ -503,27 +516,27 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private DefaultMapping parseFlowOutputMapping(OutputModel output) {
-        ExpressionParser parser = getLocalContext().getExpressionParser();
-        String name = output.getName();
+    private DefaultMapping parseFlowOutputMapping(final OutputModel output) {
+        final ExpressionParser parser = getLocalContext().getExpressionParser();
+        final String name = output.getName();
         String value = null;
         if (StringUtils.hasText(output.getValue())) {
             value = output.getValue();
         } else {
             value = name;
         }
-        Expression source = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
-        Expression target = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
-        DefaultMapping mapping = new DefaultMapping(source, target);
+        final Expression source = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
+        final Expression target = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
+        final DefaultMapping mapping = new DefaultMapping(source, target);
         parseAndSetMappingConversionExecutor(output, mapping);
         parseAndSetMappingRequired(output, mapping);
         return mapping;
     }
 
-    private Mapper parseSubflowOutputMapper(List<OutputModel> outputs) {
+    private Mapper parseSubflowOutputMapper(final List<OutputModel> outputs) {
         if (outputs != null && !outputs.isEmpty()) {
-            DefaultMapper outputMapper = new DefaultMapper();
-            for (OutputModel outputModel : outputs) {
+            final DefaultMapper outputMapper = new DefaultMapper();
+            for (final OutputModel outputModel : outputs) {
                 outputMapper.addMapping(parseSubflowOutputMapping(outputModel));
             }
             return outputMapper;
@@ -532,41 +545,41 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private DefaultMapping parseSubflowOutputMapping(OutputModel output) {
-        ExpressionParser parser = getLocalContext().getExpressionParser();
-        String name = output.getName();
+    private DefaultMapping parseSubflowOutputMapping(final OutputModel output) {
+        final ExpressionParser parser = getLocalContext().getExpressionParser();
+        final String name = output.getName();
         String value = null;
         if (StringUtils.hasText(output.getValue())) {
             value = output.getValue();
         } else {
             value = "flowScope." + name;
         }
-        Expression source = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
-        Expression target = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
-        DefaultMapping mapping = new DefaultMapping(source, target);
+        final Expression source = parser.parseExpression(name, new FluentParserContext().evaluate(MutableAttributeMap.class));
+        final Expression target = parser.parseExpression(value, new FluentParserContext().evaluate(RequestContext.class));
+        final DefaultMapping mapping = new DefaultMapping(source, target);
         parseAndSetMappingConversionExecutor(output, mapping);
         parseAndSetMappingRequired(output, mapping);
         return mapping;
     }
 
-    private void parseAndSetMappingConversionExecutor(AbstractMappingModel mappingModel, DefaultMapping mapping) {
+    private void parseAndSetMappingConversionExecutor(final AbstractMappingModel mappingModel, final DefaultMapping mapping) {
         if (StringUtils.hasText(mappingModel.getType())) {
-            Class<?> type = toClass(mappingModel.getType());
-            ConversionExecutor typeConverter =
+            final Class<?> type = toClass(mappingModel.getType());
+            final ConversionExecutor typeConverter =
                     new RuntimeBindingConversionExecutor(type, getLocalContext().getConversionService());
             mapping.setTypeConverter(typeConverter);
         }
     }
 
-    private void parseAndSetMappingRequired(AbstractMappingModel mappingModel, DefaultMapping mapping) {
+    private void parseAndSetMappingRequired(final AbstractMappingModel mappingModel, final DefaultMapping mapping) {
         if (StringUtils.hasText(mappingModel.getRequired())) {
-            boolean required = ((Boolean) fromStringTo(Boolean.class).execute(mappingModel.getRequired()));
+            final boolean required = ((Boolean) fromStringTo(Boolean.class).execute(mappingModel.getRequired()));
             mapping.setRequired(required);
         }
     }
 
-    private void parseAndAddViewState(ViewStateModel state, Flow flow) {
-        ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), false, state.getBinder());
+    private void parseAndAddViewState(final ViewStateModel state, final Flow flow) {
+        final ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), false, state.getBinder());
         Boolean redirect = null;
         if (StringUtils.hasText(state.getRedirect())) {
             redirect = (Boolean) fromStringTo(Boolean.class).execute(state.getRedirect());
@@ -575,7 +588,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         if (StringUtils.hasText(state.getPopup())) {
             popup = ((Boolean) fromStringTo(Boolean.class).execute(state.getPopup()));
         }
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
         if (state.getModel() != null) {
             attributes.put("model", getLocalContext().getExpressionParser().parseExpression(state.getModel(),
                     new FluentParserContext().evaluate(RequestContext.class)));
@@ -592,8 +605,8 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 parseActions(state.getOnExitActions()), attributes);
     }
 
-    private void parseAndAddActionState(ActionStateModel state, Flow flow) {
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
+    private void parseAndAddActionState(final ActionStateModel state, final Flow flow) {
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
         parseAndPutSecured(state.getSecured(), attributes);
         getLocalContext().getFlowArtifactFactory().createActionState(state.getId(), flow,
                 parseActions(state.getOnEntryActions()), parseActions(state.getActions()),
@@ -602,8 +615,8 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 parseActions(state.getOnExitActions()), attributes);
     }
 
-    private void parseAndAddDecisionState(DecisionStateModel state, Flow flow) {
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
+    private void parseAndAddDecisionState(final DecisionStateModel state, final Flow flow) {
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
         parseAndPutSecured(state.getSecured(), attributes);
         getLocalContext().getFlowArtifactFactory().createDecisionState(state.getId(), flow,
                 parseActions(state.getOnEntryActions()), parseIfs(state.getIfs()),
@@ -611,8 +624,8 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 attributes);
     }
 
-    private void parseAndAddSubflowState(SubflowStateModel state, Flow flow) {
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
+    private void parseAndAddSubflowState(final SubflowStateModel state, final Flow flow) {
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
         parseAndPutSecured(state.getSecured(), attributes);
         getLocalContext().getFlowArtifactFactory().createSubflowState(state.getId(), flow,
                 parseActions(state.getOnEntryActions()), parseSubflowExpression(state.getSubflow()),
@@ -621,14 +634,14 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 parseActions(state.getOnExitActions()), attributes);
     }
 
-    private void parseAndAddEndState(EndStateModel state, Flow flow) {
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
+    private void parseAndAddEndState(final EndStateModel state, final Flow flow) {
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(state.getAttributes());
         if (StringUtils.hasText(state.getCommit())) {
             attributes.put("commit", fromStringTo(Boolean.class).execute(state.getCommit()));
         }
         parseAndPutSecured(state.getSecured(), attributes);
         Action finalResponseAction;
-        ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), true, null);
+        final ViewFactory viewFactory = parseViewFactory(state.getView(), state.getId(), true, null);
         if (viewFactory != null) {
             finalResponseAction = new ViewFactoryActionAdapter(viewFactory);
         } else {
@@ -639,50 +652,50 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 parseExceptionHandlers(state.getExceptionHandlers(), null), attributes);
     }
 
-    private ViewFactory parseViewFactory(String view, String stateId, boolean endState, BinderModel binderModel) {
+    private ViewFactory parseViewFactory(String view, final String stateId, final boolean endState, final BinderModel binderModel) {
         if (!StringUtils.hasText(view)) {
             if (endState) {
                 return null;
             } else {
                 view = getLocalContext().getViewFactoryCreator().getViewIdByConvention(stateId);
-                Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
+                final Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
                         new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
                 return createViewFactory(viewId, binderModel);
             }
         } else if (view.startsWith("externalRedirect:")) {
-            String encodedUrl = view.substring("externalRedirect:".length());
-            Expression externalUrl = getLocalContext().getExpressionParser().parseExpression(encodedUrl,
+            final String encodedUrl = view.substring("externalRedirect:".length());
+            final Expression externalUrl = getLocalContext().getExpressionParser().parseExpression(encodedUrl,
                     new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
             return new ActionExecutingViewFactory(new ExternalRedirectAction(externalUrl));
         } else if (view.startsWith("flowRedirect:")) {
-            String flowRedirect = view.substring("flowRedirect:".length());
-            Expression expression = getLocalContext().getExpressionParser().parseExpression(flowRedirect,
+            final String flowRedirect = view.substring("flowRedirect:".length());
+            final Expression expression = getLocalContext().getExpressionParser().parseExpression(flowRedirect,
                     new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
             return new ActionExecutingViewFactory(new FlowDefinitionRedirectAction(expression));
         } else {
-            Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
+            final Expression viewId = getLocalContext().getExpressionParser().parseExpression(view,
                     new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
             return createViewFactory(viewId, binderModel);
         }
     }
 
-    private ViewFactory createViewFactory(Expression viewId, BinderModel binderModel) {
-        BinderConfiguration binderConfiguration = createBinderConfiguration(binderModel);
+    private ViewFactory createViewFactory(final Expression viewId, final BinderModel binderModel) {
+        final BinderConfiguration binderConfiguration = createBinderConfiguration(binderModel);
         return getLocalContext().getViewFactoryCreator().createViewFactory(viewId,
                 getLocalContext().getExpressionParser(), getLocalContext().getConversionService(), binderConfiguration,
                 getLocalContext().getValidator(), getLocalContext().getValidationHintResolver());
     }
 
-    private BinderConfiguration createBinderConfiguration(BinderModel binderModel) {
+    private BinderConfiguration createBinderConfiguration(final BinderModel binderModel) {
         if (binderModel != null && binderModel.getBindings() != null) {
-            BinderConfiguration binderConfiguration = new BinderConfiguration();
-            List<BindingModel> bindings = binderModel.getBindings();
-            for (BindingModel bindingModel : bindings) {
+            final BinderConfiguration binderConfiguration = new BinderConfiguration();
+            final List<BindingModel> bindings = binderModel.getBindings();
+            for (final BindingModel bindingModel : bindings) {
                 boolean required = false;
                 if (StringUtils.hasText(bindingModel.getRequired())) {
                     required = ((Boolean) fromStringTo(Boolean.class).execute(bindingModel.getRequired()));
                 }
-                Binding binding = new Binding(bindingModel.getProperty(), bindingModel.getConverter(), required);
+                final Binding binding = new Binding(bindingModel.getProperty(), bindingModel.getConverter(), required);
                 binderConfiguration.addBinding(binding);
             }
             return binderConfiguration;
@@ -691,10 +704,10 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private ViewVariable[] parseViewVariables(List<VarModel> vars) {
+    private ViewVariable[] parseViewVariables(final List<VarModel> vars) {
         if (vars != null && !vars.isEmpty()) {
-            List<ViewVariable> variables = new ArrayList<>(vars.size());
-            for (VarModel varModel : vars) {
+            final List<ViewVariable> variables = new ArrayList<>(vars.size());
+            for (final VarModel varModel : vars) {
                 variables.add(parseViewVariable(varModel));
             }
             return variables.toArray(new ViewVariable[variables.size()]);
@@ -703,17 +716,17 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private ViewVariable parseViewVariable(VarModel var) {
-        Class<?> clazz = toClass(var.getClassName());
-        VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz,
+    private ViewVariable parseViewVariable(final VarModel var) {
+        final Class<?> clazz = toClass(var.getClassName());
+        final VariableValueFactory valueFactory = new BeanFactoryVariableValueFactory(clazz,
                 getFlow().getApplicationContext().getAutowireCapableBeanFactory());
         return new ViewVariable(var.getName(), valueFactory);
     }
 
-    private Transition[] parseIfs(List<IfModel> ifModels) {
+    private Transition[] parseIfs(final List<IfModel> ifModels) {
         if (ifModels != null && !ifModels.isEmpty()) {
-            List<Transition> transitions = new ArrayList<>(ifModels.size());
-            for (IfModel ifModel : ifModels) {
+            final List<Transition> transitions = new ArrayList<>(ifModels.size());
+            for (final IfModel ifModel : ifModels) {
                 transitions.addAll(Arrays.asList(parseIf(ifModel)));
             }
             return transitions.toArray(new Transition[transitions.size()]);
@@ -722,55 +735,55 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private Transition[] parseIf(IfModel ifModel) {
-        Transition thenTransition = parseThen(ifModel);
+    private Transition[] parseIf(final IfModel ifModel) {
+        final Transition thenTransition = parseThen(ifModel);
         if (StringUtils.hasText(ifModel.getElse())) {
-            Transition elseTransition = parseElse(ifModel);
+            final Transition elseTransition = parseElse(ifModel);
             return new Transition[] {thenTransition, elseTransition};
         } else {
             return new Transition[] {thenTransition};
         }
     }
 
-    private Transition parseThen(IfModel ifModel) {
-        Expression test = getLocalContext().getExpressionParser().parseExpression(ifModel.getTest(),
+    private Transition parseThen(final IfModel ifModel) {
+        final Expression test = getLocalContext().getExpressionParser().parseExpression(ifModel.getTest(),
                 new FluentParserContext().evaluate(RequestContext.class).expectResult(Boolean.class));
-        TransitionCriteria matchingCriteria = new DefaultTransitionCriteria(test);
-        TargetStateResolver targetStateResolver =
+        final TransitionCriteria matchingCriteria = new DefaultTransitionCriteria(test);
+        final TargetStateResolver targetStateResolver =
                 (TargetStateResolver) fromStringTo(TargetStateResolver.class).execute(ifModel.getThen());
         return getLocalContext().getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, null,
                 null);
     }
 
-    private Transition parseElse(IfModel ifModel) {
-        TargetStateResolver stateResolver =
+    private Transition parseElse(final IfModel ifModel) {
+        final TargetStateResolver stateResolver =
                 (TargetStateResolver) fromStringTo(TargetStateResolver.class).execute(ifModel.getElse());
         return getLocalContext().getFlowArtifactFactory().createTransition(stateResolver, null, null, null);
     }
 
-    private Expression parseSubflowExpression(String subflow) {
-        Expression subflowId = getLocalContext().getExpressionParser().parseExpression(subflow,
+    private Expression parseSubflowExpression(final String subflow) {
+        final Expression subflowId = getLocalContext().getExpressionParser().parseExpression(subflow,
                 new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class));
         return new CustomSubflowExpression(subflowId, getLocalContext().getFlowDefinitionLocator());
     }
 
-    private SubflowAttributeMapper parseSubflowAttributeMapper(SubflowStateModel state) {
+    private SubflowAttributeMapper parseSubflowAttributeMapper(final SubflowStateModel state) {
         if (StringUtils.hasText(state.getSubflowAttributeMapper())) {
-            String beanId = state.getSubflowAttributeMapper();
+            final String beanId = state.getSubflowAttributeMapper();
             return getLocalContext().getApplicationContext().getBean(beanId, SubflowAttributeMapper.class);
         } else {
-            Mapper inputMapper = parseSubflowInputMapper(state.getInputs());
-            Mapper outputMapper = parseSubflowOutputMapper(state.getOutputs());
+            final Mapper inputMapper = parseSubflowInputMapper(state.getInputs());
+            final Mapper outputMapper = parseSubflowOutputMapper(state.getOutputs());
             return new GenericSubflowAttributeMapper(inputMapper, outputMapper);
         }
     }
 
-    private FlowExecutionExceptionHandler[] parseExceptionHandlers(List<ExceptionHandlerModel> modelExceptionHandlers,
-            List<TransitionModel> modelTransitions) {
-        FlowExecutionExceptionHandler[] transitionExecutingHandlers =
+    private FlowExecutionExceptionHandler[] parseExceptionHandlers(final List<ExceptionHandlerModel> modelExceptionHandlers,
+            final List<TransitionModel> modelTransitions) {
+        final FlowExecutionExceptionHandler[] transitionExecutingHandlers =
                 parseTransitionExecutingExceptionHandlers(modelTransitions);
-        FlowExecutionExceptionHandler[] customHandlers = parseCustomExceptionHandlers(modelExceptionHandlers);
-        FlowExecutionExceptionHandler[] exceptionHandlers =
+        final FlowExecutionExceptionHandler[] customHandlers = parseCustomExceptionHandlers(modelExceptionHandlers);
+        final FlowExecutionExceptionHandler[] exceptionHandlers =
                 new FlowExecutionExceptionHandler[transitionExecutingHandlers.length + customHandlers.length];
         System.arraycopy(transitionExecutingHandlers, 0, exceptionHandlers, 0, transitionExecutingHandlers.length);
         System.arraycopy(customHandlers, 0, exceptionHandlers, transitionExecutingHandlers.length,
@@ -779,10 +792,10 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
     }
 
     private FlowExecutionExceptionHandler[]
-            parseTransitionExecutingExceptionHandlers(List<TransitionModel> transitionModels) {
+            parseTransitionExecutingExceptionHandlers(final List<TransitionModel> transitionModels) {
         if (transitionModels != null && !transitionModels.isEmpty()) {
-            List<FlowExecutionExceptionHandler> exceptionHandlers = new ArrayList<>(transitionModels.size());
-            for (TransitionModel model : transitionModels) {
+            final List<FlowExecutionExceptionHandler> exceptionHandlers = new ArrayList<>(transitionModels.size());
+            for (final TransitionModel model : transitionModels) {
                 if (StringUtils.hasText(model.getOnException())) {
                     if (model.getSecured() != null) {
                         throw new FlowBuilderException("Exception based transitions cannot be secured");
@@ -796,11 +809,11 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private FlowExecutionExceptionHandler parseTransitionExecutingExceptionHandler(TransitionModel transition) {
-        TransitionExecutingFlowExecutionExceptionHandler handler =
+    private FlowExecutionExceptionHandler parseTransitionExecutingExceptionHandler(final TransitionModel transition) {
+        final TransitionExecutingFlowExecutionExceptionHandler handler =
                 new TransitionExecutingFlowExecutionExceptionHandler();
-        Class<Throwable> exceptionClass = toClass(transition.getOnException(), Throwable.class);
-        TargetStateResolver targetStateResolver =
+        final Class<Throwable> exceptionClass = toClass(transition.getOnException(), Throwable.class);
+        final TargetStateResolver targetStateResolver =
                 (TargetStateResolver) fromStringTo(TargetStateResolver.class).execute(transition.getTo());
         handler.add(exceptionClass, targetStateResolver);
         handler.getActionList().addAll(parseActions(transition.getActions()));
@@ -808,10 +821,10 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
     }
 
     private FlowExecutionExceptionHandler[]
-            parseCustomExceptionHandlers(List<ExceptionHandlerModel> exceptionHandlerModels) {
+            parseCustomExceptionHandlers(final List<ExceptionHandlerModel> exceptionHandlerModels) {
         if (exceptionHandlerModels != null && !exceptionHandlerModels.isEmpty()) {
-            List<FlowExecutionExceptionHandler> exceptionHandlers = new ArrayList<>(exceptionHandlerModels.size());
-            for (ExceptionHandlerModel exceptionHandlerModel : exceptionHandlerModels) {
+            final List<FlowExecutionExceptionHandler> exceptionHandlers = new ArrayList<>(exceptionHandlerModels.size());
+            for (final ExceptionHandlerModel exceptionHandlerModel : exceptionHandlerModels) {
                 exceptionHandlers.add(parseCustomExceptionHandler(exceptionHandlerModel));
             }
             return exceptionHandlers.toArray(new FlowExecutionExceptionHandler[exceptionHandlers.size()]);
@@ -820,16 +833,16 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private FlowExecutionExceptionHandler parseCustomExceptionHandler(ExceptionHandlerModel exceptionHandler) {
+    private FlowExecutionExceptionHandler parseCustomExceptionHandler(final ExceptionHandlerModel exceptionHandler) {
         return getLocalContext().getApplicationContext().getBean(exceptionHandler.getBean(),
                 FlowExecutionExceptionHandler.class);
     }
 
-    private Transition[] parseTransitions(List<TransitionModel> transitionModels) {
+    private Transition[] parseTransitions(final List<TransitionModel> transitionModels) {
         if (transitionModels != null && !transitionModels.isEmpty()) {
-            List<Transition> transitions = new ArrayList<>(transitionModels.size());
+            final List<Transition> transitions = new ArrayList<>(transitionModels.size());
             if (transitionModels != null) {
-                for (TransitionModel transition : transitionModels) {
+                for (final TransitionModel transition : transitionModels) {
                     if (!StringUtils.hasText(transition.getOnException())) {
                         transitions.add(parseTransition(transition));
                     }
@@ -841,14 +854,14 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private Transition parseTransition(TransitionModel transition) {
-        TransitionCriteria matchingCriteria =
+    private Transition parseTransition(final TransitionModel transition) {
+        final TransitionCriteria matchingCriteria =
                 (TransitionCriteria) fromStringTo(TransitionCriteria.class).execute(transition.getOn());
-        TargetStateResolver stateResolver =
+        final TargetStateResolver stateResolver =
                 (TargetStateResolver) fromStringTo(TargetStateResolver.class).execute(transition.getTo());
-        TransitionCriteria executionCriteria =
+        final TransitionCriteria executionCriteria =
                 TransitionCriteriaChain.criteriaChainFor(parseActions(transition.getActions()));
-        MutableAttributeMap<Object> attributes = parseMetaAttributes(transition.getAttributes());
+        final MutableAttributeMap<Object> attributes = parseMetaAttributes(transition.getAttributes());
         if (StringUtils.hasText(transition.getBind())) {
             attributes.put("bind", fromStringTo(Boolean.class).execute(transition.getBind()));
         }
@@ -867,10 +880,10 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                 executionCriteria, attributes);
     }
 
-    private Action[] parseActions(List<AbstractActionModel> actionModels) {
+    private Action[] parseActions(final List<AbstractActionModel> actionModels) {
         if (actionModels != null && !actionModels.isEmpty()) {
-            List<AnnotatedAction> actions = new ArrayList<>(actionModels.size());
-            for (AbstractActionModel actionModel : actionModels) {
+            final List<AnnotatedAction> actions = new ArrayList<>(actionModels.size());
+            for (final AbstractActionModel actionModel : actionModels) {
                 Action action;
                 if (actionModel instanceof EvaluateModel) {
                     action = parseEvaluateAction((EvaluateModel) actionModel);
@@ -882,7 +895,7 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
                     action = null;
                 }
                 if (action != null) {
-                    AnnotatedAction annotatedAction = new AnnotatedAction(action);
+                    final AnnotatedAction annotatedAction = new AnnotatedAction(action);
                     annotatedAction.getAttributes().putAll(parseMetaAttributes(actionModel.getAttributes()));
                     actions.add(annotatedAction);
                 }
@@ -893,12 +906,12 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private Action parseEvaluateAction(EvaluateModel evaluate) {
-        FluentParserContext evaluateExpressionParserContext = new FluentParserContext().evaluate(RequestContext.class);
+    private Action parseEvaluateAction(final EvaluateModel evaluate) {
+        final FluentParserContext evaluateExpressionParserContext = new FluentParserContext().evaluate(RequestContext.class);
         if (StringUtils.hasText(evaluate.getResultType())) {
             evaluateExpressionParserContext.expectResult(toClass(evaluate.getResultType()));
         }
-        Expression evaluateExpression = getLocalContext().getExpressionParser()
+        final Expression evaluateExpression = getLocalContext().getExpressionParser()
                 .parseExpression(evaluate.getExpression(), evaluateExpressionParserContext);
         Expression resultExpression = null;
         if (StringUtils.hasText(evaluate.getResult())) {
@@ -908,35 +921,35 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         return new EvaluateAction(evaluateExpression, resultExpression);
     }
 
-    private Action parseRenderAction(RenderModel render) {
+    private Action parseRenderAction(final RenderModel render) {
         String[] fragmentExpressionStrings = StringUtils.commaDelimitedListToStringArray(render.getFragments());
         fragmentExpressionStrings = StringUtils.trimArrayElements(fragmentExpressionStrings);
-        ParserContext context =
+        final ParserContext context =
                 new FluentParserContext().template().evaluate(RequestContext.class).expectResult(String.class);
-        Expression[] fragments = new Expression[fragmentExpressionStrings.length];
+        final Expression[] fragments = new Expression[fragmentExpressionStrings.length];
         for (int i = 0; i < fragmentExpressionStrings.length; i++) {
-            String fragment = fragmentExpressionStrings[i];
+            final String fragment = fragmentExpressionStrings[i];
             fragments[i] = getLocalContext().getExpressionParser().parseExpression(fragment, context);
         }
         return new RenderAction(fragments);
     }
 
-    private Action parseSetAction(SetModel set) {
-        Expression nameExpression = getLocalContext().getExpressionParser().parseExpression(set.getName(),
+    private Action parseSetAction(final SetModel set) {
+        final Expression nameExpression = getLocalContext().getExpressionParser().parseExpression(set.getName(),
                 new FluentParserContext().evaluate(RequestContext.class));
-        FluentParserContext valueParserContext = new FluentParserContext().evaluate(RequestContext.class);
+        final FluentParserContext valueParserContext = new FluentParserContext().evaluate(RequestContext.class);
         if (StringUtils.hasText(set.getType())) {
             valueParserContext.expectResult(toClass(set.getType()));
         }
-        Expression valueExpression =
+        final Expression valueExpression =
                 getLocalContext().getExpressionParser().parseExpression(set.getValue(), valueParserContext);
         return new SetAction(nameExpression, valueExpression);
     }
 
-    private MutableAttributeMap<Object> parseMetaAttributes(List<AttributeModel> attributeModels) {
+    private MutableAttributeMap<Object> parseMetaAttributes(final List<AttributeModel> attributeModels) {
         if (attributeModels != null && !attributeModels.isEmpty()) {
-            LocalAttributeMap<Object> attributes = new LocalAttributeMap<>();
-            for (AttributeModel attributeModel : attributeModels) {
+            final LocalAttributeMap<Object> attributes = new LocalAttributeMap<>();
+            for (final AttributeModel attributeModel : attributeModels) {
                 parseAndPutMetaAttribute(attributeModel, attributes);
             }
             return attributes;
@@ -945,33 +958,33 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private void parseAndPutMetaAttribute(AttributeModel attribute, MutableAttributeMap<Object> attributes) {
-        String name = attribute.getName();
-        String value = attribute.getValue();
+    private void parseAndPutMetaAttribute(final AttributeModel attribute, final MutableAttributeMap<Object> attributes) {
+        final String name = attribute.getName();
+        final String value = attribute.getValue();
         attributes.put(name, parseAttributeValueIfNecessary(attribute, value));
     }
 
-    private Object parseAttributeValueIfNecessary(AttributeModel attribute, String stringValue) {
+    private Object parseAttributeValueIfNecessary(final AttributeModel attribute, final String stringValue) {
         if (StringUtils.hasText(attribute.getType())) {
-            Class<?> targetClass = toClass(attribute.getType());
+            final Class<?> targetClass = toClass(attribute.getType());
             return fromStringTo(targetClass).execute(stringValue);
         } else {
             return stringValue;
         }
     }
 
-    private void parseAndPutPersistenceContext(PersistenceContextModel persistenceContext,
-            MutableAttributeMap<Object> attributes) {
+    private void parseAndPutPersistenceContext(final PersistenceContextModel persistenceContext,
+            final MutableAttributeMap<Object> attributes) {
         if (persistenceContext != null) {
             attributes.put("persistenceContext", true);
         }
     }
 
-    private void parseAndPutSecured(SecuredModel secured, MutableAttributeMap<Object> attributes) {
+    private void parseAndPutSecured(final SecuredModel secured, final MutableAttributeMap<Object> attributes) {
         if (secured != null) {
-            SecurityRule rule = new SecurityRule();
+            final SecurityRule rule = new SecurityRule();
             rule.setAttributes(SecurityRule.commaDelimitedListToSecurityAttributes(secured.getAttributes()));
-            String comparisonType = secured.getMatch();
+            final String comparisonType = secured.getMatch();
             if ("any".equals(comparisonType)) {
                 rule.setComparisonType(SecurityRule.COMPARISON_ANY);
             } else if ("all".equals(comparisonType)) {
@@ -984,31 +997,32 @@ public class CustomFlowModelFlowBuilder extends AbstractFlowBuilder {
         }
     }
 
-    private ConversionExecutor fromStringTo(Class<?> targetType) throws ConversionExecutionException {
+    private ConversionExecutor fromStringTo(final Class<?> targetType) throws ConversionExecutionException {
         return getLocalContext().getConversionService().getConversionExecutor(String.class, targetType);
     }
 
-    private Class<?> toClass(String name) {
-        Class<?> clazz = getLocalContext().getConversionService().getClassForAlias(name);
+    private Class<?> toClass(final String name) {
+        final Class<?> clazz = getLocalContext().getConversionService().getClassForAlias(name);
         if (clazz != null) {
             return clazz;
         } else {
             try {
-                ClassLoader classLoader = getLocalContext().getApplicationContext().getClassLoader();
+                final ClassLoader classLoader = getLocalContext().getApplicationContext().getClassLoader();
                 return ClassUtils.forName(name, classLoader);
-            } catch (ClassNotFoundException e) {
+            } catch (final ClassNotFoundException e) {
                 throw new IllegalArgumentException("Unable to load class '" + name + "'");
             }
         }
     }
 
     @SuppressWarnings("unchecked")
-    private <T> Class<T> toClass(String name, Class<T> superType) {
-        Class<?> clazz = toClass(name);
+    private <T> Class<T> toClass(final String name, final Class<T> superType) {
+        final Class<?> clazz = toClass(name);
         Assert.isAssignable(superType, clazz);
         return (Class<T>) clazz;
     }
 
+    @Override
     public String toString() {
         return new ToStringCreator(this).append("flowModelResource", flowModelHolder.getFlowModelResource()).toString();
     }
diff --git a/pom.xml b/pom.xml
index 2ab435d..cc5119e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,8 @@
         <duo.client.version>1.1.3</duo.client.version>
         <opensaml.groupId>org.opensaml</opensaml.groupId>
         <opensaml.version>4.2.0</opensaml.version>
-        <java-support.version>8.3.1</java-support.version>      
+        <java-support.version>8.3.1</java-support.version>   
+        <spring-extensions.version>6.2.0</spring-extensions.version>   
         <oidc.common.version>2.0.0</oidc.common.version>
         <checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
 	<jdk.version>15.3</jdk.version>
@@ -119,7 +120,7 @@
                 <artifactId>java-support</artifactId>
                 <version>${java-support.version}</version>
                 <scope>provided</scope>
-            </dependency>
+            </dependency>           
             <!-- Duo client and plugin dependencies -->
             <dependency>
                 <groupId>com.duosecurity</groupId>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list