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.orchestra.urlParamNav;
20  
21  import javax.faces.FacesException;
22  import javax.faces.application.ViewHandler;
23  import javax.faces.component.UIViewRoot;
24  import javax.faces.context.FacesContext;
25  import javax.faces.el.ValueBinding;
26  
27  import org.apache.myfaces.orchestra.lib.OrchestraException;
28  
29  import java.io.IOException;
30  import java.lang.reflect.Method;
31  import java.util.Locale;
32  
33  /**
34   * Allow the to-view-id URL in a faces-config navigation case to include
35   * query parameters and EL expressions.
36   * <p>
37   * This class plays a few tricks to hide from the real NavigationHandler
38   * and ViewHandler classes the fact that a URL contains non-standard data.
39   * <p>
40   * This class also plays a few reflection-based tricks so that the code can
41   * be compiled against JSF1.1, and work with both JSF1.1 and JSF1.2. The
42   * code is a little fragile and will probably need to be updated to work
43   * correctly with JSF2.0, but that is the fault of the JSF spec.
44   */
45  public class UrlParameterViewHandler extends ViewHandler
46  {
47      private static final Method CALC_CHAR_ENC_METHOD;
48      private static final Method INIT_VIEW_METHOD;
49  
50      private final ViewHandler original;
51  
52      /**
53       * Static initialization block.
54       */
55      static
56      {
57          CALC_CHAR_ENC_METHOD = getMethodOpt(ViewHandler.class,
58                  "calculateCharacterEncoding",
59                  new Class[] {FacesContext.class});
60  
61          INIT_VIEW_METHOD = getMethodOpt(ViewHandler.class,
62                  "initView",
63                  new Class[] {FacesContext.class});
64      }
65  
66      /**
67       * If the specified class has a method with the specified name and params, return
68       * it else return null.
69       */
70      private static Method getMethodOpt(Class clazz, String methodName, Class[] args)
71      {
72          try
73          {
74              return clazz.getMethod(methodName, args);
75          }
76          catch(NoSuchMethodException e)
77          {
78              return null;
79          }
80      }
81  
82      /**
83       * Constructor.
84       */
85      public UrlParameterViewHandler(final ViewHandler original)
86      {
87          this.original = original;
88      }
89  
90      /**
91       * Delegate to wrapped instance. 
92       * <p>
93       * This method was added in JSF1.2. We must therefore use reflection
94       * to invoke the method on the wrapped instance. Note that this method
95       * is never invoked unless this is a JSF1.2 environment.
96       * 
97       * @since 1.3
98       */
99      public java.lang.String calculateCharacterEncoding(FacesContext context)
100     {
101         try
102         {
103             Object ret = CALC_CHAR_ENC_METHOD.invoke(original, new Object[] {context});
104             return (String) ret;
105         }
106         catch(Exception e)
107         {
108             throw new OrchestraException("Unable to invoke calculateCharacterEncoding on wrapped ViewHandler");
109         }
110     }
111 
112     /**
113      * Delegate to wrapped instance. 
114      * <p>
115      * This method was added in JSF1.2. We must therefore use reflection
116      * to invoke the method on the wrapped instance. Note that this method
117      * is never invoked unless this is a JSF1.2 environment. 
118      * 
119      * @since 1.3
120      */
121     public void initView(FacesContext context)
122     throws FacesException
123     {
124         try
125         {
126             INIT_VIEW_METHOD.invoke(original, new Object[] {context});
127         }
128         catch(Exception e)
129         {
130             throw new OrchestraException("Unable to invoke initView on wrapped ViewHandler");
131         }
132     }
133 
134     public Locale calculateLocale(FacesContext context)
135     {
136         return original.calculateLocale(context);
137     }
138 
139     public String calculateRenderKitId(FacesContext context)
140     {
141         return original.calculateRenderKitId(context);
142     }
143 
144     public UIViewRoot createView(FacesContext context, String viewId)
145     {
146         return original.createView(context, viewId);
147     }
148 
149     public String getActionURL(FacesContext context, String viewId)
150     {
151         if (viewId != null)
152         {
153             // Expand any EL expression in the URL.
154             //
155             // This handles a call from a NavigationHandler which is processing a redirect
156             // navigation case. A NavigationHandler must call the following in order:
157             //  * ViewHandler.getActionURL,
158             //  * ExternalContext.encodeActionURL
159             //  * ExternalContext.redirect
160             //
161             // Orchestra hooks into ExternalContext.encodeActionURL to trigger the
162             // RequestParameterProviderManager which then inserts various query params
163             // into the URL.
164             //
165             // So here, ensure that any EL expressions are expanded before the
166             // RequestParameterProviderManager is invoked. An alternative would be for
167             // the RequestParameterProviderManager to do the encoding, but at the current
168             // time that class is not JSF-dependent in any way, so calling JSF expression
169             // expansion from there is not possible.
170             //
171             // Note that this method is also called from a Form component when rendering
172             // its 'action' attribute. This code therefore has the side-effect of
173             // permitting EL expressions in a form's action. This is not particularly
174             // useful, however, as they are expected to have been expanded before this
175             // method is invoked.. 
176             viewId = expandExpressions(context, viewId);
177 
178             // Hide query parameters from the standard ViewHandlerImpl. The standard
179             // implementation of ViewHandlerImpl.getActionUrl method does not handle
180             // query params well. So strip them off, invoke the processing, then reattach
181             // them afterwards.
182             int pos = viewId.indexOf('?');
183             if (pos > -1)
184             {
185                 String realViewId = viewId.substring(0, pos);
186                 String params = viewId.substring(pos);
187 
188                 return original.getActionURL(context, realViewId) + params;
189             }
190         }
191         return original.getActionURL(context, viewId);
192     }
193 
194     public String getResourceURL(FacesContext context, String path)
195     {
196         return original.getResourceURL(context, path);
197     }
198 
199     public void renderView(FacesContext context, UIViewRoot viewToRender)
200         throws IOException, FacesException
201     {
202         original.renderView(context, viewToRender);
203     }
204 
205     public UIViewRoot restoreView(FacesContext context, String viewId)
206     {
207         return original.restoreView(context, viewId);
208     }
209 
210     public void writeState(FacesContext context)
211         throws IOException
212     {
213         original.writeState(context);
214     }
215 
216     private static String expandExpressions(FacesContext context, String url)
217     {
218         int pos = url.indexOf("#{");
219         if (pos > -1 && url.indexOf("}", pos) > -1)
220         {
221             // There is at least one EL expression, so evaluate the whole url string.
222             // Note that something like "aaa#{foo}bbb#{bar}ccc" is fine; both the
223             // el expressions will get replaced.
224             ValueBinding vb = context.getApplication().createValueBinding(url);
225             return (String) vb.getValue(context);
226         }
227 
228         return url;
229     }
230 }