001: /*
002: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003: *
004: * "The contents of this file are subject to the Mozilla Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
011: * License for the specific language governing rights and limitations under
012: * the License.
013: *
014: * The Original Code is ICEfaces 1.5 open source software code, released
015: * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
016: * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
017: * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
018: *
019: * Contributor(s): _____________________.
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
023: * License), in which case the provisions of the LGPL License are
024: * applicable instead of those above. If you wish to allow use of your
025: * version of this file only under the terms of the LGPL License and not to
026: * allow others to use your version of this file under the MPL, indicate
027: * your decision by deleting the provisions above and replace them with
028: * the notice and other provisions required by the LGPL License. If you do
029: * not delete the provisions above, a recipient may use your version of
030: * this file under either the MPL or the LGPL License."
031: *
032: */
033:
034: package com.icesoft.faces.facelets;
035:
036: import com.icesoft.faces.application.D2DViewHandler;
037: import com.icesoft.faces.context.BridgeFacesContext;
038: import com.sun.facelets.Facelet;
039: import com.sun.facelets.FaceletFactory;
040: import com.sun.facelets.compiler.Compiler;
041: import com.sun.facelets.compiler.SAXCompiler;
042: import com.sun.facelets.compiler.TagLibraryConfig;
043: import com.sun.facelets.impl.DefaultFaceletFactory;
044: import com.sun.facelets.impl.DefaultResourceResolver;
045: import com.sun.facelets.impl.ResourceResolver;
046: import com.sun.facelets.tag.TagDecorator;
047: import com.sun.facelets.tag.TagLibrary;
048: import com.sun.facelets.tag.jsf.ComponentSupport;
049: import org.apache.commons.logging.Log;
050: import org.apache.commons.logging.LogFactory;
051:
052: import javax.faces.FacesException;
053: import javax.faces.application.ViewHandler;
054: import javax.faces.component.UIComponent;
055: import javax.faces.component.UIViewRoot;
056: import javax.faces.context.ExternalContext;
057: import javax.faces.context.FacesContext;
058: import javax.faces.context.ResponseWriter;
059: import javax.servlet.http.HttpServletResponse;
060: import java.io.FileNotFoundException;
061: import java.io.IOException;
062: import java.net.URL;
063: import java.util.HashMap;
064: import java.util.Iterator;
065: import java.util.ArrayList;
066:
067: /**
068: * <B>D2DViewHandler</B> is the ICEfaces Facelet ViewHandler implementation
069: *
070: * @see javax.faces.application.ViewHandler
071: */
072: public class D2DFaceletViewHandler extends D2DViewHandler {
073:
074: //Facelets parameter constants
075: public final static long DEFAULT_REFRESH_PERIOD = 2;
076: public final static String PARAM_REFRESH_PERIOD = "facelets.REFRESH_PERIOD";
077: public final static String PARAM_SKIP_COMMENTS = "facelets.SKIP_COMMENTS";
078: public final static String PARAM_VIEW_MAPPINGS = "facelets.VIEW_MAPPINGS";
079: public final static String PARAM_LIBRARIES = "facelets.LIBRARIES";
080: public final static String PARAM_DECORATORS = "facelets.DECORATORS";
081: public final static String PARAM_RESOURCE_RESOLVER = "facelets.RESOURCE_RESOLVER";
082:
083: // Log instance for this class
084: private static Log log = LogFactory
085: .getLog(D2DFaceletViewHandler.class);
086:
087: protected FaceletFactory faceletFactory;
088:
089: public D2DFaceletViewHandler() {
090: }
091:
092: public D2DFaceletViewHandler(ViewHandler delegate) {
093: super (delegate);
094: }
095:
096: protected void faceletInitialize() {
097: try {
098: if (faceletFactory == null) {
099: com.sun.facelets.compiler.Compiler c = new SAXCompiler();
100: initializeCompiler(c);
101: faceletFactory = createFaceletFactory(c);
102: }
103: } catch (Throwable t) {
104: if (log.isErrorEnabled()) {
105: log.error("Failed initializing facelet instance", t);
106: }
107: }
108: }
109:
110: protected void initializeCompiler(Compiler c) {
111: FacesContext ctx = FacesContext.getCurrentInstance();
112: ExternalContext ext = ctx.getExternalContext();
113:
114: // Use a TagLibrary to create UIXhtmlComponents from all xhtml Tags
115: c.addTagLibrary(new UIXhtmlTagLibrary());
116: c.addTagDecorator(new UIXhtmlTagDecorator());
117:
118: c.addTagDecorator(new JspTagDetector());
119:
120: // Load libraries
121: String paramLibraries = ext.getInitParameter(PARAM_LIBRARIES);
122: if (paramLibraries != null) {
123: paramLibraries = paramLibraries.trim();
124: String[] paramLibrariesArray = paramLibraries.split(";");
125: for (int i = 0; i < paramLibrariesArray.length; i++) {
126: try {
127: URL url = ext.getResource(paramLibrariesArray[i]);
128: if (url == null) {
129: throw new FileNotFoundException(
130: paramLibrariesArray[i]);
131: }
132: TagLibrary tagLibrary = TagLibraryConfig
133: .create(url);
134: c.addTagLibrary(tagLibrary);
135: if (log.isDebugEnabled()) {
136: log.debug("Loaded library: "
137: + paramLibrariesArray[i]);
138: }
139: } catch (IOException e) {
140: if (log.isWarnEnabled()) {
141: log.warn("Problem loading library: "
142: + paramLibrariesArray[i], e);
143: }
144: }
145: }
146: }
147:
148: // Load decorators
149: String paramDecorators = ext.getInitParameter(PARAM_DECORATORS);
150: if (paramDecorators != null) {
151: paramDecorators = paramDecorators.trim();
152: String[] paramDecoratorsArray = paramDecorators.split(";");
153: for (int i = 0; i < paramDecoratorsArray.length; i++) {
154: try {
155: Class tagDecoratorClass = Class
156: .forName(paramDecoratorsArray[i]);
157: TagDecorator tagDecorator = (TagDecorator) tagDecoratorClass
158: .newInstance();
159: c.addTagDecorator(tagDecorator);
160: if (log.isDebugEnabled()) {
161: log.debug("Loaded decorator: "
162: + paramDecoratorsArray[i]);
163: }
164: } catch (Exception e) {
165: if (log.isWarnEnabled()) {
166: log.warn("Problem loading decorator: "
167: + paramDecoratorsArray[i], e);
168: }
169: }
170: }
171: }
172:
173: // Load whether to skip comments or not. For our hierarchial
174: // UIComponent tree, we have to throw away most useless text nodes,
175: // so this is a bit redundant getting the parameter. But who knows,
176: // things might change later, so best to preserve this code.
177: String paramSkipComments = ext
178: .getInitParameter(PARAM_SKIP_COMMENTS);
179: // Default is true. I think this behaviour has changed over time
180: // is stock Facelets builds from 1.0.x to 1.1.x
181: if (paramSkipComments != null
182: && paramSkipComments.equals("false")) {
183: c.setTrimmingComments(false);
184: }
185:
186: // This has to be true, otherwise table or other container
187: // UIComponents will have text children, when they're
188: // expecting only real UIComponents
189: c.setTrimmingWhitespace(true);
190: c.setTrimmingComments(true);
191: c.setTrimmingXmlDeclarations(true);
192: c.setTrimmingDoctypeDeclarations(true);
193: }
194:
195: protected FaceletFactory createFaceletFactory(Compiler c) {
196: long refreshPeriod = DEFAULT_REFRESH_PERIOD;
197: FacesContext ctx = FacesContext.getCurrentInstance();
198: String paramRefreshPeriod = ctx.getExternalContext()
199: .getInitParameter(PARAM_REFRESH_PERIOD);
200: if (paramRefreshPeriod != null
201: && paramRefreshPeriod.length() > 0) {
202: try {
203: refreshPeriod = Long.parseLong(paramRefreshPeriod);
204: } catch (NumberFormatException nfe) {
205: if (log.isWarnEnabled()) {
206: log.warn("Problem parsing refresh period: "
207: + paramRefreshPeriod, nfe);
208: }
209: }
210: }
211:
212: ResourceResolver resourceResolver = null;
213: String paramResourceResolver = ctx.getExternalContext()
214: .getInitParameter(PARAM_RESOURCE_RESOLVER);
215: if (paramResourceResolver != null
216: && paramResourceResolver.length() > 0) {
217: try {
218: Class resourceResolverClass = Class.forName(
219: paramResourceResolver, true, Thread
220: .currentThread()
221: .getContextClassLoader());
222: resourceResolver = (ResourceResolver) resourceResolverClass
223: .newInstance();
224: } catch (Exception e) {
225: throw new FacesException(
226: "Problem initializing ResourceResolver: "
227: + paramResourceResolver, e);
228: }
229: }
230: if (resourceResolver == null)
231: resourceResolver = new DefaultResourceResolver();
232:
233: resourceResolver = preChainResourceResolver(resourceResolver);
234:
235: return new DefaultFaceletFactory(c, resourceResolver,
236: refreshPeriod);
237: }
238:
239: /**
240: * When D2DFaceletViewHandler is setting up the ResourceResolver for
241: * Facelets, it uses this callback to allow for any subclass to
242: * define a ResourceResolver of higher precedence, that would have
243: * first crack at resolving resources, and then could delegate to
244: * the standard mechanism.
245: *
246: * @param after The standard ResourceResolver that Facelets would ordinarily use
247: * @return Either the new pre-chained ResourceResolver if one is being added,
248: * or just the given one if nothing is being chained in
249: */
250: protected ResourceResolver preChainResourceResolver(
251: ResourceResolver after) {
252: return after;
253: }
254:
255: protected String getRenderedViewId(FacesContext context,
256: String actionId) {
257: ExternalContext extCtx = context.getExternalContext();
258: String viewId = actionId;
259: if (extCtx.getRequestPathInfo() == null) {
260: String facesSuffix = actionId.substring(actionId
261: .lastIndexOf('.'));
262: String viewSuffix = context.getExternalContext()
263: .getInitParameter(
264: ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
265: if (viewSuffix != null) {
266: viewId = actionId.replaceFirst(facesSuffix, viewSuffix);
267: } else {
268: if (log.isErrorEnabled()) {
269: log
270: .error("The "
271: + ViewHandler.DEFAULT_SUFFIX_PARAM_NAME
272: + " context parameter is not set in web.xml. "
273: + "Please define the filename extension used for "
274: + "your source JSF pages. Example:\n"
275: + "<context-param>\n"
276: + " <param-name>javax.faces.DEFAULT_SUFFIX</param-name>\n"
277: + " <param-value>.xhtml</param-value>\n"
278: + "</context-param>");
279: }
280: }
281: }
282: return viewId;
283: }
284:
285: protected void renderResponse(FacesContext facesContext)
286: throws IOException {
287: if (log.isTraceEnabled()) {
288: log.trace("renderResponse(FC)");
289: }
290: BridgeFacesContext context = (BridgeFacesContext) facesContext;
291: try {
292: clearSession(context);
293: ResponseWriter responseWriter = context
294: .createAndSetResponseWriter();
295:
296: UIViewRoot viewToRender = context.getViewRoot();
297: String renderedViewId = getRenderedViewId(context,
298: viewToRender.getViewId());
299: viewToRender.setViewId(renderedViewId);
300: if (viewToRender.getId() == null) {
301: viewToRender.setId(viewToRender.createUniqueId());
302: }
303:
304: ComponentSupport.removeTransient(viewToRender);
305:
306: // grab our FaceletFactory and create a Facelet
307: faceletInitialize();
308: Facelet f = null;
309: FaceletFactory.setInstance(faceletFactory);
310: try {
311: f = faceletFactory.getFacelet(viewToRender.getViewId());
312: } finally {
313: FaceletFactory.setInstance(null);
314: }
315:
316: // Populate UIViewRoot
317: f.apply(context, viewToRender);
318:
319: verifyUniqueComponentIds(context, viewToRender);
320:
321: // Uses D2DViewHandler logging
322: tracePrintComponentTree(context);
323:
324: responseWriter.startDocument();
325: renderResponse(context, viewToRender);
326: responseWriter.endDocument();
327: } catch (Exception e) {
328: if (log.isErrorEnabled()) {
329: log.error("Problem in renderResponse: "
330: + e.getMessage(), e);
331: }
332: throw new FacesException("Problem in renderResponse: "
333: + e.getMessage(), e);
334: }
335: }
336:
337: protected static void removeTransient(UIComponent c) {
338: UIComponent d, e;
339: if (c.getChildCount() > 0) {
340: for (Iterator itr = c.getChildren().iterator(); itr
341: .hasNext();) {
342: d = (UIComponent) itr.next();
343: if (d.getFacets().size() > 0) {
344: for (Iterator jtr = d.getFacets().values()
345: .iterator(); jtr.hasNext();) {
346: e = (UIComponent) jtr.next();
347: if (e.isTransient()) {
348: jtr.remove();
349: } else {
350: D2DFaceletViewHandler.removeTransient(e);
351: }
352: }
353: }
354: if (d.isTransient()) {
355: itr.remove();
356: } else {
357: D2DFaceletViewHandler.removeTransient(d);
358: }
359: }
360: }
361: if (c.getFacets().size() > 0) {
362: for (Iterator itr = c.getFacets().values().iterator(); itr
363: .hasNext();) {
364: d = (UIComponent) itr.next();
365: if (d.isTransient()) {
366: itr.remove();
367: } else {
368: D2DFaceletViewHandler.removeTransient(d);
369: }
370: }
371: }
372: }
373:
374: /**
375: * For performance reasons, when there aren't id collisions
376: * we want this to be as fast as possible. When there are
377: * collisions, then we'll take some extra time to do a second
378: * pass to provide more information
379: * It could have all been done in one pass, but that would penalise
380: * the typical case, where there are not duplicate ids
381: *
382: * @param comp UIComponent to recurse down through, searching for
383: * duplicate ids. Should be the UIViewRoot
384: */
385: protected static void verifyUniqueComponentIds(
386: FacesContext context, UIComponent comp) {
387: if (!log.isDebugEnabled())
388: return;
389:
390: HashMap ids = new HashMap(512);
391: ArrayList duplicateIds = new ArrayList(256);
392: quicklyDetectDuplicateComponentIds(comp, ids, duplicateIds);
393:
394: if (!duplicateIds.isEmpty()) {
395: HashMap duplicateIds2comps = new HashMap(512);
396: compileDuplicateComponentIds(comp, duplicateIds2comps,
397: duplicateIds);
398: reportDuplicateComponentIds(context, duplicateIds2comps,
399: duplicateIds);
400: }
401: }
402:
403: /**
404: * Do the least amount of work to find if there are any duplicate ids,
405: * with the assumption being that we won't typically find any
406: * We also mention any null ids, just to be safe
407: *
408: * @param comp UIComponent to recurse down through, searching for
409: * duplicate ids.
410: * @param ids HashMap<String id, String id> allows for detecting
411: * if an id has already been encountered or not
412: * @param duplicateIds ArrayList<String id> duplicate ids encountered
413: * as we recurse down
414: */
415: private static void quicklyDetectDuplicateComponentIds(
416: UIComponent comp, HashMap ids, ArrayList duplicateIds) {
417: String id = comp.getId();
418: if (id == null) {
419: log.debug("UIComponent has null id: " + comp);
420: } else {
421: if (ids.containsKey(id)) {
422: if (!duplicateIds.contains(id))
423: duplicateIds.add(id);
424: } else {
425: ids.put(id, id);
426: }
427: }
428: Iterator children = comp.getFacetsAndChildren();
429: while (children.hasNext()) {
430: UIComponent child = (UIComponent) children.next();
431: quicklyDetectDuplicateComponentIds(child, ids, duplicateIds);
432: }
433: }
434:
435: /**
436: * Make a list of every UIComponent that has a duplicate id, as found
437: * in the duplicateIds parameter.
438: *
439: * @param comp UIComponent to recurse down through, searching for
440: * duplicate ids.
441: * @param duplicateIds2comps HashMap< String id, ArrayList<UIComponent> >
442: * save every UIComponent with one of the
443: * duplicate ids, so we can list them all
444: * @param duplicateIds ArrayList<String id> duplicate ids encountered
445: * before
446: */
447: private static void compileDuplicateComponentIds(UIComponent comp,
448: HashMap duplicateIds2comps, ArrayList duplicateIds) {
449: String id = comp.getId();
450: if (id != null && duplicateIds.contains(id)) {
451: ArrayList duplicateComps = (ArrayList) duplicateIds2comps
452: .get(id);
453: if (duplicateComps == null) {
454: duplicateComps = new ArrayList();
455: duplicateIds2comps.put(id, duplicateComps);
456: }
457: duplicateComps.add(comp);
458: }
459: Iterator children = comp.getFacetsAndChildren();
460: while (children.hasNext()) {
461: UIComponent child = (UIComponent) children.next();
462: compileDuplicateComponentIds(child, duplicateIds2comps,
463: duplicateIds);
464: }
465: }
466:
467: /**
468: * Given a list of duplicate ids, and a mapping to each list of
469: * UIComponents sharing each id, log this info so that the user
470: * can most easily debug their application.
471: *
472: * @param duplicateIds2comps HashMap< String id, ArrayList<UIComponent> >
473: * for each duplicated id, the UIComponents
474: * sharing that id
475: * @param duplicateIds ArrayList<String id> duplicate ids encountered
476: * before
477: */
478: private static void reportDuplicateComponentIds(
479: FacesContext context, HashMap duplicateIds2comps,
480: ArrayList duplicateIds) {
481: // We don't simply iterate over duplicateIds2comps's keys, since that
482: // sequence is will probably not be very useful, whereas duplicateIds
483: // is sequenced in the order that we encountered the ids in the
484: // component tree, and thus in the source .xhtml file.
485:
486: int numDuplicateIds = duplicateIds.size();
487: log
488: .debug("There were "
489: + numDuplicateIds
490: + " ids found which are duplicates, meaning that multiple UIComponents share that same id");
491: for (int i = 0; i < numDuplicateIds; i++) {
492: String id = (String) duplicateIds.get(i);
493: ArrayList duplicateComps = (ArrayList) duplicateIds2comps
494: .get(id);
495: StringBuffer sb = new StringBuffer(512);
496: sb.append("Duplicate id: ");
497: sb.append(id);
498: sb.append(". Number of UIComponents sharing that id: ");
499: sb.append(Integer.toString(duplicateComps.size()));
500: sb.append('.');
501: for (int c = 0; c < duplicateComps.size(); c++) {
502: UIComponent comp = (UIComponent) duplicateComps.get(c);
503: sb.append("\n clientId: ");
504: sb.append(comp.getClientId(context));
505: if (comp.isTransient())
506: sb.append(". TRANSIENT");
507: sb.append(". component: ");
508: sb.append(comp.toString());
509: }
510: log.debug(sb.toString());
511: }
512: }
513: }
|