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