1/*
2 * Copyright 2009 Mike Cumings
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.kenai.jbosh;
18
19import java.io.ByteArrayInputStream;
20import java.io.IOException;
21import java.io.InputStream;
22import java.lang.ref.SoftReference;
23import java.util.logging.Level;
24import java.util.logging.Logger;
25import javax.xml.parsers.ParserConfigurationException;
26import javax.xml.parsers.SAXParser;
27import javax.xml.parsers.SAXParserFactory;
28import org.xml.sax.Attributes;
29import org.xml.sax.SAXException;
30import org.xml.sax.helpers.DefaultHandler;
31
32/**
33 * Implementation of the BodyParser interface which uses the SAX API
34 * that is part of the JDK.  Due to the fact that we can cache and reuse
35 * SAXPArser instances, this has proven to be significantly faster than the
36 * use of the javax.xml.stream API introduced in Java 6 while simultaneously
37 * providing an implementation accessible to Java 5 users.
38 */
39final class BodyParserSAX implements BodyParser {
40
41    /**
42     * Logger.
43     */
44    private static final Logger LOG =
45            Logger.getLogger(BodyParserSAX.class.getName());
46
47    /**
48     * SAX parser factory.
49     */
50    private static final SAXParserFactory SAX_FACTORY;
51    static {
52        SAX_FACTORY = SAXParserFactory.newInstance();
53        SAX_FACTORY.setNamespaceAware(true);
54        SAX_FACTORY.setValidating(false);
55    }
56
57    /**
58     * Thread local to contain a SAX parser instance for each thread that
59     * attempts to use one.  This allows us to gain an order of magnitude of
60     * performance as a result of not constructing parsers for each
61     * invocation while retaining thread safety.
62     */
63    private static final ThreadLocal<SoftReference<SAXParser>> PARSER =
64        new ThreadLocal<SoftReference<SAXParser>>() {
65            @Override protected SoftReference<SAXParser> initialValue() {
66                return new SoftReference<SAXParser>(null);
67            }
68        };
69
70    /**
71     * SAX event handler class.
72     */
73    private static final class Handler extends DefaultHandler {
74        private final BodyParserResults result;
75        private final SAXParser parser;
76        private String defaultNS = null;
77
78        private Handler(SAXParser theParser, BodyParserResults results) {
79            parser = theParser;
80            result = results;
81        }
82
83        /**
84         * {@inheritDoc}
85         */
86        @Override
87        public void startElement(
88                final String uri,
89                final String localName,
90                final String qName,
91                final Attributes attributes) {
92            if (LOG.isLoggable(Level.FINEST)) {
93                LOG.finest("Start element: " + qName);
94                LOG.finest("    URI: " + uri);
95                LOG.finest("    local: " + localName);
96            }
97
98            BodyQName bodyName = AbstractBody.getBodyQName();
99            // Make sure the first element is correct
100            if (!(bodyName.getNamespaceURI().equals(uri)
101                    && bodyName.getLocalPart().equals(localName))) {
102                throw(new IllegalStateException(
103                        "Root element was not '" + bodyName.getLocalPart()
104                        + "' in the '" + bodyName.getNamespaceURI()
105                        + "' namespace.  (Was '" + localName + "' in '" + uri
106                        + "')"));
107            }
108
109            // Read in the attributes, making sure to expand the namespaces
110            // as needed.
111            for (int idx=0; idx < attributes.getLength(); idx++) {
112                String attrURI = attributes.getURI(idx);
113                if (attrURI.length() == 0) {
114                    attrURI = defaultNS;
115                }
116                String attrLN = attributes.getLocalName(idx);
117                String attrVal = attributes.getValue(idx);
118                if (LOG.isLoggable(Level.FINEST)) {
119                    LOG.finest("    Attribute: {" + attrURI + "}"
120                            + attrLN + " = '" + attrVal + "'");
121                }
122
123                BodyQName aqn = BodyQName.create(attrURI, attrLN);
124                result.addBodyAttributeValue(aqn, attrVal);
125            }
126
127            parser.reset();
128        }
129
130        /**
131         * {@inheritDoc}
132         *
133         * This implementation uses this event hook to keep track of the
134         * default namespace on the body element.
135         */
136        @Override
137        public void startPrefixMapping(
138                final String prefix,
139                final String uri) {
140            if (prefix.length() == 0) {
141                if (LOG.isLoggable(Level.FINEST)) {
142                    LOG.finest("Prefix mapping: <DEFAULT> => " + uri);
143                }
144                defaultNS = uri;
145            } else {
146                if (LOG.isLoggable(Level.FINEST)) {
147                    LOG.info("Prefix mapping: " + prefix + " => " + uri);
148                }
149            }
150        }
151    }
152
153    ///////////////////////////////////////////////////////////////////////////
154    // BodyParser interface methods:
155
156    /**
157     * {@inheritDoc}
158     */
159    public BodyParserResults parse(String xml) throws BOSHException {
160        BodyParserResults result = new BodyParserResults();
161        Exception thrown;
162        try {
163            InputStream inStream = new ByteArrayInputStream(xml.getBytes());
164            SAXParser parser = getSAXParser();
165            parser.parse(inStream, new Handler(parser, result));
166            return result;
167        } catch (SAXException saxx) {
168            thrown = saxx;
169        } catch (IOException iox) {
170            thrown = iox;
171        }
172        throw(new BOSHException("Could not parse body:\n" + xml, thrown));
173    }
174
175    ///////////////////////////////////////////////////////////////////////////
176    // Private methods:
177
178    /**
179     * Gets a SAXParser for use in parsing incoming messages.
180     *
181     * @return parser instance
182     */
183    private static SAXParser getSAXParser() {
184        SoftReference<SAXParser> ref = PARSER.get();
185        SAXParser result = ref.get();
186        if (result == null) {
187            Exception thrown;
188            try {
189                result = SAX_FACTORY.newSAXParser();
190                ref = new SoftReference<SAXParser>(result);
191                PARSER.set(ref);
192                return result;
193            } catch (ParserConfigurationException ex) {
194                thrown = ex;
195            } catch (SAXException ex) {
196                thrown = ex;
197            }
198            throw(new IllegalStateException(
199                    "Could not create SAX parser", thrown));
200        } else {
201            result.reset();
202            return result;
203        }
204    }
205
206}
207