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 78 private Handler(SAXParser theParser, BodyParserResults results) { 79 parser = theParser; 80 result = results; 81 } 82 83 /** 84 * {@inheritDoc} 85 */ 86 @Override 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 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 */ 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 */ 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