Shuyi Chen | d7955ce | 2013-05-22 14:51:55 -0700 | [diff] [blame] | 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("'", "'"); |
| 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 | } |