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