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    }