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
17package com.kenai.jbosh;
18
19import java.io.IOException;
20import java.io.StringReader;
21import java.lang.ref.SoftReference;
22import java.util.logging.Level;
23import java.util.logging.Logger;
24import javax.xml.XMLConstants;
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlPullParserException;
27import 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 */
34final 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