diff options
Diffstat (limited to 'src/com/kenai/jbosh/BodyParserSAX.java')
-rw-r--r-- | src/com/kenai/jbosh/BodyParserSAX.java | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/src/com/kenai/jbosh/BodyParserSAX.java b/src/com/kenai/jbosh/BodyParserSAX.java new file mode 100644 index 0000000..54c6c01 --- /dev/null +++ b/src/com/kenai/jbosh/BodyParserSAX.java @@ -0,0 +1,206 @@ +/* + * Copyright 2009 Mike Cumings + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kenai.jbosh; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.SoftReference; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Implementation of the BodyParser interface which uses the SAX API + * that is part of the JDK. Due to the fact that we can cache and reuse + * SAXPArser instances, this has proven to be significantly faster than the + * use of the javax.xml.stream API introduced in Java 6 while simultaneously + * providing an implementation accessible to Java 5 users. + */ +final class BodyParserSAX implements BodyParser { + + /** + * Logger. + */ + private static final Logger LOG = + Logger.getLogger(BodyParserSAX.class.getName()); + + /** + * SAX parser factory. + */ + private static final SAXParserFactory SAX_FACTORY; + static { + SAX_FACTORY = SAXParserFactory.newInstance(); + SAX_FACTORY.setNamespaceAware(true); + SAX_FACTORY.setValidating(false); + } + + /** + * Thread local to contain a SAX parser instance for each thread that + * attempts to use one. This allows us to gain an order of magnitude of + * performance as a result of not constructing parsers for each + * invocation while retaining thread safety. + */ + private static final ThreadLocal<SoftReference<SAXParser>> PARSER = + new ThreadLocal<SoftReference<SAXParser>>() { + @Override protected SoftReference<SAXParser> initialValue() { + return new SoftReference<SAXParser>(null); + } + }; + + /** + * SAX event handler class. + */ + private static final class Handler extends DefaultHandler { + private final BodyParserResults result; + private final SAXParser parser; + private String defaultNS = null; + + private Handler(SAXParser theParser, BodyParserResults results) { + parser = theParser; + result = results; + } + + /** + * {@inheritDoc} + */ + @Override + public void startElement( + final String uri, + final String localName, + final String qName, + final Attributes attributes) { + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest("Start element: " + qName); + LOG.finest(" URI: " + uri); + LOG.finest(" local: " + localName); + } + + BodyQName bodyName = AbstractBody.getBodyQName(); + // Make sure the first element is correct + if (!(bodyName.getNamespaceURI().equals(uri) + && bodyName.getLocalPart().equals(localName))) { + throw(new IllegalStateException( + "Root element was not '" + bodyName.getLocalPart() + + "' in the '" + bodyName.getNamespaceURI() + + "' namespace. (Was '" + localName + "' in '" + uri + + "')")); + } + + // Read in the attributes, making sure to expand the namespaces + // as needed. + for (int idx=0; idx < attributes.getLength(); idx++) { + String attrURI = attributes.getURI(idx); + if (attrURI.length() == 0) { + attrURI = defaultNS; + } + String attrLN = attributes.getLocalName(idx); + String attrVal = attributes.getValue(idx); + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest(" Attribute: {" + attrURI + "}" + + attrLN + " = '" + attrVal + "'"); + } + + BodyQName aqn = BodyQName.create(attrURI, attrLN); + result.addBodyAttributeValue(aqn, attrVal); + } + + parser.reset(); + } + + /** + * {@inheritDoc} + * + * This implementation uses this event hook to keep track of the + * default namespace on the body element. + */ + @Override + public void startPrefixMapping( + final String prefix, + final String uri) { + if (prefix.length() == 0) { + if (LOG.isLoggable(Level.FINEST)) { + LOG.finest("Prefix mapping: <DEFAULT> => " + uri); + } + defaultNS = uri; + } else { + if (LOG.isLoggable(Level.FINEST)) { + LOG.info("Prefix mapping: " + prefix + " => " + uri); + } + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // BodyParser interface methods: + + /** + * {@inheritDoc} + */ + public BodyParserResults parse(String xml) throws BOSHException { + BodyParserResults result = new BodyParserResults(); + Exception thrown; + try { + InputStream inStream = new ByteArrayInputStream(xml.getBytes()); + SAXParser parser = getSAXParser(); + parser.parse(inStream, new Handler(parser, result)); + return result; + } catch (SAXException saxx) { + thrown = saxx; + } catch (IOException iox) { + thrown = iox; + } + throw(new BOSHException("Could not parse body:\n" + xml, thrown)); + } + + /////////////////////////////////////////////////////////////////////////// + // Private methods: + + /** + * Gets a SAXParser for use in parsing incoming messages. + * + * @return parser instance + */ + private static SAXParser getSAXParser() { + SoftReference<SAXParser> ref = PARSER.get(); + SAXParser result = ref.get(); + if (result == null) { + Exception thrown; + try { + result = SAX_FACTORY.newSAXParser(); + ref = new SoftReference<SAXParser>(result); + PARSER.set(ref); + return result; + } catch (ParserConfigurationException ex) { + thrown = ex; + } catch (SAXException ex) { + thrown = ex; + } + throw(new IllegalStateException( + "Could not create SAX parser", thrown)); + } else { + result.reset(); + return result; + } + } + +} |