Announcement Announcement Module
Collapse
No announcement yet.
Best way of intercepting flowExecution storage lifecycle? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Best way of intercepting flowExecution storage lifecycle?

    Hi,

    ATM I am using ClientSideContinuation, however the flow scope contains large uploaded files. I need to intercept the hydration of flow scope and swap out the uploadedFile objects. When the flow scope is rehydrated I need to swap these back in.

    So I decorate FlowExecutionStorage (EventAwareFlowExecutionStorageDecorator) and expose a lifecycle which listeners (FlowExecutionStorageLifecyleListener) can consume. One implementation of this is PersistableFlowExecutionStorageLifecycleListener which will use an implementation of Storage to swap out named objects.

    Code:
    package uk.ac.warwick.sbr.webflow.storage;
    
    import java.io.Serializable;
    import java.util.Collection;
    
    import org.springframework.webflow.Event;
    import org.springframework.webflow.execution.FlowExecution;
    import org.springframework.webflow.execution.FlowExecutionStorage;
    import org.springframework.webflow.execution.FlowExecutionStorageException;
    import org.springframework.webflow.execution.NoSuchFlowExecutionException;
    
    /**
     * <p>Implementation simply calls listeners on event lifecycles.</p>
     * @author xusqac
     */
    public final class EventAwareFlowExecutionStorageDecorator implements FlowExecutionStorage &#123;
        private final FlowExecutionStorage decorated;
        private final Collection<FlowExecutionStorageLifecyleListener> listeners;
    
        public EventAwareFlowExecutionStorageDecorator&#40;final FlowExecutionStorage storage, final Collection<FlowExecutionStorageLifecyleListener> theListeners&#41; &#123;
            this.decorated = storage;
            this.listeners = theListeners;
        &#125;
    
        public FlowExecution load&#40;final Serializable id, final Event requestingEvent&#41; throws NoSuchFlowExecutionException, FlowExecutionStorageException &#123;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.preLoad&#40;id, requestingEvent&#41;;
            &#125;
            FlowExecution flowExecution = decorated.load&#40;id, requestingEvent&#41;;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.postLoad&#40;flowExecution, id, requestingEvent&#41;;
            &#125;
            return flowExecution;
        &#125;
    
        public Serializable save&#40;final Serializable id, final FlowExecution flowExecution, final Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.preSave&#40;id, flowExecution, requestingEvent&#41;;
            &#125;
            Serializable result = decorated.save&#40;id, flowExecution, requestingEvent&#41;;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.postSave&#40;result, id, flowExecution, requestingEvent&#41;;
            &#125;
            return result;
        &#125;
    
        public void remove&#40;final Serializable id, final Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.preRemove&#40;id, requestingEvent&#41;;
            &#125;
            decorated.remove&#40;id, requestingEvent&#41;;
            for &#40;FlowExecutionStorageLifecyleListener listener&#58; listeners&#41; &#123;
                listener.postRemove&#40;id, requestingEvent&#41;;
            &#125;
        &#125;
    &#125;
    Code:
    package uk.ac.warwick.sbr.webflow.storage;
    
    import java.io.Serializable;
    
    import org.springframework.webflow.Event;
    import org.springframework.webflow.execution.FlowExecution;
    import org.springframework.webflow.execution.FlowExecutionStorageException;
    
    /**
     * Interface which exposes lifecycle events for FlowExecutionStorage.
     *
     * @author xusqac
     * @todo This might make more sense if it extends FlowExecutionStorage?
     */
    public interface FlowExecutionStorageLifecyleListener &#123;
        void preLoad&#40;Serializable id, final Event requestingEvent&#41; throws FlowExecutionStorageException;
        void postLoad&#40;final FlowExecution flowExecution, final Serializable id, Event requestingEvent&#41; throws FlowExecutionStorageException;
    
        void preSave&#40;final Serializable id, final FlowExecution flowExecution, final Event requestingEvent&#41; throws FlowExecutionStorageException;
        void postSave&#40;final Serializable newId, final Serializable originalId, final FlowExecution flowExecution, Event requestingEvent&#41; throws FlowExecutionStorageException;
    
        void preRemove&#40;final Serializable id, final Event requestingEvent&#41; throws FlowExecutionStorageException;
        void postRemove&#40;final Serializable id, final Event requestingEvent&#41; throws FlowExecutionStorageException;
    &#125;
    Code:
    package uk.ac.warwick.sbr.webflow.storage;
    
    import java.util.Collection;
    
    import org.springframework.webflow.Event;
    import org.springframework.webflow.Scope;
    import org.springframework.webflow.execution.FlowExecution;
    import org.springframework.webflow.execution.FlowExecutionStorageException;
    
    /**
     * This implementation will delegate the persistence of specified attributes to the Persister.
     *
     * <p>Every attribute that is to be persisted will be moved out of FlowScope, and reloaded when
     * the FlowScope is to be loaded.  When the FlowScope is saved, every attribute will be replaced with
     * a placeholder which will record it's name and the persistence token.</p>
     *
     * @author xusqac
     */
    public final class PersistableFlowExecutionStorageLifecycleListener extends StorageLifecycleImpl &#123;
        public static final String PREFIX = "~persisted&#58;";
        private final Storage storage;
        private final Collection<String> attributesToPersist;
    
        public PersistableFlowExecutionStorageLifecycleListener&#40;final Storage theStorage, final Collection<String> theNames&#41; &#123;
            this.storage = theStorage;
            this.attributesToPersist = theNames;
        &#125;
    
        public void preSave&#40;final String id, final FlowExecution flowExecution, final Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
            // persist all attributes
            Scope scope = flowExecution.getActiveSession&#40;&#41;.getScope&#40;&#41;;
            for &#40;String attName&#58; attributesToPersist&#41; &#123;
                if &#40;scope.containsAttribute&#40;attName&#41;&#41; &#123;
                    Object value = scope.getAttribute&#40;attName&#41;;
                    Object token = storage.persist&#40;value&#41;;
                    scope.removeAttribute&#40;attName&#41;;
                    scope.setAttribute&#40;PREFIX + attName, token&#41;;
                &#125;
            &#125;
        &#125;
    
        public void postLoad&#40;final FlowExecution flowExecution, final String id, final Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
            // rehydrate all attributes
            Scope scope = flowExecution.getActiveSession&#40;&#41;.getScope&#40;&#41;;
            for &#40;String attName&#58; attributesToPersist&#41; &#123;
                String mangledName = PREFIX + attName;
                if &#40;scope.containsAttribute&#40;mangledName&#41;&#41; &#123;
                    Object token = scope.getAttribute&#40;mangledName&#41;;
                    Object value = storage.load&#40;token&#41;;
                    scope.removeAttribute&#40;mangledName&#41;;
                    scope.setAttribute&#40;attName, value&#41;;
                &#125;
            &#125;
        &#125;
    
        public void preRemove&#40;final String id, final Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
        &#125;
    &#125;
    Code:
    package uk.ac.warwick.sbr.webflow.storage;
    
    /**
     * Simple interface which is expected to be able to persist and retrieve objects.
     *
     * @author xusqac
     */
    public interface Storage &#123;
    	Object persist&#40;final Object object&#41;;
    	Object load&#40;final Object token&#41;;
    &#125;
    Code:
    package uk.ac.warwick.sbr.webflow.storage;
    
    import java.io.ByteArrayInputStream;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    import org.springframework.util.FileCopyUtils;
    
    /**
     * Implementation which is backed by the file store.
     *
     * @author xusqac
     */
    public final class FileBackedStorage implements Storage &#123;
        public Object persist&#40;final Object object&#41; &#123;
            File file = null;
            String fileName = System.currentTimeMillis&#40;&#41; + "_" + object.hashCode&#40;&#41;;
            try &#123;
                file = File.createTempFile&#40;fileName, null&#41;;
            &#125; catch &#40;final IOException e&#41; &#123;
                throw new IllegalStateException&#40;"Cannot create &#91;" + fileName + "&#93;", e&#41;;
            &#125;
    
            try &#123;
                FileOutputStream fos = new FileOutputStream&#40;file&#41;;
                ObjectOutputStream oos = new ObjectOutputStream&#40;fos&#41;;
                oos.writeObject&#40;object&#41;;
                oos.flush&#40;&#41;;
            &#125; catch &#40;final IOException e&#41; &#123;
                throw new IllegalArgumentException&#40;"Cannot serialize " + object, e&#41;;
            &#125;
    
            return file.getAbsolutePath&#40;&#41;;
        &#125;
    
        public Object load&#40;final Object token&#41; &#123;
            File file = new File&#40;&#40;String&#41; token&#41;;
    
            if &#40;!file.exists&#40;&#41;&#41; &#123;
                throw new IllegalStateException&#40;"File " + file + " does not exist for token " + token&#41;;
            &#125;
            try &#123;
                byte&#91;&#93; contents = FileCopyUtils.copyToByteArray&#40;file&#41;;
                ObjectInputStream ois = new ObjectInputStream&#40;
                        new ByteArrayInputStream&#40;contents&#41;&#41;;
                return ois.readObject&#40;&#41;;
            &#125; catch &#40;final IOException e&#41; &#123;
                throw new IllegalStateException&#40;"Cannot load state from " + token, e&#41;;
            &#125; catch &#40;final ClassNotFoundException e&#41; &#123;
                throw new IllegalStateException&#40;"Cannot reconstruct class for token " + token, e&#41;;
            &#125;
        &#125;
    &#125;
    It is wired up:

    Code:
        <property name="storage">
          <bean class="uk.ac.warwick.sbr.webflow.storage.EventAwareFlowExecutionStorageDecorator">
            <constructor-arg index="0">
              <bean class="org.springframework.webflow.execution.ClientContinuationFlowExecutionStorage">
                <property name="compress" value="true"/>
              </bean>
            </constructor-arg>
            <constructor-arg index="1">
              <list>
                <bean class="uk.ac.warwick.sbr.webflow.storage.PersistableFlowExecutionStorageLifecycleListener">
                  <constructor-arg index="0">
                    <bean class="uk.ac.warwick.sbr.webflow.storage.FileBackedStorage"/>
                  </constructor-arg>
                  <constructor-arg index="1">
                    <list>
                      <value>uploadFileForm</value>
                      <value>uploadedFiles</value>
                      <value>validFileNames</value>
                      <value>invalidFileNames</value>
                      <value>emptyFiles</value>
                      <value>nonEmptyFiles</value>
                      <value>createdFiles</value>
                      <value>duplicateFiles</value>
                    </list>
                  </constructor-arg>
                </bean>
              </list>
            </constructor-arg>
          </bean>
        </property>
    Whenever I have to write this much infrastructure code I always wonder if I have missed the point Does WebFlow have anything like this already?

    Ta.

    Col

    P.S. Yes, there are lots of enhancements, but I don't want to do them if I am re-inventing the wheel (but in a wobble, kinda square shape ).

  • #2
    SWF doesn't currently provide anything like this out-of-the-box, so you didn't miss the point .

    Thanks for posting the code, I'm sure other people will find this usefull!

    Erwin

    Comment


    • #3
      My pleasure.

      Is it worth opening up a JIRA for this?

      Ta.

      Comment

      Working...
      X