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: skitching $)
48   * @author Anton Koinov
49   * @version $Revision: 684459 $ $Date: 2008-08-10 06:13:56 -0500 (Sun, 10 Aug 2008) $
50   */
51  public class NavigationHandlerImpl
52      extends NavigationHandler
53  {
54      private static final Log log = LogFactory.getLog(NavigationHandlerImpl.class);
55  
56      private static final String ASTERISK = "*";
57  
58      private Map<String, List> _navigationCases = null;
59      private List<String> _wildcardKeys = new ArrayList<String>();
60  
61      public NavigationHandlerImpl()
62      {
63          if (log.isTraceEnabled()) log.trace("New NavigationHandler instance created");
64      }
65  
66      public void handleNavigation(FacesContext facesContext, String fromAction, String outcome)
67      {
68          if (outcome == null)
69          {
70              // stay on current ViewRoot
71              return;
72          }
73  
74          NavigationCase navigationCase = getNavigationCase(facesContext, fromAction, outcome);
75  
76          if (navigationCase != null)
77          {
78              if (log.isTraceEnabled())
79              {
80                  log.trace("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
81                            " toViewId =" + navigationCase.getToViewId() +
82                            " redirect=" + navigationCase.isRedirect());
83              }
84              if (navigationCase.isRedirect() &&
85                  (!PortletUtil.isPortletRequest(facesContext)))
86              { // Spec section 7.4.2 says "redirects not possible" in this case for portlets
87                  ExternalContext externalContext = facesContext.getExternalContext();
88                  ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
89                  String redirectPath = viewHandler.getActionURL(facesContext, navigationCase.getToViewId());
90  
91                  try
92                  {
93                      externalContext.redirect(externalContext.encodeActionURL(redirectPath));
94                  }
95                  catch (IOException e)
96                  {
97                      throw new FacesException(e.getMessage(), e);
98                  }
99              }
100             else
101             {
102                 ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
103                 //create new view
104                 String newViewId = navigationCase.getToViewId();
105                 UIViewRoot viewRoot = viewHandler.createView(facesContext, newViewId);
106                 facesContext.setViewRoot(viewRoot);
107                 facesContext.renderResponse();
108             }
109         }
110         else
111         {
112             // no navigationcase found, stay on current ViewRoot
113             if (log.isTraceEnabled())
114             {
115                 log.trace("handleNavigation fromAction=" + fromAction + " outcome=" + outcome +
116                           " no matching navigation-case found, staying on current ViewRoot");
117             }
118         }
119     }
120 
121 
122     /**
123      * Returns the navigation case that applies for the given action and outcome
124      */
125     public NavigationCase getNavigationCase(FacesContext facesContext, String fromAction, String outcome)
126     {
127         String viewId = facesContext.getViewRoot().getViewId();
128         Map<String, List> casesMap = getNavigationCases(facesContext);
129         NavigationCase navigationCase = null;
130 
131         List casesList = casesMap.get(viewId);
132         if (casesList != null)
133         {
134             // Exact match?
135             navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
136         }
137 
138         if (navigationCase == null)
139         {
140             // Wildcard match?
141             List<String> keys = getSortedWildcardKeys();
142             for (int i = 0, size = keys.size(); i < size; i++)
143             {
144                 String fromViewId = keys.get(i);
145                 if (fromViewId.length() > 2)
146                 {
147                     String prefix = fromViewId.substring(0, fromViewId.length() - 1);
148                     if (viewId != null && viewId.startsWith(prefix))
149                     {
150                         casesList = casesMap.get(fromViewId);
151                         if (casesList != null)
152                         {
153                             navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
154                             if (navigationCase != null) break;
155                         }
156                     }
157                 }
158                 else
159                 {
160                     casesList = casesMap.get(fromViewId);
161                     if (casesList != null)
162                     {
163                         navigationCase = calcMatchingNavigationCase(casesList, fromAction, outcome);
164                         if (navigationCase != null) break;
165                     }
166                 }
167             }
168         }
169         return navigationCase;
170     }
171 
172     /**
173      * Returns the view ID that would be created for the given action and outcome
174      */
175     public String getViewId(FacesContext context, String fromAction, String outcome)
176     {
177         return this.getNavigationCase(context, fromAction, outcome).getToViewId();
178     }
179 
180     /**
181      * TODO
182      * Invoked by the navigation handler before the new view component is created.
183      * @param viewId The view ID to be created
184      * @return The view ID that should be used instead. If null, the view ID passed
185      * in will be used without modification.
186      */
187     public String beforeNavigation(String viewId)
188     {
189         return null;
190     }
191 
192     private NavigationCase calcMatchingNavigationCase(List casesList, String actionRef, String outcome)
193     {
194         for (int i = 0, size = casesList.size(); i < size; i++)
195         {
196             NavigationCase caze = (NavigationCase)casesList.get(i);
197             String cazeOutcome = caze.getFromOutcome();
198             String cazeActionRef = caze.getFromAction();
199             if ((cazeOutcome == null || cazeOutcome.equals(outcome)) &&
200                 (cazeActionRef == null || cazeActionRef.equals(actionRef)))
201             {
202                 return caze;
203             }
204         }
205         return null;
206     }
207 
208     private List<String> getSortedWildcardKeys()
209     {
210         return _wildcardKeys;
211     }
212 
213     private Map<String, List> getNavigationCases(FacesContext facesContext)
214     {
215         ExternalContext externalContext = facesContext.getExternalContext();
216         RuntimeConfig runtimeConfig = RuntimeConfig.getCurrentInstance(externalContext);
217 
218         if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
219         {
220             synchronized(this)
221             {
222                 if (_navigationCases == null || runtimeConfig.isNavigationRulesChanged())
223                 {
224                     Collection rules = runtimeConfig.getNavigationRules();
225                     int rulesSize = rules.size();
226                     Map<String, List> cases = new HashMap<String, List>(HashMapUtils.calcCapacity(rulesSize));
227                     List<String> wildcardKeys = new ArrayList<String>();
228 
229                     for (Iterator iterator = rules.iterator(); iterator.hasNext();)
230                     {
231                         NavigationRule rule = (NavigationRule) iterator.next();
232                         String fromViewId = rule.getFromViewId();
233 
234                         //specification 7.4.2 footnote 4 - missing fromViewId is allowed:
235                         if (fromViewId == null)
236                         {
237                             fromViewId = ASTERISK;
238                         }
239                         else
240                         {
241                             fromViewId = fromViewId.trim();
242                         }
243 
244                         List list = cases.get(fromViewId);
245                         if (list == null)
246                         {
247                             list = new ArrayList(rule.getNavigationCases());
248                             cases.put(fromViewId, list);
249                             if (fromViewId.endsWith(ASTERISK))
250                             {
251                                 wildcardKeys.add(fromViewId);
252                             }
253                         } else {
254                             list.addAll(rule.getNavigationCases());
255                         }
256 
257                     }
258                     Collections.sort(wildcardKeys, new KeyComparator());
259 
260                     synchronized (cases)
261                     {
262                         // We do not really need this sychronization at all, but this
263                         // gives us the peace of mind that some good optimizing compiler
264                         // will not rearrange the execution of the assignment to an
265                         // earlier time, before all init code completes
266                         _navigationCases = cases;
267                         _wildcardKeys = wildcardKeys;
268 
269                         runtimeConfig.setNavigationRulesChanged(false);
270                     }
271                 }
272             }
273         }
274         return _navigationCases;
275     }
276 
277     private static final class KeyComparator
278         implements Comparator
279     {
280         public int compare(Object o1, Object o2)
281         {
282             return -(((String)o1).compareTo((String)o2));
283         }
284     }
285 }