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 }