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  "License");
7  * you may not use this file except in compliance with the License.
8  * 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, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: OutputPropertiesFactory.java 468654 2006-10-28 07:09:23Z minchau $
20  */
21 package org.apache.xml.serializer;
22 
23 import java.io.BufferedInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.security.AccessController;
27 import java.security.PrivilegedAction;
28 import java.util.Enumeration;
29 import java.util.Properties;
30 
31 import javax.xml.transform.OutputKeys;
32 
33 import org.apache.xml.serializer.utils.MsgKey;
34 import org.apache.xml.serializer.utils.Utils;
35 import org.apache.xml.serializer.utils.WrappedRuntimeException;
36 
37 /**
38  * This class is a factory to generate a set of default properties
39  * of key/value pairs that are used to create a serializer through the
40  * factory {@link SerializerFactory SerilizerFactory}.
41  * The properties generated by this factory
42  * may be modified to non-default values before the SerializerFactory is used to
43  * create a Serializer.
44  * <p>
45  * The given output types supported are "xml", "text", and "html".
46  * These type strings can be obtained from the
47  * {@link Method Method} class in this package.
48  * <p>
49  * Other constants defined in this class are the non-standard property keys
50  * that can be used to set non-standard property values on a java.util.Properties object
51  * that is used to create or configure a serializer. Here are the non-standard keys:
52  * <ul>
53  * <li> <b>S_KEY_INDENT_AMOUNT </b> -
54  * The non-standard property key to use to set the indentation amount.
55  * The "indent" key needs to have a value of "yes", and this
56  * properties value is a the number of whitespaces to indent by per
57  * indentation level.
58  *
59  * <li> <b>S_KEY_CONTENT_HANDLER </b> -
60  * This non-standard property key is used to set the name of the fully qualified
61  * Java class that implements the ContentHandler interface.
62  * The output of the serializer will be SAX events sent to this an
63  * object of this class.
64  *
65  * <li> <b>S_KEY_ENTITIES </b> -
66  * This non-standard property key is used to specify the name of the property file
67  * that specifies character to entity reference mappings. A line in such a
68  * file is has the name of the entity and the numeric (base 10) value
69  * of the corresponding character, like this one: <br> quot=34 <br>
70  *
71  * <li> <b>S_USE_URL_ESCAPING </b> -
72  * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
73  *  use %xx escaping.
74  *
75  * <li> <b>S_OMIT_META_TAG </b> -
76  * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
77  *  otherwise be supplied.
78  * </ul>
79  *
80  * @see SerializerFactory
81  * @see Method
82  * @see Serializer
83  */
84 public final class OutputPropertiesFactory
85 {
86     /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace
87      *(http://xml.apache.org/xalan) predefined to signify Xalan's
88      * built-in XSLT Extensions. When used in stylesheets, this is often
89      * bound to the "xalan:" prefix.
90      */
91     private static final String
92       S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";
93 
94     /**
95      * The old built-in extension url. It is still supported for
96      * backward compatibility.
97      */
98     private static final String
99       S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt";
100 
101     //************************************************************
102     //*  PUBLIC CONSTANTS
103     //************************************************************
104     /**
105      * This is not a public API.
106      * This is the built-in extensions namespace,
107      * reexpressed in {namespaceURI} syntax
108      * suitable for prepending to a localname to produce a "universal
109      * name".
110      */
111     public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL =
112         "{" + S_BUILTIN_EXTENSIONS_URL + "}";
113 
114     // Some special Xalan keys.
115 
116     /**
117      * The non-standard property key to use to set the
118      * number of whitepaces to indent by, per indentation level,
119      * if indent="yes".
120      */
121     public static final String S_KEY_INDENT_AMOUNT =
122         S_BUILTIN_EXTENSIONS_UNIVERSAL + "indent-amount";
123 
124     /**
125      * The non-standard property key to use to set the
126      * characters to write out as at the end of a line,
127      * rather than the default ones from the runtime.
128      */
129     public static final String S_KEY_LINE_SEPARATOR =
130         S_BUILTIN_EXTENSIONS_UNIVERSAL + "line-separator";
131 
132     /** This non-standard property key is used to set the name of the fully qualified
133      * Java class that implements the ContentHandler interface.
134      * Fully qualified name of class with a default constructor that
135      *  implements the ContentHandler interface, where the result tree events
136      *  will be sent to.
137      */
138 
139     public static final String S_KEY_CONTENT_HANDLER =
140         S_BUILTIN_EXTENSIONS_UNIVERSAL + "content-handler";
141 
142     /**
143      * This non-standard property key is used to specify the name of the property file
144      * that specifies character to entity reference mappings.
145      */
146     public static final String S_KEY_ENTITIES =
147         S_BUILTIN_EXTENSIONS_UNIVERSAL + "entities";
148 
149     /**
150      * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
151      *  use %xx escaping. */
152     public static final String S_USE_URL_ESCAPING =
153         S_BUILTIN_EXTENSIONS_UNIVERSAL + "use-url-escaping";
154 
155     /**
156      * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
157      *  otherwise be supplied.
158      */
159     public static final String S_OMIT_META_TAG =
160         S_BUILTIN_EXTENSIONS_UNIVERSAL + "omit-meta-tag";
161 
162     /**
163      * The old built-in extension namespace, this is not a public API.
164      */
165     public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL =
166         "{" + S_BUILTIN_OLD_EXTENSIONS_URL + "}";
167 
168     /**
169      * This is not a public API, it is only public because it is used
170      * by outside of this package,
171      * it is the length of the old built-in extension namespace.
172      */
173     public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN =
174         S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();
175 
176     //************************************************************
177     //*  PRIVATE CONSTANTS
178     //************************************************************
179 
180     private static final String S_XSLT_PREFIX = "xslt.output.";
181     private static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
182     private static final String S_XALAN_PREFIX = "org.apache.xslt.";
183     private static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX.length();
184 
185     /** Synchronization object for lazy initialization of the above tables. */
186     private static Integer m_synch_object = new Integer(1);
187 
188     /** the directory in which the various method property files are located */
189     private static final String PROP_DIR = SerializerBase.PKG_PATH+'/';
190     /** property file for default XML properties */
191     private static final String PROP_FILE_XML = "output_xml.properties";
192     /** property file for default TEXT properties */
193     private static final String PROP_FILE_TEXT = "output_text.properties";
194     /** property file for default HTML properties */
195     private static final String PROP_FILE_HTML = "output_html.properties";
196     /** property file for default UNKNOWN (Either XML or HTML, to be determined later) properties */
197     private static final String PROP_FILE_UNKNOWN = "output_unknown.properties";
198 
199     //************************************************************
200     //*  PRIVATE STATIC FIELDS
201     //************************************************************
202 
203     /** The default properties of all output files. */
204     private static Properties m_xml_properties = null;
205 
206     /** The default properties when method="html". */
207     private static Properties m_html_properties = null;
208 
209     /** The default properties when method="text". */
210     private static Properties m_text_properties = null;
211 
212     /** The properties when method="" for the "unknown" wrapper */
213     private static Properties m_unknown_properties = null;
214 
215     private static final Class
216         ACCESS_CONTROLLER_CLASS = findAccessControllerClass();
217 
findAccessControllerClass()218     private static Class findAccessControllerClass() {
219         try
220         {
221             // This Class was introduced in JDK 1.2. With the re-architecture of
222             // security mechanism ( starting in JDK 1.2 ), we have option of
223             // giving privileges to certain part of code using doPrivileged block.
224             // In JDK1.1.X applications won't be having security manager and if
225             // there is security manager ( in applets ), code need to be signed
226             // and trusted for having access to resources.
227 
228             return Class.forName("java.security.AccessController");
229         }
230         catch (Exception e)
231         {
232             //User may be using older JDK ( JDK <1.2 ). Allow him/her to use it.
233             // But don't try to use doPrivileged
234         }
235 
236         return null;
237     }
238 
239     /**
240      * Creates an empty OutputProperties with the property key/value defaults specified by
241      * a property file.  The method argument is used to construct a string of
242      * the form output_[method].properties (for instance, output_html.properties).
243      * The output_xml.properties file is always used as the base.
244      *
245      * <p>Anything other than 'text', 'xml', and 'html', will
246      * use the output_xml.properties file.</p>
247      *
248      * @param   method non-null reference to method name.
249      *
250      * @return Properties object that holds the defaults for the given method.
251      */
getDefaultMethodProperties(String method)252     static public final Properties getDefaultMethodProperties(String method)
253     {
254         String fileName = null;
255         Properties defaultProperties = null;
256         // According to this article : Double-check locking does not work
257         // http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
258         try
259         {
260             synchronized (m_synch_object)
261             {
262                 if (null == m_xml_properties) // double check
263                 {
264                     fileName = PROP_FILE_XML;
265                     m_xml_properties = loadPropertiesFile(fileName, null);
266                 }
267             }
268 
269             if (method.equals(Method.XML))
270             {
271                 defaultProperties = m_xml_properties;
272             }
273             else if (method.equals(Method.HTML))
274             {
275                 if (null == m_html_properties) // double check
276                 {
277                     fileName = PROP_FILE_HTML;
278                     m_html_properties =
279                         loadPropertiesFile(fileName, m_xml_properties);
280                 }
281 
282                 defaultProperties = m_html_properties;
283             }
284             else if (method.equals(Method.TEXT))
285             {
286                 if (null == m_text_properties) // double check
287                 {
288                     fileName = PROP_FILE_TEXT;
289                     m_text_properties =
290                         loadPropertiesFile(fileName, m_xml_properties);
291                     if (null
292                         == m_text_properties.getProperty(OutputKeys.ENCODING))
293                     {
294                         String mimeEncoding = Encodings.getMimeEncoding(null);
295                         m_text_properties.put(
296                             OutputKeys.ENCODING,
297                             mimeEncoding);
298                     }
299                 }
300 
301                 defaultProperties = m_text_properties;
302             }
303             else if (method.equals(Method.UNKNOWN))
304             {
305                 if (null == m_unknown_properties) // double check
306                 {
307                     fileName = PROP_FILE_UNKNOWN;
308                     m_unknown_properties =
309                         loadPropertiesFile(fileName, m_xml_properties);
310                 }
311 
312                 defaultProperties = m_unknown_properties;
313             }
314             else
315             {
316                 // TODO: Calculate res file from name.
317                 defaultProperties = m_xml_properties;
318             }
319         }
320         catch (IOException ioe)
321         {
322             throw new WrappedRuntimeException(
323                 Utils.messages.createMessage(
324                     MsgKey.ER_COULD_NOT_LOAD_METHOD_PROPERTY,
325                     new Object[] { fileName, method }),
326                 ioe);
327         }
328         // wrap these cached defaultProperties in a new Property object just so
329         // that the caller of this method can't modify the default values
330         return new Properties(defaultProperties);
331     }
332 
333     /**
334      * Load the properties file from a resource stream.  If a
335      * key name such as "org.apache.xslt.xxx", fix up the start of
336      * string to be a curly namespace.  If a key name starts with
337      * "xslt.output.xxx", clip off "xslt.output.".  If a key name *or* a
338      * key value is discovered, check for \u003a in the text, and
339      * fix it up to be ":", since earlier versions of the JDK do not
340      * handle the escape sequence (at least in key names).
341      *
342      * @param resourceName non-null reference to resource name.
343      * @param defaults Default properties, which may be null.
344      */
loadPropertiesFile( final String resourceName, Properties defaults)345     static private Properties loadPropertiesFile(
346         final String resourceName,
347         Properties defaults)
348         throws IOException
349     {
350 
351         // This static method should eventually be moved to a thread-specific class
352         // so that we can cache the ContextClassLoader and bottleneck all properties file
353         // loading throughout Xalan.
354 
355         Properties props = new Properties(defaults);
356 
357         InputStream is = null;
358         BufferedInputStream bis = null;
359 
360         try
361         {
362             if (ACCESS_CONTROLLER_CLASS != null)
363             {
364                 is = (InputStream) AccessController
365                     .doPrivileged(new PrivilegedAction() {
366                         public Object run()
367                         {
368                             return OutputPropertiesFactory.class
369                                 .getResourceAsStream(resourceName);
370                         }
371                     });
372             }
373             else
374             {
375                 // User may be using older JDK ( JDK < 1.2 )
376                 is = OutputPropertiesFactory.class
377                     .getResourceAsStream(resourceName);
378             }
379 
380             bis = new BufferedInputStream(is);
381             props.load(bis);
382         }
383         catch (IOException ioe)
384         {
385             if (defaults == null)
386             {
387                 throw ioe;
388             }
389             else
390             {
391                 throw new WrappedRuntimeException(
392                     Utils.messages.createMessage(
393                         MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
394                         new Object[] { resourceName }),
395                     ioe);
396                 //"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
397             }
398         }
399         catch (SecurityException se)
400         {
401             // Repeat IOException handling for sandbox/applet case -sc
402             if (defaults == null)
403             {
404                 throw se;
405             }
406             else
407             {
408                 throw new WrappedRuntimeException(
409                     Utils.messages.createMessage(
410                         MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
411                         new Object[] { resourceName }),
412                     se);
413                 //"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
414             }
415         }
416         finally
417         {
418             if (bis != null)
419             {
420                 bis.close();
421             }
422             if (is != null)
423             {
424                 is.close();
425             }
426         }
427 
428         // Note that we're working at the HashTable level here,
429         // and not at the Properties level!  This is important
430         // because we don't want to modify the default properties.
431         // NB: If fixupPropertyString ends up changing the property
432         // name or value, we need to remove the old key and re-add
433         // with the new key and value.  However, then our Enumeration
434         // could lose its place in the HashTable.  So, we first
435         // clone the HashTable and enumerate over that since the
436         // clone will not change.  When we migrate to Collections,
437         // this code should be revisited and cleaned up to use
438         // an Iterator which may (or may not) alleviate the need for
439         // the clone.  Many thanks to Padraig O'hIceadha
440         // <padraig@gradient.ie> for finding this problem.  Bugzilla 2000.
441 
442         Enumeration keys = ((Properties) props.clone()).keys();
443         while (keys.hasMoreElements())
444         {
445             String key = (String) keys.nextElement();
446             // Now check if the given key was specified as a
447             // System property. If so, the system property
448             // overides the default value in the propery file.
449             String value = null;
450             try
451             {
452                 value = System.getProperty(key);
453             }
454             catch (SecurityException se)
455             {
456                 // No-op for sandbox/applet case, leave null -sc
457             }
458             if (value == null)
459                 value = (String) props.get(key);
460 
461             String newKey = fixupPropertyString(key, true);
462             String newValue = null;
463             try
464             {
465                 newValue = System.getProperty(newKey);
466             }
467             catch (SecurityException se)
468             {
469                 // No-op for sandbox/applet case, leave null -sc
470             }
471             if (newValue == null)
472                 newValue = fixupPropertyString(value, false);
473             else
474                 newValue = fixupPropertyString(newValue, false);
475 
476             if (key != newKey || value != newValue)
477             {
478                 props.remove(key);
479                 props.put(newKey, newValue);
480             }
481 
482         }
483 
484         return props;
485     }
486 
487     /**
488      * Fix up a string in an output properties file according to
489      * the rules of {@link #loadPropertiesFile}.
490      *
491      * @param s non-null reference to string that may need to be fixed up.
492      * @return A new string if fixup occured, otherwise the s argument.
493      */
fixupPropertyString(String s, boolean doClipping)494     static private String fixupPropertyString(String s, boolean doClipping)
495     {
496         int index;
497         if (doClipping && s.startsWith(S_XSLT_PREFIX))
498         {
499             s = s.substring(S_XSLT_PREFIX_LEN);
500         }
501         if (s.startsWith(S_XALAN_PREFIX))
502         {
503             s =
504                 S_BUILTIN_EXTENSIONS_UNIVERSAL
505                     + s.substring(S_XALAN_PREFIX_LEN);
506         }
507         if ((index = s.indexOf("\\u003a")) > 0)
508         {
509             String temp = s.substring(index + 6);
510             s = s.substring(0, index) + ":" + temp;
511 
512         }
513         return s;
514     }
515 
516 }
517