001    /* $Id: XercesParser.java 471661 2006-11-06 08:09:25Z skitching $
002     *
003     * Licensed to the Apache Software Foundation (ASF) under one or more
004     * contributor license agreements.  See the NOTICE file distributed with
005     * this work for additional information regarding copyright ownership.
006     * The ASF licenses this file to You under the Apache License, Version 2.0
007     * (the "License"); you may not use this file except in compliance with
008     * the License.  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    
020    package org.apache.commons.digester.parser;
021    
022    import java.lang.reflect.Method;
023    import java.util.Properties;
024    
025    import javax.xml.parsers.ParserConfigurationException;
026    import javax.xml.parsers.SAXParser;
027    import javax.xml.parsers.SAXParserFactory;
028    
029    import org.apache.commons.logging.Log;
030    import org.apache.commons.logging.LogFactory;
031    import org.xml.sax.SAXException;
032    import org.xml.sax.SAXNotRecognizedException;
033    import org.xml.sax.SAXNotSupportedException;
034    
035    /**
036     * Create a <code>SAXParser</code> based on the underlying Xerces version.
037     * Currently, Xerces 2.3 and up doesn't implement schema validation the same way
038     * 2.1 was. In other to support schema validation in a portable way between 
039     * parser, some features/properties need to be set.
040     *
041     * @since 1.6
042     */
043    
044    public class XercesParser{
045    
046        /**
047         * The Log to which all SAX event related logging calls will be made.
048         */
049        protected static Log log =
050            LogFactory.getLog("org.apache.commons.digester.Digester.sax");
051    
052    
053        /**
054         * The JAXP 1.2 property required to set up the schema location.
055         */
056        private static final String JAXP_SCHEMA_SOURCE =
057            "http://java.sun.com/xml/jaxp/properties/schemaSource";
058    
059    
060        /**
061         * The JAXP 1.2 property to set up the schemaLanguage used.
062         */
063        protected static String JAXP_SCHEMA_LANGUAGE =
064            "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
065    
066    
067        /**
068         * Xerces dynamic validation property
069         */
070        protected static String XERCES_DYNAMIC = 
071            "http://apache.org/xml/features/validation/dynamic";
072    
073    
074        /**
075         * Xerces schema validation property
076         */
077        protected static String XERCES_SCHEMA =
078            "http://apache.org/xml/features/validation/schema";
079    
080    
081        /**
082         * A <code>float</code> representing the underlying Xerces version
083         */
084        protected static float version;
085    
086    
087        /**
088         * The current Xerces version.
089         */
090        protected static String versionNumber = null;
091    
092    
093        /**
094         * Return the current Xerces version.
095         * @return the current Xerces version.
096         */
097        private static String getXercesVersion() {
098            // If for some reason we can't get the version, set it to 1.0.
099            String versionNumber = "1.0";
100            try{
101                // Use reflection to avoid a build dependency with Xerces.
102                Class versionClass = 
103                                Class.forName("org.apache.xerces.impl.Version");
104                // Will return Xerces-J 2.x.0
105                Method method = 
106                    versionClass.getMethod("getVersion", (Class[])null); 
107                String version = (String)method.invoke(null, (Object[])null);
108                versionNumber = version.substring( "Xerces-J".length() , 
109                                                   version.lastIndexOf(".") ); 
110            } catch (Exception ex){
111                // Do nothing.
112            }
113            return versionNumber;
114        }
115    
116    
117        /**
118         * Create a <code>SAXParser</code> based on the underlying
119         * <code>Xerces</code> version.
120         * @param properties parser specific properties/features
121         * @return an XML Schema/DTD enabled <code>SAXParser</code>
122         */
123        public static SAXParser newSAXParser(Properties properties) 
124                throws ParserConfigurationException, 
125                       SAXException,
126                       SAXNotSupportedException {
127    
128            SAXParserFactory factory =  
129                            (SAXParserFactory)properties.get("SAXParserFactory");
130    
131            if (versionNumber == null){
132                versionNumber = getXercesVersion();
133                version = new Float( versionNumber ).floatValue();
134            }
135    
136            // Note: 2.2 is completely broken (with XML Schema). 
137            if (version > 2.1) {
138    
139                configureXerces(factory);
140                return factory.newSAXParser();
141            } else {
142                SAXParser parser = factory.newSAXParser();
143                configureOldXerces(parser,properties);
144                return parser;
145            }
146        }
147    
148    
149        /**
150         * Configure schema validation as recommended by the JAXP 1.2 spec.
151         * The <code>properties</code> object may contains information about
152         * the schema local and language. 
153         * @param properties parser optional info
154         */
155        private static void configureOldXerces(SAXParser parser, 
156                                               Properties properties) 
157                throws ParserConfigurationException, 
158                       SAXNotSupportedException {
159    
160            String schemaLocation = (String)properties.get("schemaLocation");
161            String schemaLanguage = (String)properties.get("schemaLanguage");
162    
163            try{
164                if (schemaLocation != null) {
165                    parser.setProperty(JAXP_SCHEMA_LANGUAGE, schemaLanguage);
166                    parser.setProperty(JAXP_SCHEMA_SOURCE, schemaLocation);
167                }
168            } catch (SAXNotRecognizedException e){
169                log.info(parser.getClass().getName() + ": " 
170                                            + e.getMessage() + " not supported."); 
171            }
172    
173        }
174    
175    
176        /**
177         * Configure schema validation as recommended by the Xerces spec. 
178         * Both DTD and Schema validation will be enabled simultaneously.
179         * <p>
180         * NOTE: This method is broken. It is supposed to set up validation
181         * against the schema specified in property "schemaLocation", but
182         * it doesn't.
183         *
184         * @param factory SAXParserFactory to be configured
185         */
186        private static void configureXerces(SAXParserFactory factory)
187                throws ParserConfigurationException, 
188                       SAXNotRecognizedException, 
189                       SAXNotSupportedException {
190    
191            factory.setFeature(XERCES_DYNAMIC, true);
192            factory.setFeature(XERCES_SCHEMA, true);
193    
194        }
195    }