Home | History | Annotate | Download | only in serializer
      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 
    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 (at) 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