blob: cb4192703e23e545483caa5a78b690457faccacf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* $Id: OutputPropertiesFactory.java 468654 2006-10-28 07:09:23Z minchau $
*/
package org.apache.xml.serializer;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Enumeration;
import java.util.Properties;
import javax.xml.transform.OutputKeys;
import org.apache.xml.serializer.utils.MsgKey;
import org.apache.xml.serializer.utils.Utils;
import org.apache.xml.serializer.utils.WrappedRuntimeException;
/**
* This class is a factory to generate a set of default properties
* of key/value pairs that are used to create a serializer through the
* factory {@link SerializerFactory SerilizerFactory}.
* The properties generated by this factory
* may be modified to non-default values before the SerializerFactory is used to
* create a Serializer.
* <p>
* The given output types supported are "xml", "text", and "html".
* These type strings can be obtained from the
* {@link Method Method} class in this package.
* <p>
* Other constants defined in this class are the non-standard property keys
* that can be used to set non-standard property values on a java.util.Properties object
* that is used to create or configure a serializer. Here are the non-standard keys:
* <ul>
* <li> <b>S_KEY_INDENT_AMOUNT </b> -
* The non-standard property key to use to set the indentation amount.
* The "indent" key needs to have a value of "yes", and this
* properties value is a the number of whitespaces to indent by per
* indentation level.
*
* <li> <b>S_KEY_CONTENT_HANDLER </b> -
* This non-standard property key is used to set the name of the fully qualified
* Java class that implements the ContentHandler interface.
* The output of the serializer will be SAX events sent to this an
* object of this class.
*
* <li> <b>S_KEY_ENTITIES </b> -
* This non-standard property key is used to specify the name of the property file
* that specifies character to entity reference mappings. A line in such a
* file is has the name of the entity and the numeric (base 10) value
* of the corresponding character, like this one: <br> quot=34 <br>
*
* <li> <b>S_USE_URL_ESCAPING </b> -
* This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
* use %xx escaping.
*
* <li> <b>S_OMIT_META_TAG </b> -
* This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
* otherwise be supplied.
* </ul>
*
* @see SerializerFactory
* @see Method
* @see Serializer
*/
public final class OutputPropertiesFactory
{
/** S_BUILTIN_EXTENSIONS_URL is a mnemonic for the XML Namespace
*(http://xml.apache.org/xalan) predefined to signify Xalan's
* built-in XSLT Extensions. When used in stylesheets, this is often
* bound to the "xalan:" prefix.
*/
private static final String
S_BUILTIN_EXTENSIONS_URL = "http://xml.apache.org/xalan";
/**
* The old built-in extension url. It is still supported for
* backward compatibility.
*/
private static final String
S_BUILTIN_OLD_EXTENSIONS_URL = "http://xml.apache.org/xslt";
//************************************************************
//* PUBLIC CONSTANTS
//************************************************************
/**
* This is not a public API.
* This is the built-in extensions namespace,
* reexpressed in {namespaceURI} syntax
* suitable for prepending to a localname to produce a "universal
* name".
*/
public static final String S_BUILTIN_EXTENSIONS_UNIVERSAL =
"{" + S_BUILTIN_EXTENSIONS_URL + "}";
// Some special Xalan keys.
/**
* The non-standard property key to use to set the
* number of whitepaces to indent by, per indentation level,
* if indent="yes".
*/
public static final String S_KEY_INDENT_AMOUNT =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "indent-amount";
/**
* The non-standard property key to use to set the
* characters to write out as at the end of a line,
* rather than the default ones from the runtime.
*/
public static final String S_KEY_LINE_SEPARATOR =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "line-separator";
/** This non-standard property key is used to set the name of the fully qualified
* Java class that implements the ContentHandler interface.
* Fully qualified name of class with a default constructor that
* implements the ContentHandler interface, where the result tree events
* will be sent to.
*/
public static final String S_KEY_CONTENT_HANDLER =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "content-handler";
/**
* This non-standard property key is used to specify the name of the property file
* that specifies character to entity reference mappings.
*/
public static final String S_KEY_ENTITIES =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "entities";
/**
* This non-standard property key is used to set a value of "yes" if the href values for HTML serialization should
* use %xx escaping. */
public static final String S_USE_URL_ESCAPING =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "use-url-escaping";
/**
* This non-standard property key is used to set a value of "yes" if the META tag should be omitted where it would
* otherwise be supplied.
*/
public static final String S_OMIT_META_TAG =
S_BUILTIN_EXTENSIONS_UNIVERSAL + "omit-meta-tag";
/**
* The old built-in extension namespace, this is not a public API.
*/
public static final String S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL =
"{" + S_BUILTIN_OLD_EXTENSIONS_URL + "}";
/**
* This is not a public API, it is only public because it is used
* by outside of this package,
* it is the length of the old built-in extension namespace.
*/
public static final int S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL_LEN =
S_BUILTIN_OLD_EXTENSIONS_UNIVERSAL.length();
//************************************************************
//* PRIVATE CONSTANTS
//************************************************************
private static final String S_XSLT_PREFIX = "xslt.output.";
private static final int S_XSLT_PREFIX_LEN = S_XSLT_PREFIX.length();
private static final String S_XALAN_PREFIX = "org.apache.xslt.";
private static final int S_XALAN_PREFIX_LEN = S_XALAN_PREFIX.length();
/** Synchronization object for lazy initialization of the above tables. */
private static Integer m_synch_object = new Integer(1);
/** the directory in which the various method property files are located */
private static final String PROP_DIR = SerializerBase.PKG_PATH+'/';
/** property file for default XML properties */
private static final String PROP_FILE_XML = "output_xml.properties";
/** property file for default TEXT properties */
private static final String PROP_FILE_TEXT = "output_text.properties";
/** property file for default HTML properties */
private static final String PROP_FILE_HTML = "output_html.properties";
/** property file for default UNKNOWN (Either XML or HTML, to be determined later) properties */
private static final String PROP_FILE_UNKNOWN = "output_unknown.properties";
//************************************************************
//* PRIVATE STATIC FIELDS
//************************************************************
/** The default properties of all output files. */
private static Properties m_xml_properties = null;
/** The default properties when method="html". */
private static Properties m_html_properties = null;
/** The default properties when method="text". */
private static Properties m_text_properties = null;
/** The properties when method="" for the "unknown" wrapper */
private static Properties m_unknown_properties = null;
private static final Class
ACCESS_CONTROLLER_CLASS = findAccessControllerClass();
private static Class findAccessControllerClass() {
try
{
// This Class was introduced in JDK 1.2. With the re-architecture of
// security mechanism ( starting in JDK 1.2 ), we have option of
// giving privileges to certain part of code using doPrivileged block.
// In JDK1.1.X applications won't be having security manager and if
// there is security manager ( in applets ), code need to be signed
// and trusted for having access to resources.
return Class.forName("java.security.AccessController");
}
catch (Exception e)
{
//User may be using older JDK ( JDK <1.2 ). Allow him/her to use it.
// But don't try to use doPrivileged
}
return null;
}
/**
* Creates an empty OutputProperties with the property key/value defaults specified by
* a property file. The method argument is used to construct a string of
* the form output_[method].properties (for instance, output_html.properties).
* The output_xml.properties file is always used as the base.
*
* <p>Anything other than 'text', 'xml', and 'html', will
* use the output_xml.properties file.</p>
*
* @param method non-null reference to method name.
*
* @return Properties object that holds the defaults for the given method.
*/
static public final Properties getDefaultMethodProperties(String method)
{
String fileName = null;
Properties defaultProperties = null;
// According to this article : Double-check locking does not work
// http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-toolbox.html
try
{
synchronized (m_synch_object)
{
if (null == m_xml_properties) // double check
{
fileName = PROP_FILE_XML;
m_xml_properties = loadPropertiesFile(fileName, null);
}
}
if (method.equals(Method.XML))
{
defaultProperties = m_xml_properties;
}
else if (method.equals(Method.HTML))
{
if (null == m_html_properties) // double check
{
fileName = PROP_FILE_HTML;
m_html_properties =
loadPropertiesFile(fileName, m_xml_properties);
}
defaultProperties = m_html_properties;
}
else if (method.equals(Method.TEXT))
{
if (null == m_text_properties) // double check
{
fileName = PROP_FILE_TEXT;
m_text_properties =
loadPropertiesFile(fileName, m_xml_properties);
if (null
== m_text_properties.getProperty(OutputKeys.ENCODING))
{
String mimeEncoding = Encodings.getMimeEncoding(null);
m_text_properties.put(
OutputKeys.ENCODING,
mimeEncoding);
}
}
defaultProperties = m_text_properties;
}
else if (method.equals(Method.UNKNOWN))
{
if (null == m_unknown_properties) // double check
{
fileName = PROP_FILE_UNKNOWN;
m_unknown_properties =
loadPropertiesFile(fileName, m_xml_properties);
}
defaultProperties = m_unknown_properties;
}
else
{
// TODO: Calculate res file from name.
defaultProperties = m_xml_properties;
}
}
catch (IOException ioe)
{
throw new WrappedRuntimeException(
Utils.messages.createMessage(
MsgKey.ER_COULD_NOT_LOAD_METHOD_PROPERTY,
new Object[] { fileName, method }),
ioe);
}
// wrap these cached defaultProperties in a new Property object just so
// that the caller of this method can't modify the default values
return new Properties(defaultProperties);
}
/**
* Load the properties file from a resource stream. If a
* key name such as "org.apache.xslt.xxx", fix up the start of
* string to be a curly namespace. If a key name starts with
* "xslt.output.xxx", clip off "xslt.output.". If a key name *or* a
* key value is discovered, check for \u003a in the text, and
* fix it up to be ":", since earlier versions of the JDK do not
* handle the escape sequence (at least in key names).
*
* @param resourceName non-null reference to resource name.
* @param defaults Default properties, which may be null.
*/
static private Properties loadPropertiesFile(
final String resourceName,
Properties defaults)
throws IOException
{
// This static method should eventually be moved to a thread-specific class
// so that we can cache the ContextClassLoader and bottleneck all properties file
// loading throughout Xalan.
Properties props = new Properties(defaults);
InputStream is = null;
BufferedInputStream bis = null;
try
{
if (ACCESS_CONTROLLER_CLASS != null)
{
is = (InputStream) AccessController
.doPrivileged(new PrivilegedAction() {
public Object run()
{
return OutputPropertiesFactory.class
.getResourceAsStream(resourceName);
}
});
}
else
{
// User may be using older JDK ( JDK < 1.2 )
is = OutputPropertiesFactory.class
.getResourceAsStream(resourceName);
}
bis = new BufferedInputStream(is);
props.load(bis);
}
catch (IOException ioe)
{
if (defaults == null)
{
throw ioe;
}
else
{
throw new WrappedRuntimeException(
Utils.messages.createMessage(
MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
new Object[] { resourceName }),
ioe);
//"Could not load '"+resourceName+"' (check CLASSPATH), now using just the defaults ", ioe);
}
}
catch (SecurityException se)
{
// Repeat IOException handling for sandbox/applet case -sc
if (defaults == null)
{
throw se;
}
else
{
throw new WrappedRuntimeException(
Utils.messages.createMessage(
MsgKey.ER_COULD_NOT_LOAD_RESOURCE,
new Object[] { resourceName }),
se);
//"Could not load '"+resourceName+"' (check CLASSPATH, applet security), now using just the defaults ", se);
}
}
finally
{
if (bis != null)
{
bis.close();
}
if (is != null)
{
is.close();
}
}
// Note that we're working at the HashTable level here,
// and not at the Properties level! This is important
// because we don't want to modify the default properties.
// NB: If fixupPropertyString ends up changing the property
// name or value, we need to remove the old key and re-add
// with the new key and value. However, then our Enumeration
// could lose its place in the HashTable. So, we first
// clone the HashTable and enumerate over that since the
// clone will not change. When we migrate to Collections,
// this code should be revisited and cleaned up to use
// an Iterator which may (or may not) alleviate the need for
// the clone. Many thanks to Padraig O'hIceadha
// <padraig@gradient.ie> for finding this problem. Bugzilla 2000.
Enumeration keys = ((Properties) props.clone()).keys();
while (keys.hasMoreElements())
{
String key = (String) keys.nextElement();
// Now check if the given key was specified as a
// System property. If so, the system property
// overides the default value in the propery file.
String value = null;
try
{
value = System.getProperty(key);
}
catch (SecurityException se)
{
// No-op for sandbox/applet case, leave null -sc
}
if (value == null)
value = (String) props.get(key);
String newKey = fixupPropertyString(key, true);
String newValue = null;
try
{
newValue = System.getProperty(newKey);
}
catch (SecurityException se)
{
// No-op for sandbox/applet case, leave null -sc
}
if (newValue == null)
newValue = fixupPropertyString(value, false);
else
newValue = fixupPropertyString(newValue, false);
if (key != newKey || value != newValue)
{
props.remove(key);
props.put(newKey, newValue);
}
}
return props;
}
/**
* Fix up a string in an output properties file according to
* the rules of {@link #loadPropertiesFile}.
*
* @param s non-null reference to string that may need to be fixed up.
* @return A new string if fixup occured, otherwise the s argument.
*/
static private String fixupPropertyString(String s, boolean doClipping)
{
int index;
if (doClipping && s.startsWith(S_XSLT_PREFIX))
{
s = s.substring(S_XSLT_PREFIX_LEN);
}
if (s.startsWith(S_XALAN_PREFIX))
{
s =
S_BUILTIN_EXTENSIONS_UNIVERSAL
+ s.substring(S_XALAN_PREFIX_LEN);
}
if ((index = s.indexOf("\\u003a")) > 0)
{
String temp = s.substring(index + 6);
s = s.substring(0, index) + ":" + temp;
}
return s;
}
}