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.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