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.net.URI; 20 import javax.net.ssl.SSLContext; 21 22 /** 23 * BOSH client configuration information. Instances of this class contain 24 * all information necessary to establish connectivity with a remote 25 * connection manager. 26 * <p/> 27 * Instances of this class are immutable, thread-safe, 28 * and can be re-used to configure multiple client session instances. 29 */ 30 public final class BOSHClientConfig { 31 32 /** 33 * Connection manager URI. 34 */ 35 private final URI uri; 36 37 /** 38 * Target domain. 39 */ 40 private final String to; 41 42 /** 43 * Client ID of this station. 44 */ 45 private final String from; 46 47 /** 48 * Default XML language. 49 */ 50 private final String lang; 51 52 /** 53 * Routing information for messages sent to CM. 54 */ 55 private final String route; 56 57 /** 58 * Proxy host. 59 */ 60 private final String proxyHost; 61 62 /** 63 * Proxy port. 64 */ 65 private final int proxyPort; 66 67 /** 68 * SSL context. 69 */ 70 private final SSLContext sslContext; 71 72 /** 73 * Flag indicating that compression should be attempted, if possible. 74 */ 75 private final boolean compressionEnabled; 76 77 /////////////////////////////////////////////////////////////////////////// 78 // Classes: 79 80 /** 81 * Class instance builder, after the builder pattern. This allows each 82 * {@code BOSHClientConfig} instance to be immutable while providing 83 * flexibility when building new {@code BOSHClientConfig} instances. 84 * <p/> 85 * Instances of this class are <b>not</b> thread-safe. If template-style 86 * use is desired, see the {@code create(BOSHClientConfig)} method. 87 */ 88 public static final class Builder { 89 // Required args 90 private final URI bURI; 91 private final String bDomain; 92 93 // Optional args 94 private String bFrom; 95 private String bLang; 96 private String bRoute; 97 private String bProxyHost; 98 private int bProxyPort; 99 private SSLContext bSSLContext; 100 private Boolean bCompression; 101 102 /** 103 * Creates a new builder instance, used to create instances of the 104 * {@code BOSHClientConfig} class. 105 * 106 * @param cmURI URI to use to contact the connection manager 107 * @param domain target domain to communicate with 108 */ 109 private Builder(final URI cmURI, final String domain) { 110 bURI = cmURI; 111 bDomain = domain; 112 } 113 114 /** 115 * Creates a new builder instance, used to create instances of the 116 * {@code BOSHClientConfig} class. 117 * 118 * @param cmURI URI to use to contact the connection manager 119 * @param domain target domain to communicate with 120 * @return builder instance 121 */ 122 public static Builder create(final URI cmURI, final String domain) { 123 if (cmURI == null) { 124 throw(new IllegalArgumentException( 125 "Connection manager URI must not be null")); 126 } 127 if (domain == null) { 128 throw(new IllegalArgumentException( 129 "Target domain must not be null")); 130 } 131 String scheme = cmURI.getScheme(); 132 if (!("http".equals(scheme) || "https".equals(scheme))) { 133 throw(new IllegalArgumentException( 134 "Only 'http' and 'https' URI are allowed")); 135 } 136 return new Builder(cmURI, domain); 137 } 138 139 /** 140 * Creates a new builder instance using the existing configuration 141 * provided as a starting point. 142 * 143 * @param cfg configuration to copy 144 * @return builder instance 145 */ 146 public static Builder create(final BOSHClientConfig cfg) { 147 Builder result = new Builder(cfg.getURI(), cfg.getTo()); 148 result.bFrom = cfg.getFrom(); 149 result.bLang = cfg.getLang(); 150 result.bRoute = cfg.getRoute(); 151 result.bProxyHost = cfg.getProxyHost(); 152 result.bProxyPort = cfg.getProxyPort(); 153 result.bSSLContext = cfg.getSSLContext(); 154 result.bCompression = cfg.isCompressionEnabled(); 155 return result; 156 } 157 158 /** 159 * Set the ID of the client station, to be forwarded to the connection 160 * manager when new sessions are created. 161 * 162 * @param id client ID 163 * @return builder instance 164 */ 165 public Builder setFrom(final String id) { 166 if (id == null) { 167 throw(new IllegalArgumentException( 168 "Client ID must not be null")); 169 } 170 bFrom = id; 171 return this; 172 } 173 174 /** 175 * Set the default language of any human-readable content within the 176 * XML. 177 * 178 * @param lang XML language ID 179 * @return builder instance 180 */ 181 public Builder setXMLLang(final String lang) { 182 if (lang == null) { 183 throw(new IllegalArgumentException( 184 "Default language ID must not be null")); 185 } 186 bLang = lang; 187 return this; 188 } 189 190 /** 191 * Sets the destination server/domain that the client should connect to. 192 * Connection managers may be configured to enable sessions with more 193 * that one server in different domains. When requesting a session with 194 * such a "proxy" connection manager, a client should use this method to 195 * specify the server with which it wants to communicate. 196 * 197 * @param protocol connection protocol (e.g, "xmpp") 198 * @param host host or domain to be served by the remote server. Note 199 * that this is not necessarily the host name or domain name of the 200 * remote server. 201 * @param port port number of the remote server 202 * @return builder instance 203 */ 204 public Builder setRoute( 205 final String protocol, 206 final String host, 207 final int port) { 208 if (protocol == null) { 209 throw(new IllegalArgumentException("Protocol cannot be null")); 210 } 211 if (protocol.contains(":")) { 212 throw(new IllegalArgumentException( 213 "Protocol cannot contain the ':' character")); 214 } 215 if (host == null) { 216 throw(new IllegalArgumentException("Host cannot be null")); 217 } 218 if (host.contains(":")) { 219 throw(new IllegalArgumentException( 220 "Host cannot contain the ':' character")); 221 } 222 if (port <= 0) { 223 throw(new IllegalArgumentException("Port number must be > 0")); 224 } 225 bRoute = protocol + ":" + host + ":" + port; 226 return this; 227 } 228 229 /** 230 * Specify the hostname and port of an HTTP proxy to connect through. 231 * 232 * @param hostName proxy hostname 233 * @param port proxy port number 234 * @return builder instance 235 */ 236 public Builder setProxy(final String hostName, final int port) { 237 if (hostName == null || hostName.length() == 0) { 238 throw(new IllegalArgumentException( 239 "Proxy host name cannot be null or empty")); 240 } 241 if (port <= 0) { 242 throw(new IllegalArgumentException( 243 "Proxy port must be > 0")); 244 } 245 bProxyHost = hostName; 246 bProxyPort = port; 247 return this; 248 } 249 250 /** 251 * Set the SSL context to use for this session. This can be used 252 * to configure certificate-based authentication, etc.. 253 * 254 * @param ctx SSL context 255 * @return builder instance 256 */ 257 public Builder setSSLContext(final SSLContext ctx) { 258 if (ctx == null) { 259 throw(new IllegalArgumentException( 260 "SSL context cannot be null")); 261 } 262 bSSLContext = ctx; 263 return this; 264 } 265 266 /** 267 * Set whether or not compression of the underlying data stream 268 * should be attempted. By default, compression is disabled. 269 * 270 * @param enabled set to {@code true} if compression should be 271 * attempted when possible, {@code false} to disable compression 272 * @return builder instance 273 */ 274 public Builder setCompressionEnabled(final boolean enabled) { 275 bCompression = Boolean.valueOf(enabled); 276 return this; 277 } 278 279 /** 280 * Build the immutable object instance with the current configuration. 281 * 282 * @return BOSHClientConfig instance 283 */ 284 public BOSHClientConfig build() { 285 // Default XML language 286 String lang; 287 if (bLang == null) { 288 lang = "en"; 289 } else { 290 lang = bLang; 291 } 292 293 // Default proxy port 294 int port; 295 if (bProxyHost == null) { 296 port = 0; 297 } else { 298 port = bProxyPort; 299 } 300 301 // Default compression 302 boolean compression; 303 if (bCompression == null) { 304 compression = false; 305 } else { 306 compression = bCompression.booleanValue(); 307 } 308 309 return new BOSHClientConfig( 310 bURI, 311 bDomain, 312 bFrom, 313 lang, 314 bRoute, 315 bProxyHost, 316 port, 317 bSSLContext, 318 compression); 319 } 320 321 } 322 323 /////////////////////////////////////////////////////////////////////////// 324 // Constructor: 325 326 /** 327 * Prevent direct construction. 328 * 329 * @param cURI URI of the connection manager to connect to 330 * @param cDomain the target domain of the first stream 331 * @param cFrom client ID 332 * @param cLang default XML language 333 * @param cRoute target route 334 * @param cProxyHost proxy host 335 * @param cProxyPort proxy port 336 * @param cSSLContext SSL context 337 * @param cCompression compression enabled flag 338 */ 339 private BOSHClientConfig( 340 final URI cURI, 341 final String cDomain, 342 final String cFrom, 343 final String cLang, 344 final String cRoute, 345 final String cProxyHost, 346 final int cProxyPort, 347 final SSLContext cSSLContext, 348 final boolean cCompression) { 349 uri = cURI; 350 to = cDomain; 351 from = cFrom; 352 lang = cLang; 353 route = cRoute; 354 proxyHost = cProxyHost; 355 proxyPort = cProxyPort; 356 sslContext = cSSLContext; 357 compressionEnabled = cCompression; 358 } 359 360 /** 361 * Get the URI to use to contact the connection manager. 362 * 363 * @return connection manager URI. 364 */ 365 public URI getURI() { 366 return uri; 367 } 368 369 /** 370 * Get the ID of the target domain. 371 * 372 * @return domain id 373 */ 374 public String getTo() { 375 return to; 376 } 377 378 /** 379 * Get the ID of the local client. 380 * 381 * @return client id, or {@code null} 382 */ 383 public String getFrom() { 384 return from; 385 } 386 387 /** 388 * Get the default language of any human-readable content within the 389 * XML. Defaults to "en". 390 * 391 * @return XML language ID 392 */ 393 public String getLang() { 394 return lang; 395 } 396 397 /** 398 * Get the routing information for messages sent to the CM. 399 * 400 * @return route attribute string, or {@code null} if no routing 401 * info was provided. 402 */ 403 public String getRoute() { 404 return route; 405 } 406 407 /** 408 * Get the HTTP proxy host to use. 409 * 410 * @return proxy host, or {@code null} if no proxy information was specified 411 */ 412 public String getProxyHost() { 413 return proxyHost; 414 } 415 416 /** 417 * Get the HTTP proxy port to use. 418 * 419 * @return proxy port, or 0 if no proxy information was specified 420 */ 421 public int getProxyPort() { 422 return proxyPort; 423 } 424 425 /** 426 * Get the SSL context to use for this session. 427 * 428 * @return SSL context instance to use, or {@code null} if no 429 * context instance was provided. 430 */ 431 public SSLContext getSSLContext() { 432 return sslContext; 433 } 434 435 /** 436 * Determines whether or not compression of the underlying data stream 437 * should be attempted/allowed. Defaults to {@code false}. 438 * 439 * @return {@code true} if compression should be attempted, {@code false} 440 * if compression is disabled or was not specified 441 */ 442 boolean isCompressionEnabled() { 443 return compressionEnabled; 444 } 445 446 } 447