KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > webflow > execution > repository > continuation > ClientContinuationFlowExecutionRepository


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.springframework.webflow.execution.repository.continuation;
17
18 import java.io.Serializable JavaDoc;
19
20 import org.apache.commons.codec.binary.Base64;
21 import org.springframework.util.Assert;
22 import org.springframework.webflow.conversation.Conversation;
23 import org.springframework.webflow.conversation.ConversationException;
24 import org.springframework.webflow.conversation.ConversationId;
25 import org.springframework.webflow.conversation.ConversationManager;
26 import org.springframework.webflow.conversation.ConversationParameters;
27 import org.springframework.webflow.conversation.NoSuchConversationException;
28 import org.springframework.webflow.core.collection.CollectionUtils;
29 import org.springframework.webflow.execution.FlowExecution;
30 import org.springframework.webflow.execution.repository.FlowExecutionKey;
31 import org.springframework.webflow.execution.repository.FlowExecutionRestorationFailureException;
32 import org.springframework.webflow.execution.repository.support.AbstractConversationFlowExecutionRepository;
33 import org.springframework.webflow.execution.repository.support.FlowExecutionStateRestorer;
34
35 /**
36  * Stores flow execution state client side, requiring no use of server-side
37  * state.
38  * <p>
39  * More specifically, instead of putting {@link FlowExecution} objects in a
40  * server-side store this repository <i>encodes</i> them directly into the
41  * <code>continuationId</code> of the generated {@link FlowExecutionKey}.
42  * When asked to load a flow execution by its key this repository decodes the
43  * serialized <code>continuationId</code>, restoring the
44  * {@link FlowExecution} object at the state it was in when encoded.
45  * <p>
46  * Note: currently this repository implementation does not by default support
47  * <i>conversation management</i>. This has two consequences. First, there is no
48  * <i>conversation invalidation after completion</i>, which enables automatic
49  * prevention of duplicate submission after a conversation has completed.
50  * Secondly, The contents of <i>conversation scope</i> will not be maintained
51  * across requests. Support for these features requires tracking active
52  * conversations using a conversation service backed by some centralized storage
53  * medium like a database table. If you want to have proper conversation management,
54  * configure this class with an appropriate conversation manager (the default
55  * conversation manager used does nothing).
56  * <p>
57  * Warning: storing state (a flow execution continuation) on the client entails
58  * a certain security risk. This implementation does not provide a secure way of
59  * storing state on the client, so a malicious client could reverse engineer a
60  * continuation and get access to possible sensitive data stored in the flow
61  * execution. If you need more security and still want to store continuations on
62  * the client, subclass this class and override the methods
63  * {@link #encode(FlowExecution)} and {@link #decode(String)}, implementing
64  * them with a secure encoding/decoding algorithm, e.g. based on public/private
65  * key encryption.
66  * <p>
67  * This class depends on the <code>Jakarta Commons Codec</code> library to do
68  * <code>BASE64</code> encoding. Codec code must be available in the classpath
69  * when using this implementation.
70  *
71  * @see Base64
72  *
73  * @author Keith Donald
74  * @author Erwin Vervaet
75  */

