aboutsummaryrefslogtreecommitdiff
path: root/src/com/kenai/jbosh/BodyParserSAX.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/kenai/jbosh/BodyParserSAX.java')
-rw-r--r--src/com/kenai/jbosh/BodyParserSAX.java206
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;
+ }
+ }
+
+}