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.util.Collections;
     20 import java.util.HashMap;
     21 import java.util.Map;
     22 import java.util.concurrent.atomic.AtomicReference;
     23 import java.util.regex.Matcher;
     24 import java.util.regex.Pattern;
     25 import javax.xml.XMLConstants;
     26 
     27 /**
     28  * Implementation of the {@code AbstractBody} class which allows for the
     29  * definition of  messages from individual elements of a body.
     30  * <p/>
     31  * A message is constructed by creating a builder, manipulating the
     32  * configuration of the builder, and then building it into a class instance,
     33  * as in the following example:
     34  * <pre>
     35  * ComposableBody body = ComposableBody.builder()
     36  *     .setNamespaceDefinition("foo", "http://foo.com/bar")
     37  *     .setPayloadXML("<foo:data>Data to send to remote server</foo:data>")
     38  *     .build();
     39  * </pre>
     40  * Class instances can also be "rebuilt", allowing them to be used as templates
     41  * when building many similar messages:
     42  * <pre>
     43  * ComposableBody body2 = body.rebuild()
     44  *     .setPayloadXML("<foo:data>More data to send</foo:data>")
     45  *     .build();
     46  * </pre>
     47  * This class does only minimal syntactic and semantic checking with respect
     48  * to what the generated XML will look like.  It is up to the developer to
     49  * protect against the definition of malformed XML messages when building
     50  * instances of this class.
     51  * <p/>
     52  * Instances of this class are immutable and thread-safe.
     53  */
     54 public final class ComposableBody extends AbstractBody {
     55 
     56     /**
     57      * Pattern used to identify the beginning {@code body} element of a
     58      * BOSH message.
     59      */
     60     private static final Pattern BOSH_START =
     61             Pattern.compile("<" + "(?:(?:[^:\t\n\r >]+:)|(?:\\{[^\\}>]*?}))?"
     62             + "body" + "(?:[\t\n\r ][^>]*?)?" + "(/>|>)");
     63 
     64     /**
     65      * Map of all attributes to their values.
     66      */
     67     private final Map<BodyQName, String> attrs;
     68 
     69     /**
     70      * Payload XML.
     71      */
     72     private final String payload;
     73 
     74     /**
     75      * Computed raw XML.
     76      */
     77     private final AtomicReference<String> computed =
     78             new AtomicReference<String>();
     79 
     80     /**
     81      * Class instance builder, after the builder pattern.  This allows each
     82      * message instance to be immutable while providing flexibility when
     83      * building new messages.
     84      * <p/>
     85      * Instances of this class are <b>not</b> thread-safe.
     86      */
     87     public static final class Builder {
     88         private Map<BodyQName, String> map;
     89         private boolean doMapCopy;
     90         private String payloadXML;
     91 
     92         /**
     93          * Prevent direct construction.
     94          */
     95         private Builder() {
     96             // Empty
     97         }
     98 
     99         /**
    100          * Creates a builder which is initialized to the values of the
    101          * provided {@code ComposableBody} instance.  This allows an
    102          * existing {@code ComposableBody} to be used as a
    103          * template/starting point.
    104          *
    105          * @param source body template
    106          * @return builder instance
    107          */
    108         private static Builder fromBody(final ComposableBody source) {
    109             Builder result = new Builder();
    110             result.map = source.getAttributes();
    111             result.doMapCopy = true;
    112             result.payloadXML = source.payload;
    113             return result;
    114         }
    115 
    116         /**
    117          * Set the body message's wrapped payload content.  Any previous
    118          * content will be replaced.
    119          *
    120          * @param xml payload XML content
    121          * @return builder instance
    122          */
    123         public Builder setPayloadXML(final String xml) {
    124             if (xml == null) {
    125                 throw(new IllegalArgumentException(
    126                         "payload XML argument cannot be null"));
    127             }
    128             payloadXML = xml;
    129             return this;
    130         }
    131 
    132         /**
    133          * Set an attribute on the message body / wrapper element.
    134          *
    135          * @param name qualified name of the attribute
    136          * @param value value of the attribute
    137          * @return builder instance
    138          */
    139         public Builder setAttribute(
    140                 final BodyQName name, final String value) {
    141             if (map == null) {
    142                 map = new HashMap<BodyQName, String>();
    143             } else if (doMapCopy) {
    144                 map = new HashMap<BodyQName, String>(map);
    145                 doMapCopy = false;
    146             }
    147             if (value == null) {
    148                 map.remove(name);
    149             } else {
    150                 map.put(name, value);
    151             }
    152             return this;
    153         }
    154 
    155         /**
    156          * Convenience method to set a namespace definition. This would result
    157          * in a namespace prefix definition similar to:
    158          * {@code <body xmlns:prefix="uri"/>}
    159          *
    160          * @param prefix prefix to define
    161          * @param uri namespace URI to associate with the prefix
    162          * @return builder instance
    163          */
    164         public Builder setNamespaceDefinition(
    165                 final String prefix, final String uri) {
    166             BodyQName qname = BodyQName.createWithPrefix(
    167                     XMLConstants.XML_NS_URI, prefix,
    168                     XMLConstants.XMLNS_ATTRIBUTE);
    169             return setAttribute(qname, uri);
    170         }
    171 
    172         /**
    173          * Build the immutable object instance with the current configuration.
    174          *
    175          * @return composable body instance
    176          */
    177         public ComposableBody build() {
    178             if (map == null) {
    179                 map = new HashMap<BodyQName, String>();
    180             }
    181             if (payloadXML == null) {
    182                 payloadXML = "";
    183             }
    184             return new ComposableBody(map, payloadXML);
    185         }
    186     }
    187 
    188     ///////////////////////////////////////////////////////////////////////////
    189     // Constructors:
    190 
    191     /**
    192      * Prevent direct construction.  This constructor is for body messages
    193      * which are dynamically assembled.
    194      */
    195     private ComposableBody(
    196             final Map<BodyQName, String> attrMap,
    197             final String payloadXML) {
    198         super();
    199         attrs = attrMap;
    200         payload = payloadXML;
    201     }
    202 
    203     /**
    204      * Parse a static body instance into a composable instance.  This is an
    205      * expensive operation and should not be used lightly.
    206      * <p/>
    207      * The current implementation does not obtain the payload XML by means of
    208      * a proper XML parser.  It uses some string pattern searching to find the
    209      * first @{code body} element and the last element's closing tag.  It is
    210      * assumed that the static body's XML is well formed, etc..  This
    211      * implementation may change in the future.
    212      *
    213      * @param body static body instance to convert
    214      * @return composable bosy instance
    215      * @throws BOSHException
    216      */
    217     static ComposableBody fromStaticBody(final StaticBody body)
    218     throws BOSHException {
    219         String raw = body.toXML();
    220         Matcher matcher = BOSH_START.matcher(raw);
    221         if (!matcher.find()) {
    222             throw(new BOSHException(
    223                     "Could not locate 'body' element in XML.  The raw XML did"
    224                     + " not match the pattern: " + BOSH_START));
    225         }
    226         String payload;
    227         if (">".equals(matcher.group(1))) {
    228             int first = matcher.end();
    229             int last = raw.lastIndexOf("</");
    230             if (last < first) {
    231                 last = first;
    232             }
    233             payload = raw.substring(first, last);
    234         } else {
    235             payload = "";
    236         }
    237 
    238         return new ComposableBody(body.getAttributes(), payload);
    239     }
    240 
    241     /**
    242      * Create a builder instance to build new instances of this class.
    243      *
    244      * @return AbstractBody instance
    245      */
    246     public static Builder builder() {
    247         return new Builder();
    248     }
    249 
    250     /**
    251      * If this {@code ComposableBody} instance is a dynamic instance, uses this
    252      * {@code ComposableBody} instance as a starting point, create a builder
    253      * which can be used to create another {@code ComposableBody} instance
    254      * based on this one. This allows a {@code ComposableBody} instance to be
    255      * used as a template.  Note that the use of the returned builder in no
    256      * way modifies or manipulates the current {@code ComposableBody} instance.
    257      *
    258      * @return builder instance which can be used to build similar
    259      *  {@code ComposableBody} instances
    260      */
    261     public Builder rebuild() {
    262         return Builder.fromBody(this);
    263     }
    264 
    265     ///////////////////////////////////////////////////////////////////////////
    266     // Accessors:
    267 
    268     /**
    269      * {@inheritDoc}
    270      */
    271     public Map<BodyQName, String> getAttributes() {
    272         return Collections.unmodifiableMap(attrs);
    273     }
    274 
    275     /**
    276      * {@inheritDoc}
    277      */
    278     public String toXML() {
    279         String comp = computed.get();
    280         if (comp == null) {
    281             comp = computeXML();
    282             computed.set(comp);
    283         }
    284         return comp;
    285     }
    286 
    287     /**
    288      * Get the paylaod XML in String form.
    289      *
    290      * @return payload XML
    291      */
    292     public String getPayloadXML() {
    293         return payload;
    294     }
    295 
    296     ///////////////////////////////////////////////////////////////////////////
    297     // Private methods:
    298 
    299     /**
    300      * Escape the value of an attribute to ensure we maintain valid
    301      * XML syntax.
    302      *
    303      * @param value value to escape
    304      * @return escaped value
    305      */
    306     private String escape(final String value) {
    307         return value.replace("'", "&apos;");
    308     }
    309 
    310     /**
    311      * Generate a String representation of the message body.
    312      *
    313      * @return XML string representation of the body
    314      */
    315     private String computeXML() {
    316         BodyQName bodyName = getBodyQName();
    317         StringBuilder builder = new StringBuilder();
    318         builder.append("<");
    319         builder.append(bodyName.getLocalPart());
    320         for (Map.Entry<BodyQName, String> entry : attrs.entrySet()) {
    321             builder.append(" ");
    322             BodyQName name = entry.getKey();
    323             String prefix = name.getPrefix();
    324             if (prefix != null && prefix.length() > 0) {
    325                 builder.append(prefix);
    326                 builder.append(":");
    327             }
    328             builder.append(name.getLocalPart());
    329             builder.append("='");
    330             builder.append(escape(entry.getValue()));
    331             builder.append("'");
    332         }
    333         builder.append(" ");
    334         builder.append(XMLConstants.XMLNS_ATTRIBUTE);
    335         builder.append("='");
    336         builder.append(bodyName.getNamespaceURI());
    337         builder.append("'>");
    338         if (payload != null) {
    339             builder.append(payload);
    340         }
    341         builder.append("</body>");
    342         return builder.toString();
    343     }
    344 
    345 }
    346