View Javadoc

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 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  	// This id is attached as a query parameter to every url rendered in a page
45  	// (forms and links) so that if that url is invoked then the request will
46  	// cause the same context to be used from the user's http session. 
47  	private final long id;
48  
49  	// See addAttribute
50  	private final Map attributes = new TreeMap();
51  
52  	// The conversations held by this context, keyed by conversation name.
53  	private final Map conversations = new TreeMap();
54  
55  	// time at which this was last accessed, used for timeouts.
56  	private long lastAccess;
57  
58  	// default timeout for contexts: 30 minutes.
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 }