1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.myfaces.orchestra.conversation;
21
22 import java.util.Arrays;
23 import java.util.Iterator;
24 import java.util.Map;
25 import java.util.TreeMap;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.myfaces.orchestra.lib._ReentrantLock;
30
31 /**
32 * A ConversationContext is a container for a set of conversations.
33 * <p>
34 * Normally there is only one ConversationContext per http session. However there can
35 * be multiple instances if the user has multiple concurrent windows open into the same
36 * webapp, using the ox:separateConversationContext or other similar mechanism.
37 * <p>
38 * Like the conversation class, a context can also have a timeout which will cause it
39 * to be ended automatically if not accessed within the given period.
40 */
41 public class ConversationContext
42 {
43 private final Log log = LogFactory.getLog(ConversationContext.class);
44
45 // This id is attached as a query parameter to every url rendered in a page
46 // (forms and links) so that if that url is invoked then the request will
47 // cause the same context to be used from the user's http session.
48 private final long id;
49
50 // See addAttribute
51 private final Map attributes = new TreeMap();
52
53 // The conversations held by this context, keyed by conversation name.
54 private final Map conversations = new TreeMap();
55
56 // time at which this was last accessed, used for timeouts.
57 private long lastAccess;
58
59 // default timeout for contexts: 30 minutes.
60 private long timeoutMillis = 30 * 60 * 1000;
61
62 private final _ReentrantLock lock = new _ReentrantLock();
63
64 protected ConversationContext(long id)
65 {
66 this.id = id;
67
68 touch();
69 }
70
71 /**
72 * The conversation context id, unique within the current http session.
73 */
74 public long getId()
75 {
76 return id;
77 }
78
79 /**
80 * Mark this context as having been used.
81 */
82 protected void touch()
83 {
84 lastAccess = System.currentTimeMillis();
85 }
86
87 /**
88 * The system time in millis when this conversation has been accessed last.
89 */
90 public long getLastAccess()
91 {
92 return lastAccess;
93 }
94
95 /**
96 * Get the timeout after which this context will be closed.
97 *
98 * @see #setTimeout
99 */
100 public long getTimeout()
101 {
102 return timeoutMillis;
103 }
104
105 /**
106 * Set the timeout after which this context will be closed.
107 * <p>
108 * A value of -1 means no timeout checking.
109 */
110 public void setTimeout(long timeoutMillis)
111 {
112 this.timeoutMillis = timeoutMillis;
113 }
114
115 /**
116 * Invalidate all conversations within this context.
117 */
118 protected void clear()
119 {
120 synchronized (this)
121 {
122 Conversation[] convArray = new Conversation[conversations.size()];
123 conversations.values().toArray(convArray);
124
125 for (int i = 0; i < convArray.length; i++)
126 {
127 Conversation conversation = convArray[i];
128 conversation.invalidate();
129 }
130
131 conversations.clear();
132 }
133 }
134
135 /**
136 * Start a conversation if not already started.
137 */
138 protected Conversation startConversation(String name, ConversationFactory factory)
139 {
140 synchronized (this)
141 {
142 touch();
143 Conversation conversation = (Conversation) conversations.get(name);
144 if (conversation == null)
145 {
146 conversation = factory.createConversation(this, name);
147
148 conversations.put(name, conversation);
149 }
150 return conversation;
151 }
152 }
153
154 /**
155 * Remove the conversation from this context.
156 *
157 * <p>Notice: It is assumed that the conversation has already been invalidated.</p>
158 */
159 protected void removeConversation(Conversation conversation)
160 {
161 synchronized (this)
162 {
163 touch();
164 conversations.remove(conversation.getName());
165 }
166 }
167
168 /**
169 * Remove the conversation with the given name from this context.
170 *
171 * <p>Notice: Its assumed that the conversation has already been invalidated</p>
172 */
173 protected void removeConversation(String name)
174 {
175 synchronized (this)
176 {
177 touch();
178 Conversation conversation = (Conversation) conversations.get(name);
179 if (conversation != null)
180 {
181 removeConversation(conversation);
182 }
183 }
184 }
185
186 /**
187 * See if there is a conversation with the specified name.
188 */
189 protected boolean hasConversations()
190 {
191 synchronized (this)
192 {
193 touch();
194 return conversations.size() > 0;
195 }
196 }
197
198 /**
199 * Check if the given conversation exists.
200 */
201 protected boolean hasConversation(String name)
202 {
203 synchronized (this)
204 {
205 touch();
206 return conversations.get(name) != null;
207 }
208 }
209
210 /**
211 * Get a conversation by name.
212 */
213 protected Conversation getConversation(String name)
214 {
215 synchronized (this)
216 {
217 touch();
218
219 Conversation conv = (Conversation) conversations.get(name);
220 if (conv != null)
221 {
222 conv.touch();
223 }
224
225 return conv;
226 }
227 }
228
229 /**
230 * Iterates over all the conversations in this context.
231 *
232 * @return An iterator over a copy of the conversation list. It is safe to remove objects from
233 * the conversation list while iterating, as the iterator refers to a different collection.
234 */
235 public Iterator iterateConversations()
236 {
237 synchronized (this)
238 {
239 touch();
240
241 Conversation[] convs = (Conversation[]) conversations.values().toArray(new Conversation[conversations.size()]);
242 return Arrays.asList(convs).iterator();
243 }
244 }
245
246 /**
247 * Check the timeout for every conversation in this context.
248 * <p>
249 * This method does not check the timeout for this context object itself.
250 */
251 protected void checkConversationTimeout()
252 {
253 synchronized (this)
254 {
255 Conversation[] convArray = new Conversation[conversations.size()];
256 conversations.values().toArray(convArray);
257
258 for (int i = 0; i < convArray.length; i++)
259 {
260 Conversation conversation = convArray[i];
261
262 ConversationTimeoutableAspect timeoutAspect =
263 (ConversationTimeoutableAspect)
264 conversation.getAspect(ConversationTimeoutableAspect.class);
265
266 if (timeoutAspect != null && timeoutAspect.isTimeoutReached())
267 {
268 if (log.isDebugEnabled())
269 {
270 log.debug("end conversation due to timeout: " + conversation.getName());
271 }
272
273 conversation.invalidate();
274 }
275 }
276 }
277 }
278
279 /**
280 * Add an attribute to the conversationContext.
281 * <p>
282 * A context provides a map into which any arbitrary objects can be stored. It
283 * isn't a major feature of the context, but can occasionally be useful.
284 */
285 public void setAttribute(String name, Object attribute)
286 {
287 synchronized(attributes)
288 {
289 attributes.remove(name);
290 attributes.put(name, attribute);
291 }
292 }
293
294 /**
295 * Check if this conversationContext holds a specific attribute.
296 */
297 public boolean hasAttribute(String name)
298 {
299 synchronized(attributes)
300 {
301 return attributes.containsKey(name);
302 }
303 }
304
305 /**
306 * Get a specific attribute.
307 */
308 public Object getAttribute(String name)
309 {
310 synchronized(attributes)
311 {
312 return attributes.get(name);
313 }
314 }
315
316 /**
317 * Remove an attribute from the conversationContext.
318 */
319 public Object removeAttribute(String name)
320 {
321 synchronized(attributes)
322 {
323 return attributes.remove(name);
324 }
325 }
326
327 /**
328 * Block until no other thread has this instance marked as reserved, then
329 * mark the object as reserved for this thread.
330 * <p>
331 * It is safe to call this method multiple times.
332 * <p>
333 * If this method is called, then an equal number of calls to
334 * unlockForCurrentThread <b>MUST</b> made, or this context object
335 * will remain locked until the http session times out.
336 *
337 * @since 1.1
338 */
339 public void lockInterruptablyForCurrentThread() throws InterruptedException
340 {
341 lock.lockInterruptibly();
342 }
343
344 /**
345 * Block until no other thread has this instance marked as reserved, then
346 * mark the object as reserved for this thread.
347 *
348 * @since 1.1
349 */
350 public void unlockForCurrentThread()
351 {
352 lock.unlock();
353 }
354
355 /**
356 * Return true if this object is currently locked by the calling thread.
357 *
358 * @since 1.1
359 */
360 public boolean isLockedForCurrentThread()
361 {
362 return lock.isHeldByCurrentThread();
363 }
364 }