Home | History | Annotate | Download | only in jbosh
      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      */
     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      */
    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