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.IOException; 20 import java.io.StringReader; 21 import java.lang.ref.SoftReference; 22 import java.util.logging.Level; 23 import java.util.logging.Logger; 24 import javax.xml.XMLConstants; 25 import org.xmlpull.v1.XmlPullParser; 26 import org.xmlpull.v1.XmlPullParserException; 27 import org.xmlpull.v1.XmlPullParserFactory; 28 29 /** 30 * Implementation of the BodyParser interface which uses the XmlPullParser 31 * API. When available, this API provides an order of magnitude performance 32 * improvement over the default SAX parser implementation. 33 */ 34 final class BodyParserXmlPull implements BodyParser { 35 36 /** 37 * Logger. 38 */ 39 private static final Logger LOG = 40 Logger.getLogger(BodyParserXmlPull.class.getName()); 41 42 /** 43 * Thread local to contain a XmlPullParser instance for each thread that 44 * attempts to use one. This allows us to gain an order of magnitude of 45 * performance as a result of not constructing parsers for each 46 * invocation while retaining thread safety. 47 */ 48 private static final ThreadLocal<SoftReference<XmlPullParser>> XPP_PARSER = 49 new ThreadLocal<SoftReference<XmlPullParser>>() { 50 @Override protected SoftReference<XmlPullParser> initialValue() { 51 return new SoftReference<XmlPullParser>(null); 52 } 53 }; 54 55 /////////////////////////////////////////////////////////////////////////// 56 // BodyParser interface methods: 57 58 /** 59 * {@inheritDoc} 60 */ parse(final String xml)61 public BodyParserResults parse(final String xml) throws BOSHException { 62 BodyParserResults result = new BodyParserResults(); 63 Exception thrown; 64 try { 65 XmlPullParser xpp = getXmlPullParser(); 66 67 xpp.setInput(new StringReader(xml)); 68 int eventType = xpp.getEventType(); 69 while (eventType != XmlPullParser.END_DOCUMENT) { 70 if (eventType == XmlPullParser.START_TAG) { 71 if (LOG.isLoggable(Level.FINEST)) { 72 LOG.finest("Start tag: " + xpp.getName()); 73 } 74 } else { 75 eventType = xpp.next(); 76 continue; 77 } 78 79 String prefix = xpp.getPrefix(); 80 if (prefix == null) { 81 prefix = XMLConstants.DEFAULT_NS_PREFIX; 82 } 83 String uri = xpp.getNamespace(); 84 String localName = xpp.getName(); 85 QName name = new QName(uri, localName, prefix); 86 if (LOG.isLoggable(Level.FINEST)) { 87 LOG.finest("Start element: "); 88 LOG.finest(" prefix: " + prefix); 89 LOG.finest(" URI: " + uri); 90 LOG.finest(" local: " + localName); 91 } 92 93 BodyQName bodyName = AbstractBody.getBodyQName(); 94 if (!bodyName.equalsQName(name)) { 95 throw(new IllegalStateException( 96 "Root element was not '" + bodyName.getLocalPart() 97 + "' in the '" + bodyName.getNamespaceURI() 98 + "' namespace. (Was '" + localName 99 + "' in '" + uri + "')")); 100 } 101 102 for (int idx=0; idx < xpp.getAttributeCount(); idx++) { 103 String attrURI = xpp.getAttributeNamespace(idx); 104 if (attrURI.length() == 0) { 105 attrURI = xpp.getNamespace(null); 106 } 107 String attrPrefix = xpp.getAttributePrefix(idx); 108 if (attrPrefix == null) { 109 attrPrefix = XMLConstants.DEFAULT_NS_PREFIX; 110 } 111 String attrLN = xpp.getAttributeName(idx); 112 String attrVal = xpp.getAttributeValue(idx); 113 BodyQName aqn = BodyQName.createWithPrefix( 114 attrURI, attrLN, attrPrefix); 115 if (LOG.isLoggable(Level.FINEST)) { 116 LOG.finest(" Attribute: {" + attrURI + "}" 117 + attrLN + " = '" + attrVal + "'"); 118 } 119 result.addBodyAttributeValue(aqn, attrVal); 120 } 121 break; 122 } 123 return result; 124 } catch (RuntimeException rtx) { 125 thrown = rtx; 126 } catch (XmlPullParserException xmlppx) { 127 thrown = xmlppx; 128 } catch (IOException iox) { 129 thrown = iox; 130 } 131 throw(new BOSHException("Could not parse body:\n" + xml, thrown)); 132 } 133 134 /////////////////////////////////////////////////////////////////////////// 135 // Private methods: 136 137 /** 138 * Gets a XmlPullParser for use in parsing incoming messages. 139 * 140 * @return parser instance 141 */ getXmlPullParser()142 private static XmlPullParser getXmlPullParser() { 143 SoftReference<XmlPullParser> ref = XPP_PARSER.get(); 144 XmlPullParser result = ref.get(); 145 if (result == null) { 146 Exception thrown; 147 try { 148 XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); 149 factory.setNamespaceAware(true); 150 factory.setValidating(false); 151 result = factory.newPullParser(); 152 ref = new SoftReference<XmlPullParser>(result); 153 XPP_PARSER.set(ref); 154 return result; 155 } catch (Exception ex) { 156 thrown = ex; 157 } 158 throw(new IllegalStateException( 159 "Could not create XmlPull parser", thrown)); 160 } else { 161 return result; 162 } 163 } 164 165 } 166