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 */
21package org.apache.xml.serializer;
22
23import java.io.BufferedInputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.security.AccessController;
27import java.security.PrivilegedAction;
28import java.util.Enumeration;
29import java.util.Properties;
30
31import javax.xml.transform.OutputKeys;
32
33import org.apache.xml.serializer.utils.MsgKey;
34import org.apache.xml.serializer.utils.Utils;
35import 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 */
84public 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
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     */
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     */
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     */
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