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.redirectTracker;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
24  import org.apache.myfaces.custom.redirectTracker.policy.NoopRedirectTrackPolicy;
25  
26  import javax.faces.application.FacesMessage;
27  import javax.faces.context.FacesContext;
28  import javax.faces.context.ExternalContext;
29  import javax.faces.el.ValueBinding;
30  import javax.faces.FacesException;
31  import java.io.Serializable;
32  import java.util.ArrayList;
33  import java.util.Iterator;
34  import java.util.List;
35  import java.util.Locale;
36  import java.util.Map;
37  import java.util.TreeMap;
38  
39  /**
40   * The redirect tracker maintains a list/map of data needed to restore the myfaces
41   * system after a navigation redirect
42   */
43  public class RedirectTrackerManager implements Serializable
44  {
45      private final static String INIT_POLICY = "org.apache.myfaces.redirectTracker.POLICY";
46      private final static String INIT_MAX_REDIRECTS = "org.apache.myfaces.redirectTracker.MAX_REDIRECTS";
47      public static final int TRACK_REDIRECTS = 20;
48  
49      private final static Log log = LogFactory.getLog(RedirectTrackerManager.class);
50  
51      public static final String SESSION_KEY = RedirectTrackerManager.class.getName();
52      public static final String REDIRECT_ARG = "_rtid";
53  
54      private final String redirectTrackerPolicy;
55      private final int redirects;
56      private transient Map requestBeanMap;
57  
58      private Map redirectEntryMap;
59      private List redirectEntryList;
60      private long requests;
61  
62      static class Entry implements Serializable
63      {
64          private final String mapKey;
65          private List messages;
66          private Map beanMap = new TreeMap();
67          private Locale locale;
68  
69          private Entry(String mapKey)
70          {
71              this.mapKey = mapKey;
72          }
73  
74          void addMessage(Object clientId, Object message)
75          {
76              if (messages == null)
77              {
78                  messages = new ArrayList();
79              }
80              messages.add(new MessageEntry(clientId, message));
81          }
82  
83          public Iterator getMessages()
84          {
85              if (messages == null)
86              {
87                  return null;
88              }
89  
90              return messages.iterator();
91          }
92  
93          public boolean hasCapturedData()
94          {
95              return (messages != null && messages.size() > 0)
96                  || (beanMap != null && beanMap.size() > 0)
97                  || locale != null;
98          }
99      }
100 
101     private static class MessageEntry implements Serializable
102     {
103         private final Object clientId;
104         private final Object message;
105 
106         public MessageEntry(Object clientId, Object message)
107         {
108             this.clientId = clientId;
109             this.message = message;
110         }
111     }
112 
113     /**
114      * Instantiate the tracker
115      *
116      * @param redirects nuof redirects to track
117      */
118     public RedirectTrackerManager(final int redirects, final String redirectTrackerPolicy)
119     {
120         this.redirects = redirects;
121         this.redirectTrackerPolicy = redirectTrackerPolicy;
122     }
123 
124     protected void initRedirectEntryMap()
125     {
126         if (redirectEntryMap == null)
127         {
128             redirectEntryMap = new TreeMap();
129             redirectEntryList = new ArrayList(redirects);
130         }
131     }
132 
133     /**
134      * check if this was a redirect, and if, process it
135      */
136     public void processTrackedRequest(FacesContext facesContext)
137     {
138         Object rtid = facesContext.getExternalContext().getRequestParameterMap().get(REDIRECT_ARG);
139         if (!isRedirectedRequest(rtid))
140         {
141             return;
142         }
143 
144         setupFaces(facesContext, rtid);
145     }
146 
147     /**
148      * check to see if the request parameter contains the identifier to the saved states
149      */
150     protected boolean isRedirectedRequest(Object rtid)
151     {
152         if (rtid == null)
153         {
154             return false;
155         }
156 
157         initRedirectEntryMap();
158         synchronized (redirectEntryMap)
159         {
160             return redirectEntryMap.containsKey(rtid);
161         }
162     }
163 
164     /**
165      * access the redirect tracker
166      */
167     public static RedirectTrackerManager getInstance(FacesContext facesContext)
168     {    
169         ExternalContext exCtx = facesContext.getExternalContext();
170         Map sessionMap = exCtx.getSessionMap();
171         Object session = exCtx.getSession(false);
172 
173         if (session == null || sessionMap == null)
174         {
175             return null;
176         }
177         RedirectTrackerManager redirectManager = (RedirectTrackerManager) sessionMap.get(SESSION_KEY);
178         if (redirectManager == null)
179         {
180             redirectManager = createRedirectTrackerManager(facesContext);
181             sessionMap.put(SESSION_KEY, redirectManager);
182         }
183 
184         return redirectManager;
185     }
186 
187     /**
188      * create a new redirect tracker
189      */
190     protected static RedirectTrackerManager createRedirectTrackerManager(FacesContext facesContext)
191     {
192         String initPolicy = (String) facesContext.getExternalContext().getInitParameter(INIT_POLICY);
193         if (initPolicy == null)
194         {
195             initPolicy = NoopRedirectTrackPolicy.class.getName();
196             if (log.isInfoEnabled())
197             {
198                 log.info("No context init parameter '" + INIT_POLICY + "' found, using default value " + initPolicy);
199             }
200         }
201 
202         String maxRedirects = (String) facesContext.getExternalContext().getInitParameter(INIT_MAX_REDIRECTS);
203         int numMaxRedirects;
204         if (maxRedirects == null)
205         {
206             numMaxRedirects = TRACK_REDIRECTS;
207             if (log.isInfoEnabled())
208             {
209                 log.info("No context init parameter '" + INIT_MAX_REDIRECTS + "' found, using default value " + numMaxRedirects);
210             }
211         }
212         else
213         {
214             numMaxRedirects = Integer.parseInt(maxRedirects, 10);
215         }
216 
217         return new RedirectTrackerManager(numMaxRedirects, initPolicy);
218     }
219 
220     /**
221      * add the current state to the redirect map/list
222      * @return the new path used for redirect
223      */
224     public String trackRedirect(FacesContext facesContext, String redirectPath)
225     {
226         long rtid = getNextRequestNo();
227         String mapKey = Long.toString(rtid, Character.MAX_RADIX);
228 
229         Entry entry = new Entry(mapKey);
230 
231         RedirectTrackerContext context = new RedirectTrackerContext(this, entry, facesContext);
232 
233         RedirectTrackerPolicy policy = getRedirectTrackerPolicy();
234         redirectPath = policy.trackRedirect(context, redirectPath);
235 
236         // saveBeans(entry);
237         // saveMessages(facesContext, entry);
238 
239         // prepare for next redirect
240         clearSaveStateBean();
241 
242         if (!entry.hasCapturedData())
243         {
244             // nothing to restore
245             return redirectPath;
246         }
247 
248         initRedirectEntryMap();
249         synchronized (redirectEntryMap)
250         {
251             redirectEntryMap.put(mapKey, entry);
252             redirectEntryList.add(entry);
253 
254             while (redirectEntryList.size() > redirects)
255             {
256                 Entry prevEntry = (Entry) redirectEntryList.remove(0);
257                 redirectEntryMap.remove(prevEntry.mapKey);
258             }
259         }
260 
261         if (redirectPath.indexOf('?') == -1)
262         {
263             return redirectPath + "?" + REDIRECT_ARG + "=" + mapKey;
264         }
265         else
266         {
267             return redirectPath + "&" + REDIRECT_ARG + "=" + mapKey;
268         }
269     }
270 
271     protected RedirectTrackerPolicy getRedirectTrackerPolicy()
272     {
273         try
274         {
275             return (RedirectTrackerPolicy) ClassUtils.classForName(redirectTrackerPolicy).newInstance();
276         }
277         catch (InstantiationException e)
278         {
279             throw new FacesException(e);
280         }
281         catch (IllegalAccessException e)
282         {
283             throw new FacesException(e);
284         }
285         catch (ClassNotFoundException e)
286         {
287             throw new FacesException(e);
288         }
289     }
290 
291     protected void saveBeans(Entry entry)
292     {
293         if (requestBeanMap != null)
294         {
295             entry.beanMap.putAll(requestBeanMap);
296         }
297     }
298 
299     protected void saveBean(Entry entry, String name, Object value)
300     {
301         entry.beanMap.put(name, value);
302     }
303 
304     /**
305      * Add the object to the current request holder
306      */
307     public void addSaveStateBean(String expressionString, Object value)
308     {
309         if (log.isDebugEnabled())
310         {
311             log.debug("addSaveStateBean: " + expressionString + " value=" + value);
312         }
313 
314         getRequestBeanMap().put(expressionString, value);
315     }
316 
317     protected Map getRequestBeanMap()
318     {
319         if (requestBeanMap == null)
320         {
321             requestBeanMap = new TreeMap();
322         }
323 
324         return requestBeanMap;
325     }
326 
327     /**
328      * request done, clear saveState beanmap
329      */
330     public void clearSaveStateBean()
331     {
332         if (requestBeanMap != null)
333         {
334             requestBeanMap.clear();
335         }
336     }
337 
338     protected void saveMessages(FacesContext facesContext, Entry entry)
339     {
340         Iterator iterClientIds = facesContext.getClientIdsWithMessages();
341         while (iterClientIds.hasNext())
342         {
343             String clientId = (String) iterClientIds.next();
344             Iterator iterMessages = facesContext.getMessages(clientId);
345             while (iterMessages.hasNext())
346             {
347                 Object message = iterMessages.next();
348 
349                 if (log.isDebugEnabled())
350                 {
351                     log.debug("saveMessage: " + message);
352                 }
353 
354                 entry.addMessage(clientId, message);
355             }
356         }
357     }
358 
359     protected void restoreMessages(FacesContext facesContext, Entry entry)
360     {
361         Iterator iterMessages = entry.getMessages();
362         if (iterMessages == null)
363         {
364             return;
365         }
366 
367         while (iterMessages.hasNext())
368         {
369             MessageEntry message = (MessageEntry) iterMessages.next();
370             facesContext.addMessage((String) message.clientId, (FacesMessage) message.message);
371         }
372     }
373 
374     protected void saveLocale(FacesContext facesContext, Entry entry)
375     {
376         if (facesContext.getViewRoot() != null && facesContext.getViewRoot().getLocale() != null)
377         {
378             if (log.isDebugEnabled())
379             {
380                 log.debug("saveLocale: " + facesContext.getViewRoot().getLocale());
381             }
382 
383             entry.locale = facesContext.getViewRoot().getLocale();
384         }
385     }
386 
387     protected void restoreLocale(FacesContext facesContext, Entry entry)
388     {
389         if (entry.locale != null && facesContext.getViewRoot() != null)
390         {
391             facesContext.getViewRoot().setLocale(entry.locale);
392         }
393     }
394 
395     /**
396      * resetup faces after redirect request
397      */
398     protected void setupFaces(FacesContext facesContext, Object rtid)
399     {
400         Entry entry;
401         initRedirectEntryMap();
402         synchronized (redirectEntryMap)
403         {
404             entry = (Entry) redirectEntryMap.get(rtid);
405         }
406 
407         if (entry == null)
408         {
409             // entry lost?
410             return;
411         }
412 
413         restoreLocale(facesContext, entry);
414         restoreMessages(facesContext, entry);
415         restoreBeans(facesContext, entry);
416     }
417 
418     protected void restoreBeans(FacesContext facesContext, Entry entry)
419     {
420         Iterator iterBeanMap = entry.beanMap.entrySet().iterator();
421         while (iterBeanMap.hasNext())
422         {
423             Map.Entry bean = (Map.Entry) iterBeanMap.next();
424 
425             String beanName = bean.getKey().toString();
426 
427             if (log.isDebugEnabled())
428             {
429                 log.debug("restore bean: " + beanName + " value=" + bean.getValue());
430             }
431 
432             if (beanName.startsWith("#{") && beanName.endsWith("}"))
433             {
434                 ValueBinding vb = facesContext.getApplication().createValueBinding(bean.getKey().toString());
435                 vb.setValue(facesContext, bean.getValue());
436             }
437             else
438             {
439                 facesContext.getExternalContext().getRequestMap().put(beanName, bean.getValue());
440 
441                 // if we add the bean directly to the request map, also put it again into our own list
442                 addSaveStateBean(beanName, bean.getValue());
443             }
444         }
445     }
446 
447     /**
448      * get the next request number
449      */
450     protected synchronized long getNextRequestNo()
451     {
452         requests++;
453         return requests;
454     }
455 }