76 public class ClientContinuationFlowExecutionRepository extends AbstractConversationFlowExecutionRepository {
77
78     /**
79      * The continuation factory that will be used to create new continuations to
80      * be added to active conversations.
81      */

82     private FlowExecutionContinuationFactory continuationFactory = new SerializedFlowExecutionContinuationFactory();
83
84     /**
85      * Creates a new client continuation repository. Uses a 'no op' conversation manager by default.
86      * @param executionStateRestorer the transient flow execution state restorer
87      */

88     public ClientContinuationFlowExecutionRepository(FlowExecutionStateRestorer executionStateRestorer) {
89         super(executionStateRestorer, new NoOpConversationManager());
90     }
91     
92     /**
93      * Creates a new client continuation repository. Use this contructor when you want
94      * to use a particular conversation manager, e.g. one that does proper conversation
95      * management.
96      * @param executionStateRestorer the transient flow execution state restorer
97      * @param conversationManager the conversation manager for managing centralized conversational state
98      */

99     public ClientContinuationFlowExecutionRepository(FlowExecutionStateRestorer executionStateRestorer,
100             ConversationManager conversationManager) {
101         super(executionStateRestorer, conversationManager);
102     }
103
104     /**
105      * Returns the continuation factory in use by this repository.
106      */

107     protected FlowExecutionContinuationFactory getContinuationFactory() {
108         return continuationFactory;
109     }
110
111     /**
112      * Sets the continuation factory used by this repository.
113      */

114     public void setContinuationFactory(FlowExecutionContinuationFactory continuationFactory) {
115         Assert.notNull(continuationFactory, "The continuation factory is required");
116         this.continuationFactory = continuationFactory;
117     }
118
119     public FlowExecution getFlowExecution(FlowExecutionKey key) {
120         // note that the call to getConversationScope() below will try to obtain
121
// the conversation identified by the key, which will fail if that conversation
122
// is no longer managed by the conversation manager (i.e. it has expired)
123

124         FlowExecutionContinuation continuation = decode((String JavaDoc)getContinuationId(key));
125         try {
126             FlowExecution execution = continuation.unmarshal();
127             // the flox execution was deserialized so we need to restore transient
128
// state
129
return getExecutionStateRestorer().restoreState(execution, getConversationScope(key));
130         }
131         catch (ContinuationUnmarshalException e) {
132             throw new FlowExecutionRestorationFailureException(key, e);
133         }
134     }
135
136     public void putFlowExecution(FlowExecutionKey key, FlowExecution flowExecution) {
137         // note that the call to putConversationScope() below will try to obtain
138
// the conversation identified by the key, which will fail if that conversation
139
// is no longer managed by the conversation manager (i.e. it has expired)
140

141         // the flow execution state is already stored in the key, so
142
// there's nothing we need to do to store it
143
putConversationScope(key, flowExecution.getConversationScope());
144     }
145
146     protected final Serializable JavaDoc generateContinuationId(FlowExecution flowExecution) {
147         return encode(flowExecution);
148     }
149
150     protected final Serializable JavaDoc parseContinuationId(String JavaDoc encodedId) {
151         // just return here, continuation decoding happens in getFlowExecution
152
return encodedId;
153     }
154
155     /**
156      * Encode given flow execution object into data that can be stored on the
157      * client.
158      * <p>
159      * Subclasses can override this to change the encoding algorithm. This class
160      * just does a BASE64 encoding of the serialized flow execution.
161      * @param flowExecution the flow execution instance
162      * @return the encoded representation
163      */

164     protected Serializable JavaDoc encode(FlowExecution flowExecution) {
165         FlowExecutionContinuation continuation = continuationFactory.createContinuation(flowExecution);
166         return new String JavaDoc(Base64.encodeBase64(continuation.toByteArray()));
167     }
168
169     /**
170      * Decode given data, received from the client, and return the corresponding
171      * flow execution object.
172      * <p>
173      * Subclasses can override this to change the decoding algorithm. This class
174      * just does a <code>BASE64</code> decoding and then deserializes the flow
175      * execution.
176      * @param encodedContinuation the encoded flow execution data
177      * @return the decoded flow execution instance
178      */

179     protected FlowExecutionContinuation decode(String JavaDoc encodedContinuation) {
180         byte[] bytes = Base64.decodeBase64(encodedContinuation.getBytes());
181         return continuationFactory.createContinuation(bytes);
182     }
183
184     /**
185      * Conversation manager that doesn't do anything - the default. Does not support
186      * conversation scope or conversation invalidation.
187      *
188      * @author Keith Donald
189      */

190     private static class NoOpConversationManager implements ConversationManager {
191
192         /**
193          * The single conversation managed by the manager.
194          */

195         private static final NoOpConversation INSTANCE = new NoOpConversation();
196
197         public Conversation beginConversation(ConversationParameters conversationParameters)
198                 throws ConversationException {
199             return INSTANCE;
200         }
201
202         public Conversation getConversation(ConversationId id) throws NoSuchConversationException {
203             return INSTANCE;
204         }
205
206         public ConversationId parseConversationId(String JavaDoc encodedId) throws ConversationException {
207             return NoOpConversation.ID;
208         }
209
210         private static class NoOpConversation implements Conversation {
211             
212             private static final ConversationId ID = new ConversationId() {
213                 public String JavaDoc toString() {
214                     return "NoOpConversation id";
215                 }
216             };
217
218             public ConversationId getId() {
219                 return ID;
220             }
221
222             public void lock() {
223             }
224
225             public Object JavaDoc getAttribute(Object JavaDoc name) {
226                 return CollectionUtils.EMPTY_ATTRIBUTE_MAP;
227             }
228
229             public void putAttribute(Object JavaDoc name, Object JavaDoc value) {
230             }
231
232             public void removeAttribute(Object JavaDoc name) {
233             }
234
235             public void end() {
236             }
237
238             public void unlock() {
239             }
240         }
241     }
242 }
Popular Tags