001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package com.sun.rave.web.ui.renderer.template.xml;
042:
043: import com.sun.rave.web.ui.renderer.template.LayoutDefinitionManager;
044: import com.sun.rave.web.ui.component.util.descriptors.LayoutDefinition;
045:
046: import java.beans.Beans;
047: import java.io.File;
048: import java.io.InputStream;
049: import java.io.IOException;
050: import java.io.OutputStreamWriter;
051: import java.io.PrintWriter;
052: import java.io.UnsupportedEncodingException;
053: import java.lang.reflect.InvocationTargetException;
054: import java.lang.reflect.Method;
055: import java.net.URL;
056: import java.util.HashMap;
057: import java.util.Map;
058:
059: import javax.faces.context.FacesContext;
060:
061: import org.xml.sax.EntityResolver;
062: import org.xml.sax.ErrorHandler;
063: import org.xml.sax.InputSource;
064: import org.xml.sax.SAXException;
065: import org.xml.sax.SAXParseException;
066:
067: /**
068: * <P> This class is a concrete implmentation of the abstract class
069: * {@link LayoutDefinitionManager}. It obtains {@link LayoutDefinition}
070: * objects by interpreting the <code>key</code> passed to
071: * {@link #getLayoutDefinition(String)} as a path to an XML file
072: * describing the {@link LayoutDefinition}. It will first attempt to
073: * resolve this path from the document root of the ServletContext or
074: * PortletCotnext. If that fails, it will attempt to use the Classloader
075: * to resolve it.</P>
076: *
077: * <P> Locating the dtd for the XML file is done in a similar manner. It
078: * will first attempt to locate the dtd relative to the ServletContext
079: * (or PortletContext) root. If that fails it will attempt to use the
080: * ClassLoader to resolve it. Optionally a different EntityResolver may
081: * be supplied to provide a custom way of locating the dtd, this is done
082: * via {@link #setEntityResolver}.</P>
083: *
084: * <P> This class is a singleton. This means modifications to this class
085: * effect all threads using this class. This includes setting
086: * EntityResolvers and ErrorHandlers. These values only need to be set
087: * once to remain in effect as long as the JVM is running.</P>
088: *
089: * @author Ken Paulsen (ken.paulsen@sun.com)
090: */
091: public class XMLLayoutDefinitionManager extends LayoutDefinitionManager {
092:
093: /**
094: * Constructor.
095: */
096: protected XMLLayoutDefinitionManager() {
097: super ();
098:
099: // Set the default XMLError Handler
100: try {
101: setErrorHandler(new XMLErrorHandler(new PrintWriter(
102: new OutputStreamWriter(System.err, "UTF-8"), true)));
103: } catch (UnsupportedEncodingException ex) {
104: throw new RuntimeException(ex);
105: }
106:
107: // Set the default EntityResolver
108: setEntityResolver(new ClassLoaderEntityResolver());
109: }
110:
111: /**
112: * This method returns an instance of this LayoutDefinitionManager. The
113: * object returned is a singleton (only 1 instance will be created per
114: * JVM).
115: *
116: * @return <code>XMLLayoutDefinitionManager</code> instance
117: */
118: public static LayoutDefinitionManager getInstance() {
119: if (instance == null) {
120: instance = new XMLLayoutDefinitionManager();
121: }
122: return instance;
123: }
124:
125: /**
126: * <p> This method is responsible for finding the requested
127: * {@link LayoutDefinition} for the given <code>key</code>.</p>
128: *
129: * @param key Key identifying the desired {@link LayoutDefinition}
130: *
131: * @return The requested {@link LayoutDefinition}.
132: */
133: public LayoutDefinition getLayoutDefinition(String key) {
134: LayoutDefinition ld = (LayoutDefinition) layouts.get(key);
135: if (DEBUG) {
136: // Disable caching
137: ld = null;
138: }
139: if (ld == null) {
140: String baseURI = getBaseURI();
141:
142: // Attempt to load the LayoutDefinition from the CLASSPATH...
143: // Check for XML file in docroot. Use docroot for the baseURI,
144: // and get the full path to the xml file
145: URL ldURL = null;
146: Object ctx = FacesContext.getCurrentInstance()
147: .getExternalContext().getContext();
148: String url;
149:
150: // The following should work w/ a ServletContext or PortletContext
151: Method method = null;
152: try {
153: method = ctx.getClass().getMethod("getRealPath",
154: GET_REAL_PATH_ARGS);
155: } catch (NoSuchMethodException ex) {
156: throw new RuntimeException(ex);
157: }
158: try {
159: if (baseURI == null) {
160: baseURI = "file:///"
161: + method.invoke(ctx, new Object[] { "/" });
162: }
163: url = (String) method.invoke(ctx, new Object[] { key });
164: } catch (IllegalAccessException ex) {
165: throw new RuntimeException(ex);
166: } catch (InvocationTargetException ex) {
167: throw new RuntimeException(ex);
168: }
169:
170: // Verify file exists...
171: if (!(new File(url).canRead())) {
172: url = null;
173: }
174:
175: // Create a URL to the xml file
176: if (url != null) {
177: try {
178: ldURL = new URL("file:///" + url);
179: } catch (Exception ex) {
180: throw new RuntimeException(
181: "Unable to create URL: 'file:///" + url
182: + "' while attempting to locate '"
183: + key + "'", ex);
184: }
185: }
186:
187: if (ldURL == null) {
188: // Check the classpath for the xml file
189: ldURL = getClass().getClassLoader().getResource(key);
190: if (ldURL == null) {
191: int idx = key.indexOf('/');
192: if (idx > -1) {
193: ldURL = getClass().getClassLoader()
194: .getResource(key.substring(idx + 1));
195: }
196: }
197: if (Beans.isDesignTime()) {
198: String path = ldURL.getPath();
199: int i = path.indexOf("/" + key);
200: if (i > -1) {
201: baseURI = path.substring(0, i);
202: }
203: }
204: }
205:
206: // Make sure we found the url
207: if (ldURL == null) {
208: throw new RuntimeException("Unable to locate '" + key
209: + "'");
210: }
211:
212: if (baseURI == null && Beans.isDesignTime()) {
213: String path = ldURL.getPath();
214: int i = path.indexOf("/" + key);
215: if (i > -1) {
216: baseURI = path.substring(0, i);
217: }
218: }
219:
220: // Read the XML file
221: try {
222: ld = new XMLLayoutDefinitionReader(ldURL,
223: getEntityResolver(), getErrorHandler(), baseURI)
224: .read();
225: } catch (IOException ex) {
226: throw new RuntimeException(ex);
227: }
228:
229: // Cache the LayoutDefinition
230: synchronized (layouts) {
231: layouts.put(key, ld);
232: }
233: }
234: return ld;
235: }
236:
237: /**
238: * This returns the LDM's entity resolver, null if not set.
239: *
240: * @return EntityResolver
241: */
242: public EntityResolver getEntityResolver() {
243: return (EntityResolver) getAttribute(ENTITY_RESOLVER);
244: }
245:
246: /**
247: * This method sets the LDM's entity resolver.
248: *
249: * @param entityResolver The EntityResolver to use.
250: */
251: public void setEntityResolver(EntityResolver entityResolver) {
252: setAttribute(ENTITY_RESOLVER, entityResolver);
253: }
254:
255: /**
256: * This returns the LDM's XML parser ErrorHandler, null if not set.
257: *
258: * @return ErrorHandler
259: */
260: public ErrorHandler getErrorHandler() {
261: return (ErrorHandler) getAttribute(ERROR_HANDLER);
262: }
263:
264: /**
265: * This method sets the LDM's ErrorHandler.
266: *
267: * @param errorHandler The ErrorHandler to use.
268: */
269: public void setErrorHandler(ErrorHandler errorHandler) {
270: setAttribute(ERROR_HANDLER, errorHandler);
271: }
272:
273: /**
274: * This returns the LDM's XML parser baseURI which will be used to
275: * resolve relative URI's, null if not set.
276: *
277: * @return The base URI as a String
278: */
279: public String getBaseURI() {
280: return (String) getAttribute(BASE_URI);
281: }
282:
283: /**
284: * This method sets the LDM's BaseURI.
285: *
286: * @param baseURI The BaseURI to use.
287: */
288: public void setBaseURI(String baseURI) {
289: setAttribute(BASE_URI, baseURI);
290: }
291:
292: /**
293: * This class handles XML parser errors.
294: */
295: private static class XMLErrorHandler implements ErrorHandler {
296: /** Error handler output goes here */
297: private PrintWriter out;
298:
299: XMLErrorHandler(PrintWriter outWriter) {
300: this .out = outWriter;
301: }
302:
303: /**
304: * Returns a string describing parse exception details
305: */
306: private String getParseExceptionInfo(SAXParseException spe) {
307: String systemId = spe.getSystemId();
308: if (systemId == null) {
309: systemId = "null";
310: }
311: String info = "URI=" + systemId + " Line="
312: + spe.getLineNumber() + ": " + spe.getMessage();
313: return info;
314: }
315:
316: // The following methods are standard SAX ErrorHandler methods.
317: // See SAX documentation for more info.
318:
319: public void warning(SAXParseException spe) throws SAXException {
320: out.println("Warning: " + getParseExceptionInfo(spe));
321: }
322:
323: public void error(SAXParseException spe) throws SAXException {
324: String message = "Error: " + getParseExceptionInfo(spe);
325: throw new SAXException(message, spe);
326: }
327:
328: public void fatalError(SAXParseException spe)
329: throws SAXException {
330: String message = "Fatal Error: "
331: + getParseExceptionInfo(spe);
332: throw new SAXException(message, spe);
333: }
334: }
335:
336: /**
337: * <P> This entity reolver looks for xml & dtd files that are
338: * included as SYSTEM entities in the java class-path. If the file is
339: * not found in the class path the resolver returns null, allowing
340: * default mechanism to search for the file on the file system.</P>
341: */
342: public static class ClassLoaderEntityResolver implements
343: EntityResolver {
344:
345: /**
346: * Constructor.
347: */
348: public ClassLoaderEntityResolver() {
349: super ();
350: }
351:
352: /**
353: * <P> This method attempts resolves the <code>systemId</code>. The
354: * systemId must end in <code>.dtd</code> or <code>.xml</code>
355: * for this method to do anything. If it does, it will attempt
356: * to resolve the value via the classpath. If it is unable to
357: * locate it in the classpath, it will return null to single
358: * default behavior (locate the file on the filesystem).</P>
359: *
360: * <P> The dtd in the LayoutDefinition XML file should be specified
361: * as follows:</P>
362: *
363: * <P><code><!DOCTYPE layoutDefinition SYSTEM
364: * "/layout/layout.dtd"></code></P>
365: *
366: * @param publicId Not used.
367: * @param systemId The id to resolve.
368: *
369: * @return The InputSource (null if it should use default behavior)
370: */
371: public InputSource resolveEntity(String publicId,
372: String systemId) {
373: InputSource source = null;
374: if ((systemId != null)
375: && (systemId.endsWith(".xml") || systemId
376: .endsWith(".dtd"))) {
377: if (systemId.startsWith("file:")) {
378: int i = 5;
379: while (systemId.charAt(i) == '/') {
380: i++;
381: }
382: systemId = systemId.substring(i);
383: }
384: InputStream resourceStream = getClass()
385: .getClassLoader().getResourceAsStream(systemId);
386: if (resourceStream != null) {
387: source = new InputSource(resourceStream);
388: }
389: }
390:
391: // Return the InputSource (if null, it will use default behavior)
392: return source;
393: }
394: }
395:
396: /**
397: * Static map of LayoutDefinitionManagers. Normally this will only
398: * contain the default LayoutManager.
399: */
400: private static Map layouts = new HashMap();
401:
402: /**
403: * This is used to ensure that only 1 instance of this class is created
404: * (per JVM).
405: */
406: private static LayoutDefinitionManager instance = null;
407:
408: /**
409: *
410: */
411: private static final Class[] GET_REAL_PATH_ARGS = new Class[] { String.class };
412:
413: /**
414: *
415: */
416: private static final int FILE_PREFIX_LENGTH = "file:///".length();
417:
418: /**
419: * This is an attribute key which can be used to provide an
420: * EntityResolver to the XML parser.
421: */
422: public static final String ENTITY_RESOLVER = "entityResolver";
423:
424: /**
425: *
426: */
427: public static final String ERROR_HANDLER = "errorHandler";
428:
429: /**
430: *
431: */
432: public static final String BASE_URI = "baseURI";
433:
434: public static boolean DEBUG = Boolean
435: .getBoolean("com.sun.rave.web.ui.DEBUG");
436: }
|