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 org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 import java.util.Arrays;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.TreeMap;
29
30 /***
31 * A ConversationContext is a container for a set of conversations.
32 * <p>
33 * Normally there is only one ConversationContext per http session. However there can
34 * be multiple instances if the user has multiple concurrent windows open into the same
35 * webapp, using the ox:separateConversationContext or other similar mechanism.
36 * <p>
37 * Like the conversation class, a context can also have a timeout which will cause it
38 * to be ended automatically if not accessed within the given period.
39 */
40 public class ConversationContext
41 {
42 private final Log log = LogFactory.getLog(ConversationContext.class);
43
44
45
46
47 private final long id;
48
49
50 private final Map attributes = new TreeMap();
51
52
53 private final Map conversations = new TreeMap();
54
55
56 private long lastAccess;
57
58
59 private long timeoutMillis = 30 * 60 * 1000;
60
61 protected ConversationContext(long id)
62 {
63 this.id = id;
64
65 touch();
66 }
67
68 /***
69 * The conversation context id, unique within the current http session.
70 */
71 public long getId()
72 {
73 return id;
74 }
75
76 /***
77 * Mark this context as having been used.
78 */
79 protected void touch()
80 {
81 lastAccess = System.currentTimeMillis();
82 }
83
84 /***
85 * The system time in millis when this conversation has been accessed last.
86 */
87 public long getLastAccess()
88 {
89 return lastAccess;
90 }
91
92 /***
93 * Get the timeout after which this context will be closed.
94 *
95 * @see #setTimeout
96 */
97 public long getTimeout()
98 {
99 return timeoutMillis;
100 }
101
102 /***
103 * Set the timeout after which this context will be closed.
104 * <p>
105 * A value of -1 means no timeout checking.
106 */
107 public void setTimeout(long timeoutMillis)
108 {
109 this.timeoutMillis = timeoutMillis;
110 }
111
112 /***
113 * Invalidate all conversations within this context.
114 */
115 protected void clear()
116 {
117 synchronized (this)
118 {
119 Conversation[] convArray = new Conversation[conversations.size()];
120 conversations.values().toArray(convArray);
121
122 for (int i = 0; i < convArray.length; i++)
123 {
124 Conversation conversation = convArray[i];
125 conversation.invalidate();
126 }
127
128 conversations.clear();
129 }
130 }
131
132 /***
133 * Start a conversation if not already started.
134 */
135 protected Conversation startConversation(String name, ConversationFactory factory)
136 {
137 synchronized (this)
138 {
139 touch();
140 Conversation conversation = (Conversation) conversations.get(name);
141 if (conversation == null)
142 {
143 conversation = factory.createConversation(this, name);
144
145 conversations.put(name, conversation);
146 }
147 return conversation;
148 }
149 }
150
151 /***
152 * Remove the conversation from this context.
153 *
154 * <p>Notice: It is assumed that the conversation has already been invalidated.</p>
155 */
156 protected void removeConversation(Conversation conversation)
157 {
158 synchronized (this)
159 {
160 touch();
161 conversations.remove(conversation.getName());
162 }
163 }
164
165 /***
166 * Remove the conversation with the given name from this context.
167 *
168 * <p>Notice: Its assumed that the conversation has already been invalidated</p>
169 */
170 protected void removeConversation(String name)
171 {
172 synchronized (this)
173 {
174 touch();
175 Conversation conversation = (Conversation) conversations.get(name);
176 if (conversation != null)
177 {
178 removeConversation(conversation);
179 }
180 }
181 }
182
183 /***
184 * See if there is a conversation with the specified name.
185 */
186 protected boolean hasConversations()
187 {
188 synchronized (this)
189 {
190 touch();
191 return conversations.size() > 0;
192 }
193 }
194
195 /***
196 * Check if the given conversation exists.
197 */
198 protected boolean hasConversation(String name)
199 {
200 synchronized (this)
201 {
202 touch();
203 return conversations.get(name) != null;
204 }
205 }
206
207 /***
208 * Get a conversation by name.
209 */
210 protected Conversation getConversation(String name)
211 {
212 synchronized (this)
213 {
214 touch();
215
216 Conversation conv = (Conversation) conversations.get(name);
217 if (conv != null)
218 {
219 conv.touch();
220 }
221
222 return conv;
223 }
224 }
225
226 /***
227 * Iterates over all the conversations in this context.
228 *
229 * @return An iterator over a copy of the conversation list. It is safe to remove objects from
230 * the conversation list while iterating, as the iterator refers to a different collection.
231 */
232 public Iterator iterateConversations()
233 {
234 synchronized (this)
235 {
236 touch();
237
238 Conversation[] convs = (Conversation[]) conversations.values().toArray(new Conversation[conversations.size()]);
239 return Arrays.asList(convs).iterator();
240 }
241 }
242
243 /***
244 * Check the timeout for every conversation in this context.
245 * <p>
246 * This method does not check the timeout for this context object itself.
247 */
248 protected void checkConversationTimeout()
249 {
250 synchronized (this)
251 {
252 Conversation[] convArray = new Conversation[conversations.size()];
253 conversations.values().toArray(convArray);
254
255 for (int i = 0; i < convArray.length; i++)
256 {
257 Conversation conversation = convArray[i];
258
259 ConversationTimeoutableAspect timeoutAspect =
260 (ConversationTimeoutableAspect)
261 conversation.getAspect(ConversationTimeoutableAspect.class);
262
263 if (timeoutAspect != null && timeoutAspect.isTimeoutReached())
264 {
265 if (log.isDebugEnabled())
266 {
267 log.debug("end conversation due to timeout: " + conversation.getName());
268 }
269
270 conversation.invalidate();
271 }
272 }
273 }
274 }
275
276 /***
277 * Add an attribute to the conversationContext.
278 * <p>
279 * A context provides a map into which any arbitrary objects can be stored. It
280 * isn't a major feature of the context, but can occasionally be useful.
281 */
282 public void setAttribute(String name, Object attribute)
283 {
284 synchronized(attributes)
285 {
286 attributes.remove(name);
287 attributes.put(name, attribute);
288 }
289 }
290
291 /***
292 * Check if this conversationContext holds a specific attribute.
293 */
294 public boolean hasAttribute(String name)
295 {
296 synchronized(attributes)
297 {
298 return attributes.containsKey(name);
299 }
300 }
301
302 /***
303 * Get a specific attribute.
304 */
305 public Object getAttribute(String name)
306 {
307 synchronized(attributes)
308 {
309 return attributes.get(name);
310 }
311 }
312
313 /***
314 * Remove an attribute from the conversationContext.
315 */
316 public Object removeAttribute(String name)
317 {
318 synchronized(attributes)
319 {
320 return attributes.remove(name);
321 }
322 }
323 }