• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.kenai.jbosh;
18 
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.lang.ref.SoftReference;
23 import java.util.logging.Level;
24 import java.util.logging.Logger;
25 import javax.xml.parsers.ParserConfigurationException;
26 import javax.xml.parsers.SAXParser;
27 import javax.xml.parsers.SAXParserFactory;
28 import org.xml.sax.Attributes;
29 import org.xml.sax.SAXException;
30 import 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  */
39 final 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 
Handler(SAXParser theParser, BodyParserResults results)78         private Handler(SAXParser theParser, BodyParserResults results) {
79             parser = theParser;
80             result = results;
81         }
82 
83         /**
84          * {@inheritDoc}
85          */
86         @Override
startElement( final String uri, final String localName, final String qName, final Attributes attributes)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
startPrefixMapping( final String prefix, final String uri)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      */
parse(String xml)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      */
getSAXParser()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