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.shared.renderkit.html.util;
20  
21  import java.io.IOException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.logging.Level;
28  import java.util.logging.Logger;
29  import javax.faces.FacesException;
30  import javax.faces.application.ConfigurableNavigationHandler;
31  import javax.faces.application.NavigationCase;
32  import javax.faces.application.NavigationHandler;
33  import javax.faces.application.ProjectStage;
34  import javax.faces.application.ViewHandler;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.UIOutcomeTarget;
37  import javax.faces.component.UIParameter;
38  import javax.faces.context.FacesContext;
39  import javax.faces.flow.FlowHandler;
40  import javax.faces.lifecycle.ClientWindow;
41  import org.apache.myfaces.shared.application.NavigationUtils;
42  import org.apache.myfaces.shared.renderkit.JSFAttr;
43  import org.apache.myfaces.shared.renderkit.RendererUtils;
44  import org.apache.myfaces.shared.renderkit.html.HtmlRendererUtils;
45  
46  /**
47   * Utility methods for OutcomeTarget components.
48   */
49  public class OutcomeTargetUtils
50  {
51      
52      private static final Logger log = Logger.getLogger(OutcomeTargetUtils.class
53              .getName());
54      
55      public static String getOutcomeTargetHref(FacesContext facesContext,
56              UIOutcomeTarget component) throws IOException
57      {
58          String outcome = component.getOutcome();
59          outcome = (outcome == null) ? facesContext.getViewRoot().getViewId()
60                  : outcome;
61          outcome = ((outcome == null) ? HtmlRendererUtils.STR_EMPTY : outcome.trim());
62          // Get the correct URL for the outcome.
63          NavigationHandler nh = facesContext.getApplication().getNavigationHandler();
64          if (!(nh instanceof ConfigurableNavigationHandler))
65          {
66              throw new FacesException(
67                      "Navigation handler must be an instance of "
68                              + "ConfigurableNavigationHandler for using h:link or h:button");
69          }
70          ConfigurableNavigationHandler navigationHandler = (ConfigurableNavigationHandler) nh;
71          
72          // handle faces flow 
73          // 1. check to-flow-document-id
74          String toFlowDocumentId = (String) component.getAttributes().get(
75              JSFAttr.TO_FLOW_DOCUMENT_ID_ATTR);
76          
77          // fromAction is null because there is no action method that was called to get the outcome
78          NavigationCase navigationCase = null;
79          if (toFlowDocumentId == null)
80          {
81              navigationCase = navigationHandler.getNavigationCase(
82                  facesContext, null, outcome);
83          }
84          else
85          {
86              navigationCase = navigationHandler.getNavigationCase(
87                  facesContext, null, outcome, toFlowDocumentId);            
88          }
89          
90          // when navigation case is null, force the link or button to be disabled and log a warning
91          if (navigationCase == null)
92          {
93              // log a warning
94              log.warning("Could not determine NavigationCase for UIOutcomeTarget component "
95                      + RendererUtils.getPathToComponent(component) + " with outcome " + outcome);
96  
97              return null;
98          }
99          Map<String, List<String>> parameters = null;
100         // handle URL parameters
101         if (component.getChildCount() > 0)
102         {
103             List<UIParameter> validParams = getValidUIParameterChildren(
104                     facesContext, component.getChildren(), true, false);
105             if (validParams.size() > 0)
106             {
107                 parameters = new HashMap<String, List<String>>();
108             }
109             for (int i = 0, size = validParams.size(); i < size; i++)
110             {
111                 UIParameter param = validParams.get(i);
112                 String name = param.getName();
113                 Object value = param.getValue();
114                 if (parameters.containsKey(name))
115                 {
116                     parameters.get(name).add(value.toString());
117                 }
118                 else
119                 {
120                     List<String> list = new ArrayList<String>(1);
121                     list.add(value.toString());
122                     parameters.put(name, list);
123                 }
124             }
125         }
126         
127         // From the navigation case, use getToFlowDocumentId() to identify when
128         // a navigation case is a flow call or a flow return.
129         if (navigationCase.getToFlowDocumentId() != null)
130         {
131             if (parameters == null)
132             {
133                 parameters = new HashMap<String, List<String>>();
134             }
135             if (!parameters.containsKey(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME))
136             {
137                 List<String> list = new ArrayList<String>(1);
138                 list.add(navigationCase.getToFlowDocumentId());
139                 parameters.put(FlowHandler.TO_FLOW_DOCUMENT_ID_REQUEST_PARAM_NAME, list);
140             }
141             if (!parameters.containsKey(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME))
142             {
143                 List<String> list2 = new ArrayList<String>(1);
144                 list2.add(navigationCase.getFromOutcome());
145                 parameters.put(FlowHandler.FLOW_ID_REQUEST_PARAM_NAME, list2);
146             }
147         }
148         
149         // handle NavigationCase parameters
150         Map<String, List<String>> navigationCaseParams = 
151             NavigationUtils.getEvaluatedNavigationParameters(facesContext,
152                 navigationCase.getParameters());
153         if (navigationCaseParams != null)
154         {
155             if (parameters == null)
156             {
157                 parameters = new HashMap<String, List<String>>();
158             }
159             //parameters.putAll(navigationCaseParams);
160             for (Map.Entry<String, List<String>> entry : navigationCaseParams
161                     .entrySet())
162             {
163                 if (!parameters.containsKey(entry.getKey()))
164                 {
165                     parameters.put(entry.getKey(), entry.getValue());
166                 }
167             }
168         }
169         if (parameters == null)
170         {
171             parameters = Collections.emptyMap();
172         }
173         boolean disableClientWindow = component.isDisableClientWindow();
174         ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
175         String href;
176         try
177         {
178             if (clientWindow != null && disableClientWindow)
179             {
180                 clientWindow.disableClientWindowRenderMode(facesContext);
181             }
182             // In theory the precedence order to deal with params is this:
183             // component parameters, navigation-case parameters, view parameters
184             // getBookmarkableURL deal with this details.
185             ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
186             href = viewHandler.getBookmarkableURL(facesContext,
187                     navigationCase.getToViewId(facesContext),
188                     parameters, navigationCase.isIncludeViewParams() || component.isIncludeViewParams());
189         }
190         finally
191         {
192             if (clientWindow != null && disableClientWindow)
193             {
194                 clientWindow.enableClientWindowRenderMode(facesContext);
195             }
196         }
197         // handle fragment (viewId#fragment)
198         String fragment = (String) component.getAttributes().get("fragment");
199         if (fragment != null)
200         {
201             fragment = fragment.trim();
202 
203             if (fragment.length() > 0)
204             {
205                 href += "#" + fragment;
206             }
207         }
208         return href;
209     }
210 
211     /**
212      * Calls getValidUIParameterChildren(facesContext, children, skipNullValue, skipUnrendered, true);
213      *
214      * @param facesContext
215      * @param children
216      * @param skipNullValue
217      * @param skipUnrendered
218      * @return ArrayList size > 0 if any parameter found
219      */
220     public static List<UIParameter> getValidUIParameterChildren(
221             FacesContext facesContext, List<UIComponent> children,
222             boolean skipNullValue, boolean skipUnrendered)
223     {
224         return getValidUIParameterChildren(facesContext, children,
225                 skipNullValue, skipUnrendered, true);
226     }
227     
228     
229     /**
230      * Returns a List of all valid UIParameter children from the given children.
231      * Valid means that the UIParameter is not disabled, its name is not null
232      * (if skipNullName is true), its value is not null (if skipNullValue is true)
233      * and it is rendered (if skipUnrendered is true). This method also creates a
234      * warning for every UIParameter with a null-name (again, if skipNullName is true)
235      * and, if ProjectStage is Development and skipNullValue is true, it informs the
236      * user about every null-value.
237      *
238      * @param facesContext
239      * @param children
240      * @param skipNullValue  should UIParameters with a null value be skipped
241      * @param skipUnrendered should UIParameters with isRendered() returning false be skipped
242      * @param skipNullName   should UIParameters with a null name be skipped
243      *                       (normally true, but in the case of h:outputFormat false)
244      * @return ArrayList size > 0 if any parameter found 
245      */
246     public static List<UIParameter> getValidUIParameterChildren(
247             FacesContext facesContext, List<UIComponent> children,
248             boolean skipNullValue, boolean skipUnrendered, boolean skipNullName)
249     {
250         List<UIParameter> params = null;
251         for (int i = 0, size = children.size(); i < size; i++)
252         {
253             UIComponent child = children.get(i);
254             if (child instanceof UIParameter)
255             {
256                 UIParameter param = (UIParameter) child;
257                 // check for the disable attribute (since 2.0)
258                 // and the render attribute (only if skipUnrendered is true)
259                 if (param.isDisable() || (skipUnrendered && !param.isRendered()))
260                 {
261                     // ignore this UIParameter and continue
262                     continue;
263                 }
264                 // check the name
265                 String name = param.getName();
266                 if (skipNullName && (name == null || HtmlRendererUtils.STR_EMPTY.equals(name)))
267                 {
268                     // warn for a null-name
269                     log.log(Level.WARNING, "The UIParameter " + RendererUtils.getPathToComponent(param)
270                                     + " has a name of null or empty string and thus will not be added to the URL.");
271                     // and skip it
272                     continue;
273                 }
274                 // check the value
275                 if (skipNullValue && param.getValue() == null)
276                 {
277                     if (facesContext.isProjectStage(ProjectStage.Development))
278                     {
279                         // inform the user about the null value when in Development stage
280                         log.log(Level.INFO, "The UIParameter " + RendererUtils.getPathToComponent(param)
281                                         + " has a value of null and thus will not be added to the URL.");
282                     }
283                     // skip a null-value
284                     continue;
285                 }
286                 // add the param
287                 if (params == null)
288                 {
289                     params = new ArrayList<UIParameter>();
290                 }
291                 params.add(param);
292             }
293         }
294         if (params == null)
295         {
296             params = Collections.emptyList();
297         }
298         return params;
299     }
300 
301 }