1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.orchestra.conversation;
21
22 import java.io.IOException;
23 import java.io.ObjectStreamException;
24 import java.io.Serializable;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter;
33 import org.apache.myfaces.orchestra.requestParameterProvider.RequestParameterProviderManager;
34
35 /***
36 * Deals with the various conversation contexts in the current session.
37 * <p>
38 * A new conversation context will be created if the servlet request did
39 * not specify an existing conversation context id.
40 * <p>
41 * At the current time, this object does not serialize well. Any attempt to serialize
42 * this object (including any serialization of the user session) will just cause it
43 * to be discarded.
44 * <p>
45 * TODO: fix serialization issues.
46 */
47 public class ConversationManager implements Serializable
48 {
49 final static String CONVERSATION_CONTEXT_PARAM = "conversationContext";
50
51 private final static String CONVERSATION_MANAGER_KEY = "org.apache.myfaces.ConversationManager";
52 private final static String CONVERSATION_CONTEXT_REQ = "org.apache.myfaces.ConversationManager.conversationContext";
53
54 private static final Iterator EMPTY_ITERATOR = Collections.EMPTY_LIST.iterator();
55
56
57
58 private static long nextConversationContextId = 1;
59
60 private final Log log = LogFactory.getLog(ConversationManager.class);
61
62
63
64 private final Map conversationContexts = new HashMap();
65
66 protected ConversationManager()
67 {
68
69 }
70
71 /***
72 * Get the conversation manager. This creates a new one if none exists.
73 */
74 public static ConversationManager getInstance()
75 {
76 return getInstance(true);
77 }
78
79 /***
80 * Get the conversation manager.
81 * <p>
82 * When create is true, an instance is always returned; one is
83 * created if none currently exists for the current user session.
84 * <p>
85 * When create is false, null is returned if no instance yet
86 * exists for the current user session.
87 */
88 public static ConversationManager getInstance(boolean create)
89 {
90 ConversationManager conversationManager = (ConversationManager) FrameworkAdapter.getCurrentInstance().getSessionAttribute(CONVERSATION_MANAGER_KEY);
91 if (conversationManager == null && create)
92 {
93
94
95 conversationManager = new ConversationManager();
96
97
98 RequestParameterProviderManager.getInstance().register(new ConversationRequestParameterProvider());
99
100
101 FrameworkAdapter.getCurrentInstance().setSessionAttribute(CONVERSATION_MANAGER_KEY, conversationManager);
102 }
103
104 return conversationManager;
105 }
106
107 /***
108 * Get the current, or create a new unique conversationContextId.
109 * <p>
110 * The current conversationContextId will retrieved from the request parameters. If no such parameter is
111 * present then a new id will be allocated and a new ConversationContext created.
112 * <p>
113 * In either case the result will be stored within the request for faster lookup.
114 * <p>
115 * Note that there is no security flaw regarding injection of fake context ids; the id must match one already
116 * in the session and there is no security problem with two windows in the same session exchanging ids.
117 */
118 public Long getConversationContextId()
119 {
120 Long conversationContextId = (Long) FrameworkAdapter.getCurrentInstance().getRequestAttribute(CONVERSATION_CONTEXT_REQ);
121 if (conversationContextId == null)
122 {
123 if (FrameworkAdapter.getCurrentInstance().containsRequestParameterAttribute(CONVERSATION_CONTEXT_PARAM))
124 {
125 String urlConversationContextId = FrameworkAdapter.getCurrentInstance().getRequestParameterAttribute(CONVERSATION_CONTEXT_PARAM).toString();
126 conversationContextId = new Long(Long.parseLong(urlConversationContextId, Character.MAX_RADIX));
127 }
128 else
129 {
130 synchronized (ConversationManager.class)
131 {
132 conversationContextId = new Long(nextConversationContextId);
133 nextConversationContextId++;
134 }
135 }
136
137 FrameworkAdapter.getCurrentInstance().setRequestAttribute(CONVERSATION_CONTEXT_REQ, conversationContextId);
138 }
139
140 return conversationContextId;
141 }
142
143 /***
144 * Get the conversation context for the given id
145 */
146 protected ConversationContext getConversationContext(Long conversationContextId)
147 {
148 synchronized (this)
149 {
150 return (ConversationContext) conversationContexts.get(conversationContextId);
151 }
152 }
153
154 /***
155 * Get the conversation context for the given id. <br />
156 * If there is no conversation context a new one will be created
157 */
158 protected ConversationContext getOrCreateConversationContext(Long conversationContextId)
159 {
160 synchronized (this)
161 {
162 ConversationContext conversationContext = (ConversationContext) conversationContexts.get(conversationContextId);
163 if (conversationContext == null)
164 {
165 conversationContext = new ConversationContext(conversationContextId.longValue());
166 conversationContexts.put(conversationContextId, conversationContext);
167 }
168
169 return conversationContext;
170 }
171 }
172
173 /***
174 * Ends all conversations within the current context; the context itself will remain active.
175 */
176 public void clearCurrentConversationContext()
177 {
178 Long conversationContextId = getConversationContextId();
179 if (conversationContextId == null)
180 {
181 return;
182 }
183
184 ConversationContext conversationContext = getConversationContext(conversationContextId);
185 if (conversationContext != null)
186 {
187 conversationContext.clear();
188 }
189 }
190
191 /***
192 * <p>Destroy the given conversation context</p>
193 * <p/>
194 * <p>Notice: its assumed that the context is already been destroyed</p>
195 */
196 protected void removeConversationContext(Long conversationContextId)
197 {
198 synchronized (this)
199 {
200 conversationContexts.remove(conversationContextId);
201 }
202 }
203
204 /***
205 * Start a conversation.
206 *
207 * @see ConversationContext#startConversation(String, ConversationFactory)
208 */
209 public Conversation startConversation(String name, ConversationFactory factory)
210 {
211 Long conversationContextId = getConversationContextId();
212 ConversationContext conversationContext = getOrCreateConversationContext(conversationContextId);
213 return conversationContext.startConversation(name, factory);
214 }
215
216 /***
217 * Remove a conversation
218 *
219 * <p>Notice: Its assumed that the conversation has already been invalidated</p>
220 *
221 * @see ConversationContext#removeConversation(String)
222 */
223 protected void removeConversation(String name)
224 {
225 Long conversationContextId = getConversationContextId();
226 ConversationContext conversationContext = getConversationContext(conversationContextId);
227 if (conversationContext != null)
228 {
229 conversationContext.removeConversation(name);
230 }
231 }
232
233 /***
234 * Get the conversation with the given name
235 *
236 * @return null if no conversation context is active or if the conversation did not exist.
237 */
238 public Conversation getConversation(String name)
239 {
240 ConversationContext conversationContext = getCurrentConversationContext();
241 if (conversationContext == null)
242 {
243 return null;
244 }
245 return conversationContext.getConversation(name);
246 }
247
248 /***
249 * check if the given conversation is active
250 */
251 public boolean hasConversation(String name)
252 {
253 ConversationContext conversationContext = getCurrentConversationContext();
254 if (conversationContext == null)
255 {
256 return false;
257 }
258 return conversationContext.hasConversation(name);
259 }
260
261 /***
262 * Returns an iterator over all the Conversation objects in the current conversation
263 * context. Never returns null, even if no conversation context exists.
264 */
265 public Iterator iterateConversations()
266 {
267 ConversationContext conversationContext = getCurrentConversationContext();
268 if (conversationContext == null)
269 {
270 return EMPTY_ITERATOR;
271 }
272
273 return conversationContext.iterateConversations();
274 }
275
276 /***
277 * Get the current conversation context.
278 *
279 * @return null if there is no context active
280 */
281 public ConversationContext getCurrentConversationContext()
282 {
283 Long conversationContextId = getConversationContextId();
284 if (conversationContextId == null)
285 {
286 return null;
287 }
288
289 ConversationContext conversationContext = getConversationContext(conversationContextId);
290 return conversationContext;
291 }
292
293 /***
294 * check if we have a conversation context
295 */
296 public boolean hasConversationContext()
297 {
298 return
299 (
300 FrameworkAdapter.getCurrentInstance().containsRequestAttribute(CONVERSATION_CONTEXT_REQ) ||
301 FrameworkAdapter.getCurrentInstance().containsRequestParameterAttribute(CONVERSATION_CONTEXT_REQ)) &&
302 getCurrentConversationContext() != null;
303 }
304
305 /***
306 * Get the Messager used to inform the user about anomalies.
307 * <p>
308 * What instance is returned is controlled by the FrameworkAdapter. See
309 * {@link org.apache.myfaces.orchestra.frameworkAdapter.FrameworkAdapter} for details.
310 */
311 public ConversationMessager getMessager()
312 {
313 return FrameworkAdapter.getCurrentInstance().getConversationMessager();
314 }
315
316 /***
317 * Check the timeout for each conversation context, and all conversations
318 * within those contexts.
319 * <p>
320 * If any conversation has not been accessed within its timeout period
321 * then clear the context.
322 * <p>
323 * Invoke the checkTimeout method on each context so that any conversation
324 * that has not been accessed within its timeout is invalidated.
325 */
326 protected void checkTimeouts()
327 {
328 Map.Entry[] contexts;
329 synchronized (this)
330 {
331 contexts = new Map.Entry[conversationContexts.size()];
332 conversationContexts.entrySet().toArray(contexts);
333 }
334
335 long checkTime = System.currentTimeMillis();
336
337 for (int i = 0; i<contexts.length; i++)
338 {
339 Map.Entry context = contexts[i];
340
341 Long conversationContextId = (Long) context.getKey();
342 ConversationContext conversationContext = (ConversationContext) context.getValue();
343 conversationContext.checkConversationTimeout();
344
345 if (conversationContext.getTimeout() > -1 &&
346 (conversationContext.getLastAccess() +
347 conversationContext.getTimeout()) < checkTime)
348 {
349 if (log.isDebugEnabled())
350 {
351 log.debug("end conversation context due to timeout: " + conversationContext.getId());
352 }
353
354 conversationContext.clear();
355 synchronized (this)
356 {
357 conversationContexts.remove(conversationContextId);
358 }
359 }
360 }
361 }
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381 private void writeObject(java.io.ObjectOutputStream out) throws IOException
382 {
383
384
385 }
386
387 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
388 {
389
390 }
391
392 private Object readResolve() throws ObjectStreamException
393 {
394
395 return null;
396 }
397 }