/* * 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; } } }