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  package org.apache.myfaces.custom.conversation;
20  
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.Map;
24  import java.io.Serializable;
25  import java.io.IOException;
26  import java.io.ObjectStreamException;
27  
28  import javax.faces.FacesException;
29  import javax.faces.context.FacesContext;
30  import javax.servlet.http.HttpSession;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.myfaces.custom.requestParameterProvider.RequestParameterProviderManager;
35  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
36  
37  /**
38   * The manager will deal with the various contexts in the current session.
39   * A new context will be created if the current window has none associated.
40   *
41   * @author imario@apache.org
42   */
43  public class ConversationManager implements Serializable
44  {
45      private final static Log log = LogFactory.getLog(ConversationManager.class);
46  
47      public final static String CONVERSATION_CONTEXT_PARAM = "conversationContext";
48  
49      private final static String INIT_PERSISTENCE_MANAGER_FACOTRY = "org.apache.myfaces.conversation.PERSISTENCE_MANAGER_FACTORY";
50      private final static String INIT_MESSAGER = "org.apache.myfaces.conversation.MESSAGER";
51      private final static String INIT_BEAN_ELEVATOR = "org.apache.myfaces.conversation.BEAN_ELEVATOR";
52  
53      private final static String CONVERSATION_MANAGER_KEY = "org.apache.myfaces.ConversationManager";
54      private final static String CONVERSATION_CONTEXT_REQ = "org.apache.myfaces.ConversationManager.conversationContext";
55  
56      private static long NEXT_CONVERSATION_CONTEXT = 1;
57  
58      private PersistenceManagerFactory persistenceManagerFactory;
59      private ConversationMessager conversationMessager;
60      private ConversationBeanElevator conversationBeanElevator;
61  
62      private final Map conversationContexts = new HashMap();
63  
64      // private final List registeredEndConversations = new ArrayList(10);
65  
66      private class ContextWiperThread extends Thread
67      {
68          private final static long CHECK_TIME = 5 * 60 * 1000; // every 5 min
69          public ContextWiperThread()
70          {
71              setDaemon(true);
72              setName("ConversationManager.ContextWiperThread");
73          }
74  
75          public void run()
76          {
77              while (!isInterrupted())
78              {
79                  checkContextTimeout();
80  
81                  try
82                  {
83                      Thread.sleep(CHECK_TIME);
84                  }
85                  catch (InterruptedException e)
86                  {
87                      log.warn(e.getLocalizedMessage(), e);
88                  }
89              }
90          }
91      }
92      private ContextWiperThread wiperThread = new ContextWiperThread();
93  
94      protected ConversationManager()
95      {
96          wiperThread.start();
97      }
98  
99      /**
100      * Get the conversation manager
101      */
102     public static ConversationManager getInstance()
103     {
104         FacesContext context = FacesContext.getCurrentInstance();
105         if (context == null)
106         {
107             // check if we have a chance outside the FacesContext
108             ConversationManager cm = ConversationServletFilter.getConversationManager();
109             if (cm != null)
110             {
111                 return cm;
112             }
113 
114             throw new IllegalStateException("no faces context available");
115         }
116         return getInstance(context);
117     }
118 
119     /**
120      * Get the conversation manager
121      */
122     public static ConversationManager getInstance(FacesContext context)
123     {
124         ConversationManager conversationManager = (ConversationManager) context.getExternalContext().getSessionMap().get(CONVERSATION_MANAGER_KEY);
125         if (conversationManager == null)
126         {
127             if (!Boolean.TRUE.equals(context.getExternalContext().getRequestMap().get(ConversationServletFilter.CONVERSATION_FILTER_CALLED)))
128             {
129                 throw new IllegalStateException("ConversationServletFilter not called. Please configure the filter org.apache.myfaces.custom.conversation.ConversationServletFilter in your web.xml to cover your faces requests.");
130             }
131 
132             conversationManager = createConversationManager();
133 
134             // initialize environmental systems
135             RequestParameterProviderManager.getInstance(context).register(new ConversationRequestParameterProvider());
136 
137             // set mark
138             context.getExternalContext().getSessionMap().put(CONVERSATION_MANAGER_KEY, conversationManager);
139         }
140 
141         return conversationManager;
142     }
143 
144     protected static ConversationManager createConversationManager()
145     {
146         ConversationManager conversationManager;
147 
148         // create manager
149         conversationManager = new ConversationManager();
150 
151         // initialize the messager
152         conversationManager.createMessager();
153 
154         // initialize the bean elevator
155         conversationManager.createBeanElevator();
156 
157         return conversationManager;
158     }
159 
160     /**
161      * Get the conversation manager from the http session. This will <b>not</b> create a conversation manager if none exists.
162      */
163     public static ConversationManager getInstance(HttpSession session)
164     {
165         if (session == null)
166         {
167             return null;
168         }
169         return (ConversationManager) session.getAttribute(CONVERSATION_MANAGER_KEY);
170     }
171 
172     /**
173      * Get the current, or create a new unique conversationContextId.<br />
174      * The current conversationContextId will retrieved from the request parameters, if we cant find it there
175      * a new one will be created. In either case the result will be stored within the request for faster lookup.
176      */
177     protected Long getConversationContextId()
178     {
179         Map requestMap;
180         Map requestParameterMap;
181 
182         FacesContext context = FacesContext.getCurrentInstance();
183         if (context != null)
184         {
185             requestMap = context.getExternalContext().getRequestMap();
186             requestParameterMap = context.getExternalContext().getRequestParameterMap();
187         }
188         else
189         {
190             ConversationExternalContext ccontext = ConversationServletFilter.getConversationExternalContext();
191             if (ccontext != null)
192             {
193                 requestMap = ccontext.getRequestMap();
194                 requestParameterMap = ccontext.getRequestParameterMap();
195             }
196             else
197             {
198                 throw new IllegalStateException("cant find a requestMap or requestParameterMap");
199             }
200         }
201 
202         Long conversationContextId = (Long) requestMap.get(CONVERSATION_CONTEXT_REQ);
203         if (conversationContextId == null)
204         {
205             if (requestParameterMap.containsKey(CONVERSATION_CONTEXT_PARAM))
206             {
207                 String urlConversationContextId = requestParameterMap.get(CONVERSATION_CONTEXT_PARAM).toString();
208                 conversationContextId = new Long(Long.parseLong(urlConversationContextId, Character.MAX_RADIX));
209             }
210             else
211             {
212                 synchronized (ConversationManager.class)
213                 {
214                     conversationContextId = new Long(NEXT_CONVERSATION_CONTEXT);
215                     NEXT_CONVERSATION_CONTEXT++;
216                 }
217             }
218 
219             requestMap.put(CONVERSATION_CONTEXT_REQ, conversationContextId);
220         }
221 
222         return conversationContextId;
223     }
224 
225     /**
226      * Get the conversation context for the given id
227      */
228     protected ConversationContext getConversationContext(Long conversationContextId)
229     {
230         synchronized (conversationContexts)
231         {
232             return (ConversationContext) conversationContexts.get(conversationContextId);
233         }
234     }
235 
236     /**
237      * Get the conversation context for the given id. <br />
238      * If there is no conversation context a new one will be created
239      */
240     protected ConversationContext getOrCreateConversationContext(Long conversationContextId)
241     {
242         synchronized (conversationContexts)
243         {
244             ConversationContext conversationContext = (ConversationContext) conversationContexts.get(conversationContextId);
245             if (conversationContext == null)
246             {
247                 conversationContext = new ConversationContext(conversationContextId.longValue());
248                 conversationContexts.put(conversationContextId, conversationContext);
249             }
250 
251             return conversationContext;
252         }
253     }
254 
255     /**
256      * Destroy the given conversation context
257      */
258     protected void destroyConversationContext(Long conversationContextId)
259     {
260         synchronized (conversationContexts)
261         {
262             conversationContexts.remove(conversationContextId);
263         }
264     }
265 
266     /**
267      * Start a conversation
268      * @see ConversationContext#startConversation(String, boolean)
269      */
270     public void startConversation(String name, boolean persistence)
271     {
272         Long conversationContextId = getConversationContextId();
273         ConversationContext conversationContext = getOrCreateConversationContext(conversationContextId);
274         conversationContext.startConversation(name, persistence);
275     }
276 
277     /**
278      * End a conversation
279      * @see ConversationContext#endConversation(String, boolean)
280      */
281     public void endConversation(String name, boolean regularEnd)
282     {
283         Long conversationContextId = getConversationContextId();
284         ConversationContext conversationContext = getConversationContext(conversationContextId);
285         if (conversationContext != null)
286         {
287             conversationContext.endConversation(name, regularEnd);
288 
289             if (!conversationContext.hasConversations())
290             {
291                 destroyConversationContext(conversationContextId);
292             }
293         }
294     }
295 
296     /**
297      * Get the conversation with the given name
298      *
299      * @return null if no conversation context is active or if the conversation did not exist.
300      */
301     public Conversation getConversation(String name)
302     {
303         ConversationContext conversationContext = getConversationContext();
304         if (conversationContext == null)
305         {
306             return null;
307         }
308         return conversationContext.getConversation(name);
309     }
310 
311     /**
312      * Find the conversation which holds the given instance
313      *
314      * @return null if no conversation context is active or if the conversation did not exist.
315      */
316     public Conversation findConversation(Object instance)
317     {
318         ConversationContext conversationContext = getConversationContext();
319         if (conversationContext == null)
320         {
321             return null;
322         }
323         return conversationContext.findConversation(instance);
324     }
325 
326     /**
327      * check if the given conversation is active
328      */
329     public boolean hasConversation(String name)
330     {
331         ConversationContext conversationContext = getConversationContext();
332         if (conversationContext == null)
333         {
334             return false;
335         }
336         return conversationContext.hasConversation(name);
337     }
338 
339     /**
340      * Get the current conversation context.
341      * @return null if there is no context active
342      */
343     protected ConversationContext getConversationContext()
344     {
345         Long conversationContextId = getConversationContextId();
346         ConversationContext conversationContext = getConversationContext(conversationContextId);
347         return conversationContext;
348     }
349 
350     /**
351      * Register the conversation to be ended after the cycle
352     protected void registerEndConversation(String conversationName)
353     {
354         synchronized (registeredEndConversations)
355         {
356             registeredEndConversations.add(conversationName);
357         }
358     }
359      */
360 
361     /**
362      * Get all registered conversations
363     protected List getRegisteredEndConversations()
364     {
365         return registeredEndConversations;
366     }
367      */
368 
369     /**
370      * check if we have a conversation context
371      */
372     public boolean hasConversationContext()
373     {
374         FacesContext context = FacesContext.getCurrentInstance();
375 
376         return
377             (context.getExternalContext().getRequestMap().containsKey(CONVERSATION_CONTEXT_REQ) ||
378             context.getExternalContext().getRequestParameterMap().containsKey(CONVERSATION_CONTEXT_PARAM)) &&
379             getConversationContext() != null;
380     }
381 
382     /**
383      * get the persistence manager responsible for the current conversation
384      */
385     public PersistenceManager getPersistenceManager()
386     {
387         ConversationContext conversationContext = getConversationContext();
388         if (conversationContext == null)
389         {
390             return null;
391         }
392 
393         return conversationContext.getPersistenceManager();
394     }
395 
396     /**
397      * create a persistence manager
398      */
399     protected PersistenceManager createPersistenceManager()
400     {
401         return getPersistenceManagerFactory().create();
402     }
403 
404     /**
405      * Get the Messager used to inform the user about anomalies.<br />
406      * The factory can be configured in your web.xml using the init parameter named
407      * <code>org.apache.myfaces.conversation.MESSAGER</code>
408      */
409     protected ConversationMessager getMessager()
410     {
411         return conversationMessager;
412     }
413 
414     /**
415      * Create the Messager used to inform the user about anomalies.<br />
416      * The factory can be configured in your web.xml using the init parameter named
417      * <code>org.apache.myfaces.conversation.MESSAGER</code>
418      */
419     protected void createMessager()
420     {
421         String conversationMessagerName = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(INIT_MESSAGER);
422         if (conversationMessagerName == null)
423         {
424             conversationMessager = new DefaultConversationMessager();
425         }
426         else
427         {
428             try
429             {
430                 conversationMessager = (ConversationMessager) ClassUtils.classForName(conversationMessagerName).newInstance();
431             }
432             catch (InstantiationException e)
433             {
434                 throw new FacesException("error creating messager: " + conversationMessagerName, e);
435             }
436             catch (IllegalAccessException e)
437             {
438                 throw new FacesException("error creating messager: " + conversationMessagerName, e);
439             }
440             catch (ClassNotFoundException e)
441             {
442                 throw new FacesException("error creating messager: " + conversationMessagerName, e);
443             }
444         }
445     }
446 
447     /**
448      * Create the BeanElevator used to find a bean within the different contexts and elevate it
449      * into the conversation scope.<br />
450      * You can configure it in your web.xml using the init parameter named
451      * <code>org.apache.myfaces.conversation.BEAN_ELEVATOR</code>
452      */
453     protected void createBeanElevator()
454     {
455         String conversationBeanElevatorName = FacesContext.getCurrentInstance().getExternalContext().getInitParameter(INIT_BEAN_ELEVATOR);
456         if (conversationBeanElevatorName == null)
457         {
458             conversationBeanElevator = new DefaultConversationBeanElevator();
459         }
460         else
461         {
462             try
463             {
464                 conversationBeanElevator = (DefaultConversationBeanElevator) ClassUtils.classForName(conversationBeanElevatorName).newInstance();
465             }
466             catch (InstantiationException e)
467             {
468                 throw new FacesException("error creating bean elevator: " + conversationBeanElevatorName, e);
469             }
470             catch (IllegalAccessException e)
471             {
472                 throw new FacesException("error creating bean elevator: " + conversationBeanElevatorName, e);
473             }
474             catch (ClassNotFoundException e)
475             {
476                 throw new FacesException("error creating bean elevator: " + conversationBeanElevatorName, e);
477             }
478         }
479     }
480 
481     /**
482      * Get the persistenceManagerFactory.<br />
483      * The factory can be configured in your web.xml using the init parameter named
484      * <code>org.apache.myfaces.conversation.PERSISTENCE_MANAGER_FACTORY</code>
485      */
486     protected PersistenceManagerFactory getPersistenceManagerFactory()
487     {
488         if (persistenceManagerFactory == null)
489         {
490             String persistenceManagerFactoryName = (String) ConversationServletFilter.getConversationExternalContext().getInitParameterMap().get(INIT_PERSISTENCE_MANAGER_FACOTRY);
491             if (persistenceManagerFactoryName == null)
492             {
493                 throw new IllegalArgumentException("please configure '" + INIT_PERSISTENCE_MANAGER_FACOTRY + "' in your web.xml");
494             }
495             try
496             {
497                 persistenceManagerFactory =  (PersistenceManagerFactory) ClassUtils.classForName(persistenceManagerFactoryName).newInstance();
498             }
499             catch (InstantiationException e)
500             {
501                 throw new FacesException("error creating persistenceManagerFactory named: " + persistenceManagerFactoryName, e);
502             }
503             catch (IllegalAccessException e)
504             {
505                 throw new FacesException("error creating persistenceManagerFactory named: " + persistenceManagerFactoryName, e);
506             }
507             catch (ClassNotFoundException e)
508             {
509                 throw new FacesException("error creating persistenceManagerFactory named: " + persistenceManagerFactoryName, e);
510             }
511         }
512 
513         return persistenceManagerFactory;
514     }
515 
516     protected void attachPersistence()
517     {
518         ConversationContext conversationContext = getConversationContext();
519         if (conversationContext != null)
520         {
521             conversationContext.attachPersistence();
522         }
523     }
524 
525     protected void detachPersistence()
526     {
527         ConversationContext conversationContext = getConversationContext();
528         if (conversationContext != null)
529         {
530             conversationContext.detachPersistence();
531         }
532     }
533 
534     protected void purgePersistence()
535     {
536         ConversationContext conversationContext = getConversationContext();
537         if (conversationContext != null)
538         {
539             conversationContext.purgePersistence();
540         }
541     }
542 
543     protected void checkContextTimeout()
544     {
545         synchronized (conversationContexts)
546         {
547             long timeToLive = 30 * 60 * 1000;
548             long checkTime = System.currentTimeMillis();
549 
550             Iterator iterContexts = conversationContexts.values().iterator();
551             while (iterContexts.hasNext())
552             {
553                 ConversationContext conversationContext = (ConversationContext) iterContexts.next();
554                 if (conversationContext.getLastAccess() + timeToLive < checkTime)
555                 {
556                     if (log.isDebugEnabled())
557                     {
558                         log.debug("end conversation context due to timeout: " + conversationContext.getId());
559                     }
560                     conversationContext.shutdownContext();
561                     iterContexts.remove();
562                 }
563             }
564         }
565     }
566 
567 
568     /**
569      * the bean elevator used to get beans int the conversation scope
570      */
571     public ConversationBeanElevator getConversationBeanElevator()
572     {
573         return conversationBeanElevator;
574     }
575 
576     private void writeObject(java.io.ObjectOutputStream out) throws IOException
577     {
578         // the conversation manager is not (yet) serializable, we just implement it
579         // to make it work with distributed sessions
580     }
581 
582     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
583     {
584         // nothing written, so nothing to read
585     }
586 
587     private Object readResolve() throws ObjectStreamException
588     {
589         // do not return a real object, that way on first request a new conversation manager will be created
590         return null;
591     }
592 }