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 { private final FlowExecutionStorage decorated; private final Collection<FlowExecutionStorageLifecyleListener> listeners; public EventAwareFlowExecutionStorageDecorator(final FlowExecutionStorage storage, final Collection<FlowExecutionStorageLifecyleListener> theListeners) { this.decorated = storage; this.listeners = theListeners; } public FlowExecution load(final Serializable id, final Event requestingEvent) throws NoSuchFlowExecutionException, FlowExecutionStorageException { for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.preLoad(id, requestingEvent); } FlowExecution flowExecution = decorated.load(id, requestingEvent); for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.postLoad(flowExecution, id, requestingEvent); } return flowExecution; } public Serializable save(final Serializable id, final FlowExecution flowExecution, final Event requestingEvent) throws FlowExecutionStorageException { for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.preSave(id, flowExecution, requestingEvent); } Serializable result = decorated.save(id, flowExecution, requestingEvent); for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.postSave(result, id, flowExecution, requestingEvent); } return result; } public void remove(final Serializable id, final Event requestingEvent) throws FlowExecutionStorageException { for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.preRemove(id, requestingEvent); } decorated.remove(id, requestingEvent); for (FlowExecutionStorageLifecyleListener listener: listeners) { listener.postRemove(id, requestingEvent); } } }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 { void preLoad(Serializable id, final Event requestingEvent) throws FlowExecutionStorageException; void postLoad(final FlowExecution flowExecution, final Serializable id, Event requestingEvent) throws FlowExecutionStorageException; void preSave(final Serializable id, final FlowExecution flowExecution, final Event requestingEvent) throws FlowExecutionStorageException; void postSave(final Serializable newId, final Serializable originalId, final FlowExecution flowExecution, Event requestingEvent) throws FlowExecutionStorageException; void preRemove(final Serializable id, final Event requestingEvent) throws FlowExecutionStorageException; void postRemove(final Serializable id, final Event requestingEvent) throws FlowExecutionStorageException; }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 { public static final String PREFIX = "~persisted:"; private final Storage storage; private final Collection<String> attributesToPersist; public PersistableFlowExecutionStorageLifecycleListener(final Storage theStorage, final Collection<String> theNames) { this.storage = theStorage; this.attributesToPersist = theNames; } public void preSave(final String id, final FlowExecution flowExecution, final Event requestingEvent) throws FlowExecutionStorageException { // persist all attributes Scope scope = flowExecution.getActiveSession().getScope(); for (String attName: attributesToPersist) { if (scope.containsAttribute(attName)) { Object value = scope.getAttribute(attName); Object token = storage.persist(value); scope.removeAttribute(attName); scope.setAttribute(PREFIX + attName, token); } } } public void postLoad(final FlowExecution flowExecution, final String id, final Event requestingEvent) throws FlowExecutionStorageException { // rehydrate all attributes Scope scope = flowExecution.getActiveSession().getScope(); for (String attName: attributesToPersist) { String mangledName = PREFIX + attName; if (scope.containsAttribute(mangledName)) { Object token = scope.getAttribute(mangledName); Object value = storage.load(token); scope.removeAttribute(mangledName); scope.setAttribute(attName, value); } } } public void preRemove(final String id, final Event requestingEvent) throws FlowExecutionStorageException { } }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 { Object persist(final Object object); Object load(final Object token); }It is wired up: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 { public Object persist(final Object object) { File file = null; String fileName = System.currentTimeMillis() + "_" + object.hashCode(); try { file = File.createTempFile(fileName, null); } catch (final IOException e) { throw new IllegalStateException("Cannot create [" + fileName + "]", e); } try { FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(object); oos.flush(); } catch (final IOException e) { throw new IllegalArgumentException("Cannot serialize " + object, e); } return file.getAbsolutePath(); } public Object load(final Object token) { File file = new File((String) token); if (!file.exists()) { throw new IllegalStateException("File " + file + " does not exist for token " + token); } try { byte[] contents = FileCopyUtils.copyToByteArray(file); ObjectInputStream ois = new ObjectInputStream( new ByteArrayInputStream(contents)); return ois.readObject(); } catch (final IOException e) { throw new IllegalStateException("Cannot load state from " + token, e); } catch (final ClassNotFoundException e) { throw new IllegalStateException("Cannot reconstruct class for token " + token, e); } } }
Whenever I have to write this much infrastructure code I always wonder if I have missed the pointCode:<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>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).


Does WebFlow have anything like this already?
Reply With Quote