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 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 }