2009/05/20 - Apache Shale has been retired.
For more information, please explore the Attic.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.shale.clay.faces;
22
23 import java.io.IOException;
24 import java.io.StringWriter;
25 import java.util.Iterator;
26 import java.util.Locale;
27
28 import javax.faces.application.StateManager;
29 import javax.faces.application.ViewHandler;
30 import javax.faces.component.UIComponent;
31 import javax.faces.component.UIViewRoot;
32 import javax.faces.context.FacesContext;
33 import javax.faces.context.ResponseWriter;
34 import javax.faces.el.MethodBinding;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.shale.clay.component.Clay;
40 import org.apache.shale.clay.config.Globals;
41 import org.apache.shale.clay.config.beans.PageNotFoundException;
42 import org.apache.shale.clay.utils.JSFRuntimeTracker;
43
44 /***
45 * <p>This <code>ViewHandler</code> will handle full HTML template views using the
46 * {@link Clay} component as the single subtree under the view root. Views will
47 * be intercepted having a suffix matching the registered clay template suffix
48 * in the web deployment descriptor. The default suffixes are ".html" and "*.xml".
49 * All other view render requests that don't match the suffixes will be delegated to the
50 * original decorated view handler.</p>
51 */
52 public class ClayViewHandler extends ViewHandler {
53
54 /***
55 * <p>
56 * Commons logger utility class instance.
57 * </p>
58 */
59 private static Log log;
60 static {
61 log = LogFactory.getLog(ClayViewHandler.class);
62 }
63
64 /***
65 * <p>The decorated view handler.</p>
66 */
67 private ViewHandler original = null;
68
69 /***
70 * <p>The {@link Clay} component <code>id</code> property value.</p>
71 */
72 private static final String CLAY_VIEW_ID = "clayView";
73
74 /***
75 * <p>{@link Clay} <code>componentType</code> that is used to create a instance using
76 * the faces Application object.</p>
77 */
78 private static final String CLAY_COMPONENT_TYPE = "org.apache.shale.clay.component.Clay";
79
80 /***
81 * <p>Holds the suffixes used to identify a Clay HTML or XML full view template.</p>
82 */
83 private String[] suffixes = null;
84
85 /***
86 * <p>This is an overloaded constructor passing the <code>original</code>
87 * view handler.</p>
88 *
89 * @param original view handler
90 */
91 public ClayViewHandler(ViewHandler original) {
92 this.original = original;
93
94 if (log.isInfoEnabled()) {
95 log.info("Loading Clay View Handler");
96 }
97 }
98
99 /***
100 * @param context faces context
101 * @return locale calculated from the original handler
102 */
103 public Locale calculateLocale(FacesContext context) {
104 return original.calculateLocale(context);
105 }
106
107 /***
108 * <p>Application scope attribute under which the
109 * <code>ViewControllerMapper</code> for translating view identifiers
110 * to class names of the corresponding <code>ViewController</code>
111 * is stored.</p>
112 */
113 public static final String VIEW_MAPPER =
114 "org$apache$shale$view$VIEW_MAPPER";
115
116
117 /***
118 * @param context faces context
119 * @return render kit id calculated from the original handler
120 */
121 public String calculateRenderKitId(FacesContext context) {
122 return original.calculateRenderKitId(context);
123 }
124
125 /***
126 * <p>This method is overridden to check to see if the target view
127 * is a clay html or xml template. If it is, the view id is normalized
128 * before the original implementation is invoked.
129 * </p>
130 *
131 * @param context faces context
132 * @param viewId name of the page
133 * @return root of the component tree for the view id
134 */
135 public UIViewRoot createView(FacesContext context, String viewId) {
136 String id = viewId;
137 int index = indexOfClayTemplateSuffix(context, id);
138
139 if (index != -1) {
140 id = normalizeViewId(id, index);
141 }
142
143 return original.createView(context, id);
144 }
145
146 /***
147 * <p>Changes the suffix of the <code>viewId</code> to that of the
148 * suffix of a clay template defined by the <code>index</code> into
149 * the <code>suffixes</code> array.
150 *
151 * @param viewId name of the page
152 * @param index into the <code>suffixes</code> array
153 * @return viewId with a suffix matching the suffixes[index]
154 */
155 private String normalizeViewId(String viewId, int index) {
156
157 StringBuffer buff = new StringBuffer(viewId);
158 int i = buff.lastIndexOf(".");
159 if (i > -1) {
160 buff.setLength(i);
161 buff.append(suffixes[index]);
162 }
163
164 return buff.toString();
165
166 }
167
168 /***
169 * <p>
170 * If the <code>viewId</code> is suffixed with the Clay template suffix,
171 * rewrite the returned actionUrl with a clay suffix. The super
172 * implementation will assume ".jsp" or whatever the
173 * <code>javax.faces.DEFAULT_SUFFIX</code> is set to in the web deployment
174 * descriptor.
175 * </p>
176 *
177 * @param context faces context
178 * @param viewId name of the page
179 * @return action attribute of the UIForm component
180 */
181 public String getActionURL(FacesContext context, String viewId) {
182 String actionURL = original.getActionURL(context, viewId);
183 int index = indexOfClayTemplateSuffix(context, viewId);
184 if (index != -1) {
185 actionURL = normalizeViewId(actionURL, index);
186 } else if (context.getExternalContext().getRequestMap().get(Globals.CLAY_FULL_VIEW_RESTORE_IND) != null) {
187
188
189
190
191 int i = actionURL.lastIndexOf("/");
192 if (i > -1) {
193 StringBuffer tmp = new StringBuffer(actionURL);
194 tmp.setLength(i);
195 tmp.append(viewId);
196 actionURL = tmp.toString();
197 }
198
199 }
200
201 return actionURL;
202 }
203
204 /***
205 * @param context faces context
206 * @param path context root relative path
207 * @return full path to the resource
208 */
209 public String getResourceURL(FacesContext context, String path) {
210 return original.getResourceURL(context, path);
211 }
212
213 /***
214 * <p>This method looks to see if the target view identified by the viewId is a
215 * {@link org.apache.shale.clay.component.Clay} HTML or XML full view.
216 * This is determined by a value cached in request scope by the
217 * {@link ClayViewHandlerCommand} or the suffix of the viewId. If a match
218 * is found, the index position into the suffixes array is returned;
219 * Otherwise a -1 is returned.</p>
220 *
221 * @param context faces context
222 * @param viewId name of the page
223 * @return index into the suffixes array or -1 if not found
224 */
225 protected int indexOfClayTemplateSuffix(FacesContext context, String viewId) {
226
227 if (suffixes == null) {
228 suffixes = new String[2];
229
230 suffixes[0] = context.getExternalContext().getInitParameter(
231 Globals.CLAY_HTML_TEMPLATE_SUFFIX);
232 if (suffixes[0] == null) {
233 suffixes[0] = Globals.CLAY_DEFAULT_HTML_TEMPLATE_SUFFIX;
234 }
235
236 suffixes[1] = context.getExternalContext().getInitParameter(
237 Globals.CLAY_XML_TEMPLATE_SUFFIX);
238 if (suffixes[1] == null) {
239 suffixes[1] = Globals.CLAY_DEFAULT_XML_TEMPLATE_SUFFIX;
240 }
241
242 }
243
244
245 String originalSuffix = (String) context.getExternalContext()
246 .getRequestMap().get(Globals.CLAY_FULL_VIEW_SUFFIX);
247 if (originalSuffix != null) {
248 for (int i = 0; i < suffixes.length; i++) {
249 if (originalSuffix.equals(suffixes[i])) {
250 return i;
251 }
252 }
253 }
254
255
256 for (int i = 0; i < suffixes.length; i++) {
257 if (viewId.endsWith(suffixes[i])) {
258 return i;
259 }
260 }
261
262 return -1;
263 }
264
265 /***
266 * <p>The default view handler implementation will try to
267 * make the viewId end with ".jsp". If the viewId ends
268 * in the clay template suffix, use the state manager
269 * to restore the view.</p>
270 *
271 * @param context faces context
272 * @param viewId name of the page
273 * @return root of the page
274 */
275 public UIViewRoot restoreView(FacesContext context, String viewId) {
276 UIViewRoot view = null;
277
278 int index = indexOfClayTemplateSuffix(context, viewId);
279 if (index != -1) {
280
281 if (log.isDebugEnabled()) {
282 log.debug("Clay template restoreView for " + viewId);
283 }
284
285 StateManager stateManager = context.getApplication().getStateManager();
286 view = stateManager.restoreView(context, normalizeViewId(viewId, index), calculateRenderKitId(context));
287
288
289 if (view != null) {
290 context.getExternalContext().getRequestMap().put(Globals.CLAY_FULL_VIEW_SUFFIX, null);
291 context.getExternalContext().getRequestMap().put(Globals.CLAY_FULL_VIEW_RESTORE_IND, Boolean.TRUE);
292 }
293
294 } else {
295 view = original.restoreView(context, viewId);
296 }
297
298 return view;
299 }
300
301 /***
302 * <p>Invokes the original view handler's writeState.</p>
303 *
304 * @param context faces context
305 * @exception IOException serializing tree
306 */
307 public void writeState(FacesContext context) throws IOException {
308 original.writeState(context);
309 }
310
311 /***
312 * <p>HTML form markers for client side state saving for MyFaces and Sun RI
313 * implementations.</p>
314 */
315 protected static final String[] FORM_MARKERS = {
316 "com.sun.faces.saveStateFieldMarker",
317 "<!--@@JSF_FORM_STATE_MARKER@@-->",
318 "~com.sun.faces.saveStateFieldMarker~"};
319
320 /***
321 * <p>Returns an index into the <code>FORM_MAKKERS</code> array. The index will be used to
322 * get the form marker matching the JSF runtime. Only the myfaces and Sun RI are supported.
323 * The form marker is determined by trying to load the myfaces view handler. Next,
324 * the Sun RI 1.2 JSPVersionTracker is attempted to be loaded. The default
325 * is the marker for the Sun RI.</p>
326 *
327 * @return index into the FORM_MARKERS array
328 */
329 protected int indexOfFormMarker() {
330 int i = 0;
331 if (JSFRuntimeTracker.getJsfRuntime() == JSFRuntimeTracker.MYFACES_1_1) {
332 i = 1;
333 } else if (JSFRuntimeTracker.getJsfRuntime() == JSFRuntimeTracker.RI_1_2) {
334 i = 2;
335 }
336 return i;
337 }
338
339 /***
340 * <p>The <code>viewId</code> is check to see if it ends with the
341 * same suffix as the full HTML or XML views. This match might be performed
342 * by the {@link ClayViewHandlerCommand} when using the myfaces jsf implementation.
343 * If a match is not found, control is passed to the decorated view handler.
344 * Otherwise, a {@link org.apache.shale.clay.component.Clay} component is
345 * instantiated as a single subtree under the
346 * view root. The component's <code>id</code> property is set with a constant,
347 * <code>CLAY_VIEW_ID</code>. The <code>jsfid</code>
348 * property is set to the <code>viewId</code>. The <code>managedBeanName</code> property
349 * is set with the Shale <code>ViewControllerMapper</code>. A <code>ResponseWriter</code>
350 * is created and rendering is invoked on the component. This differs from the base implementation.
351 * The base implementation would dispatch to a JSP that would assemble the component tree
352 * and invoke rendering to the response writer.</p>
353 *
354 * @param context faces context
355 * @param view root of the component tree
356 * @exception IOException response writer
357 */
358 public void renderView(FacesContext context, UIViewRoot view)
359 throws IOException {
360
361 int index = indexOfClayTemplateSuffix(context, view.getViewId());
362
363 if (index != -1) {
364
365 if (log.isDebugEnabled()) {
366 log.debug("Clay template renderView for " + view.getViewId());
367 }
368
369
370 UIComponent component = view.findComponent(CLAY_VIEW_ID);
371 if (component == null) {
372
373 String viewId = normalizeViewId(view.getViewId(), index);
374 view.setViewId(viewId);
375 component = context.getApplication().createComponent(CLAY_COMPONENT_TYPE);
376 ((Clay) component).setId(CLAY_VIEW_ID);
377 ((Clay) component).setJsfid(viewId);
378 ((Clay) component).setManagedBeanName(getManagedBeanName(context, viewId));
379 view.getChildren().add(view.getChildren().size(), component);
380 }
381
382
383 HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
384
385 if (response.getContentType() == null) {
386 response.setContentType("text/html");
387 }
388
389 response.setLocale(context.getViewRoot().getLocale());
390
391
392 StringWriter writer = new StringWriter();
393
394 ResponseWriter buffResponsewriter = context.getRenderKit()
395 .createResponseWriter(writer, null, response.getCharacterEncoding());
396
397 context.setResponseWriter(buffResponsewriter);
398
399 buffResponsewriter.startDocument();
400
401 try {
402 recursiveRender(view, context);
403 } catch (PageNotFoundException e) {
404
405 if (e.getResource().equals(view.getViewId())) {
406 response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getResource());
407 context.responseComplete();
408 return;
409 }
410
411 throw e;
412 }
413
414 buffResponsewriter.endDocument();
415
416
417 StateManager stateManager = context.getApplication().getStateManager();
418 StateManager.SerializedView serializedview = stateManager.saveSerializedView(context);
419
420 ResponseWriter responsewriter = context.getRenderKit()
421 .createResponseWriter(response.getWriter(), null, response.getCharacterEncoding());
422
423 context.setResponseWriter(responsewriter);
424
425 StringBuffer buff = writer.getBuffer();
426 if (stateManager.isSavingStateInClient(context)) {
427 int curPos = 0;
428 int fndPos = 0;
429 int frmMkrIdx = indexOfFormMarker();
430
431
432 do {
433 fndPos = buff.indexOf(FORM_MARKERS[frmMkrIdx], curPos);
434 if (fndPos > -1) {
435 responsewriter.write(buff.substring(curPos, fndPos));
436 stateManager.writeState(context, serializedview);
437 curPos = fndPos + FORM_MARKERS[frmMkrIdx].length();
438 } else {
439 responsewriter.write(buff.substring(curPos));
440 }
441 } while(curPos < buff.length() && fndPos > -1);
442
443 } else {
444
445 responsewriter.write(buff.toString());
446 }
447
448 } else {
449
450 original.renderView(context, view);
451 }
452 }
453
454 /***
455 * <p>
456 * Recursively invokes the rendering of the sub component tree.
457 * </p>
458 *
459 * @param child component to invoke renderering on
460 * @param context faces context
461 * @exception IOException writing markup
462 */
463 protected void recursiveRender(UIComponent child,
464 FacesContext context) throws IOException {
465
466 if (!child.getRendersChildren()) {
467 child.encodeBegin(context);
468 Iterator ci = child.getChildren().iterator();
469 while (ci.hasNext()) {
470 UIComponent c = (UIComponent) ci.next();
471 c.encodeBegin(context);
472
473 if (!c.getRendersChildren()) {
474 recursiveRender(c, context);
475 } else {
476 c.encodeChildren(context);
477 }
478
479 c.encodeEnd(context);
480 c = null;
481 }
482 child.encodeEnd(context);
483 } else {
484
485 child.encodeBegin(context);
486 child.encodeChildren(context);
487 child.encodeEnd(context);
488 }
489 }
490
491 /***
492 * <p>Returns the "@managed-bean-name" the view controller is registered under. The
493 * assumed mapping will be the same as in core Shale.</p>
494 *
495 * @param context faces context
496 * @param viewId name of the page
497 * @return default managed bean name associated with the view
498 */
499 protected String getManagedBeanName(FacesContext context, String viewId) {
500 String managedBeanName = null;
501
502 Object mapper = context.getApplication().getVariableResolver()
503 .resolveVariable(context, VIEW_MAPPER);
504
505 if (mapper != null) {
506 StringBuffer el = new StringBuffer();
507 el.append("#{").append(VIEW_MAPPER).append(".mapViewId").append("}");
508 MethodBinding mb = context.getApplication()
509 .createMethodBinding(el.toString(), new Class[] {String.class});
510 managedBeanName = (String) mb.invoke(context, new Object[] {viewId});
511 }
512
513 return managedBeanName;
514 }
515
516 }