001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: OutputPropertiesFactory.java 468654 2006-10-28 07:09:23Z minchau $ 020 */ 021 package org.apache.xml.serializer; 022 023 import java.io.BufferedInputStream; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.security.AccessController; 027 import java.security.PrivilegedAction; 028 import java.util.Enumeration; 029 import java.util.Properties; 030 031 import javax.xml.transform.OutputKeys; 032 033 import org.apache.xml.serializer.utils.MsgKey; 034 import org.apache.xml.serializer.utils.Utils; 035 import org.apache.xml.serializer.utils.WrappedRuntimeException; 036 037 /** 038 * This class is a factory to generate a set of default properties 039 * of key/value pairs that are used to create a serializer through the 040 * factory {@link SerializerFactory SerilizerFactory}. 041 * The properties generated by this factory 042 * may be modified to non-default values before the SerializerFactory is used to 043 * create a Serializer. 044 * <p> 045 * The given output types supported are "xml", "text", and "html". 046 * These type strings can be obtained from the 047 * {@link Method Method} class in this package. 048 * <p> 049 * Other constants defined in this class are the non-standard property keys 050 * that can be used to set non-standard property values on a java.util.Properties object 051 * that is used to create or configure a serializer. Here are the non-standard keys: 052 * <ul> 053 * <li> <b>S_KEY_INDENT_AMOUNT </b> - 054 * The non-standard property key to use to set the indentation amount. 055 * The "indent" key needs to have a value of "yes", and this 056 * properties value is a the number of whitespaces to indent by per 057 * indentation level. 058 * 059 * <li> <b>S_KEY_CONTENT_HANDLER </b> - 060 * This non-standard property key is used to set the name of the fully qualified 061 * Java class that implements the ContentHandler interface. 062 * The output of the serializer will be SAX events sent to this an 063 * object of this class. 064 * 065 * <li> <b>S_KEY_ENTITIES </b> - 066 * This non-standard property key is used to specify the name of the property file 067 * that specifies character to entity reference mappings. A line in such a 068 * file is has the name of the entity and the numeric (base 10) value 069 * of the corresponding character, like this one: <br> quot=34 <br> 070 * 071 * <li> <b>S_USE_URL_ESCAPING </b> - 072 * This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should 073 * use %xx escaping. 074 * 075 * <li> <b>S_OMIT_META_TAG </b> - 076 * This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would 077 * otherwise be supplied. 078 * </ul> 079 * 080 * @see SerializerFactory 081 * @see Method 082 * @see Serializer 083 */ 084 public final class OutputPropertiesFactory 085 { 086 /** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace 087 *(http://xml.apache.org/xalan) predefined to signify Xalan's 088 * built-in XSLT Extensions. When used in stylesheets, this is often 089 * bound to the "xalan:" prefix. 090 */ 091 private static final String 092 S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan"; 093 094 /** 095 * The old built-in extension url. It is still supported for 096 * backward compatibility. 097 */ 098 private static final String 099 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 }