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