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.application;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.Map;
30  
31  import javax.faces.FacesException;
32  import javax.faces.application.NavigationHandler;
33  import javax.faces.application.ViewHandler;
34  import javax.faces.component.UIViewRoot;
35  import javax.faces.context.ExternalContext;
36  import javax.faces.context.FacesContext;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.myfaces.config.RuntimeConfig;
41  import org.apache.myfaces.config.element.NavigationCase;
42  import org.apache.myfaces.config.element.NavigationRule;
43  import org.apache.myfaces.portlet.PortletUtil;
44  import org.apache.myfaces.shared_impl.util.HashMapUtils;
45  
46  /**
47   * @author Thomas Spiegl (latest modification by $Author: mmarinschek $)
48   * @author Anton Koinov
49   * @version $Revision: 526934 $ $Date: 2007-04-09 16:54:18 -0500 (Mon, 09 Apr 2007) $
50   */
51  public class NavigationHandlerImpl
52      extends NavigationHandler
53  {
54      private static final Log log = LogFactory.getLog(NavigationHandlerImpl.class);
55      private static final String PARTIAL_STATE_SAVING_METHOD_PARAM_NAME = "javax.faces.PARTIAL_STATE_SAVING_METHOD";
56      private static final String PARTIAL_STATE_SAVING_METHOD_ON = "true";
57      private static final String PARTIAL_STATE_SAVING_METHOD_OFF = "false";
58  
59      private static final String ASTERISK = "*";
60      private Boolean _partialStateSaving = null;
61  
62      private boolean isPartialStateSavingOn(javax.faces.context.FacesContext context)
63      {
64          if(context == null) throw new NullPointerException("context");
65          if (_partialStateSaving != null) return _partialStateSaving.booleanValue();
66          String stateSavingMethod = context.getExternalContext().getInitParameter(PARTIAL_STATE_SAVING_METHOD_PARAM_NAME);
67          if (stateSavingMethod == null)
68          {
69              _partialStateSaving = Boolean.FALSE; //Specs 10.1.3: default server saving
70              context.getExternalContext().log("No context init parameter '"+PARTIAL_STATE_SAVING_METHOD_PARAM_NAME+"' found; no partial state saving method defined, assuming default partial state saving method off.");
71          }
72          else if (stateSavingMethod.equals(PARTIAL_STATE_SAVING_METHOD_ON))
73          {
74              _partialStateSaving = Boolean.TRUE;
75          }
76          else if (stateSavingMethod.equals(PARTIAL_STATE_SAVING_METHOD_OFF))
77          {
78              _partialStateSaving = Boolean.FALSE;
79          }
80          else
81          {
82              _partialStateSaving = Boolean.FALSE; //Specs 10.1.3: default server saving
83              context.getExternalContext().log("Illegal partial state saving method '" + stateSavingMethod + "', default partial state saving will be used (partial state saving off).");
84          }
85          return _partialStateSaving.booleanValue();
86      }
87  
88  
89      private Map _navigationCases = null;
90      private List _wildcardKeys = new ArrayList();
91  
92      public NavigationHandlerImpl()
93      {
94          if (log.isTraceEnabled()) log.trace("New NavigationHandler instance created");
95      }
96  
97      public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
98      {
99          if (outcome == null)
100         {
101             // stay on current ViewRoot
102             return;
103         }
104 
105         NavigationCase navigationCase = getNavigationCase(facesContext, fromAction, outcome);
106 
107         if (navigationCase != null)
108         {
109             if (log.isTraceEnabled())
110             {
111                 log.trace("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
112                           " toViewId =" + navigationCase.getToViewId() +
113                           " redirect=" + navigationCase.isRedirect());
114             }
115             if (navigationCase.isRedirect() &&
116                 (!PortletUtil.isPortletRequest(facesContext)))
117             { // Spec section 7.4.2 says "redirects not possible" in this case for portlets
118                 ExternalContext externalContext = facesContext.getExternalContext();
119                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
120                 String redirectPath = viewHandler.getActionURL(facesContext, navigationCase.getToViewId());
121 
122                 try
123                 {
124                     externalContext.redirect(externalContext.encodeActionURL(redirectPath));
125                 }
126                 catch (IOException e)
127                 {
128                     throw new FacesException(e.getMessage(), e);
129                 }
130             }
131             else
132             {
133                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
134                 //create new view
135                 String newViewId = navigationCase.getToViewId();
136                 UIViewRoot viewRoot = null;
137                 if (isPartialStateSavingOn(facesContext)) {
138                     viewRoot = viewHandler.restoreView(facesContext,newViewId);
139                 } else {
140                     viewRoot = viewHandler.createView(facesContext, newViewId);
141                 }
142                 facesContext.setViewRoot(viewRoot);
143                 facesContext.renderResponse();
144             }
145         }
146         else
147         {
148             // no navigationcase found, stay on current ViewRoot
149             if (log.isTraceEnabled())
150             {
151                 log.trace("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
152                           " no matching navigation-case found, staying on current ViewRoot");
153             }
154         }
155     }
156 
157     /**
158      * Returns the <code>NavigationCase</code>that applies for the given action and outcome
159      */
160     public NavigationCase getNavigationCase(FacesContext facesContext, String fromAction, String outcome)
161     {
162         String viewId = facesContext.getViewRoot().getViewId();
163         Map casesMap = getNavigationCases(facesContext);
164         NavigationCase navigationCase = null;
165 
166         List casesList = (List)casesMap.get(viewId);
167         if (casesList != null)
168         {
169             // Exact match?
170             navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
171         }
172 
173         if (navigationCase == null)
174         {
175             // Wildcard match?
176             List keys = getSortedWildcardKeys();
177             for (int i = 0, size = keys.size(); i < size; i++)
178             {
179                 String fromViewId = (String)keys.get(i);
180                 if (fromViewId.length() > 2)
181                 {
182                     String prefix = fromViewId.substring(0, fromViewId.length() - 1);
183                     if (viewId != null && viewId.startsWith(prefix))
184                     {
185                         casesList = (List)casesMap.get(fromViewId);
186                         if (casesList != null)
187                         {
188                             navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
189                             if (navigationCase != null) break;
190                         }
191                     }
192                 }
193                 else
194                 {
195                     casesList = (List)casesMap.get(fromViewId);
196                     if (casesList != null)
197                     {
198                         navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
199                         if (navigationCase != null) break;
200                     }
201                 }
202             }
203         }
204         return navigationCase;
205     }
206 
207     /**
208      * Returns the view ID that would be created for the given action and outcome
209      */
210     public String getViewId(FacesContext context, String fromAction, String outcome)
211     {
212         return this.getNavigationCase(context, fromAction, outcome).getToViewId();
213     }
214 
215     /**
216      * Invoked by the navigation handler before the new view component is created.
217      * @param viewId The view ID to be created
218      * @return The view ID that should be used instead. If null, the view ID passed
219      * in will be used without modification.
220      * 
221      * <p><b>returns NULL</b></p>
222      * 
223      * <p>not implemented/called by Apache MyFaces</p>
224      */
225     public String beforeNavigation(String viewId)
226     {
227         return null;
228     }
229 
230     private NavigationCase calcMatchingNavigationCase(List casesList, String actionRef, String outcome)
231     {
232         for (int i = 0, size = casesList.size(); i < size; i++)
233         {
234             NavigationCase caze = (NavigationCase)casesList.get(i);
235             String cazeOutcome = caze.getFromOutcome();
236             String cazeActionRef = caze.getFromAction();
237             if ((cazeOutcome == null || cazeOutcome.equals(outcome)) &&
238                 (cazeActionRef == null || cazeActionRef.equals(actionRef)))
239             {
240                 return caze;
241             }
242         }
243         return null;
244     }
245 
246     private List getSortedWildcardKeys()
247     {
248         return _wildcardKeys;
249     }
250 
251     private Map getNavigationCases(FacesContext facesContext)
252     {
253         ExternalContext externalContext = facesContext.getExternalContext();
254         RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
255 
256         if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
257         {
258             synchronized(this)
259             {
260                 if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
261                 {
262                     Collection rules = runtimeConfig.getNavigationRules();
263                     int rulesSize = rules.size();
264                     Map cases = new HashMap(HashMapUtils.calcCapacity(rulesSize));
265                     List wildcardKeys = new ArrayList();
266 
267                     for (Iterator iterator = rules.iterator(); iterator.hasNext();)
268                     {
269                         NavigationRule rule = (NavigationRule) iterator.next();
270                         String fromViewId = rule.getFromViewId();
271 
272                         //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
273                         if (fromViewId == null)
274                         {
275                             fromViewId = ASTERISK;
276                         }
277                         else
278                         {
279                             fromViewId = fromViewId.trim();
280                         }
281 
282                         List list = (List) cases.get(fromViewId);
283                         if (list == null)
284                         {
285                             list = new ArrayList(rule.getNavigationCases());
286                             cases.put(fromViewId, list);
287                             if (fromViewId.endsWith(ASTERISK))
288                             {
289                                 wildcardKeys.add(fromViewId);
290                             }
291                         } else {
292                             list.addAll(rule.getNavigationCases());
293                         }
294 
295                     }
296                     Collections.sort(wildcardKeys, new KeyComparator());
297 
298                     synchronized (cases)
299                     {
300                         // We do not really need this sychronization at all, but this
301                         // gives us the peace of mind that some good optimizing compiler
302                         // will not rearrange the execution of the assignment to an
303                         // earlier time, before all init code completes
304                         _navigationCases = cases;
305                         _wildcardKeys = wildcardKeys;
306 
307                         runtimeConfig.setNavigationRulesChanged(false);
308                     }
309                 }
310             }
311         }
312         return _navigationCases;
313     }
314 
315     private static final class KeyComparator
316         implements Comparator
317     {
318         public int compare(Object o1, Object o2)
319         {
320             return -(((String)o1).compareTo((String)o2));
321         }
322     }
323 }