001    /*
002     * $Id: SAXHelper.java,v 1.4 2010/09/06 09:19:11 oboehm Exp $
003     *
004     * Copyright (c) 2008 by Oliver Boehm
005     *
006     * Licensed 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 orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 17.12.2008 by oliver (ob@oasd.de)
019     */
020    package patterntesting.exception.xml;
021    
022    import java.io.*;
023    
024    import org.apache.commons.io.IOUtils;
025    import org.apache.commons.lang.StringUtils;
026    import org.apache.commons.logging.*;
027    import org.xml.sax.SAXException;
028    
029    import patterntesting.annotation.check.runtime.MayReturnNull;
030    
031    /**
032     * The class SAXHelper was introduced to be able to provide better
033     * SAXExceptions.
034     *
035     * @author <a href="boehm@javatux.de">oliver</a>
036     * @since 17.12.2008
037     * @version $Revision: 1.4 $
038     */
039    public final class SAXHelper {
040    
041        private static final Log log = LogFactory.getLog(SAXHelper.class);
042    
043        /** The Constant DRIVER_PROPERTY. */
044        public static final String DRIVER_PROPERTY = "org.xml.sax.driver";
045    
046        /** The Constant DRIVER_RESOURCE. */
047        public static final String DRIVER_RESOURCE = "META-INF/services/" + DRIVER_PROPERTY;
048    
049        /**
050         * Transforms a SAXException with too less infos into a better SAXException
051         * with more infos.
052         * <br/>
053         * NOTE: Amonst other things the system property "org.xml.sax.driver" are
054         * used to get a better exception. Normally this should be synchronized
055         * because other threads may changes this system property so that this
056         * method may misinterpret this property. But it is very unlikely (except
057         * for testing) that this happen. So no synchronization is done to avoid
058         * blocked threads. So in some very, very rare circumstances the "better
059         * SAXException" may be perhaps confuses you (or the tester ;-).
060         *
061         * @param saxe the saxe
062         *
063         * @return the sAX exception
064         */
065        public static SAXException betterSAXException(final SAXException saxe) {
066            String msg = saxe.getMessage();
067            String className = parseMessage(msg);
068            if (className == null) {
069                if (log.isTraceEnabled()) {
070                    log.trace("unknown message '" + msg + "' - returning " + saxe
071                            + "...");
072                }
073                return saxe;
074            }
075            if (isDriverPropertySetWith(className)) {
076                if (log.isTraceEnabled()) {
077                    log.trace("conflict with property " + DRIVER_PROPERTY + "="
078                            + className + " detected");
079                }
080                msg = msg + " - check property \"" + DRIVER_PROPERTY + "\"";
081                return betterSAXException(saxe, msg);
082            } else if (isDriverResourceSetWith(className)) {
083                if (log.isTraceEnabled()) {
084                    log.trace("conflict with resource " + DRIVER_RESOURCE + "="
085                            + className + " detected");
086                }
087                msg = msg + " - check system resource \"" + DRIVER_RESOURCE + "\"";
088                return betterSAXException(saxe, msg);
089            } else {
090                return saxe;
091            }
092        }
093    
094        /**
095         * Better sax exception.
096         *
097         * @param saxe the saxe
098         * @param newMessage the new message
099         *
100         * @return the sAX exception
101         */
102        public static SAXException betterSAXException(final SAXException saxe, final String newMessage) {
103            Exception cause = saxe.getException();
104            SAXException betterException = new SAXException(newMessage, cause);
105            betterException.setStackTrace(saxe.getStackTrace());
106            return betterException;
107        }
108    
109        @MayReturnNull
110        private static String parseMessage(final String msg) {
111            String prefix = "SAX2 driver class ";
112            if (msg.startsWith(prefix)) {
113                return StringUtils.substringBetween(msg, prefix, " ");
114            } else {
115                return null;
116            }
117        }
118    
119        private static boolean isDriverPropertySetWith(final String className) {
120            String prop = System.getProperty(DRIVER_PROPERTY);
121            return className.equals(prop);
122        }
123    
124        private static boolean isDriverResourceSetWith(final String className) {
125            InputStream in = ClassLoader.getSystemResourceAsStream(DRIVER_RESOURCE);
126            if (in == null) {
127                return false;
128            }
129            try {
130                BufferedReader reader = new BufferedReader(new InputStreamReader(
131                        in, "UTF8"));
132                String driver = reader.readLine();
133                reader.close();
134                if (StringUtils.isBlank(driver)) {
135                    log.warn("no classname found in " + DRIVER_RESOURCE);
136                    return false;
137                }
138                return driver.equals(className);
139            } catch (IOException ioe) {
140                log.warn("can't read " + DRIVER_RESOURCE + " (" + ioe + ")");
141                return false;
142            } finally {
143                IOUtils.closeQuietly(in);
144            }
145        }
146    
147    }