Announcement Announcement Module
Collapse
No announcement yet.
Getting flow definition location path from its id Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Getting flow definition location path from its id

    Hi,

    I need to load a specific resource bundle (other than messages.properties), located next to each XML flow definition file. I have searched the API to find the way to get the location of the flow definition file, but haven't found a clean one of doing that. Hereunder a summary of what I've found.

    The class Flow inherit from FlowDefinition and doesn't provide any information on the location of its configuration file.

    The class DefaultFlowRegistry provide a way to retrieve a FlowModelRegistry, which in turn provide a way toget a FlowModelHolder. The FlowModelHolder contains all the information I'm seeking for, but there's no way of obtaining one because the class DefaultFlowRegistry is visibility package and the method FlowModelRegistryImpl.getFlowModelHolder(String id) is private.

    Finally, there is the class FlowRegistryFactoryBean that is initialized by FlowRegistryBeanDefinitionParser, but those two classes are also package restricted.

    In the end, I would like to mimic what is done in FlowModelFlowBuilder in the method registerMessageSource() with the use of flowResource.createRelative("messages.properties") with my specific bundle.

    One crappy id is to use reflection to make all this stuff accessible (yuk ), but no way to get a package visible class accessible with that trick. Any other more seducing idea would be interesting.

    Thanks in advance.
    Last edited by PomCompot; Dec 13th, 2010, 11:51 AM.

  • #2
    Hook to give access to FlowModelRegistry#getFlowModelHolder

    I have finally found a way (not really clean) to achieve my goal. Hereunder the code. I will post an enhancement ticket on JIRA to ask for giving access to those informations through the exposed Web Flow API. Sample use in the next post.

    Code:
    package org.springframework.webflow.config;
    
    import javax.annotation.PostConstruct;
    import javax.inject.Inject;
    
    import org.springframework.util.Assert;
    import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
    import org.springframework.webflow.engine.model.registry.AccessibleFlowModelRegistryAdapter;
    import org.springframework.webflow.engine.model.registry.FlowModelHolder;
    import org.springframework.webflow.engine.model.registry.FlowModelRegistry;
    import org.springframework.webflow.engine.model.registry.FlowModelRegistryImpl;
    
    /**
     * <p>
     * This class is a hook to give accessibility to the package visible class
     * {@link DefaultFlowRegistry}, base implementation of the flow registry, which
     * transitively give access to the {@link FlowModelRegistry}.
     * </p>
     * 
     * <p>
     * The final goal is to retrieve {@link FlowModelHolder} thanks to the
     * {@link FlowModelRegistry} with the flow id.
     * </p>
     * 
     * <p>
     * The typical use is based on {@link AccessibleFlowModelRegistryAdapter},
     * Spring bean that call this class to retrieve the {@link FlowModelRegistry}.
     * See the documentation of this class for a sample use case.
     * </p>
     * 
     * @see <a href="http://forum.springsource.org/showthread.php?p=334261">Spring
     *      forum related post</a>
     * @see AccessibleFlowModelRegistryAdapter
     */
    public class AccessibleFlowDefinitionRegistryAdapter extends DefaultFlowRegistry {
        /**
         * The official Web Flow flows registry.
         */
        @Inject
        private FlowDefinitionRegistry flowRegistry;
    
        /**
         * Checks to detect a modification of implementation either of
         * {@link FlowDefinitionRegistry}, or of {@link FlowModelRegistry}.
         */
        @PostConstruct
        public void postConstruct() {
            Assert.notNull(flowRegistry, "flowRegistry bean has not been injected.");
            Assert.isInstanceOf(DefaultFlowRegistry.class, flowRegistry,
                    "The implementation class of FlowDefinitionRegistry doesn't match the expected one (DefaultFlowRegistry). "
                            + "Check if a version upgrade of Spring Web Flow hasn't modified the underlying implementation.");
            Assert.notNull(((DefaultFlowRegistry) flowRegistry).getFlowModelRegistry(), "No FlowModelRegistry found inside flowRegistry bean."
                    + "Check if a version upgrade of Spring Web Flow hasn't modified the underlying implementation.");
            Assert.isInstanceOf(FlowModelRegistryImpl.class, ((DefaultFlowRegistry) flowRegistry).getFlowModelRegistry(),
                    "The implementation class of FlowModelRegistry doesn't match the expected one (FlowModelRegistryImpl). "
                            + "Check if a version upgrade of Spring Web Flow hasn't modified the underlying implementation");
        }
    
        /**
         * Retrieve the {@link FlowModelRegistry} associated to the default
         * {@link FlowDefinitionRegistry}.
         * 
         * @return the {@link FlowModelRegistry}
         */
        @Override
        public FlowModelRegistry getFlowModelRegistry() {
            return ((DefaultFlowRegistry) flowRegistry).getFlowModelRegistry();
        }
    }
    Code:
    package org.springframework.webflow.engine.model.registry;
    
    import java.lang.reflect.Method;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanDefinition;
    import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
    import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.RootBeanDefinition;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.util.Assert;
    import org.springframework.util.ReflectionUtils;
    import org.springframework.webflow.config.AccessibleFlowDefinitionRegistryAdapter;
    
    /**
     * <p>
     * This class is a hook to give accessibility to the {@link FlowModelRegistry},
     * and more specifically to its private methode
     * {@link FlowModelRegistry#getFlowModelHolder(String)}. It is based on another
     * hook through the class {@link AccessibleFlowDefinitionRegistryAdapter}.
     * </p>
     * 
     * <p>
     * The final goal is to retrieve {@link FlowModelHolder} thanks to the
     * {@link FlowModelRegistry} with the flow id.
     * </p>
     * 
     * <h1>Sample use</h1>
     * 
     * <h2>Spring configuration file</h2>
     * <code><pre>&lt;bean class="org.springframework.webflow.engine.model.registry.AccessibleFlowModelRegistryAdapter" primary="false" /&gt;</pre></code>
     * 
     * <h2>Concrete use</h2>
     * <p>
     * <code><pre>@Inject
    private AccessibleFlowModelRegistryAdapter flowModelRegistry;
    
    RequestContext requestContext = RequestContextHolder.getRequestContext();
    String flowId = requestContext.getActiveFlow().getId();
    
    FlowModelHolder flowModelHolder = flowModelRegistry.getFlowModelHolder(flowId);</pre></code>
     * 
     * <p>
     * This class implements {@link BeanFactoryPostProcessor} and
     * {@link ApplicationContextAware} interfaces because it's responsible for
     * registering and retrieveing the
     * {@link AccessibleFlowDefinitionRegistryAdapter}.
     * </p>
     * 
     * @see <a href="http://forum.springsource.org/showthread.php?p=334261">Spring
     *      forum related post</a>
     * @see AccessibleFlowDefinitionRegistryAdapter
     */
    public class AccessibleFlowModelRegistryAdapter extends FlowModelRegistryImpl implements BeanFactoryPostProcessor, ApplicationContextAware {
        /**
         * The {@link ApplicationContext} setted by Spring when the bean is
         * initialized thanks to the implementation {@link ApplicationContextAware}.
         */
        private ApplicationContext applicationContext;
    
        /**
         * <p>
         * Key method which is the main goal of all this hook. It give the ability
         * to retrieve a {@link FlowModelHolder} associated to a flow from its
         * flowId. This methode retrieve the {@link FlowModelHolder} from the
         * underlying {@link FlowModelRegistry}.
         * </p>
         * 
         * <p>
         * This method give access to the normally inaccessible private
         * {@link #getFlowModelHolder(String)} of {@link FlowModelRegistryImpl}
         * through some reflection.
         * </p>
         * 
         * @param id
         *            the flowId for which we want to retrieve the
         *            {@link FlowModelHolder}
         * @return the {@link FlowModelHolder} of the specified flow or
         *         <code>null</code> if there is no {@link FlowModelHolder}
         *         associated to this flowId
         */
        public FlowModelHolder getFlowModelHolder(String id) {
            Method getFlowModelHolderMethod = ReflectionUtils.findMethod(FlowModelRegistryImpl.class, "getFlowModelHolder", String.class);
            Assert.notNull(getFlowModelHolderMethod, "La méthode getFlowModelHolder(String id) n'a pas été trouvée sur la classe FlowModelRegistryImpl."
                    + "Vérifier si une montée de version de Spring Web Flow n'aurait pas modifié l'implémentation sous-jacente.");
            ReflectionUtils.makeAccessible(getFlowModelHolderMethod);
            FlowModelHolder flowModelHolder = (FlowModelHolder) ReflectionUtils.invokeMethod(getFlowModelHolderMethod, getFlowRegistry()
                    .getFlowModelRegistry(), id);
            return flowModelHolder;
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        /**
         * Getter to retrieve the {@link AccessibleFlowDefinitionRegistryAdapter}
         * bean. This bean is not injected because its definition is registered by
         * this self class (through the implementation of
         * {@link BeanFactoryPostProcessor}) and injecting a bean in the bean
         * registering it doesn't work. So we retrieve it through
         * {@link ApplicationContext#getBean(String, Class)}.
         * 
         * @return the {@link AccessibleFlowDefinitionRegistryAdapter}
         */
        private AccessibleFlowDefinitionRegistryAdapter getFlowRegistry() {
            return applicationContext.getBean(getAccessibleFlowDefinitionRegistryAdapterName(), AccessibleFlowDefinitionRegistryAdapter.class);
        }
    
        /**
         * The implentation of {@link BeanFactoryPostProcessor} preserve from
         * declaring the two hook beans (
         * {@link AccessibleFlowDefinitionRegistryAdapter} and
         * {@link AccessibleFlowModelRegistryAdapter}) in the XML configuration.
         * 
         * {@inheritDoc}
         */
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            BeanDefinition flowDefinitionRegistryBean = new RootBeanDefinition(AccessibleFlowDefinitionRegistryAdapter.class);
            flowDefinitionRegistryBean.setPrimary(false);
            registry.registerBeanDefinition(getAccessibleFlowDefinitionRegistryAdapterName(), flowDefinitionRegistryBean);
        }
    
        /**
         * Retrieve a camel case unique identifier of the
         * {@link AccessibleFlowDefinitionRegistryAdapter} bean derived from the
         * className.
         * 
         * @return the unique identifier of the
         *         {@link AccessibleFlowDefinitionRegistryAdapter} bean
         */
        private String getAccessibleFlowDefinitionRegistryAdapterName() {
            String className = AccessibleFlowDefinitionRegistryAdapter.class.getName();
            return className.substring(0, 1).toLowerCase() + className.substring(1);
        }
    }

    Comment


    • #3
      One more class and Sample use

      AccessibleFlowRelativeResourceLoader class :

      Code:
      package org.springframework.webflow.engine.builder.model;
      
      import org.springframework.context.support.ReloadableResourceBundleMessageSource;
      import org.springframework.core.io.Resource;
      
      /**
       * <p>
       * This class is a very little hook to give access to the class
       * {@link FlowRelativeResourceLoader}. This class give the ability to load
       * resources that are located next to the flow.
       * </p>
       * 
       * <p>
       * For instance, it can be used to define another resource loader than the
       * default one on a{@link ReloadableResourceBundleMessageSource} to load some
       * properties file next to the flow.
       * </p>
       */
      public class AccessibleFlowRelativeResourceLoader extends FlowRelativeResourceLoader {
          /**
           * Build a new {@link AccessibleFlowRelativeResourceLoader}, delegating the
           * construction to the super class.
           * 
           * @param resource
           *            the flow resource which is used as reference to load neighbour
           *            resources.
           */
          public AccessibleFlowRelativeResourceLoader(Resource resource) {
              super(resource);
          }
      }
      Sample use :

      Code:
      <bean class="org.springframework.webflow.engine.model.registry.AccessibleFlowModelRegistryAdapter" primary="false" />
      Code:
      private static final String[] DEFAULT_FLOW_ERROR_BUNDLES = new String[] {"errors"};
      
      @Inject
      private AccessibleFlowModelRegistryAdapter flowModelRegistry;
      
      [...]
      
      FlowModelHolder flowModelHolder = flowModelRegistry.getFlowModelHolder(flowId);
      Resource flowModelResource = flowModelHolder.getFlowModelResource();
      AccessibleFlowRelativeResourceLoader flowRelativeResourceLoader = new AccessibleFlowRelativeResourceLoader(flowModelResource);
      
      ReloadableResourceBundleMessageSource flowErrorResourceBundle = new ReloadableResourceBundleMessageSource();
      flowErrorResourceBundle.setResourceLoader(flowRelativeResourceLoader);
      flowErrorResourceBundle.setBasenames(DEFAULT_FLOW_ERROR_BUNDLES);
      flowErrorMessageSource = flowErrorResourceBundle;

      Comment


      • #4
        JIRA Ticket

        Here the related JIRA ticket: https://jira.springframework.org/browse/SWF-1441.

        Comment

        Working...
        X