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 }