001: /*
002: * Copyright 1999-2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: /*
017: * $Id: OutputPropertiesFactory.java,v 1.11 2005/06/01 19:17:08 minchau Exp $
018: */
019: package org.apache.xml.serializer;
020:
021: import java.io.BufferedInputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.security.AccessController;
025: import java.security.PrivilegedAction;
026: import java.util.Enumeration;
027: import java.util.Properties;
028:
029: import javax.xml.transform.OutputKeys;
030:
031: import org.apache.xml.serializer.utils.MsgKey;
032: import org.apache.xml.serializer.utils.Utils;
033: import org.apache.xml.serializer.utils.WrappedRuntimeException;
034:
035: /**
036: * This class is a factory to generate a set of default properties
037: * of key/value pairs that are used to create a serializer through the
038: * factory {@link SerializerFactory SerilizerFactory}.
039: * The properties generated by this factory
040: * may be modified to non-default values before the SerializerFactory is used to
041: * create a Serializer.
042: * <p>
043: * The given output types supported are "xml", "text", and "html".
044: * These type strings can be obtained from the
045: * {@link Method Method} class in this package.
046: * <p>
047: * Other constants defined in this class are the non-standard property keys
048: * that can be used to set non-standard property values on a java.util.Properties object
049: * that is used to create or configure a serializer. Here are the non-standard keys:
050: * <ul>
051: * <li> <b>S_KEY_INDENT_AMOUNT </b> -
052: * The non-standard property key to use to set the indentation amount.
053: * The "indent" key needs to have a value of "yes", and this
054: * properties value is a the number of whitespaces to indent by per
055: * indentation level.
056: *
057: * <li> <b>S_KEY_CONTENT_HANDLER </b> -
058: * This non-standard property key is used to set the name of the fully qualified
059: * Java class that implements the ContentHandler interface.
060: * The output of the serializer will be SAX events sent to this an
061: * object of this class.
062: *
063: * <li> <b>S_KEY_ENTITIES </b> -
064: * This non-standard property key is used to specify the name of the property file
065: * that specifies character to entity reference mappings. A line in such a
066: * file is has the name of the entity and the numeric (base 10) value
067: * of the corresponding character, like this one: <br> quot=34 <br>
068: *
069: * <li> <b>S_USE_URL_ESCAPING </b> -
070: * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
071: * use %xx escaping.
072: *
073: * <li> <b>S_OMIT_META_TAG </b> -
074: * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
075: * otherwise be supplied.
076: * </ul>
077: *
078: * @see SerializerFactory
079: * @see Method
080: * @see Serializer
081: */
082: public final class OutputPropertiesFactory {
083: /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace
084: *(http://xml.apache.org/xalan) predefined to signify Xalan's
085: * built-in XSLT Extensions. When used in stylesheets, this is often
086: * bound to the "xalan:" prefix.
087: */
088: private static final String S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";
089:
090: /**
091: * The old built-in extension url. It is still supported for
092: * backward compatibility.
093: */
094: private static final String S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt";
095:
096: //************************************************************
097: //* PUBLIC CONSTANTS
098: //************************************************************
099: /**
100: * This is not a public API.
101: * This is the built-in extensions namespace,
102: * reexpressed in {namespaceURI} syntax
103: * suitable for prepending to a localname to produce a "universal
104: * name".
105: */
106: public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL = "{"
107: + S_BUILTIN_EXTENSIONS_URL + "}";
108:
109: // Some special Xalan keys.
110:
111: /**
112: * The non-standard property key to use to set the
113: * number of whitepaces to indent by, per indentation level,
114: * if indent="yes".
115: */
116: public static final String S_KEY_INDENT_AMOUNT = S_BUILTIN_EXTENSIONS_UNIVERSAL
117: + "indent-amount";
118:
119: /**
120: * The non-standard property key to use to set the
121: * number of whitepaces to indent by, per indentation level,
122: * if indent="yes".
123: */
124: public static final String S_KEY_LINE_SEPARATOR = S_BUILTIN_EXTENSIONS_UNIVERSAL
125: + "line-separator";
126:
127: /** This non-standard property key is used to set the name of the fully qualified
128: * Java class that implements the ContentHandler interface.
129: * Fully qualified name of class with a default constructor that
130: * implements the ContentHandler interface, where the result tree events
131: * will be sent to.
132: */
133:
134: public static final String S_KEY_CONTENT_HANDLER = S_BUILTIN_EXTENSIONS_UNIVERSAL
135: + "content-handler";
136:
137: /**
138: * This non-standard property key is used to specify the name of the property file
139: * that specifies character to entity reference mappings.
140: */
141: public static final String S_KEY_ENTITIES = S_BUILTIN_EXTENSIONS_UNIVERSAL
142: + "entities";
143:
144: /**
145: * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
146: * use %xx escaping. */
147: public static final String S_USE_URL_ESCAPING = S_BUILTIN_EXTENSIONS_UNIVERSAL
148: + "use-url-escaping";
149:
150: /**
151: * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
152: * otherwise be supplied.
153: */
154: public static final String S_OMIT_META_TAG = S_BUILTIN_EXTENSIONS_UNIVERSAL
155: + "omit-meta-tag";
156:
157: /**
158: * The old built-in extension namespace, this is not a public API.
159: */
160: public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL = "{"
161: + S_BUILTIN_OLD_EXTENSIONS_URL + "}";
162:
163: /**
164: * This is not a public API, it is only public because it is used
165: * by outside of this package,
166: * it is the length of the old built-in extension namespace.
167: */
168: public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN = S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL
169: .length();
170:
171: //************************************************************
172: //* PRIVATE CONSTANTS
173: //************************************************************
174:
175: private static final String S_XSLT_PREFIX = "xslt.output.";
176: private static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
177: private static final String S_XALAN_PREFIX = "org.apache.xslt.";
178: private static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX
179: .length();
180:
181: /** Synchronization object for lazy initialization of the above tables. */
182: private static Integer m_synch_object = new Integer(1);
183:
184: /** the directory in which the various method property files are located */
185: private static final String PROP_DIR = "org/apache/xml/serializer/";
186: /** property file for default XML properties */
187: private static final String PROP_FILE_XML = "output_xml.properties";
188: /** property file for default TEXT properties */
189: private static final String PROP_FILE_TEXT = "output_text.properties";
190: /** property file for default HTML properties */
191: private static final String PROP_FILE_HTML = "output_html.properties";
192: /** property file for default UNKNOWN (Either XML or HTML, to be determined later) properties */
193: private static final String PROP_FILE_UNKNOWN = "output_unknown.properties";
194:
195: //************************************************************
196: //* PRIVATE STATIC FIELDS
197: //************************************************************
198:
199: /** The default properties of all output files. */
200: private static Properties m_xml_properties = null;
201:
202: /** The default properties when method="html". */
203: private static Properties m_html_properties = null;
204:
205: /** The default properties when method="text". */
206: private static Properties m_text_properties = null;
207:
208: /** The properties when method="" for the "unknown" wrapper */
209: private static Properties m_unknown_properties = null;
210:
211: private static final Class ACCESS_CONTROLLER_CLASS = findAccessControllerClass();
212:
213: private static Class findAccessControllerClass() {
214: try {
215: // This Class was introduced in JDK 1.2. With the re-architecture of
216: // security mechanism ( starting in JDK 1.2 ), we have option of
217: // giving privileges to certain part of code using doPrivileged block.
218: // In JDK1.1.X applications won't be having security manager and if
219: // there is security manager ( in applets ), code need to be signed
220: // and trusted for having access to resources.
221:
222: return Class.forName("java.security.AccessController");
223: } catch (Exception e) {
224: //User may be using older JDK ( JDK <1.2 ). Allow him/her to use it.
225: // But don't try to use doPrivileged
226: }
227:
228: return null;
229: }
230:
231: /**
232: * Creates an empty OutputProperties with the property key/value defaults specified by
233: * a property file. The method argument is used to construct a string of
234: * the form output_[method].properties (for instance, output_html.properties).
235: * The output_xml.properties file is always used as the base.
236: *
237: * <p>Anything other than 'text', 'xml', and 'html', will
238: * use the output_xml.properties file.</p>
239: *
240: * @param method non-null reference to method name.
241: *
242: * @return Properties object that holds the defaults for the given method.
243: */
244: static public final Properties getDefaultMethodProperties(
245: String method) {
246: String fileName = null;
247: Properties defaultProperties = null;
248: // According to this article : Double-check locking does not work
249: // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
250: try {
251: synchronized (m_synch_object) {
252: if (null == m_xml_properties) // double check
253: {
254: fileName = PROP_FILE_XML;
255: m_xml_properties = loadPropertiesFile(fileName,
256: null);
257: }
258: }
259:
260: if (method.equals(Method.XML)) {
261: defaultProperties = m_xml_properties;
262: } else if (method.equals(Method.HTML)) {
263: if (null == m_html_properties) // double check
264: {
265: fileName = PROP_FILE_HTML;
266: m_html_properties = loadPropertiesFile(fileName,
267: m_xml_properties);
268: }
269:
270: defaultProperties = m_html_properties;
271: } else if (method.equals(Method.TEXT)) {
272: if (null == m_text_properties) // double check
273: {
274: fileName = PROP_FILE_TEXT;
275: m_text_properties = loadPropertiesFile(fileName,
276: m_xml_properties);
277: if (null == m_text_properties
278: .getProperty(OutputKeys.ENCODING)) {
279: String mimeEncoding = Encodings
280: .getMimeEncoding(null);
281: m_text_properties.put(OutputKeys.ENCODING,
282: mimeEncoding);
283: }
284: }
285:
286: defaultProperties = m_text_properties;
287: } else if (method
288: .equals(org.apache.xml.serializer.Method.UNKNOWN)) {
289: if (null == m_unknown_properties) // double check
290: {
291: fileName = PROP_FILE_UNKNOWN;
292: m_unknown_properties = loadPropertiesFile(fileName,
293: m_xml_properties);
294: }
295:
296: defaultProperties = m_unknown_properties;
297: } else {
298: // TODO: Calculate res file from name.
299: defaultProperties = m_xml_properties;
300: }
301: } catch (IOException ioe) {
302: throw new WrappedRuntimeException(Utils.messages
303: .createMessage(
304: MsgKey.ER_COULD_NOT_LOAD_METHOD_PROPERTY,
305: new Object[] { fileName, method }), ioe);
306: }
307: // wrap these cached defaultProperties in a new Property object just so
308: // that the caller of this method can't modify the default values
309: return new Properties(defaultProperties);
310: }
311:
312: /**
313: * Load the properties file from a resource stream. If a
314: * key name such as "org.apache.xslt.xxx", fix up the start of
315: * string to be a curly namespace. If a key name starts with
316: * "xslt.output.xxx", clip off "xslt.output.". If a key name *or* a
317: * key value is discovered, check for \u003a in the text, and
318: * fix it up to be ":", since earlier versions of the JDK do not
319: * handle the escape sequence (at least in key names).
320: *
321: * @param resourceName non-null reference to resource name.
322: * @param defaults Default properties, which may be null.
323: */
324: static private Properties loadPropertiesFile(
325: final String resourceName, Properties defaults)
326: throws IOException {
327:
328: // This static method should eventually be moved to a thread-specific class
329: // so that we can cache the ContextClassLoader and bottleneck all properties file
330: // loading throughout Xalan.
331:
332: Properties props = new Properties(defaults);
333:
334: InputStream is = null;
335: BufferedInputStream bis = null;
336:
337: try {
338: if (ACCESS_CONTROLLER_CLASS != null) {
339: is = (InputStream) AccessController
340: .doPrivileged(new PrivilegedAction() {
341: public Object run() {
342: return OutputPropertiesFactory.class
343: .getResourceAsStream(resourceName);
344: }
345: });
346: } else {
347: // User may be using older JDK ( JDK < 1.2 )
348: is = OutputPropertiesFactory.class
349: .getResourceAsStream(resourceName);
350: }
351:
352: bis = new BufferedInputStream(is);
353: props.load(bis);
354: } catch (IOException ioe) {
355: if (defaults == null) {
356: throw ioe;
357: } else {
358: throw new WrappedRuntimeException(Utils.messages
359: .createMessage(
360: MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
361: new Object[] { resourceName }), ioe);
362: //"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
363: }
364: } catch (SecurityException se) {
365: // Repeat IOException handling for sandbox/applet case -sc
366: if (defaults == null) {
367: throw se;
368: } else {
369: throw new WrappedRuntimeException(Utils.messages
370: .createMessage(
371: MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
372: new Object[] { resourceName }), se);
373: //"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
374: }
375: } finally {
376: if (bis != null) {
377: bis.close();
378: }
379: if (is != null) {
380: is.close();
381: }
382: }
383:
384: // Note that we're working at the HashTable level here,
385: // and not at the Properties level! This is important
386: // because we don't want to modify the default properties.
387: // NB: If fixupPropertyString ends up changing the property
388: // name or value, we need to remove the old key and re-add
389: // with the new key and value. However, then our Enumeration
390: // could lose its place in the HashTable. So, we first
391: // clone the HashTable and enumerate over that since the
392: // clone will not change. When we migrate to Collections,
393: // this code should be revisited and cleaned up to use
394: // an Iterator which may (or may not) alleviate the need for
395: // the clone. Many thanks to Padraig O'hIceadha
396: // <padraig@gradient.ie> for finding this problem. Bugzilla 2000.
397:
398: Enumeration keys = ((Properties) props.clone()).keys();
399: while (keys.hasMoreElements()) {
400: String key = (String) keys.nextElement();
401: // Now check if the given key was specified as a
402: // System property. If so, the system property
403: // overides the default value in the propery file.
404: String value = null;
405: try {
406: value = System.getProperty(key);
407: } catch (SecurityException se) {
408: // No-op for sandbox/applet case, leave null -sc
409: }
410: if (value == null)
411: value = (String) props.get(key);
412:
413: String newKey = fixupPropertyString(key, true);
414: String newValue = null;
415: try {
416: newValue = System.getProperty(newKey);
417: } catch (SecurityException se) {
418: // No-op for sandbox/applet case, leave null -sc
419: }
420: if (newValue == null)
421: newValue = fixupPropertyString(value, false);
422: else
423: newValue = fixupPropertyString(newValue, false);
424:
425: if (key != newKey || value != newValue) {
426: props.remove(key);
427: props.put(newKey, newValue);
428: }
429:
430: }
431:
432: return props;
433: }
434:
435: /**
436: * Fix up a string in an output properties file according to
437: * the rules of {@link #loadPropertiesFile}.
438: *
439: * @param s non-null reference to string that may need to be fixed up.
440: * @return A new string if fixup occured, otherwise the s argument.
441: */
442: static private String fixupPropertyString(String s,
443: boolean doClipping) {
444: int index;
445: if (doClipping && s.startsWith(S_XSLT_PREFIX)) {
446: s = s.substring(S_XSLT_PREFIX_LEN);
447: }
448: if (s.startsWith(S_XALAN_PREFIX)) {
449: s = S_BUILTIN_EXTENSIONS_UNIVERSAL
450: + s.substring(S_XALAN_PREFIX_LEN);
451: }
452: if ((index = s.indexOf("\\u003a")) > 0) {
453: String temp = s.substring(index + 6);
454: s = s.substring(0, index) + ":" + temp;
455:
456: }
457: return s;
458: }
459:
460: }
|