diff --git a/metadata/info.guardianproject.otr.app.im.txt b/metadata/info.guardianproject.otr.app.im.txt
index f529f2d496..855bf941f9 100644
--- a/metadata/info.guardianproject.otr.app.im.txt
+++ b/metadata/info.guardianproject.otr.app.im.txt
@@ -25,12 +25,15 @@ sed -i 's@\(android.library.reference.1=\).*@\1$$ActionBarSherlock$$@' project.p
Build Version:0.0.10-RC6,41,0.0.10-RC6,forceversion=yes,forcevercode=yes,\
srclibs=ActionBarSherlock@4.1.0,prebuild=\
sed -i 's@\(android.library.reference.1=\).*@\1$$ActionBarSherlock$$@' project.properties
-Build Version:0.0.11-RC2,53,!bash script and private submodules 0.0.11-RC2,submodules=yes,\
-update=external/ActionBarSherlock/library;external/MemorizingTrustManager;external/OnionKit/library,\
-prebuild=sed -i '6 i \\t' \
-external/MemorizingTrustManager/AndroidManifest.xml && \
-echo "sdk-location=$$SDK$$" > external/asmack/local.properties && \
-cd external/asmack && /bin/sh build.bash && cp build/asmack-android-4.jar ../../libs
+# Can't use build.bash script so add the output as a patch and build the jar manually
+Build Version:0.0.11-RC5,57,!mysterious aapt problem at 111494ebe,\
+submodules=yes,patch=otr-asmack.patch,\
+update=.;external/ActionBarSherlock/library;external/\
+MemorizingTrustManager;external/OnionKit/library,prebuild=\
+rm -rf tests/ META-INF/ robo-tests/ gitian/ proguard-project.txt && \
+echo "sdk-location=$$SDK$$" > external/asmack/local.properties,build=\
+ant -Dbuild.all=true -f external/asmack/build.xml && \
+mv external/asmack/build/asmack-android-4.jar libs/
Auto Update Mode:None
#Seem to bump manifest at beginning and tags doesn't give the latest
diff --git a/metadata/info.guardianproject.otr.app.im/otr-asmack.patch b/metadata/info.guardianproject.otr.app.im/otr-asmack.patch
new file mode 100644
index 0000000000..9f68d455d3
--- /dev/null
+++ b/metadata/info.guardianproject.otr.app.im/otr-asmack.patch
@@ -0,0 +1,127262 @@
+Instead of using the build.bash script which doesn't work with dash shell, apply the patch as a patch
+
+diff --git a/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender b/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender
+new file mode 100644
+index 0000000..3608d8e
+--- /dev/null
++++ b/external/asmack/build/src/trunk/META-INF/services/com.kenai.jbosh.HTTPSender
+@@ -0,0 +1 @@
++com.kenai.jbosh.ApacheHTTPSender
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java
+new file mode 100644
+index 0000000..0d6f84c
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractAttr.java
+@@ -0,0 +1,116 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++ package com.kenai.jbosh;
++
++/**
++ * Abstract base class for creating BOSH attribute classes. Concrete
++ * implementations of this class will naturally inherit the underlying
++ * type's behavior for {@code equals()}, {@code hashCode()},
++ * {@code toString()}, and {@code compareTo()}, allowing for the easy
++ * creation of objects which extend existing trivial types. This was done
++ * to comply with the prefactoring rule declaring, "when you are being
++ * abstract, be abstract all the way".
++ *
++ * @param type of the extension object
++ */
++abstract class AbstractAttr
++ implements Comparable {
++
++ /**
++ * Captured value.
++ */
++ private final T value;
++
++ /**
++ * Creates a new encapsulated object instance.
++ *
++ * @param aValue encapsulated getValue
++ */
++ protected AbstractAttr(final T aValue) {
++ value = aValue;
++ }
++
++ /**
++ * Gets the encapsulated data value.
++ *
++ * @return data value
++ */
++ public final T getValue() {
++ return value;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Object method overrides:
++
++ /**
++ * {@inheritDoc}
++ *
++ * @param otherObj object to compare to
++ * @return true if the objects are equal, false otherwise
++ */
++ @Override
++ public boolean equals(final Object otherObj) {
++ if (otherObj == null) {
++ return false;
++ } else if (otherObj instanceof AbstractAttr) {
++ AbstractAttr other =
++ (AbstractAttr) otherObj;
++ return value.equals(other.value);
++ } else {
++ return false;
++ }
++ }
++
++ /**
++ * {@inheritDoc}
++ *
++ * @return hashCode of the encapsulated object
++ */
++ @Override
++ public int hashCode() {
++ return value.hashCode();
++ }
++
++ /**
++ * {@inheritDoc}
++ *
++ * @return string representation of the encapsulated object
++ */
++ @Override
++ public String toString() {
++ return value.toString();
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Comparable interface:
++
++ /**
++ * {@inheritDoc}
++ *
++ * @param otherObj object to compare to
++ * @return -1, 0, or 1
++ */
++ @SuppressWarnings("unchecked")
++ public int compareTo(final Object otherObj) {
++ if (otherObj == null) {
++ return 1;
++ } else {
++ return value.compareTo(otherObj);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java
+new file mode 100644
+index 0000000..4d66c8c
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractBody.java
+@@ -0,0 +1,104 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.Collections;
++import java.util.Map;
++import java.util.Set;
++
++/**
++ * Class representing a single message to or from the BOSH connection
++ * manager (CM).
++ *
++ * These messages consist of a single {@code body} element
++ * (qualified within the BOSH namespace:
++ * {@code http://jabber.org/protocol/httpbind}) and contain zero or more
++ * child elements (of any namespace). These child elements constitute the
++ * message payload.
++ *
++ * In addition to the message payload, the attributes of the wrapper
++ * {@code body} element may also need to be used as part of the communication
++ * protocol being implemented on top of BOSH, or to define additional
++ * namespaces used by the child "payload" elements. These attributes are
++ * exposed via accessors.
++ */
++public abstract class AbstractBody {
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructor:
++
++ /**
++ * Restrict subclasses to the local package.
++ */
++ AbstractBody() {
++ // Empty
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Public methods:
++
++ /**
++ * Get a set of all defined attribute names.
++ *
++ * @return set of qualified attribute names
++ */
++ public final Set getAttributeNames() {
++ Map attrs = getAttributes();
++ return Collections.unmodifiableSet(attrs.keySet());
++ }
++
++ /**
++ * Get the value of the specified attribute.
++ *
++ * @param attr name of the attribute to retriece
++ * @return attribute value, or {@code null} if not defined
++ */
++ public final String getAttribute(final BodyQName attr) {
++ Map attrs = getAttributes();
++ return attrs.get(attr);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Abstract methods:
++
++ /**
++ * Get a map of all defined attribute names with their corresponding values.
++ *
++ * @return map of qualified attributes
++ */
++ public abstract Map getAttributes();
++
++ /**
++ * Get an XML String representation of this message.
++ *
++ * @return XML string representing the body message
++ */
++ public abstract String toXML();
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Returns the qualified name of the root/wrapper element.
++ *
++ * @return qualified name
++ */
++ static BodyQName getBodyQName() {
++ return BodyQName.createBOSH("body");
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java
+new file mode 100644
+index 0000000..1b827f9
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AbstractIntegerAttr.java
+@@ -0,0 +1,97 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Abstract base class for attribute implementations based on {@code Integer}
++ * types. Additional support for parsing of integer values from their
++ * {@code String} representations as well as callback handling of value
++ * validity checks are also provided.
++ */
++abstract class AbstractIntegerAttr extends AbstractAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute value
++ * @throws BOSHException on parse or validation failure
++ */
++ protected AbstractIntegerAttr(final int val) throws BOSHException {
++ super(Integer.valueOf(val));
++ }
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute value in string form
++ * @throws BOSHException on parse or validation failure
++ */
++ protected AbstractIntegerAttr(final String val) throws BOSHException {
++ super(parseInt(val));
++ }
++
++ /**
++ * Utility method intended to be called by concrete implementation
++ * classes from within the {@code check()} method when the concrete
++ * class needs to ensure that the integer value does not drop below
++ * the specified minimum value.
++ *
++ * @param minVal minimum value to allow
++ * @throws BOSHException if the integer value is below the specific
++ * minimum
++ */
++ protected final void checkMinValue(int minVal) throws BOSHException {
++ int intVal = getValue();
++ if (intVal < minVal) {
++ throw(new BOSHException(
++ "Illegal attribute value '" + intVal + "' provided. "
++ + "Must be >= " + minVal));
++ }
++ }
++
++ /**
++ * Utility method to parse a {@code String} into an {@code Integer},
++ * converting any possible {@code NumberFormatException} thrown into
++ * a {@code BOSHException}.
++ *
++ * @param str string to parse
++ * @return integer value
++ * @throws BOSHException on {@code NumberFormatException}
++ */
++ private static int parseInt(final String str) throws BOSHException {
++ try {
++ return Integer.parseInt(str);
++ } catch (NumberFormatException nfx) {
++ throw(new BOSHException(
++ "Could not parse an integer from the value provided: "
++ + str,
++ nfx));
++ }
++ }
++
++ /**
++ * Returns the native {@code int} value of the underlying {@code Integer}.
++ * Will throw {@code NullPointerException} if the underlying
++ * integer was {@code null}.
++ *
++ * @return native {@code int} value
++ */
++ public int intValue() {
++ return getValue().intValue();
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java
+new file mode 100644
+index 0000000..9f6731f
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPResponse.java
+@@ -0,0 +1,253 @@
++/*
++ * Copyright 2009 Guenther Niess
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.IOException;
++import java.util.concurrent.locks.Lock;
++import java.util.concurrent.locks.ReentrantLock;
++
++import org.apache.http.HttpEntity;
++import org.apache.http.HttpResponse;
++import org.apache.http.client.HttpClient;
++import org.apache.http.client.methods.HttpPost;
++import org.apache.http.entity.ByteArrayEntity;
++
++import org.apache.http.protocol.BasicHttpContext;
++import org.apache.http.protocol.HttpContext;
++import org.apache.http.util.EntityUtils;
++
++final class ApacheHTTPResponse implements HTTPResponse {
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constants:
++
++ /**
++ * Name of the accept encoding header.
++ */
++ private static final String ACCEPT_ENCODING = "Accept-Encoding";
++
++ /**
++ * Value to use for the ACCEPT_ENCODING header.
++ */
++ private static final String ACCEPT_ENCODING_VAL =
++ ZLIBCodec.getID() + ", " + GZIPCodec.getID();
++
++ /**
++ * Name of the character set to encode the body to/from.
++ */
++ private static final String CHARSET = "UTF-8";
++
++ /**
++ * Content type to use when transmitting the body data.
++ */
++ private static final String CONTENT_TYPE = "text/xml; charset=utf-8";
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Class variables:
++
++ /**
++ * Lock used for internal synchronization.
++ */
++ private final Lock lock = new ReentrantLock();
++
++ /**
++ * The execution state of an HTTP process.
++ */
++ private final HttpContext context;
++
++ /**
++ * HttpClient instance to use to communicate.
++ */
++ private final HttpClient client;
++
++ /**
++ * The HTTP POST request is sent to the server.
++ */
++ private final HttpPost post;
++
++ /**
++ * A flag which indicates if the transmission was already done.
++ */
++ private boolean sent;
++
++ /**
++ * Exception to throw when the response data is attempted to be accessed,
++ * or {@code null} if no exception should be thrown.
++ */
++ private BOSHException toThrow;
++
++ /**
++ * The response body which was received from the server or {@code null}
++ * if that has not yet happened.
++ */
++ private AbstractBody body;
++
++ /**
++ * The HTTP response status code.
++ */
++ private int statusCode;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Create and send a new request to the upstream connection manager,
++ * providing deferred access to the results to be returned.
++ *
++ * @param client client instance to use when sending the request
++ * @param cfg client configuration
++ * @param params connection manager parameters from the session creation
++ * response, or {@code null} if the session has not yet been established
++ * @param request body of the client request
++ */
++ ApacheHTTPResponse(
++ final HttpClient client,
++ final BOSHClientConfig cfg,
++ final CMSessionParams params,
++ final AbstractBody request) {
++ super();
++ this.client = client;
++ this.context = new BasicHttpContext();
++ this.post = new HttpPost(cfg.getURI().toString());
++ this.sent = false;
++
++ try {
++ String xml = request.toXML();
++ byte[] data = xml.getBytes(CHARSET);
++
++ String encoding = null;
++ if (cfg.isCompressionEnabled() && params != null) {
++ AttrAccept accept = params.getAccept();
++ if (accept != null) {
++ if (accept.isAccepted(ZLIBCodec.getID())) {
++ encoding = ZLIBCodec.getID();
++ data = ZLIBCodec.encode(data);
++ } else if (accept.isAccepted(GZIPCodec.getID())) {
++ encoding = GZIPCodec.getID();
++ data = GZIPCodec.encode(data);
++ }
++ }
++ }
++
++ ByteArrayEntity entity = new ByteArrayEntity(data);
++ entity.setContentType(CONTENT_TYPE);
++ if (encoding != null) {
++ entity.setContentEncoding(encoding);
++ }
++ post.setEntity(entity);
++ if (cfg.isCompressionEnabled()) {
++ post.setHeader(ACCEPT_ENCODING, ACCEPT_ENCODING_VAL);
++ }
++ } catch (Exception e) {
++ toThrow = new BOSHException("Could not generate request", e);
++ }
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // HTTPResponse interface methods:
++
++ /**
++ * Abort the client transmission and response processing.
++ */
++ public void abort() {
++ if (post != null) {
++ post.abort();
++ toThrow = new BOSHException("HTTP request aborted");
++ }
++ }
++
++ /**
++ * Wait for and then return the response body.
++ *
++ * @return body of the response
++ * @throws InterruptedException if interrupted while awaiting the response
++ * @throws BOSHException on communication failure
++ */
++ public AbstractBody getBody() throws InterruptedException, BOSHException {
++ if (toThrow != null) {
++ throw(toThrow);
++ }
++ lock.lock();
++ try {
++ if (!sent) {
++ awaitResponse();
++ }
++ } finally {
++ lock.unlock();
++ }
++ return body;
++ }
++
++ /**
++ * Wait for and then return the response HTTP status code.
++ *
++ * @return HTTP status code of the response
++ * @throws InterruptedException if interrupted while awaiting the response
++ * @throws BOSHException on communication failure
++ */
++ public int getHTTPStatus() throws InterruptedException, BOSHException {
++ if (toThrow != null) {
++ throw(toThrow);
++ }
++ lock.lock();
++ try {
++ if (!sent) {
++ awaitResponse();
++ }
++ } finally {
++ lock.unlock();
++ }
++ return statusCode;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Await the response, storing the result in the instance variables of
++ * this class when they arrive.
++ *
++ * @throws InterruptedException if interrupted while awaiting the response
++ * @throws BOSHException on communication failure
++ */
++ private synchronized void awaitResponse() throws BOSHException {
++ HttpEntity entity = null;
++ try {
++ HttpResponse httpResp = client.execute(post, context);
++ entity = httpResp.getEntity();
++ byte[] data = EntityUtils.toByteArray(entity);
++ String encoding = entity.getContentEncoding() != null ?
++ entity.getContentEncoding().getValue() :
++ null;
++ if (ZLIBCodec.getID().equalsIgnoreCase(encoding)) {
++ data = ZLIBCodec.decode(data);
++ } else if (GZIPCodec.getID().equalsIgnoreCase(encoding)) {
++ data = GZIPCodec.decode(data);
++ }
++ body = StaticBody.fromString(new String(data, CHARSET));
++ statusCode = httpResp.getStatusLine().getStatusCode();
++ sent = true;
++ } catch (IOException iox) {
++ abort();
++ toThrow = new BOSHException("Could not obtain response", iox);
++ throw(toThrow);
++ } catch (RuntimeException ex) {
++ abort();
++ throw(ex);
++ }
++ }
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java
+new file mode 100644
+index 0000000..b3d3c93
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ApacheHTTPSender.java
+@@ -0,0 +1,156 @@
++/*
++ * Copyright 2009 Guenther Niess
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.concurrent.locks.Lock;
++import java.util.concurrent.locks.ReentrantLock;
++
++import org.apache.http.HttpHost;
++import org.apache.http.HttpVersion;
++import org.apache.http.client.HttpClient;
++import org.apache.http.conn.ClientConnectionManager;
++import org.apache.http.conn.params.ConnManagerParams;
++import org.apache.http.conn.params.ConnRoutePNames;
++import org.apache.http.conn.scheme.PlainSocketFactory;
++import org.apache.http.conn.scheme.Scheme;
++import org.apache.http.conn.scheme.SchemeRegistry;
++import org.apache.http.conn.ssl.SSLSocketFactory;
++import org.apache.http.impl.client.DefaultHttpClient;
++import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
++import org.apache.http.params.BasicHttpParams;
++import org.apache.http.params.HttpParams;
++import org.apache.http.params.HttpProtocolParams;
++
++/**
++ * Implementation of the {@code HTTPSender} interface which uses the
++ * Apache HttpClient API to send messages to the connection manager.
++ */
++final class ApacheHTTPSender implements HTTPSender {
++
++ /**
++ * Lock used for internal synchronization.
++ */
++ private final Lock lock = new ReentrantLock();
++
++ /**
++ * Session configuration.
++ */
++ private BOSHClientConfig cfg;
++
++ /**
++ * HttpClient instance to use to communicate.
++ */
++ private HttpClient httpClient;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent construction apart from our package.
++ */
++ ApacheHTTPSender() {
++ // Load Apache HTTP client class
++ HttpClient.class.getName();
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // HTTPSender interface methods:
++
++ /**
++ * {@inheritDoc}
++ */
++ public void init(final BOSHClientConfig session) {
++ lock.lock();
++ try {
++ cfg = session;
++ httpClient = initHttpClient(session);
++ } finally {
++ lock.unlock();
++ }
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ public void destroy() {
++ lock.lock();
++ try {
++ if (httpClient != null) {
++ httpClient.getConnectionManager().shutdown();
++ }
++ } finally {
++ cfg = null;
++ httpClient = null;
++ lock.unlock();
++ }
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ public HTTPResponse send(
++ final CMSessionParams params,
++ final AbstractBody body) {
++ HttpClient mClient;
++ BOSHClientConfig mCfg;
++ lock.lock();
++ try {
++ if (httpClient == null) {
++ httpClient = initHttpClient(cfg);
++ }
++ mClient = httpClient;
++ mCfg = cfg;
++ } finally {
++ lock.unlock();
++ }
++ return new ApacheHTTPResponse(mClient, mCfg, params, body);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ private synchronized HttpClient initHttpClient(final BOSHClientConfig config) {
++ // Create and initialize HTTP parameters
++ HttpParams params = new BasicHttpParams();
++ ConnManagerParams.setMaxTotalConnections(params, 100);
++ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
++ HttpProtocolParams.setUseExpectContinue(params, false);
++ if (config != null &&
++ config.getProxyHost() != null &&
++ config.getProxyPort() != 0) {
++ HttpHost proxy = new HttpHost(
++ config.getProxyHost(),
++ config.getProxyPort());
++ params.setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
++ }
++
++ // Create and initialize scheme registry
++ SchemeRegistry schemeRegistry = new SchemeRegistry();
++ schemeRegistry.register(
++ new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
++ SSLSocketFactory sslFactory = SSLSocketFactory.getSocketFactory();
++ sslFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
++ schemeRegistry.register(
++ new Scheme("https", sslFactory, 443));
++
++ // Create an HttpClient with the ThreadSafeClientConnManager.
++ // This connection manager must be used if more than one thread will
++ // be using the HttpClient.
++ ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
++ return new DefaultHttpClient(cm, params);
++ }
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java
+new file mode 100644
+index 0000000..4f767df
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAccept.java
+@@ -0,0 +1,74 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code accept} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrAccept extends AbstractAttr {
++
++ /**
++ * Array of the accepted encodings.
++ */
++ private final String[] encodings;
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrAccept(final String val) {
++ super(val);
++ encodings = val.split("[\\s,]+");
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrAccept createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrAccept(str);
++ }
++ }
++
++ /**
++ * Determines whether or not the specified encoding is supported.
++ *
++ * @param name encoding name
++ * @result {@code true} if the encoding is accepted, {@code false}
++ * otherwise
++ */
++ boolean isAccepted(final String name) {
++ for (String str : encodings) {
++ if (str.equalsIgnoreCase(name)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java
+new file mode 100644
+index 0000000..6cfe22b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrAck.java
+@@ -0,0 +1,52 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code ack} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrAck extends AbstractAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrAck(final String val) throws BOSHException {
++ super(val);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrAck createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrAck(str);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java
+new file mode 100644
+index 0000000..45ce78c
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrCharsets.java
+@@ -0,0 +1,71 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code charsets} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrCharsets extends AbstractAttr {
++
++ /**
++ * Array of the accepted character sets.
++ */
++ private final String[] charsets;
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ */
++ private AttrCharsets(final String val) {
++ super(val);
++ charsets = val.split("\\ +");
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ */
++ static AttrCharsets createFromString(final String str) {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrCharsets(str);
++ }
++ }
++
++ /**
++ * Determines whether or not the specified charset is supported.
++ *
++ * @param name encoding name
++ * @result {@code true} if the encoding is accepted, {@code false}
++ * otherwise
++ */
++ boolean isAccepted(final String name) {
++ for (String str : charsets) {
++ if (str.equalsIgnoreCase(name)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java
+new file mode 100644
+index 0000000..56f21dd
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrHold.java
+@@ -0,0 +1,53 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code hold} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrHold extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrHold(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(0);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrHold createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrHold(str);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java
+new file mode 100644
+index 0000000..14ab7d4
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrInactivity.java
+@@ -0,0 +1,53 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the value of the {@code inactivity} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrInactivity extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute value
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrInactivity(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(0);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return instance of the attribute for the specified string, or
++ * {@code null} if input string is {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrInactivity createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrInactivity(str);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java
+new file mode 100644
+index 0000000..8d1d98b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrMaxPause.java
+@@ -0,0 +1,65 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.concurrent.TimeUnit;
++
++/**
++ * Data type representing the getValue of the {@code maxpause} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrMaxPause extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrMaxPause(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(1);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrMaxPause createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrMaxPause(str);
++ }
++ }
++
++ /**
++ * Get the max pause time in milliseconds.
++ *
++ * @return pause tme in milliseconds
++ */
++ public int getInMilliseconds() {
++ return (int) TimeUnit.MILLISECONDS.convert(
++ intValue(), TimeUnit.SECONDS);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java
+new file mode 100644
+index 0000000..5fb3282
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPause.java
+@@ -0,0 +1,65 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.concurrent.TimeUnit;
++
++/**
++ * Data type representing the getValue of the {@code pause} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrPause extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrPause(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(1);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrPause createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrPause(str);
++ }
++ }
++
++ /**
++ * Get the pause time in milliseconds.
++ *
++ * @return pause tme in milliseconds
++ */
++ public int getInMilliseconds() {
++ return (int) TimeUnit.MILLISECONDS.convert(
++ intValue(), TimeUnit.SECONDS);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java
+new file mode 100644
+index 0000000..3f0b08d
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrPolling.java
+@@ -0,0 +1,65 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.concurrent.TimeUnit;
++
++/**
++ * Data type representing the getValue of the {@code polling} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrPolling extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrPolling(final String str) throws BOSHException {
++ super(str);
++ checkMinValue(0);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return instance of the attribute for the specified string, or
++ * {@code null} if input string is {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrPolling createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrPolling(str);
++ }
++ }
++
++ /**
++ * Get the polling interval in milliseconds.
++ *
++ * @return polling interval in milliseconds
++ */
++ public int getInMilliseconds() {
++ return (int) TimeUnit.MILLISECONDS.convert(
++ intValue(), TimeUnit.SECONDS);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java
+new file mode 100644
+index 0000000..bfdc529
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrRequests.java
+@@ -0,0 +1,53 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the value of the {@code requests} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrRequests extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute value
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrRequests(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(1);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return instance of the attribute for the specified string, or
++ * {@code null} if input string is {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrRequests createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrRequests(str);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java
+new file mode 100644
+index 0000000..1998968
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrSessionID.java
+@@ -0,0 +1,44 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code sid} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrSessionID extends AbstractAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ */
++ private AttrSessionID(final String val) {
++ super(val);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance
++ */
++ static AttrSessionID createFromString(final String str) {
++ return new AttrSessionID(str);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java
+new file mode 100644
+index 0000000..9396e3b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrVersion.java
+@@ -0,0 +1,165 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code ver} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrVersion extends AbstractAttr implements Comparable {
++
++ /**
++ * Default value if none is provided.
++ */
++ private static final AttrVersion DEFAULT;
++ static {
++ try {
++ DEFAULT = createFromString("1.8");
++ } catch (BOSHException boshx) {
++ throw(new IllegalStateException(boshx));
++ }
++ }
++
++ /**
++ * Major portion of the version.
++ */
++ private final int major;
++
++ /**
++ * Minor portion of the version.
++ */
++ private final int minor;
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrVersion(final String val) throws BOSHException {
++ super(val);
++
++ int idx = val.indexOf('.');
++ if (idx <= 0) {
++ throw(new BOSHException(
++ "Illegal ver attribute value (not in major.minor form): "
++ + val));
++ }
++
++ String majorStr = val.substring(0, idx);
++ try {
++ major = Integer.parseInt(majorStr);
++ } catch (NumberFormatException nfx) {
++ throw(new BOSHException(
++ "Could not parse ver attribute value (major ver): "
++ + majorStr,
++ nfx));
++ }
++ if (major < 0) {
++ throw(new BOSHException(
++ "Major version may not be < 0"));
++ }
++
++ String minorStr = val.substring(idx + 1);
++ try {
++ minor = Integer.parseInt(minorStr);
++ } catch (NumberFormatException nfx) {
++ throw(new BOSHException(
++ "Could not parse ver attribute value (minor ver): "
++ + minorStr,
++ nfx));
++ }
++ if (minor < 0) {
++ throw(new BOSHException(
++ "Minor version may not be < 0"));
++ }
++ }
++
++ /**
++ * Get the version of specifcation that we support.
++ *
++ * @return max spec version the code supports
++ */
++ static AttrVersion getSupportedVersion() {
++ return DEFAULT;
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrVersion createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrVersion(str);
++ }
++ }
++
++ /**
++ * Returns the 'major' portion of the version number.
++ *
++ * @return major digits only
++ */
++ int getMajor() {
++ return major;
++ }
++
++ /**
++ * Returns the 'minor' portion of the version number.
++ *
++ * @return minor digits only
++ */
++ int getMinor() {
++ return minor;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Comparable interface:
++
++ /**
++ * {@inheritDoc}
++ *
++ * @param otherObj object to compare to
++ * @return -1, 0, or 1
++ */
++ @Override
++ public int compareTo(final Object otherObj) {
++ if (otherObj instanceof AttrVersion) {
++ AttrVersion other = (AttrVersion) otherObj;
++ if (major < other.major) {
++ return -1;
++ } else if (major > other.major) {
++ return 1;
++ } else if (minor < other.minor) {
++ return -1;
++ } else if (minor > other.minor) {
++ return 1;
++ } else {
++ return 0;
++ }
++ } else {
++ return 0;
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java
+new file mode 100644
+index 0000000..d2c95f7
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/AttrWait.java
+@@ -0,0 +1,53 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Data type representing the getValue of the {@code wait} attribute of the
++ * {@code bosh} element.
++ */
++final class AttrWait extends AbstractIntegerAttr {
++
++ /**
++ * Creates a new attribute object.
++ *
++ * @param val attribute getValue
++ * @throws BOSHException on parse or validation failure
++ */
++ private AttrWait(final String val) throws BOSHException {
++ super(val);
++ checkMinValue(1);
++ }
++
++ /**
++ * Creates a new attribute instance from the provided String.
++ *
++ * @param str string representation of the attribute
++ * @return attribute instance or {@code null} if provided string is
++ * {@code null}
++ * @throws BOSHException on parse or validation failure
++ */
++ static AttrWait createFromString(final String str)
++ throws BOSHException {
++ if (str == null) {
++ return null;
++ } else {
++ return new AttrWait(str);
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java b/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java
+new file mode 100644
+index 0000000..d01541e
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/Attributes.java
+@@ -0,0 +1,64 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import javax.xml.XMLConstants;
++
++/**
++ * Class containing constants for attribute definitions used by the
++ * XEP-0124 specification. We shouldn't need to expose these outside
++ * our package, since nobody else should be needing to worry about
++ * them.
++ */
++final class Attributes {
++
++ /**
++ * Private constructor to prevent construction of library class.
++ */
++ private Attributes() {
++ super();
++ }
++
++ static final BodyQName ACCEPT = BodyQName.createBOSH("accept");
++ static final BodyQName AUTHID = BodyQName.createBOSH("authid");
++ static final BodyQName ACK = BodyQName.createBOSH("ack");
++ static final BodyQName CHARSETS = BodyQName.createBOSH("charsets");
++ static final BodyQName CONDITION = BodyQName.createBOSH("condition");
++ static final BodyQName CONTENT = BodyQName.createBOSH("content");
++ static final BodyQName FROM = BodyQName.createBOSH("from");
++ static final BodyQName HOLD = BodyQName.createBOSH("hold");
++ static final BodyQName INACTIVITY = BodyQName.createBOSH("inactivity");
++ static final BodyQName KEY = BodyQName.createBOSH("key");
++ static final BodyQName MAXPAUSE = BodyQName.createBOSH("maxpause");
++ static final BodyQName NEWKEY = BodyQName.createBOSH("newkey");
++ static final BodyQName PAUSE = BodyQName.createBOSH("pause");
++ static final BodyQName POLLING = BodyQName.createBOSH("polling");
++ static final BodyQName REPORT = BodyQName.createBOSH("report");
++ static final BodyQName REQUESTS = BodyQName.createBOSH("requests");
++ static final BodyQName RID = BodyQName.createBOSH("rid");
++ static final BodyQName ROUTE = BodyQName.createBOSH("route");
++ static final BodyQName SECURE = BodyQName.createBOSH("secure");
++ static final BodyQName SID = BodyQName.createBOSH("sid");
++ static final BodyQName STREAM = BodyQName.createBOSH("stream");
++ static final BodyQName TIME = BodyQName.createBOSH("time");
++ static final BodyQName TO = BodyQName.createBOSH("to");
++ static final BodyQName TYPE = BodyQName.createBOSH("type");
++ static final BodyQName VER = BodyQName.createBOSH("ver");
++ static final BodyQName WAIT = BodyQName.createBOSH("wait");
++ static final BodyQName XML_LANG =
++ BodyQName.createWithPrefix(XMLConstants.XML_NS_URI, "lang", "xml");
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java
+new file mode 100644
+index 0000000..b96d188
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClient.java
+@@ -0,0 +1,1536 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import com.kenai.jbosh.ComposableBody.Builder;
++import java.util.ArrayList;
++import java.util.Iterator;
++import java.util.LinkedList;
++import java.util.List;
++import java.util.Queue;
++import java.util.Set;
++import java.util.SortedSet;
++import java.util.TreeSet;
++import java.util.concurrent.CopyOnWriteArraySet;
++import java.util.concurrent.Executors;
++import java.util.concurrent.RejectedExecutionException;
++import java.util.concurrent.ScheduledExecutorService;
++import java.util.concurrent.ScheduledFuture;
++import java.util.concurrent.TimeUnit;
++import java.util.concurrent.atomic.AtomicReference;
++import java.util.concurrent.locks.Condition;
++import java.util.concurrent.locks.ReentrantLock;
++import java.util.logging.Level;
++import java.util.logging.Logger;
++
++/**
++ * BOSH Client session instance. Each communication session with a remote
++ * connection manager is represented and handled by an instance of this
++ * class. This is the main entry point for client-side communications.
++ * To create a new session, a client configuration must first be created
++ * and then used to create a client instance:
++ *
++ * Additional client configuration options are available. See the
++ * {@code BOSHClientConfig.Builder} class for more information.
++ *
++ * Once a {@code BOSHClient} instance has been created, communication with
++ * the remote connection manager can begin. No attempt will be made to
++ * establish a connection to the connection manager until the first call
++ * is made to the {@code send(ComposableBody)} method. Note that it is
++ * possible to send an empty body to cause an immediate connection attempt
++ * to the connection manager. Sending an empty message would look like
++ * the following:
++ *
++ * For more information on creating body messages with content, see the
++ * {@code ComposableBody.Builder} class documentation.
++ *
++ * Once a session has been successfully started, the client instance can be
++ * used to send arbitrary payload data. All aspects of the BOSH
++ * protocol involving setting and processing attributes in the BOSH
++ * namespace will be handled by the client code transparently and behind the
++ * scenes. The user of the client instance can therefore concentrate
++ * entirely on the content of the message payload, leaving the semantics of
++ * the BOSH protocol to the client implementation.
++ *
++ * To be notified of incoming messages from the remote connection manager,
++ * a {@code BOSHClientResponseListener} should be added to the client instance.
++ * All incoming messages will be published to all response listeners as they
++ * arrive and are processed. As with the transmission of payload data via
++ * the {@code send(ComposableBody)} method, there is no need to worry about
++ * handling of the BOSH attributes, since this is handled behind the scenes.
++ *
++ * If the connection to the remote connection manager is terminated (either
++ * explicitly or due to a terminal condition of some sort), all connection
++ * listeners will be notified. After the connection has been closed, the
++ * client instance is considered dead and a new one must be created in order
++ * to resume communications with the remote server.
++ *
++ * Instances of this class are thread-safe.
++ *
++ * @see BOSHClientConfig.Builder
++ * @see BOSHClientResponseListener
++ * @see BOSHClientConnListener
++ * @see ComposableBody.Builder
++ */
++public final class BOSHClient {
++
++ /**
++ * Logger.
++ */
++ private static final Logger LOG = Logger.getLogger(
++ BOSHClient.class.getName());
++
++ /**
++ * Value of the 'type' attribute used for session termination.
++ */
++ private static final String TERMINATE = "terminate";
++
++ /**
++ * Value of the 'type' attribute used for recoverable errors.
++ */
++ private static final String ERROR = "error";
++
++ /**
++ * Message to use for interrupted exceptions.
++ */
++ private static final String INTERRUPTED = "Interrupted";
++
++ /**
++ * Message used for unhandled exceptions.
++ */
++ private static final String UNHANDLED = "Unhandled Exception";
++
++ /**
++ * Message used whena null listener is detected.
++ */
++ private static final String NULL_LISTENER = "Listener may not b enull";
++
++ /**
++ * Default empty request delay.
++ */
++ private static final int DEFAULT_EMPTY_REQUEST_DELAY = 100;
++
++ /**
++ * Amount of time to wait before sending an empty request, in
++ * milliseconds.
++ */
++ private static final int EMPTY_REQUEST_DELAY = Integer.getInteger(
++ BOSHClient.class.getName() + ".emptyRequestDelay",
++ DEFAULT_EMPTY_REQUEST_DELAY);
++
++ /**
++ * Default value for the pause margin.
++ */
++ private static final int DEFAULT_PAUSE_MARGIN = 500;
++
++ /**
++ * The amount of time in milliseconds which will be reserved as a
++ * safety margin when scheduling empty requests against a maxpause
++ * value. This should give us enough time to build the message
++ * and transport it to the remote host.
++ */
++ private static final int PAUSE_MARGIN = Integer.getInteger(
++ BOSHClient.class.getName() + ".pauseMargin",
++ DEFAULT_PAUSE_MARGIN);
++
++ /**
++ * Flag indicating whether or not we want to perform assertions.
++ */
++ private static final boolean ASSERTIONS;
++
++ /**
++ * Connection listeners.
++ */
++ private final Set connListeners =
++ new CopyOnWriteArraySet();
++
++ /**
++ * Request listeners.
++ */
++ private final Set requestListeners =
++ new CopyOnWriteArraySet();
++
++ /**
++ * Response listeners.
++ */
++ private final Set responseListeners =
++ new CopyOnWriteArraySet();
++
++ /**
++ * Lock instance.
++ */
++ private final ReentrantLock lock = new ReentrantLock();
++
++ /**
++ * Condition indicating that there are messages to be exchanged.
++ */
++ private final Condition notEmpty = lock.newCondition();
++
++ /**
++ * Condition indicating that there are available slots for sending
++ * messages.
++ */
++ private final Condition notFull = lock.newCondition();
++
++ /**
++ * Condition indicating that there are no outstanding connections.
++ */
++ private final Condition drained = lock.newCondition();
++
++ /**
++ * Session configuration.
++ */
++ private final BOSHClientConfig cfg;
++
++ /**
++ * Processor thread runnable instance.
++ */
++ private final Runnable procRunnable = new Runnable() {
++ /**
++ * Process incoming messages.
++ */
++ public void run() {
++ processMessages();
++ }
++ };
++
++ /**
++ * Processor thread runnable instance.
++ */
++ private final Runnable emptyRequestRunnable = new Runnable() {
++ /**
++ * Process incoming messages.
++ */
++ public void run() {
++ sendEmptyRequest();
++ }
++ };
++
++ /**
++ * HTTPSender instance.
++ */
++ private final HTTPSender httpSender =
++ new ApacheHTTPSender();
++
++ /**
++ * Storage for test hook implementation.
++ */
++ private final AtomicReference exchInterceptor =
++ new AtomicReference();
++
++ /**
++ * Request ID sequence to use for the session.
++ */
++ private final RequestIDSequence requestIDSeq = new RequestIDSequence();
++
++ /**
++ * ScheduledExcecutor to use for deferred tasks.
++ */
++ private final ScheduledExecutorService schedExec =
++ Executors.newSingleThreadScheduledExecutor();
++
++ /************************************************************
++ * The following vars must be accessed via the lock instance.
++ */
++
++ /**
++ * Thread which is used to process responses from the connection
++ * manager. Becomes null when session is terminated.
++ */
++ private Thread procThread;
++
++ /**
++ * Future for sending a deferred empty request, if needed.
++ */
++ private ScheduledFuture emptyRequestFuture;
++
++ /**
++ * Connection Manager session parameters. Only available when in a
++ * connected state.
++ */
++ private CMSessionParams cmParams;
++
++ /**
++ * List of active/outstanding requests.
++ */
++ private Queue exchanges = new LinkedList();
++
++ /**
++ * Set of RIDs which have been received, for the purpose of sending
++ * response acknowledgements.
++ */
++ private SortedSet pendingResponseAcks = new TreeSet();
++
++ /**
++ * The highest RID that we've already received a response for. This value
++ * is used to implement response acks.
++ */
++ private Long responseAck = Long.valueOf(-1L);
++
++ /**
++ * List of requests which have been made but not yet acknowledged. This
++ * list remains unpopulated if the CM is not acking requests.
++ */
++ private List pendingRequestAcks =
++ new ArrayList();
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Classes:
++
++ /**
++ * Class used in testing to dynamically manipulate received exchanges
++ * at test runtime.
++ */
++ abstract static class ExchangeInterceptor {
++ /**
++ * Limit construction.
++ */
++ ExchangeInterceptor() {
++ // Empty;
++ }
++
++ /**
++ * Hook to manipulate an HTTPExchange as is is about to be processed.
++ *
++ * @param exch original exchange that would be processed
++ * @return replacement exchange instance, or {@code null} to skip
++ * processing of this exchange
++ */
++ abstract HTTPExchange interceptExchange(final HTTPExchange exch);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Determine whether or not we should perform assertions. Assertions
++ * can be specified via system property explicitly, or defaulted to
++ * the JVM assertions status.
++ */
++ static {
++ final String prop =
++ BOSHClient.class.getSimpleName() + ".assertionsEnabled";
++ boolean enabled = false;
++ if (System.getProperty(prop) == null) {
++ assert enabled = true;
++ } else {
++ enabled = Boolean.getBoolean(prop);
++ }
++ ASSERTIONS = enabled;
++ }
++
++ /**
++ * Prevent direct construction.
++ */
++ private BOSHClient(final BOSHClientConfig sessCfg) {
++ cfg = sessCfg;
++ init();
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Public methods:
++
++ /**
++ * Create a new BOSH client session using the client configuration
++ * information provided.
++ *
++ * @param clientCfg session configuration
++ * @return BOSH session instance
++ */
++ public static BOSHClient create(final BOSHClientConfig clientCfg) {
++ if (clientCfg == null) {
++ throw(new IllegalArgumentException(
++ "Client configuration may not be null"));
++ }
++ return new BOSHClient(clientCfg);
++ }
++
++ /**
++ * Get the client configuration that was used to create this client
++ * instance.
++ *
++ * @return client configuration
++ */
++ public BOSHClientConfig getBOSHClientConfig() {
++ return cfg;
++ }
++
++ /**
++ * Adds a connection listener to the session.
++ *
++ * @param listener connection listener to add, if not already added
++ */
++ public void addBOSHClientConnListener(
++ final BOSHClientConnListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ connListeners.add(listener);
++ }
++
++ /**
++ * Removes a connection listener from the session.
++ *
++ * @param listener connection listener to remove, if previously added
++ */
++ public void removeBOSHClientConnListener(
++ final BOSHClientConnListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ connListeners.remove(listener);
++ }
++
++ /**
++ * Adds a request message listener to the session.
++ *
++ * @param listener request listener to add, if not already added
++ */
++ public void addBOSHClientRequestListener(
++ final BOSHClientRequestListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ requestListeners.add(listener);
++ }
++
++ /**
++ * Removes a request message listener from the session, if previously
++ * added.
++ *
++ * @param listener instance to remove
++ */
++ public void removeBOSHClientRequestListener(
++ final BOSHClientRequestListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ requestListeners.remove(listener);
++ }
++
++ /**
++ * Adds a response message listener to the session.
++ *
++ * @param listener response listener to add, if not already added
++ */
++ public void addBOSHClientResponseListener(
++ final BOSHClientResponseListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ responseListeners.add(listener);
++ }
++
++ /**
++ * Removes a response message listener from the session, if previously
++ * added.
++ *
++ * @param listener instance to remove
++ */
++ public void removeBOSHClientResponseListener(
++ final BOSHClientResponseListener listener) {
++ if (listener == null) {
++ throw(new IllegalArgumentException(NULL_LISTENER));
++ }
++ responseListeners.remove(listener);
++ }
++
++ /**
++ * Send the provided message data to the remote connection manager. The
++ * provided message body does not need to have any BOSH-specific attribute
++ * information set. It only needs to contain the actual message payload
++ * that should be delivered to the remote server.
++ *
++ * The first call to this method will result in a connection attempt
++ * to the remote connection manager. Subsequent calls to this method
++ * will block until the underlying session state allows for the message
++ * to be transmitted. In certain scenarios - such as when the maximum
++ * number of outbound connections has been reached - calls to this method
++ * will block for short periods of time.
++ *
++ * @param body message data to send to remote server
++ * @throws BOSHException on message transmission failure
++ */
++ public void send(final ComposableBody body) throws BOSHException {
++ assertUnlocked();
++ if (body == null) {
++ throw(new IllegalArgumentException(
++ "Message body may not be null"));
++ }
++
++ HTTPExchange exch;
++ CMSessionParams params;
++ lock.lock();
++ try {
++ blockUntilSendable(body);
++ if (!isWorking() && !isTermination(body)) {
++ throw(new BOSHException(
++ "Cannot send message when session is closed"));
++ }
++
++ long rid = requestIDSeq.getNextRID();
++ ComposableBody request = body;
++ params = cmParams;
++ if (params == null && exchanges.isEmpty()) {
++ // This is the first message being sent
++ request = applySessionCreationRequest(rid, body);
++ } else {
++ request = applySessionData(rid, body);
++ if (cmParams.isAckingRequests()) {
++ pendingRequestAcks.add(request);
++ }
++ }
++ exch = new HTTPExchange(request);
++ exchanges.add(exch);
++ notEmpty.signalAll();
++ clearEmptyRequest();
++ } finally {
++ lock.unlock();
++ }
++ AbstractBody finalReq = exch.getRequest();
++ HTTPResponse resp = httpSender.send(params, finalReq);
++ exch.setHTTPResponse(resp);
++ fireRequestSent(finalReq);
++ }
++
++ /**
++ * Attempt to pause the current session. When supported by the remote
++ * connection manager, pausing the session will result in the connection
++ * manager closing out all outstanding requests (including the pause
++ * request) and increases the inactivity timeout of the session. The
++ * exact value of the temporary timeout is dependent upon the connection
++ * manager. This method should be used if a client encounters an
++ * exceptional temporary situation during which it will be unable to send
++ * requests to the connection manager for a period of time greater than
++ * the maximum inactivity period.
++ *
++ * The session will revert back to it's normal, unpaused state when the
++ * client sends it's next message.
++ *
++ * @return {@code true} if the connection manager supports session pausing,
++ * {@code false} if the connection manager does not support session
++ * pausing or if the session has not yet been established
++ */
++ public boolean pause() {
++ assertUnlocked();
++ lock.lock();
++ AttrMaxPause maxPause = null;
++ try {
++ if (cmParams == null) {
++ return false;
++ }
++
++ maxPause = cmParams.getMaxPause();
++ if (maxPause == null) {
++ return false;
++ }
++ } finally {
++ lock.unlock();
++ }
++ try {
++ send(ComposableBody.builder()
++ .setAttribute(Attributes.PAUSE, maxPause.toString())
++ .build());
++ } catch (BOSHException boshx) {
++ LOG.log(Level.FINEST, "Could not send pause", boshx);
++ }
++ return true;
++ }
++
++ /**
++ * End the BOSH session by disconnecting from the remote BOSH connection
++ * manager.
++ *
++ * @throws BOSHException when termination message cannot be sent
++ */
++ public void disconnect() throws BOSHException {
++ disconnect(ComposableBody.builder().build());
++ }
++
++ /**
++ * End the BOSH session by disconnecting from the remote BOSH connection
++ * manager, sending the provided content in the final connection
++ * termination message.
++ *
++ * @param msg final message to send
++ * @throws BOSHException when termination message cannot be sent
++ */
++ public void disconnect(final ComposableBody msg) throws BOSHException {
++ if (msg == null) {
++ throw(new IllegalArgumentException(
++ "Message body may not be null"));
++ }
++
++ Builder builder = msg.rebuild();
++ builder.setAttribute(Attributes.TYPE, TERMINATE);
++ send(builder.build());
++ }
++
++ /**
++ * Forcibly close this client session instance. The preferred mechanism
++ * to close the connection is to send a disconnect message and wait for
++ * organic termination. Calling this method simply shuts down the local
++ * session without sending a termination message, releasing all resources
++ * associated with the session.
++ */
++ public void close() {
++ dispose(new BOSHException("Session explicitly closed by caller"));
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Get the current CM session params.
++ *
++ * @return current session params, or {@code null}
++ */
++ CMSessionParams getCMSessionParams() {
++ lock.lock();
++ try {
++ return cmParams;
++ } finally {
++ lock.unlock();
++ }
++ }
++
++ /**
++ * Wait until no more messages are waiting to be processed.
++ */
++ void drain() {
++ lock.lock();
++ try {
++ LOG.finest("Waiting while draining...");
++ while (isWorking()
++ && (emptyRequestFuture == null
++ || emptyRequestFuture.isDone())) {
++ try {
++ drained.await();
++ } catch (InterruptedException intx) {
++ LOG.log(Level.FINEST, INTERRUPTED, intx);
++ }
++ }
++ LOG.finest("Drained");
++ } finally {
++ lock.unlock();
++ }
++ }
++
++ /**
++ * Test method used to forcibly discard next exchange.
++ *
++ * @param interceptor exchange interceptor
++ */
++ void setExchangeInterceptor(final ExchangeInterceptor interceptor) {
++ exchInterceptor.set(interceptor);
++ }
++
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Initialize the session. This initializes the underlying HTTP
++ * transport implementation and starts the receive thread.
++ */
++ private void init() {
++ assertUnlocked();
++
++ lock.lock();
++ try {
++ httpSender.init(cfg);
++ procThread = new Thread(procRunnable);
++ procThread.setDaemon(true);
++ procThread.setName(BOSHClient.class.getSimpleName()
++ + "[" + System.identityHashCode(this)
++ + "]: Receive thread");
++ procThread.start();
++ } finally {
++ lock.unlock();
++ }
++ }
++
++ /**
++ * Destroy this session.
++ *
++ * @param cause the reason for the session termination, or {@code null}
++ * for normal termination
++ */
++ private void dispose(final Throwable cause) {
++ assertUnlocked();
++
++ lock.lock();
++ try {
++ if (procThread == null) {
++ // Already disposed
++ return;
++ }
++ procThread = null;
++ } finally {
++ lock.unlock();
++ }
++
++ if (cause == null) {
++ fireConnectionClosed();
++ } else {
++ fireConnectionClosedOnError(cause);
++ }
++
++ lock.lock();
++ try {
++ clearEmptyRequest();
++ exchanges = null;
++ cmParams = null;
++ pendingResponseAcks = null;
++ pendingRequestAcks = null;
++ notEmpty.signalAll();
++ notFull.signalAll();
++ drained.signalAll();
++ } finally {
++ lock.unlock();
++ }
++
++ httpSender.destroy();
++ schedExec.shutdownNow();
++ }
++
++ /**
++ * Determines if the message body specified indicates a request to
++ * pause the session.
++ *
++ * @param msg message to evaluate
++ * @return {@code true} if the message is a pause request, {@code false}
++ * otherwise
++ */
++ private static boolean isPause(final AbstractBody msg) {
++ return msg.getAttribute(Attributes.PAUSE) != null;
++ }
++
++ /**
++ * Determines if the message body specified indicates a termination of
++ * the session.
++ *
++ * @param msg message to evaluate
++ * @return {@code true} if the message is a session termination,
++ * {@code false} otherwise
++ */
++ private static boolean isTermination(final AbstractBody msg) {
++ return TERMINATE.equals(msg.getAttribute(Attributes.TYPE));
++ }
++
++ /**
++ * Evaluates the HTTP response code and response message and returns the
++ * terminal binding condition that it describes, if any.
++ *
++ * @param respCode HTTP response code
++ * @param respBody response body
++ * @return terminal binding condition, or {@code null} if not a terminal
++ * binding condition message
++ */
++ private TerminalBindingCondition getTerminalBindingCondition(
++ final int respCode,
++ final AbstractBody respBody) {
++ assertLocked();
++
++ if (isTermination(respBody)) {
++ String str = respBody.getAttribute(Attributes.CONDITION);
++ return TerminalBindingCondition.forString(str);
++ }
++ // Check for deprecated HTTP Error Conditions
++ if (cmParams != null && cmParams.getVersion() == null) {
++ return TerminalBindingCondition.forHTTPResponseCode(respCode);
++ }
++ return null;
++ }
++
++ /**
++ * Determines if the message specified is immediately sendable or if it
++ * needs to block until the session state changes.
++ *
++ * @param msg message to evaluate
++ * @return {@code true} if the message can be immediately sent,
++ * {@code false} otherwise
++ */
++ private boolean isImmediatelySendable(final AbstractBody msg) {
++ assertLocked();
++
++ if (cmParams == null) {
++ // block if we're waiting for a response to our first request
++ return exchanges.isEmpty();
++ }
++
++ AttrRequests requests = cmParams.getRequests();
++ if (requests == null) {
++ return true;
++ }
++ int maxRequests = requests.intValue();
++ if (exchanges.size() < maxRequests) {
++ return true;
++ }
++ if (exchanges.size() == maxRequests
++ && (isTermination(msg) || isPause(msg))) {
++ // One additional terminate or pause message is allowed
++ return true;
++ }
++ return false;
++ }
++
++ /**
++ * Determines whether or not the session is still active.
++ *
++ * @return {@code true} if it is, {@code false} otherwise
++ */
++ private boolean isWorking() {
++ assertLocked();
++
++ return procThread != null;
++ }
++
++ /**
++ * Blocks until either the message provided becomes immediately
++ * sendable or until the session is terminated.
++ *
++ * @param msg message to evaluate
++ */
++ private void blockUntilSendable(final AbstractBody msg) {
++ assertLocked();
++
++ while (isWorking() && !isImmediatelySendable(msg)) {
++ try {
++ notFull.await();
++ } catch (InterruptedException intx) {
++ LOG.log(Level.FINEST, INTERRUPTED, intx);
++ }
++ }
++ }
++
++ /**
++ * Modifies the specified body message such that it becomes a new
++ * BOSH session creation request.
++ *
++ * @param rid request ID to use
++ * @param orig original body to modify
++ * @return modified message which acts as a session creation request
++ */
++ private ComposableBody applySessionCreationRequest(
++ final long rid, final ComposableBody orig) throws BOSHException {
++ assertLocked();
++
++ Builder builder = orig.rebuild();
++ builder.setAttribute(Attributes.TO, cfg.getTo());
++ builder.setAttribute(Attributes.XML_LANG, cfg.getLang());
++ builder.setAttribute(Attributes.VER,
++ AttrVersion.getSupportedVersion().toString());
++ builder.setAttribute(Attributes.WAIT, "60");
++ builder.setAttribute(Attributes.HOLD, "1");
++ builder.setAttribute(Attributes.RID, Long.toString(rid));
++ applyRoute(builder);
++ applyFrom(builder);
++ builder.setAttribute(Attributes.ACK, "1");
++
++ // Make sure the following are NOT present (i.e., during retries)
++ builder.setAttribute(Attributes.SID, null);
++ return builder.build();
++ }
++
++ /**
++ * Applies routing information to the request message who's builder has
++ * been provided.
++ *
++ * @param builder builder instance to add routing information to
++ */
++ private void applyRoute(final Builder builder) {
++ assertLocked();
++
++ String route = cfg.getRoute();
++ if (route != null) {
++ builder.setAttribute(Attributes.ROUTE, route);
++ }
++ }
++
++ /**
++ * Applies the local station ID information to the request message who's
++ * builder has been provided.
++ *
++ * @param builder builder instance to add station ID information to
++ */
++ private void applyFrom(final Builder builder) {
++ assertLocked();
++
++ String from = cfg.getFrom();
++ if (from != null) {
++ builder.setAttribute(Attributes.FROM, from);
++ }
++ }
++
++ /**
++ * Applies existing session data to the outbound request, returning the
++ * modified request.
++ *
++ * This method assumes the lock is currently held.
++ *
++ * @param rid request ID to use
++ * @param orig original/raw request
++ * @return modified request with session information applied
++ */
++ private ComposableBody applySessionData(
++ final long rid,
++ final ComposableBody orig) throws BOSHException {
++ assertLocked();
++
++ Builder builder = orig.rebuild();
++ builder.setAttribute(Attributes.SID,
++ cmParams.getSessionID().toString());
++ builder.setAttribute(Attributes.RID, Long.toString(rid));
++ applyResponseAcknowledgement(builder, rid);
++ return builder.build();
++ }
++
++ /**
++ * Sets the 'ack' attribute of the request to the value of the highest
++ * 'rid' of a request for which it has already received a response in the
++ * case where it has also received all responses associated with lower
++ * 'rid' values. The only exception is that, after its session creation
++ * request, the client SHOULD NOT include an 'ack' attribute in any request
++ * if it has received responses to all its previous requests.
++ *
++ * @param builder message builder
++ * @param rid current request RID
++ */
++ private void applyResponseAcknowledgement(
++ final Builder builder,
++ final long rid) {
++ assertLocked();
++
++ if (responseAck.equals(Long.valueOf(-1L))) {
++ // We have not received any responses yet
++ return;
++ }
++
++ Long prevRID = Long.valueOf(rid - 1L);
++ if (responseAck.equals(prevRID)) {
++ // Implicit ack
++ return;
++ }
++
++ builder.setAttribute(Attributes.ACK, responseAck.toString());
++ }
++
++ /**
++ * While we are "connected", process received responses.
++ *
++ * This method is run in the processing thread.
++ */
++ private void processMessages() {
++ LOG.log(Level.FINEST, "Processing thread starting");
++ try {
++ HTTPExchange exch;
++ do {
++ exch = nextExchange();
++ if (exch == null) {
++ break;
++ }
++
++ // Test hook to manipulate what the client sees:
++ ExchangeInterceptor interceptor = exchInterceptor.get();
++ if (interceptor != null) {
++ HTTPExchange newExch = interceptor.interceptExchange(exch);
++ if (newExch == null) {
++ LOG.log(Level.FINE, "Discarding exchange on request "
++ + "of test hook: RID="
++ + exch.getRequest().getAttribute(
++ Attributes.RID));
++ lock.lock();
++ try {
++ exchanges.remove(exch);
++ } finally {
++ lock.unlock();
++ }
++ continue;
++ }
++ exch = newExch;
++ }
++
++ processExchange(exch);
++ } while (true);
++ } finally {
++ LOG.log(Level.FINEST, "Processing thread exiting");
++ }
++
++ }
++
++ /**
++ * Get the next message exchange to process, blocking until one becomes
++ * available if nothing is already waiting for processing.
++ *
++ * @return next available exchange to process, or {@code null} if no
++ * exchanges are immediately available
++ */
++ private HTTPExchange nextExchange() {
++ assertUnlocked();
++
++ final Thread thread = Thread.currentThread();
++ HTTPExchange exch = null;
++ lock.lock();
++ try {
++ do {
++ if (!thread.equals(procThread)) {
++ break;
++ }
++ exch = exchanges.peek();
++ if (exch == null) {
++ try {
++ notEmpty.await();
++ } catch (InterruptedException intx) {
++ LOG.log(Level.FINEST, INTERRUPTED, intx);
++ }
++ }
++ } while (exch == null);
++ } finally {
++ lock.unlock();
++ }
++ return exch;
++ }
++
++ /**
++ * Process the next, provided exchange. This is the main processing
++ * method of the receive thread.
++ *
++ * @param exch message exchange to process
++ */
++ private void processExchange(final HTTPExchange exch) {
++ assertUnlocked();
++
++ HTTPResponse resp;
++ AbstractBody body;
++ int respCode;
++ try {
++ resp = exch.getHTTPResponse();
++ body = resp.getBody();
++ respCode = resp.getHTTPStatus();
++ } catch (BOSHException boshx) {
++ LOG.log(Level.FINEST, "Could not obtain response", boshx);
++ dispose(boshx);
++ return;
++ } catch (InterruptedException intx) {
++ LOG.log(Level.FINEST, INTERRUPTED, intx);
++ dispose(intx);
++ return;
++ }
++ fireResponseReceived(body);
++
++ // Process the message with the current session state
++ AbstractBody req = exch.getRequest();
++ CMSessionParams params;
++ List toResend = null;
++ lock.lock();
++ try {
++ // Check for session creation response info, if needed
++ if (cmParams == null) {
++ cmParams = CMSessionParams.fromSessionInit(req, body);
++
++ // The following call handles the lock. It's not an escape.
++ fireConnectionEstablished();
++ }
++ params = cmParams;
++
++ checkForTerminalBindingConditions(body, respCode);
++ if (isTermination(body)) {
++ // Explicit termination
++ lock.unlock();
++ dispose(null);
++ return;
++ }
++
++ if (isRecoverableBindingCondition(body)) {
++ // Retransmit outstanding requests
++ if (toResend == null) {
++ toResend = new ArrayList(exchanges.size());
++ }
++ for (HTTPExchange exchange : exchanges) {
++ HTTPExchange resendExch =
++ new HTTPExchange(exchange.getRequest());
++ toResend.add(resendExch);
++ }
++ for (HTTPExchange exchange : toResend) {
++ exchanges.add(exchange);
++ }
++ } else {
++ // Process message as normal
++ processRequestAcknowledgements(req, body);
++ processResponseAcknowledgementData(req);
++ HTTPExchange resendExch =
++ processResponseAcknowledgementReport(body);
++ if (resendExch != null && toResend == null) {
++ toResend = new ArrayList(1);
++ toResend.add(resendExch);
++ exchanges.add(resendExch);
++ }
++ }
++ } catch (BOSHException boshx) {
++ LOG.log(Level.FINEST, "Could not process response", boshx);
++ lock.unlock();
++ dispose(boshx);
++ return;
++ } finally {
++ if (lock.isHeldByCurrentThread()) {
++ try {
++ exchanges.remove(exch);
++ if (exchanges.isEmpty()) {
++ scheduleEmptyRequest(processPauseRequest(req));
++ }
++ notFull.signalAll();
++ } finally {
++ lock.unlock();
++ }
++ }
++ }
++
++ if (toResend != null) {
++ for (HTTPExchange resend : toResend) {
++ HTTPResponse response =
++ httpSender.send(params, resend.getRequest());
++ resend.setHTTPResponse(response);
++ fireRequestSent(resend.getRequest());
++ }
++ }
++ }
++
++ /**
++ * Clears any scheduled empty requests.
++ */
++ private void clearEmptyRequest() {
++ assertLocked();
++
++ if (emptyRequestFuture != null) {
++ emptyRequestFuture.cancel(false);
++ emptyRequestFuture = null;
++ }
++ }
++
++ /**
++ * Calculates the default empty request delay/interval to use for the
++ * active session.
++ *
++ * @return delay in milliseconds
++ */
++ private long getDefaultEmptyRequestDelay() {
++ assertLocked();
++
++ // Figure out how long we should wait before sending an empty request
++ AttrPolling polling = cmParams.getPollingInterval();
++ long delay;
++ if (polling == null) {
++ delay = EMPTY_REQUEST_DELAY;
++ } else {
++ delay = polling.getInMilliseconds();
++ }
++ return delay;
++ }
++
++ /**
++ * Schedule an empty request to be sent if no other requests are
++ * sent in a reasonable amount of time.
++ */
++ private void scheduleEmptyRequest(long delay) {
++ assertLocked();
++ if (delay < 0L) {
++ throw(new IllegalArgumentException(
++ "Empty request delay must be >= 0 (was: " + delay + ")"));
++ }
++
++ clearEmptyRequest();
++ if (!isWorking()) {
++ return;
++ }
++
++ // Schedule the transmission
++ if (LOG.isLoggable(Level.FINER)) {
++ LOG.finer("Scheduling empty request in " + delay + "ms");
++ }
++ try {
++ emptyRequestFuture = schedExec.schedule(emptyRequestRunnable,
++ delay, TimeUnit.MILLISECONDS);
++ } catch (RejectedExecutionException rex) {
++ LOG.log(Level.FINEST, "Could not schedule empty request", rex);
++ }
++ drained.signalAll();
++ }
++
++ /**
++ * Sends an empty request to maintain session requirements. If a request
++ * is sent within a reasonable time window, the empty request transmission
++ * will be cancelled.
++ */
++ private void sendEmptyRequest() {
++ assertUnlocked();
++ // Send an empty request
++ LOG.finest("Sending empty request");
++ try {
++ send(ComposableBody.builder().build());
++ } catch (BOSHException boshx) {
++ dispose(boshx);
++ }
++ }
++
++ /**
++ * Assert that the internal lock is held.
++ */
++ private void assertLocked() {
++ if (ASSERTIONS) {
++ if (!lock.isHeldByCurrentThread()) {
++ throw(new AssertionError("Lock is not held by current thread"));
++ }
++ return;
++ }
++ }
++
++ /**
++ * Assert that the internal lock is *not* held.
++ */
++ private void assertUnlocked() {
++ if (ASSERTIONS) {
++ if (lock.isHeldByCurrentThread()) {
++ throw(new AssertionError("Lock is held by current thread"));
++ }
++ return;
++ }
++ }
++
++ /**
++ * Checks to see if the response indicates a terminal binding condition
++ * (as per XEP-0124 section 17). If it does, an exception is thrown.
++ *
++ * @param body response body to evaluate
++ * @param code HTTP response code
++ * @throws BOSHException if a terminal binding condition is detected
++ */
++ private void checkForTerminalBindingConditions(
++ final AbstractBody body,
++ final int code)
++ throws BOSHException {
++ TerminalBindingCondition cond =
++ getTerminalBindingCondition(code, body);
++ if (cond != null) {
++ throw(new BOSHException(
++ "Terminal binding condition encountered: "
++ + cond.getCondition() + " ("
++ + cond.getMessage() + ")"));
++ }
++ }
++
++ /**
++ * Determines whether or not the response indicates a recoverable
++ * binding condition (as per XEP-0124 section 17).
++ *
++ * @param resp response body
++ * @return {@code true} if it does, {@code false} otherwise
++ */
++ private static boolean isRecoverableBindingCondition(
++ final AbstractBody resp) {
++ return ERROR.equals(resp.getAttribute(Attributes.TYPE));
++ }
++
++ /**
++ * Process the request to determine if the empty request delay
++ * can be determined by looking to see if the request is a pause
++ * request. If it can, the request's delay is returned, otherwise
++ * the default delay is returned.
++ *
++ * @return delay in milliseconds that should elapse prior to an
++ * empty message being sent
++ */
++ private long processPauseRequest(
++ final AbstractBody req) {
++ assertLocked();
++
++ if (cmParams != null && cmParams.getMaxPause() != null) {
++ try {
++ AttrPause pause = AttrPause.createFromString(
++ req.getAttribute(Attributes.PAUSE));
++ if (pause != null) {
++ long delay = pause.getInMilliseconds() - PAUSE_MARGIN;
++ if (delay < 0) {
++ delay = EMPTY_REQUEST_DELAY;
++ }
++ return delay;
++ }
++ } catch (BOSHException boshx) {
++ LOG.log(Level.FINEST, "Could not extract", boshx);
++ }
++ }
++
++ return getDefaultEmptyRequestDelay();
++ }
++
++ /**
++ * Check the response for request acknowledgements and take appropriate
++ * action.
++ *
++ * This method assumes the lock is currently held.
++ *
++ * @param req request
++ * @param resp response
++ */
++ private void processRequestAcknowledgements(
++ final AbstractBody req, final AbstractBody resp) {
++ assertLocked();
++
++ if (!cmParams.isAckingRequests()) {
++ return;
++ }
++
++ // If a report or time attribute is set, we aren't acking anything
++ if (resp.getAttribute(Attributes.REPORT) != null) {
++ return;
++ }
++
++ // Figure out what the highest acked RID is
++ String acked = resp.getAttribute(Attributes.ACK);
++ Long ackUpTo;
++ if (acked == null) {
++ // Implicit ack of all prior requests up until RID
++ ackUpTo = Long.parseLong(req.getAttribute(Attributes.RID));
++ } else {
++ ackUpTo = Long.parseLong(acked);
++ }
++
++ // Remove the acked requests from the list
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Removing pending acks up to: " + ackUpTo);
++ }
++ Iterator iter = pendingRequestAcks.iterator();
++ while (iter.hasNext()) {
++ AbstractBody pending = iter.next();
++ Long pendingRID = Long.parseLong(
++ pending.getAttribute(Attributes.RID));
++ if (pendingRID.compareTo(ackUpTo) <= 0) {
++ iter.remove();
++ }
++ }
++ }
++
++ /**
++ * Process the response in order to update the response acknowlegement
++ * data.
++ *
++ * This method assumes the lock is currently held.
++ *
++ * @param req request
++ */
++ private void processResponseAcknowledgementData(
++ final AbstractBody req) {
++ assertLocked();
++
++ Long rid = Long.parseLong(req.getAttribute(Attributes.RID));
++ if (responseAck.equals(Long.valueOf(-1L))) {
++ // This is the first request
++ responseAck = rid;
++ } else {
++ pendingResponseAcks.add(rid);
++ // Remove up until the first missing response (or end of queue)
++ Long whileVal = responseAck;
++ while (whileVal.equals(pendingResponseAcks.first())) {
++ responseAck = whileVal;
++ pendingResponseAcks.remove(whileVal);
++ whileVal = Long.valueOf(whileVal.longValue() + 1);
++ }
++ }
++ }
++
++ /**
++ * Process the response in order to check for and respond to any potential
++ * ack reports.
++ *
++ * This method assumes the lock is currently held.
++ *
++ * @param resp response
++ * @return exchange to transmit if a resend is to be performed, or
++ * {@code null} if no resend is necessary
++ * @throws BOSHException when a a retry is needed but cannot be performed
++ */
++ private HTTPExchange processResponseAcknowledgementReport(
++ final AbstractBody resp)
++ throws BOSHException {
++ assertLocked();
++
++ String reportStr = resp.getAttribute(Attributes.REPORT);
++ if (reportStr == null) {
++ // No report on this message
++ return null;
++ }
++
++ Long report = Long.parseLong(reportStr);
++ Long time = Long.parseLong(resp.getAttribute(Attributes.TIME));
++ if (LOG.isLoggable(Level.FINE)) {
++ LOG.fine("Received report of missing request (RID="
++ + report + ", time=" + time + "ms)");
++ }
++
++ // Find the missing request
++ Iterator iter = pendingRequestAcks.iterator();
++ AbstractBody req = null;
++ while (iter.hasNext() && req == null) {
++ AbstractBody pending = iter.next();
++ Long pendingRID = Long.parseLong(
++ pending.getAttribute(Attributes.RID));
++ if (report.equals(pendingRID)) {
++ req = pending;
++ }
++ }
++
++ if (req == null) {
++ throw(new BOSHException("Report of missing message with RID '"
++ + reportStr
++ + "' but local copy of that request was not found"));
++ }
++
++ // Resend the missing request
++ HTTPExchange exch = new HTTPExchange(req);
++ exchanges.add(exch);
++ notEmpty.signalAll();
++ return exch;
++ }
++
++ /**
++ * Notifies all request listeners that the specified request is being
++ * sent.
++ *
++ * @param request request being sent
++ */
++ private void fireRequestSent(final AbstractBody request) {
++ assertUnlocked();
++
++ BOSHMessageEvent event = null;
++ for (BOSHClientRequestListener listener : requestListeners) {
++ if (event == null) {
++ event = BOSHMessageEvent.createRequestSentEvent(this, request);
++ }
++ try {
++ listener.requestSent(event);
++ } catch (Exception ex) {
++ LOG.log(Level.WARNING, UNHANDLED, ex);
++ }
++ }
++ }
++
++ /**
++ * Notifies all response listeners that the specified response has been
++ * received.
++ *
++ * @param response response received
++ */
++ private void fireResponseReceived(final AbstractBody response) {
++ assertUnlocked();
++
++ BOSHMessageEvent event = null;
++ for (BOSHClientResponseListener listener : responseListeners) {
++ if (event == null) {
++ event = BOSHMessageEvent.createResponseReceivedEvent(
++ this, response);
++ }
++ try {
++ listener.responseReceived(event);
++ } catch (Exception ex) {
++ LOG.log(Level.WARNING, UNHANDLED, ex);
++ }
++ }
++ }
++
++ /**
++ * Notifies all connection listeners that the session has been successfully
++ * established.
++ */
++ private void fireConnectionEstablished() {
++ final boolean hadLock = lock.isHeldByCurrentThread();
++ if (hadLock) {
++ lock.unlock();
++ }
++ try {
++ BOSHClientConnEvent event = null;
++ for (BOSHClientConnListener listener : connListeners) {
++ if (event == null) {
++ event = BOSHClientConnEvent
++ .createConnectionEstablishedEvent(this);
++ }
++ try {
++ listener.connectionEvent(event);
++ } catch (Exception ex) {
++ LOG.log(Level.WARNING, UNHANDLED, ex);
++ }
++ }
++ } finally {
++ if (hadLock) {
++ lock.lock();
++ }
++ }
++ }
++
++ /**
++ * Notifies all connection listeners that the session has been
++ * terminated normally.
++ */
++ private void fireConnectionClosed() {
++ assertUnlocked();
++
++ BOSHClientConnEvent event = null;
++ for (BOSHClientConnListener listener : connListeners) {
++ if (event == null) {
++ event = BOSHClientConnEvent.createConnectionClosedEvent(this);
++ }
++ try {
++ listener.connectionEvent(event);
++ } catch (Exception ex) {
++ LOG.log(Level.WARNING, UNHANDLED, ex);
++ }
++ }
++ }
++
++ /**
++ * Notifies all connection listeners that the session has been
++ * terminated due to the exceptional condition provided.
++ *
++ * @param cause cause of the termination
++ */
++ private void fireConnectionClosedOnError(
++ final Throwable cause) {
++ assertUnlocked();
++
++ BOSHClientConnEvent event = null;
++ for (BOSHClientConnListener listener : connListeners) {
++ if (event == null) {
++ event = BOSHClientConnEvent
++ .createConnectionClosedOnErrorEvent(
++ this, pendingRequestAcks, cause);
++ }
++ try {
++ listener.connectionEvent(event);
++ } catch (Exception ex) {
++ LOG.log(Level.WARNING, UNHANDLED, ex);
++ }
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java
+new file mode 100644
+index 0000000..23915b6
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConfig.java
+@@ -0,0 +1,446 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.net.URI;
++import javax.net.ssl.SSLContext;
++
++/**
++ * BOSH client configuration information. Instances of this class contain
++ * all information necessary to establish connectivity with a remote
++ * connection manager.
++ *
++ * Instances of this class are immutable, thread-safe,
++ * and can be re-used to configure multiple client session instances.
++ */
++public final class BOSHClientConfig {
++
++ /**
++ * Connection manager URI.
++ */
++ private final URI uri;
++
++ /**
++ * Target domain.
++ */
++ private final String to;
++
++ /**
++ * Client ID of this station.
++ */
++ private final String from;
++
++ /**
++ * Default XML language.
++ */
++ private final String lang;
++
++ /**
++ * Routing information for messages sent to CM.
++ */
++ private final String route;
++
++ /**
++ * Proxy host.
++ */
++ private final String proxyHost;
++
++ /**
++ * Proxy port.
++ */
++ private final int proxyPort;
++
++ /**
++ * SSL context.
++ */
++ private final SSLContext sslContext;
++
++ /**
++ * Flag indicating that compression should be attempted, if possible.
++ */
++ private final boolean compressionEnabled;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Classes:
++
++ /**
++ * Class instance builder, after the builder pattern. This allows each
++ * {@code BOSHClientConfig} instance to be immutable while providing
++ * flexibility when building new {@code BOSHClientConfig} instances.
++ *
++ * Instances of this class are not thread-safe. If template-style
++ * use is desired, see the {@code create(BOSHClientConfig)} method.
++ */
++ public static final class Builder {
++ // Required args
++ private final URI bURI;
++ private final String bDomain;
++
++ // Optional args
++ private String bFrom;
++ private String bLang;
++ private String bRoute;
++ private String bProxyHost;
++ private int bProxyPort;
++ private SSLContext bSSLContext;
++ private Boolean bCompression;
++
++ /**
++ * Creates a new builder instance, used to create instances of the
++ * {@code BOSHClientConfig} class.
++ *
++ * @param cmURI URI to use to contact the connection manager
++ * @param domain target domain to communicate with
++ */
++ private Builder(final URI cmURI, final String domain) {
++ bURI = cmURI;
++ bDomain = domain;
++ }
++
++ /**
++ * Creates a new builder instance, used to create instances of the
++ * {@code BOSHClientConfig} class.
++ *
++ * @param cmURI URI to use to contact the connection manager
++ * @param domain target domain to communicate with
++ * @return builder instance
++ */
++ public static Builder create(final URI cmURI, final String domain) {
++ if (cmURI == null) {
++ throw(new IllegalArgumentException(
++ "Connection manager URI must not be null"));
++ }
++ if (domain == null) {
++ throw(new IllegalArgumentException(
++ "Target domain must not be null"));
++ }
++ String scheme = cmURI.getScheme();
++ if (!("http".equals(scheme) || "https".equals(scheme))) {
++ throw(new IllegalArgumentException(
++ "Only 'http' and 'https' URI are allowed"));
++ }
++ return new Builder(cmURI, domain);
++ }
++
++ /**
++ * Creates a new builder instance using the existing configuration
++ * provided as a starting point.
++ *
++ * @param cfg configuration to copy
++ * @return builder instance
++ */
++ public static Builder create(final BOSHClientConfig cfg) {
++ Builder result = new Builder(cfg.getURI(), cfg.getTo());
++ result.bFrom = cfg.getFrom();
++ result.bLang = cfg.getLang();
++ result.bRoute = cfg.getRoute();
++ result.bProxyHost = cfg.getProxyHost();
++ result.bProxyPort = cfg.getProxyPort();
++ result.bSSLContext = cfg.getSSLContext();
++ result.bCompression = cfg.isCompressionEnabled();
++ return result;
++ }
++
++ /**
++ * Set the ID of the client station, to be forwarded to the connection
++ * manager when new sessions are created.
++ *
++ * @param id client ID
++ * @return builder instance
++ */
++ public Builder setFrom(final String id) {
++ if (id == null) {
++ throw(new IllegalArgumentException(
++ "Client ID must not be null"));
++ }
++ bFrom = id;
++ return this;
++ }
++
++ /**
++ * Set the default language of any human-readable content within the
++ * XML.
++ *
++ * @param lang XML language ID
++ * @return builder instance
++ */
++ public Builder setXMLLang(final String lang) {
++ if (lang == null) {
++ throw(new IllegalArgumentException(
++ "Default language ID must not be null"));
++ }
++ bLang = lang;
++ return this;
++ }
++
++ /**
++ * Sets the destination server/domain that the client should connect to.
++ * Connection managers may be configured to enable sessions with more
++ * that one server in different domains. When requesting a session with
++ * such a "proxy" connection manager, a client should use this method to
++ * specify the server with which it wants to communicate.
++ *
++ * @param protocol connection protocol (e.g, "xmpp")
++ * @param host host or domain to be served by the remote server. Note
++ * that this is not necessarily the host name or domain name of the
++ * remote server.
++ * @param port port number of the remote server
++ * @return builder instance
++ */
++ public Builder setRoute(
++ final String protocol,
++ final String host,
++ final int port) {
++ if (protocol == null) {
++ throw(new IllegalArgumentException("Protocol cannot be null"));
++ }
++ if (protocol.contains(":")) {
++ throw(new IllegalArgumentException(
++ "Protocol cannot contain the ':' character"));
++ }
++ if (host == null) {
++ throw(new IllegalArgumentException("Host cannot be null"));
++ }
++ if (host.contains(":")) {
++ throw(new IllegalArgumentException(
++ "Host cannot contain the ':' character"));
++ }
++ if (port <= 0) {
++ throw(new IllegalArgumentException("Port number must be > 0"));
++ }
++ bRoute = protocol + ":" + host + ":" + port;
++ return this;
++ }
++
++ /**
++ * Specify the hostname and port of an HTTP proxy to connect through.
++ *
++ * @param hostName proxy hostname
++ * @param port proxy port number
++ * @return builder instance
++ */
++ public Builder setProxy(final String hostName, final int port) {
++ if (hostName == null || hostName.length() == 0) {
++ throw(new IllegalArgumentException(
++ "Proxy host name cannot be null or empty"));
++ }
++ if (port <= 0) {
++ throw(new IllegalArgumentException(
++ "Proxy port must be > 0"));
++ }
++ bProxyHost = hostName;
++ bProxyPort = port;
++ return this;
++ }
++
++ /**
++ * Set the SSL context to use for this session. This can be used
++ * to configure certificate-based authentication, etc..
++ *
++ * @param ctx SSL context
++ * @return builder instance
++ */
++ public Builder setSSLContext(final SSLContext ctx) {
++ if (ctx == null) {
++ throw(new IllegalArgumentException(
++ "SSL context cannot be null"));
++ }
++ bSSLContext = ctx;
++ return this;
++ }
++
++ /**
++ * Set whether or not compression of the underlying data stream
++ * should be attempted. By default, compression is disabled.
++ *
++ * @param enabled set to {@code true} if compression should be
++ * attempted when possible, {@code false} to disable compression
++ * @return builder instance
++ */
++ public Builder setCompressionEnabled(final boolean enabled) {
++ bCompression = Boolean.valueOf(enabled);
++ return this;
++ }
++
++ /**
++ * Build the immutable object instance with the current configuration.
++ *
++ * @return BOSHClientConfig instance
++ */
++ public BOSHClientConfig build() {
++ // Default XML language
++ String lang;
++ if (bLang == null) {
++ lang = "en";
++ } else {
++ lang = bLang;
++ }
++
++ // Default proxy port
++ int port;
++ if (bProxyHost == null) {
++ port = 0;
++ } else {
++ port = bProxyPort;
++ }
++
++ // Default compression
++ boolean compression;
++ if (bCompression == null) {
++ compression = false;
++ } else {
++ compression = bCompression.booleanValue();
++ }
++
++ return new BOSHClientConfig(
++ bURI,
++ bDomain,
++ bFrom,
++ lang,
++ bRoute,
++ bProxyHost,
++ port,
++ bSSLContext,
++ compression);
++ }
++
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructor:
++
++ /**
++ * Prevent direct construction.
++ *
++ * @param cURI URI of the connection manager to connect to
++ * @param cDomain the target domain of the first stream
++ * @param cFrom client ID
++ * @param cLang default XML language
++ * @param cRoute target route
++ * @param cProxyHost proxy host
++ * @param cProxyPort proxy port
++ * @param cSSLContext SSL context
++ * @param cCompression compression enabled flag
++ */
++ private BOSHClientConfig(
++ final URI cURI,
++ final String cDomain,
++ final String cFrom,
++ final String cLang,
++ final String cRoute,
++ final String cProxyHost,
++ final int cProxyPort,
++ final SSLContext cSSLContext,
++ final boolean cCompression) {
++ uri = cURI;
++ to = cDomain;
++ from = cFrom;
++ lang = cLang;
++ route = cRoute;
++ proxyHost = cProxyHost;
++ proxyPort = cProxyPort;
++ sslContext = cSSLContext;
++ compressionEnabled = cCompression;
++ }
++
++ /**
++ * Get the URI to use to contact the connection manager.
++ *
++ * @return connection manager URI.
++ */
++ public URI getURI() {
++ return uri;
++ }
++
++ /**
++ * Get the ID of the target domain.
++ *
++ * @return domain id
++ */
++ public String getTo() {
++ return to;
++ }
++
++ /**
++ * Get the ID of the local client.
++ *
++ * @return client id, or {@code null}
++ */
++ public String getFrom() {
++ return from;
++ }
++
++ /**
++ * Get the default language of any human-readable content within the
++ * XML. Defaults to "en".
++ *
++ * @return XML language ID
++ */
++ public String getLang() {
++ return lang;
++ }
++
++ /**
++ * Get the routing information for messages sent to the CM.
++ *
++ * @return route attribute string, or {@code null} if no routing
++ * info was provided.
++ */
++ public String getRoute() {
++ return route;
++ }
++
++ /**
++ * Get the HTTP proxy host to use.
++ *
++ * @return proxy host, or {@code null} if no proxy information was specified
++ */
++ public String getProxyHost() {
++ return proxyHost;
++ }
++
++ /**
++ * Get the HTTP proxy port to use.
++ *
++ * @return proxy port, or 0 if no proxy information was specified
++ */
++ public int getProxyPort() {
++ return proxyPort;
++ }
++
++ /**
++ * Get the SSL context to use for this session.
++ *
++ * @return SSL context instance to use, or {@code null} if no
++ * context instance was provided.
++ */
++ public SSLContext getSSLContext() {
++ return sslContext;
++ }
++
++ /**
++ * Determines whether or not compression of the underlying data stream
++ * should be attempted/allowed. Defaults to {@code false}.
++ *
++ * @return {@code true} if compression should be attempted, {@code false}
++ * if compression is disabled or was not specified
++ */
++ boolean isCompressionEnabled() {
++ return compressionEnabled;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java
+new file mode 100644
+index 0000000..0ac7943
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnEvent.java
+@@ -0,0 +1,189 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.ArrayList;
++import java.util.Collections;
++import java.util.EventObject;
++import java.util.List;
++
++/**
++ * Client connection event, notifying of changes in connection state.
++ *
++ * This class is immutable and thread-safe.
++ */
++public final class BOSHClientConnEvent extends EventObject {
++
++ /**
++ * Serialized version.
++ */
++ private static final long serialVersionUID = 1L;
++
++ /**
++ * Boolean flag indicating whether or not a session has been established
++ * and is currently active.
++ */
++ private final boolean connected;
++
++ /**
++ * List of outstanding requests which may not have been sent and/or
++ * acknowledged by the remote CM.
++ */
++ private final List requests;
++
++ /**
++ * Cause of the session termination, or {@code null}.
++ */
++ private final Throwable cause;
++
++ /**
++ * Creates a new connection event instance.
++ *
++ * @param source event source
++ * @param cConnected flag indicating whether or not the session is
++ * currently active
++ * @param cRequests outstanding requests when an error condition is
++ * detected, or {@code null} when not an error condition
++ * @param cCause cause of the error condition, or {@code null} when no
++ * error condition is present
++ */
++ private BOSHClientConnEvent(
++ final BOSHClient source,
++ final boolean cConnected,
++ final List cRequests,
++ final Throwable cCause) {
++ super(source);
++ connected = cConnected;
++ cause = cCause;
++
++ if (connected) {
++ if (cCause != null) {
++ throw(new IllegalStateException(
++ "Cannot be connected and have a cause"));
++ }
++ if (cRequests != null && cRequests.size() > 0) {
++ throw(new IllegalStateException(
++ "Cannot be connected and have outstanding requests"));
++ }
++ }
++
++ if (cRequests == null) {
++ requests = Collections.emptyList();
++ } else {
++ // Defensive copy:
++ requests = Collections.unmodifiableList(
++ new ArrayList(cRequests));
++ }
++ }
++
++ /**
++ * Creates a new connection establishment event.
++ *
++ * @param source client which has become connected
++ * @return event instance
++ */
++ static BOSHClientConnEvent createConnectionEstablishedEvent(
++ final BOSHClient source) {
++ return new BOSHClientConnEvent(source, true, null, null);
++ }
++
++ /**
++ * Creates a new successful connection closed event. This represents
++ * a clean termination of the client session.
++ *
++ * @param source client which has been disconnected
++ * @return event instance
++ */
++ static BOSHClientConnEvent createConnectionClosedEvent(
++ final BOSHClient source) {
++ return new BOSHClientConnEvent(source, false, null, null);
++ }
++
++ /**
++ * Creates a connection closed on error event. This represents
++ * an unexpected termination of the client session.
++ *
++ * @param source client which has been disconnected
++ * @param outstanding list of requests which may not have been received
++ * by the remote connection manager
++ * @param cause cause of termination
++ * @return event instance
++ */
++ static BOSHClientConnEvent createConnectionClosedOnErrorEvent(
++ final BOSHClient source,
++ final List outstanding,
++ final Throwable cause) {
++ return new BOSHClientConnEvent(source, false, outstanding, cause);
++ }
++
++ /**
++ * Gets the client from which this event originated.
++ *
++ * @return client instance
++ */
++ public BOSHClient getBOSHClient() {
++ return (BOSHClient) getSource();
++ }
++
++ /**
++ * Returns whether or not the session has been successfully established
++ * and is currently active.
++ *
++ * @return {@code true} if a session is active, {@code false} otherwise
++ */
++ public boolean isConnected() {
++ return connected;
++ }
++
++ /**
++ * Returns whether or not this event indicates an error condition. This
++ * will never return {@code true} when {@code isConnected()} returns
++ * {@code true}.
++ *
++ * @return {@code true} if the event indicates a terminal error has
++ * occurred, {@code false} otherwise.
++ */
++ public boolean isError() {
++ return cause != null;
++ }
++
++ /**
++ * Returns the underlying cause of the error condition. This method is
++ * guaranteed to return {@code null} when @{code isError()} returns
++ * {@code false}. Similarly, this method is guaranteed to return
++ * non-@{code null} if {@code isError()} returns {@code true}.
++ *
++ * @return underlying cause of the error condition, or {@code null} if
++ * this event does not represent an error condition
++ */
++ public Throwable getCause() {
++ return cause;
++ }
++
++ /**
++ * Get the list of requests which may not have been sent or were not
++ * acknowledged by the remote connection manager prior to session
++ * termination.
++ *
++ * @return list of messages which may not have been received by the remote
++ * connection manager, or an empty list if the session is still connected
++ */
++ public List getOutstandingRequests() {
++ return requests;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java
+new file mode 100644
+index 0000000..6d646cb
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientConnListener.java
+@@ -0,0 +1,34 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Interface used by parties interested in monitoring the connection state
++ * of a client session.
++ */
++public interface BOSHClientConnListener {
++
++ /**
++ * Called when the connection state of the client which the listener
++ * is registered against has changed. The event object supplied can
++ * be used to determine the current session state.
++ *
++ * @param connEvent connection event describing the state
++ */
++ void connectionEvent(BOSHClientConnEvent connEvent);
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java
+new file mode 100644
+index 0000000..2cc92f3
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientRequestListener.java
+@@ -0,0 +1,45 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Interface used by parties interested in monitoring outbound requests made
++ * by the client to the connection manager (CM). No opportunity is provided
++ * to manipulate the outbound request.
++ *
++ * The messages being sent are typically modified copies of the message
++ * body provided to the {@code BOSHClient} instance, built from the
++ * originally provided message body plus additional BOSH protocol
++ * state and information. Messages may also be sent automatically when the
++ * protocol requires it, such as maintaining a minimum number of open
++ * connections to the connection manager.
++ *
++ * Listeners are executed by the sending thread immediately prior to
++ * message transmission and should not block for any significant amount
++ * of time.
++ */
++public interface BOSHClientRequestListener {
++
++ /**
++ * Called when the listener is being notified that a client request is
++ * about to be sent to the connection manager.
++ *
++ * @param event event instance containing the message being sent
++ */
++ void requestSent(BOSHMessageEvent event);
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java
+new file mode 100644
+index 0000000..1d86e4f
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHClientResponseListener.java
+@@ -0,0 +1,37 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Interface used by parties interested in monitoring inbound responses
++ * to the client from the connection manager (CM). No opportunity is provided
++ * to manipulate the response.
++ *
++ * Listeners are executed by the message processing thread and should not
++ * block for any significant amount of time.
++ */
++public interface BOSHClientResponseListener {
++
++ /**
++ * Called when the listener is being notified that a response has been
++ * received from the connection manager.
++ *
++ * @param event event instance containing the message being sent
++ */
++ void responseReceived(BOSHMessageEvent event);
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java
+new file mode 100644
+index 0000000..e0bc05b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHException.java
+@@ -0,0 +1,50 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Exception class used by the BOSH API to minimize the number of checked
++ * exceptions which must be handled by the user of the API.
++ */
++public class BOSHException extends Exception {
++
++ /**
++ * Servial version UID.
++ */
++ private static final long serialVersionUID = 1L;
++
++ /**
++ * Creates a new exception isntance with the specified descriptive message.
++ *
++ * @param msg description of the exceptional condition
++ */
++ public BOSHException(final String msg) {
++ super(msg);
++ }
++
++ /**
++ * Creates a new exception isntance with the specified descriptive
++ * message and the underlying root cause of the exceptional condition.
++ *
++ * @param msg description of the exceptional condition
++ * @param cause root cause or instigator of the condition
++ */
++ public BOSHException(final String msg, final Throwable cause) {
++ super(msg, cause);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java
+new file mode 100644
+index 0000000..550903e
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BOSHMessageEvent.java
+@@ -0,0 +1,92 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.EventObject;
++
++/**
++ * Event representing a message sent to or from a BOSH connection manager.
++ *
++ * This class is immutable and thread-safe.
++ */
++public final class BOSHMessageEvent extends EventObject {
++
++ /**
++ * Serialized version.
++ */
++ private static final long serialVersionUID = 1L;
++
++ /**
++ * Message which was sent or received.
++ */
++ private final AbstractBody body;
++
++ /**
++ * Creates a new message event instance.
++ *
++ * @param source event source
++ * @param cBody message body
++ */
++ private BOSHMessageEvent(
++ final Object source,
++ final AbstractBody cBody) {
++ super(source);
++ if (cBody == null) {
++ throw(new IllegalArgumentException(
++ "message body may not be null"));
++ }
++ body = cBody;
++ }
++
++ /**
++ * Creates a new message event for clients sending events to the
++ * connection manager.
++ *
++ * @param source sender of the message
++ * @param body message body
++ * @return event instance
++ */
++ static BOSHMessageEvent createRequestSentEvent(
++ final BOSHClient source,
++ final AbstractBody body) {
++ return new BOSHMessageEvent(source, body);
++ }
++
++ /**
++ * Creates a new message event for clients receiving new messages
++ * from the connection manager.
++ *
++ * @param source receiver of the message
++ * @param body message body
++ * @return event instance
++ */
++ static BOSHMessageEvent createResponseReceivedEvent(
++ final BOSHClient source,
++ final AbstractBody body) {
++ return new BOSHMessageEvent(source, body);
++ }
++
++ /**
++ * Gets the message body which was sent or received.
++ *
++ * @return message body
++ */
++ public AbstractBody getBody() {
++ return body;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java
+new file mode 100644
+index 0000000..5ef5276
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParser.java
+@@ -0,0 +1,36 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Interface for parser implementations to implement in order to abstract the
++ * business of XML parsing out of the Body class. This allows us to leverage
++ * a variety of parser implementations to gain performance advantages.
++ */
++interface BodyParser {
++
++ /**
++ * Parses the XML message, extracting the useful data from the initial
++ * body element and returning it in a results object.
++ *
++ * @param xml XML to parse
++ * @return useful data parsed out of the XML
++ * @throws BOSHException on parse error
++ */
++ BodyParserResults parse(String xml) throws BOSHException;
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java
+new file mode 100644
+index 0000000..955e4bf
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserResults.java
+@@ -0,0 +1,64 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.HashMap;
++import java.util.Map;
++
++/**
++ * Data extracted from a raw XML message by a BodyParser implementation.
++ * Currently, this is limited to the attributes of the wrapper element.
++ */
++final class BodyParserResults {
++
++ /**
++ * Map of qualified names to their values. This map is defined to
++ * match the requirement of the {@code Body} class to prevent
++ * excessive copying.
++ */
++ private final Map attrs =
++ new HashMap();
++
++ /**
++ * Constructor.
++ */
++ BodyParserResults() {
++ // Empty
++ }
++
++ /**
++ * Add an attribute definition to the results.
++ *
++ * @param name attribute's qualified name
++ * @param value attribute value
++ */
++ void addBodyAttributeValue(
++ final BodyQName name,
++ final String value) {
++ attrs.put(name, value);
++ }
++
++ /**
++ * Returns the map of attributes added by the parser.
++ *
++ * @return map of atributes. Note: This is the live instance, not a copy.
++ */
++ Map getAttributes() {
++ return attrs;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java
+new file mode 100644
+index 0000000..54c6c01
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserSAX.java
+@@ -0,0 +1,206 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.ByteArrayInputStream;
++import java.io.IOException;
++import java.io.InputStream;
++import java.lang.ref.SoftReference;
++import java.util.logging.Level;
++import java.util.logging.Logger;
++import javax.xml.parsers.ParserConfigurationException;
++import javax.xml.parsers.SAXParser;
++import javax.xml.parsers.SAXParserFactory;
++import org.xml.sax.Attributes;
++import org.xml.sax.SAXException;
++import org.xml.sax.helpers.DefaultHandler;
++
++/**
++ * Implementation of the BodyParser interface which uses the SAX API
++ * that is part of the JDK. Due to the fact that we can cache and reuse
++ * SAXPArser instances, this has proven to be significantly faster than the
++ * use of the javax.xml.stream API introduced in Java 6 while simultaneously
++ * providing an implementation accessible to Java 5 users.
++ */
++final class BodyParserSAX implements BodyParser {
++
++ /**
++ * Logger.
++ */
++ private static final Logger LOG =
++ Logger.getLogger(BodyParserSAX.class.getName());
++
++ /**
++ * SAX parser factory.
++ */
++ private static final SAXParserFactory SAX_FACTORY;
++ static {
++ SAX_FACTORY = SAXParserFactory.newInstance();
++ SAX_FACTORY.setNamespaceAware(true);
++ SAX_FACTORY.setValidating(false);
++ }
++
++ /**
++ * Thread local to contain a SAX parser instance for each thread that
++ * attempts to use one. This allows us to gain an order of magnitude of
++ * performance as a result of not constructing parsers for each
++ * invocation while retaining thread safety.
++ */
++ private static final ThreadLocal> PARSER =
++ new ThreadLocal>() {
++ @Override protected SoftReference initialValue() {
++ return new SoftReference(null);
++ }
++ };
++
++ /**
++ * SAX event handler class.
++ */
++ private static final class Handler extends DefaultHandler {
++ private final BodyParserResults result;
++ private final SAXParser parser;
++ private String defaultNS = null;
++
++ private Handler(SAXParser theParser, BodyParserResults results) {
++ parser = theParser;
++ result = results;
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ @Override
++ public void startElement(
++ final String uri,
++ final String localName,
++ final String qName,
++ final Attributes attributes) {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Start element: " + qName);
++ LOG.finest(" URI: " + uri);
++ LOG.finest(" local: " + localName);
++ }
++
++ BodyQName bodyName = AbstractBody.getBodyQName();
++ // Make sure the first element is correct
++ if (!(bodyName.getNamespaceURI().equals(uri)
++ && bodyName.getLocalPart().equals(localName))) {
++ throw(new IllegalStateException(
++ "Root element was not '" + bodyName.getLocalPart()
++ + "' in the '" + bodyName.getNamespaceURI()
++ + "' namespace. (Was '" + localName + "' in '" + uri
++ + "')"));
++ }
++
++ // Read in the attributes, making sure to expand the namespaces
++ // as needed.
++ for (int idx=0; idx < attributes.getLength(); idx++) {
++ String attrURI = attributes.getURI(idx);
++ if (attrURI.length() == 0) {
++ attrURI = defaultNS;
++ }
++ String attrLN = attributes.getLocalName(idx);
++ String attrVal = attributes.getValue(idx);
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest(" Attribute: {" + attrURI + "}"
++ + attrLN + " = '" + attrVal + "'");
++ }
++
++ BodyQName aqn = BodyQName.create(attrURI, attrLN);
++ result.addBodyAttributeValue(aqn, attrVal);
++ }
++
++ parser.reset();
++ }
++
++ /**
++ * {@inheritDoc}
++ *
++ * This implementation uses this event hook to keep track of the
++ * default namespace on the body element.
++ */
++ @Override
++ public void startPrefixMapping(
++ final String prefix,
++ final String uri) {
++ if (prefix.length() == 0) {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Prefix mapping: => " + uri);
++ }
++ defaultNS = uri;
++ } else {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.info("Prefix mapping: " + prefix + " => " + uri);
++ }
++ }
++ }
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // BodyParser interface methods:
++
++ /**
++ * {@inheritDoc}
++ */
++ public BodyParserResults parse(String xml) throws BOSHException {
++ BodyParserResults result = new BodyParserResults();
++ Exception thrown;
++ try {
++ InputStream inStream = new ByteArrayInputStream(xml.getBytes());
++ SAXParser parser = getSAXParser();
++ parser.parse(inStream, new Handler(parser, result));
++ return result;
++ } catch (SAXException saxx) {
++ thrown = saxx;
++ } catch (IOException iox) {
++ thrown = iox;
++ }
++ throw(new BOSHException("Could not parse body:\n" + xml, thrown));
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Gets a SAXParser for use in parsing incoming messages.
++ *
++ * @return parser instance
++ */
++ private static SAXParser getSAXParser() {
++ SoftReference ref = PARSER.get();
++ SAXParser result = ref.get();
++ if (result == null) {
++ Exception thrown;
++ try {
++ result = SAX_FACTORY.newSAXParser();
++ ref = new SoftReference(result);
++ PARSER.set(ref);
++ return result;
++ } catch (ParserConfigurationException ex) {
++ thrown = ex;
++ } catch (SAXException ex) {
++ thrown = ex;
++ }
++ throw(new IllegalStateException(
++ "Could not create SAX parser", thrown));
++ } else {
++ result.reset();
++ return result;
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java
+new file mode 100644
+index 0000000..5f23b06
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyParserXmlPull.java
+@@ -0,0 +1,165 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.IOException;
++import java.io.StringReader;
++import java.lang.ref.SoftReference;
++import java.util.logging.Level;
++import java.util.logging.Logger;
++import javax.xml.XMLConstants;
++import org.xmlpull.v1.XmlPullParser;
++import org.xmlpull.v1.XmlPullParserException;
++import org.xmlpull.v1.XmlPullParserFactory;
++
++/**
++ * Implementation of the BodyParser interface which uses the XmlPullParser
++ * API. When available, this API provides an order of magnitude performance
++ * improvement over the default SAX parser implementation.
++ */
++final class BodyParserXmlPull implements BodyParser {
++
++ /**
++ * Logger.
++ */
++ private static final Logger LOG =
++ Logger.getLogger(BodyParserXmlPull.class.getName());
++
++ /**
++ * Thread local to contain a XmlPullParser instance for each thread that
++ * attempts to use one. This allows us to gain an order of magnitude of
++ * performance as a result of not constructing parsers for each
++ * invocation while retaining thread safety.
++ */
++ private static final ThreadLocal> XPP_PARSER =
++ new ThreadLocal>() {
++ @Override protected SoftReference initialValue() {
++ return new SoftReference(null);
++ }
++ };
++
++ ///////////////////////////////////////////////////////////////////////////
++ // BodyParser interface methods:
++
++ /**
++ * {@inheritDoc}
++ */
++ public BodyParserResults parse(final String xml) throws BOSHException {
++ BodyParserResults result = new BodyParserResults();
++ Exception thrown;
++ try {
++ XmlPullParser xpp = getXmlPullParser();
++
++ xpp.setInput(new StringReader(xml));
++ int eventType = xpp.getEventType();
++ while (eventType != XmlPullParser.END_DOCUMENT) {
++ if (eventType == XmlPullParser.START_TAG) {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Start tag: " + xpp.getName());
++ }
++ } else {
++ eventType = xpp.next();
++ continue;
++ }
++
++ String prefix = xpp.getPrefix();
++ if (prefix == null) {
++ prefix = XMLConstants.DEFAULT_NS_PREFIX;
++ }
++ String uri = xpp.getNamespace();
++ String localName = xpp.getName();
++ QName name = new QName(uri, localName, prefix);
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Start element: ");
++ LOG.finest(" prefix: " + prefix);
++ LOG.finest(" URI: " + uri);
++ LOG.finest(" local: " + localName);
++ }
++
++ BodyQName bodyName = AbstractBody.getBodyQName();
++ if (!bodyName.equalsQName(name)) {
++ throw(new IllegalStateException(
++ "Root element was not '" + bodyName.getLocalPart()
++ + "' in the '" + bodyName.getNamespaceURI()
++ + "' namespace. (Was '" + localName
++ + "' in '" + uri + "')"));
++ }
++
++ for (int idx=0; idx < xpp.getAttributeCount(); idx++) {
++ String attrURI = xpp.getAttributeNamespace(idx);
++ if (attrURI.length() == 0) {
++ attrURI = xpp.getNamespace(null);
++ }
++ String attrPrefix = xpp.getAttributePrefix(idx);
++ if (attrPrefix == null) {
++ attrPrefix = XMLConstants.DEFAULT_NS_PREFIX;
++ }
++ String attrLN = xpp.getAttributeName(idx);
++ String attrVal = xpp.getAttributeValue(idx);
++ BodyQName aqn = BodyQName.createWithPrefix(
++ attrURI, attrLN, attrPrefix);
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest(" Attribute: {" + attrURI + "}"
++ + attrLN + " = '" + attrVal + "'");
++ }
++ result.addBodyAttributeValue(aqn, attrVal);
++ }
++ break;
++ }
++ return result;
++ } catch (RuntimeException rtx) {
++ thrown = rtx;
++ } catch (XmlPullParserException xmlppx) {
++ thrown = xmlppx;
++ } catch (IOException iox) {
++ thrown = iox;
++ }
++ throw(new BOSHException("Could not parse body:\n" + xml, thrown));
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Gets a XmlPullParser for use in parsing incoming messages.
++ *
++ * @return parser instance
++ */
++ private static XmlPullParser getXmlPullParser() {
++ SoftReference ref = XPP_PARSER.get();
++ XmlPullParser result = ref.get();
++ if (result == null) {
++ Exception thrown;
++ try {
++ XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
++ factory.setNamespaceAware(true);
++ factory.setValidating(false);
++ result = factory.newPullParser();
++ ref = new SoftReference(result);
++ XPP_PARSER.set(ref);
++ return result;
++ } catch (Exception ex) {
++ thrown = ex;
++ }
++ throw(new IllegalStateException(
++ "Could not create XmlPull parser", thrown));
++ } else {
++ return result;
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java
+new file mode 100644
+index 0000000..83acdf1
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/BodyQName.java
+@@ -0,0 +1,165 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Qualified name of an attribute of the wrapper element. This class is
++ * analagous to the {@code javax.xml.namespace.QName} class.
++ * Each qualified name consists of a namespace URI and a local name.
++ *
++ * Instances of this class are immutable and thread-safe.
++ */
++public final class BodyQName {
++
++ /**
++ * BOSH namespace URI.
++ */
++ static final String BOSH_NS_URI =
++ "http://jabber.org/protocol/httpbind";
++
++ /**
++ * Namespace URI.
++ */
++ private final QName qname;
++
++ /**
++ * Private constructor to prevent direct construction.
++ *
++ * @param wrapped QName instance to wrap
++ */
++ private BodyQName(
++ final QName wrapped) {
++ qname = wrapped;
++ }
++
++ /**
++ * Creates a new qualified name using a namespace URI and local name.
++ *
++ * @param uri namespace URI
++ * @param local local name
++ * @return BodyQName instance
++ */
++ public static BodyQName create(
++ final String uri,
++ final String local) {
++ return createWithPrefix(uri, local, null);
++ }
++
++ /**
++ * Creates a new qualified name using a namespace URI and local name
++ * along with an optional prefix.
++ *
++ * @param uri namespace URI
++ * @param local local name
++ * @param prefix optional prefix or @{code null} for no prefix
++ * @return BodyQName instance
++ */
++ public static BodyQName createWithPrefix(
++ final String uri,
++ final String local,
++ final String prefix) {
++ if (uri == null || uri.length() == 0) {
++ throw(new IllegalArgumentException(
++ "URI is required and may not be null/empty"));
++ }
++ if (local == null || local.length() == 0) {
++ throw(new IllegalArgumentException(
++ "Local arg is required and may not be null/empty"));
++ }
++ if (prefix == null || prefix.length() == 0) {
++ return new BodyQName(new QName(uri, local));
++ } else {
++ return new BodyQName(new QName(uri, local, prefix));
++ }
++ }
++
++ /**
++ * Get the namespace URI of this qualified name.
++ *
++ * @return namespace uri
++ */
++ public String getNamespaceURI() {
++ return qname.getNamespaceURI();
++ }
++
++ /**
++ * Get the local part of this qualified name.
++ *
++ * @return local name
++ */
++ public String getLocalPart() {
++ return qname.getLocalPart();
++ }
++
++ /**
++ * Get the optional prefix used with this qualified name, or {@code null}
++ * if no prefix has been assiciated.
++ *
++ * @return prefix, or {@code null} if no prefix was supplied
++ */
++ public String getPrefix() {
++ return qname.getPrefix();
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ @Override
++ public boolean equals(final Object obj) {
++ if (obj instanceof BodyQName) {
++ BodyQName other = (BodyQName) obj;
++ return qname.equals(other.qname);
++ } else {
++ return false;
++ }
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ @Override
++ public int hashCode() {
++ return qname.hashCode();
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Creates a new qualified name using the BOSH namespace URI and local name.
++ *
++ * @param local local name
++ * @return BodyQName instance
++ */
++ static BodyQName createBOSH(
++ final String local) {
++ return createWithPrefix(BOSH_NS_URI, local, null);
++ }
++
++ /**
++ * Convenience method to compare this qualified name with a
++ * {@code javax.xml.namespace.QName}.
++ *
++ * @param otherName QName to compare to
++ * @return @{code true} if the qualified name is the same, {@code false}
++ * otherwise
++ */
++ boolean equalsQName(final QName otherName) {
++ return qname.equals(otherName);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java b/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java
+new file mode 100644
+index 0000000..bbed628
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/CMSessionParams.java
+@@ -0,0 +1,177 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * A BOSH connection manager session instance. This consolidates the
++ * configuration knowledge related to the CM session and provides a
++ * mechanism by which
++ */
++final class CMSessionParams {
++
++ private final AttrSessionID sid;
++
++ private final AttrWait wait;
++
++ private final AttrVersion ver;
++
++ private final AttrPolling polling;
++
++ private final AttrInactivity inactivity;
++
++ private final AttrRequests requests;
++
++ private final AttrHold hold;
++
++ private final AttrAccept accept;
++
++ private final AttrMaxPause maxPause;
++
++ private final AttrAck ack;
++
++ private final AttrCharsets charsets;
++
++ private final boolean ackingRequests;
++
++ /**
++ * Prevent direct construction.
++ */
++ private CMSessionParams(
++ final AttrSessionID aSid,
++ final AttrWait aWait,
++ final AttrVersion aVer,
++ final AttrPolling aPolling,
++ final AttrInactivity aInactivity,
++ final AttrRequests aRequests,
++ final AttrHold aHold,
++ final AttrAccept aAccept,
++ final AttrMaxPause aMaxPause,
++ final AttrAck aAck,
++ final AttrCharsets aCharsets,
++ final boolean amAckingRequests) {
++ sid = aSid;
++ wait = aWait;
++ ver = aVer;
++ polling = aPolling;
++ inactivity = aInactivity;
++ requests = aRequests;
++ hold = aHold;
++ accept = aAccept;
++ maxPause = aMaxPause;
++ ack = aAck;
++ charsets = aCharsets;
++ ackingRequests = amAckingRequests;
++ }
++
++ static CMSessionParams fromSessionInit(
++ final AbstractBody req,
++ final AbstractBody resp)
++ throws BOSHException {
++ AttrAck aAck = AttrAck.createFromString(
++ resp.getAttribute(Attributes.ACK));
++ String rid = req.getAttribute(Attributes.RID);
++ boolean acking = (aAck != null && aAck.getValue().equals(rid));
++
++ return new CMSessionParams(
++ AttrSessionID.createFromString(
++ getRequiredAttribute(resp, Attributes.SID)),
++ AttrWait.createFromString(
++ getRequiredAttribute(resp, Attributes.WAIT)),
++ AttrVersion.createFromString(
++ resp.getAttribute(Attributes.VER)),
++ AttrPolling.createFromString(
++ resp.getAttribute(Attributes.POLLING)),
++ AttrInactivity.createFromString(
++ resp.getAttribute(Attributes.INACTIVITY)),
++ AttrRequests.createFromString(
++ resp.getAttribute(Attributes.REQUESTS)),
++ AttrHold.createFromString(
++ resp.getAttribute(Attributes.HOLD)),
++ AttrAccept.createFromString(
++ resp.getAttribute(Attributes.ACCEPT)),
++ AttrMaxPause.createFromString(
++ resp.getAttribute(Attributes.MAXPAUSE)),
++ aAck,
++ AttrCharsets.createFromString(
++ resp.getAttribute(Attributes.CHARSETS)),
++ acking
++ );
++ }
++
++ private static String getRequiredAttribute(
++ final AbstractBody body,
++ final BodyQName name)
++ throws BOSHException {
++ String attrStr = body.getAttribute(name);
++ if (attrStr == null) {
++ throw(new BOSHException(
++ "Connection Manager session creation response did not "
++ + "include required '" + name.getLocalPart()
++ + "' attribute"));
++ }
++ return attrStr;
++ }
++
++ AttrSessionID getSessionID() {
++ return sid;
++ }
++
++ AttrWait getWait() {
++ return wait;
++ }
++
++ AttrVersion getVersion() {
++ return ver;
++ }
++
++ AttrPolling getPollingInterval() {
++ return polling;
++ }
++
++ AttrInactivity getInactivityPeriod() {
++ return inactivity;
++ }
++
++ AttrRequests getRequests() {
++ return requests;
++ }
++
++ AttrHold getHold() {
++ return hold;
++ }
++
++ AttrAccept getAccept() {
++ return accept;
++ }
++
++ AttrMaxPause getMaxPause() {
++ return maxPause;
++ }
++
++ AttrAck getAck() {
++ return ack;
++ }
++
++ AttrCharsets getCharsets() {
++ return charsets;
++ }
++
++ boolean isAckingRequests() {
++ return ackingRequests;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java
+new file mode 100644
+index 0000000..7f3b159
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ComposableBody.java
+@@ -0,0 +1,345 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.Collections;
++import java.util.HashMap;
++import java.util.Map;
++import java.util.concurrent.atomic.AtomicReference;
++import java.util.regex.Matcher;
++import java.util.regex.Pattern;
++import javax.xml.XMLConstants;
++
++/**
++ * Implementation of the {@code AbstractBody} class which allows for the
++ * definition of messages from individual elements of a body.
++ *
++ * A message is constructed by creating a builder, manipulating the
++ * configuration of the builder, and then building it into a class instance,
++ * as in the following example:
++ *
++ * ComposableBody body = ComposableBody.builder()
++ * .setNamespaceDefinition("foo", "http://foo.com/bar")
++ * .setPayloadXML("Data to send to remote server")
++ * .build();
++ *
++ * Class instances can also be "rebuilt", allowing them to be used as templates
++ * when building many similar messages:
++ *
++ * ComposableBody body2 = body.rebuild()
++ * .setPayloadXML("More data to send")
++ * .build();
++ *
++ * This class does only minimal syntactic and semantic checking with respect
++ * to what the generated XML will look like. It is up to the developer to
++ * protect against the definition of malformed XML messages when building
++ * instances of this class.
++ *
++ * Instances of this class are immutable and thread-safe.
++ */
++public final class ComposableBody extends AbstractBody {
++
++ /**
++ * Pattern used to identify the beginning {@code body} element of a
++ * BOSH message.
++ */
++ private static final Pattern BOSH_START =
++ Pattern.compile("<"
++ + "body" + "(?:[\t\n\r ][^>]*?)?" + "(/>|>)", Pattern.UNICODE_CASE);
++
++ /**
++ * Map of all attributes to their values.
++ */
++ private final Map attrs;
++
++ /**
++ * Payload XML.
++ */
++ private final String payload;
++
++ /**
++ * Computed raw XML.
++ */
++ private final AtomicReference computed =
++ new AtomicReference();
++
++ /**
++ * Class instance builder, after the builder pattern. This allows each
++ * message instance to be immutable while providing flexibility when
++ * building new messages.
++ *
++ * Instances of this class are not thread-safe.
++ */
++ public static final class Builder {
++ private Map map;
++ private boolean doMapCopy;
++ private String payloadXML;
++
++ /**
++ * Prevent direct construction.
++ */
++ private Builder() {
++ // Empty
++ }
++
++ /**
++ * Creates a builder which is initialized to the values of the
++ * provided {@code ComposableBody} instance. This allows an
++ * existing {@code ComposableBody} to be used as a
++ * template/starting point.
++ *
++ * @param source body template
++ * @return builder instance
++ */
++ private static Builder fromBody(final ComposableBody source) {
++ Builder result = new Builder();
++ result.map = source.getAttributes();
++ result.doMapCopy = true;
++ result.payloadXML = source.payload;
++ return result;
++ }
++
++ /**
++ * Set the body message's wrapped payload content. Any previous
++ * content will be replaced.
++ *
++ * @param xml payload XML content
++ * @return builder instance
++ */
++ public Builder setPayloadXML(final String xml) {
++ if (xml == null) {
++ throw(new IllegalArgumentException(
++ "payload XML argument cannot be null"));
++ }
++ payloadXML = xml;
++ return this;
++ }
++
++ /**
++ * Set an attribute on the message body / wrapper element.
++ *
++ * @param name qualified name of the attribute
++ * @param value value of the attribute
++ * @return builder instance
++ */
++ public Builder setAttribute(
++ final BodyQName name, final String value) {
++ if (map == null) {
++ map = new HashMap();
++ } else if (doMapCopy) {
++ map = new HashMap(map);
++ doMapCopy = false;
++ }
++ if (value == null) {
++ map.remove(name);
++ } else {
++ map.put(name, value);
++ }
++ return this;
++ }
++
++ /**
++ * Convenience method to set a namespace definition. This would result
++ * in a namespace prefix definition similar to:
++ * {@code }
++ *
++ * @param prefix prefix to define
++ * @param uri namespace URI to associate with the prefix
++ * @return builder instance
++ */
++ public Builder setNamespaceDefinition(
++ final String prefix, final String uri) {
++ BodyQName qname = BodyQName.createWithPrefix(
++ XMLConstants.XML_NS_URI, prefix,
++ XMLConstants.XMLNS_ATTRIBUTE);
++ return setAttribute(qname, uri);
++ }
++
++ /**
++ * Build the immutable object instance with the current configuration.
++ *
++ * @return composable body instance
++ */
++ public ComposableBody build() {
++ if (map == null) {
++ map = new HashMap();
++ }
++ if (payloadXML == null) {
++ payloadXML = "";
++ }
++ return new ComposableBody(map, payloadXML);
++ }
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent direct construction. This constructor is for body messages
++ * which are dynamically assembled.
++ */
++ private ComposableBody(
++ final Map attrMap,
++ final String payloadXML) {
++ super();
++ attrs = attrMap;
++ payload = payloadXML;
++ }
++
++ /**
++ * Parse a static body instance into a composable instance. This is an
++ * expensive operation and should not be used lightly.
++ *
++ * The current implementation does not obtain the payload XML by means of
++ * a proper XML parser. It uses some string pattern searching to find the
++ * first @{code body} element and the last element's closing tag. It is
++ * assumed that the static body's XML is well formed, etc.. This
++ * implementation may change in the future.
++ *
++ * @param body static body instance to convert
++ * @return composable bosy instance
++ * @throws BOSHException
++ */
++ static ComposableBody fromStaticBody(final StaticBody body)
++ throws BOSHException {
++ String raw = body.toXML();
++ Matcher matcher = BOSH_START.matcher(raw);
++ if (!matcher.find()) {
++ throw(new BOSHException(
++ "Could not locate 'body' element in XML. The raw XML did"
++ + " not match the pattern: " + BOSH_START));
++ }
++ String payload;
++ if (">".equals(matcher.group(1))) {
++ int first = matcher.end();
++ int last = raw.lastIndexOf("");
++ if (last < first) {
++ last = first;
++ }
++ payload = raw.substring(first, last);
++ } else {
++ payload = "";
++ }
++
++ return new ComposableBody(body.getAttributes(), payload);
++ }
++
++ /**
++ * Create a builder instance to build new instances of this class.
++ *
++ * @return AbstractBody instance
++ */
++ public static Builder builder() {
++ return new Builder();
++ }
++
++ /**
++ * If this {@code ComposableBody} instance is a dynamic instance, uses this
++ * {@code ComposableBody} instance as a starting point, create a builder
++ * which can be used to create another {@code ComposableBody} instance
++ * based on this one. This allows a {@code ComposableBody} instance to be
++ * used as a template. Note that the use of the returned builder in no
++ * way modifies or manipulates the current {@code ComposableBody} instance.
++ *
++ * @return builder instance which can be used to build similar
++ * {@code ComposableBody} instances
++ */
++ public Builder rebuild() {
++ return Builder.fromBody(this);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Accessors:
++
++ /**
++ * {@inheritDoc}
++ */
++ public Map getAttributes() {
++ return Collections.unmodifiableMap(attrs);
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ public String toXML() {
++ String comp = computed.get();
++ if (comp == null) {
++ comp = computeXML();
++ computed.set(comp);
++ }
++ return comp;
++ }
++
++ /**
++ * Get the paylaod XML in String form.
++ *
++ * @return payload XML
++ */
++ public String getPayloadXML() {
++ return payload;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Escape the value of an attribute to ensure we maintain valid
++ * XML syntax.
++ *
++ * @param value value to escape
++ * @return escaped value
++ */
++ private String escape(final String value) {
++ return value.replace("'", "'");
++ }
++
++ /**
++ * Generate a String representation of the message body.
++ *
++ * @return XML string representation of the body
++ */
++ private String computeXML() {
++ BodyQName bodyName = getBodyQName();
++ StringBuilder builder = new StringBuilder();
++ builder.append("<");
++ builder.append(bodyName.getLocalPart());
++ for (Map.Entry entry : attrs.entrySet()) {
++ builder.append(" ");
++ BodyQName name = entry.getKey();
++ String prefix = name.getPrefix();
++ if (prefix != null && prefix.length() > 0) {
++ builder.append(prefix);
++ builder.append(":");
++ }
++ builder.append(name.getLocalPart());
++ builder.append("='");
++ builder.append(escape(entry.getValue()));
++ builder.append("'");
++ }
++ builder.append(" ");
++ builder.append(XMLConstants.XMLNS_ATTRIBUTE);
++ builder.append("='");
++ builder.append(bodyName.getNamespaceURI());
++ builder.append("'>");
++ if (payload != null) {
++ builder.append(payload);
++ }
++ builder.append("");
++ return builder.toString();
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java b/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java
+new file mode 100644
+index 0000000..988f27f
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/GZIPCodec.java
+@@ -0,0 +1,104 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.ByteArrayInputStream;
++import java.io.ByteArrayOutputStream;
++import java.io.IOException;
++import java.util.zip.GZIPInputStream;
++import java.util.zip.GZIPOutputStream;
++
++/**
++ * Codec methods for compressing and uncompressing using GZIP.
++ */
++final class GZIPCodec {
++
++ /**
++ * Size of the internal buffer when decoding.
++ */
++ private static final int BUFFER_SIZE = 512;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent construction.
++ */
++ private GZIPCodec() {
++ // Empty
++ }
++
++ /**
++ * Returns the name of the codec.
++ *
++ * @return string name of the codec (i.e., "gzip")
++ */
++ public static String getID() {
++ return "gzip";
++ }
++
++ /**
++ * Compress/encode the data provided using the GZIP format.
++ *
++ * @param data data to compress
++ * @return compressed data
++ * @throws IOException on compression failure
++ */
++ public static byte[] encode(final byte[] data) throws IOException {
++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
++ GZIPOutputStream gzOut = null;
++ try {
++ gzOut = new GZIPOutputStream(byteOut);
++ gzOut.write(data);
++ gzOut.close();
++ byteOut.close();
++ return byteOut.toByteArray();
++ } finally {
++ gzOut.close();
++ byteOut.close();
++ }
++ }
++
++ /**
++ * Uncompress/decode the data provided using the GZIP format.
++ *
++ * @param data data to uncompress
++ * @return uncompressed data
++ * @throws IOException on decompression failure
++ */
++ public static byte[] decode(final byte[] compressed) throws IOException {
++ ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed);
++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
++ GZIPInputStream gzIn = null;
++ try {
++ gzIn = new GZIPInputStream(byteIn);
++ int read;
++ byte[] buffer = new byte[BUFFER_SIZE];
++ do {
++ read = gzIn.read(buffer);
++ if (read > 0) {
++ byteOut.write(buffer, 0, read);
++ }
++ } while (read >= 0);
++ return byteOut.toByteArray();
++ } finally {
++ gzIn.close();
++ byteOut.close();
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java
+new file mode 100644
+index 0000000..c77caf0
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPExchange.java
+@@ -0,0 +1,126 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.concurrent.locks.Condition;
++import java.util.concurrent.locks.Lock;
++import java.util.concurrent.locks.ReentrantLock;
++import java.util.logging.Level;
++import java.util.logging.Logger;
++
++/**
++ * A request and response pair representing a single exchange with a remote
++ * content manager. This is primarily a container class intended to maintain
++ * the relationship between the request and response but allows the response
++ * to be added after the fact.
++ */
++final class HTTPExchange {
++
++ /**
++ * Logger.
++ */
++ private static final Logger LOG =
++ Logger.getLogger(HTTPExchange.class.getName());
++
++ /**
++ * Request body.
++ */
++ private final AbstractBody request;
++
++ /**
++ * Lock instance used to protect and provide conditions.
++ */
++ private final Lock lock = new ReentrantLock();
++
++ /**
++ * Condition used to signal when the response has been set.
++ */
++ private final Condition ready = lock.newCondition();
++
++ /**
++ * HTTPResponse instance.
++ */
++ private HTTPResponse response;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructor:
++
++ /**
++ * Create a new request/response pair object.
++ *
++ * @param req request message body
++ */
++ HTTPExchange(final AbstractBody req) {
++ if (req == null) {
++ throw(new IllegalArgumentException("Request body cannot be null"));
++ }
++ request = req;
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Get the original request message.
++ *
++ * @return request message body.
++ */
++ AbstractBody getRequest() {
++ return request;
++ }
++
++ /**
++ * Set the HTTPResponse instance.
++ *
++ * @return HTTPResponse instance associated with the request.
++ */
++ void setHTTPResponse(HTTPResponse resp) {
++ lock.lock();
++ try {
++ if (response != null) {
++ throw(new IllegalStateException(
++ "HTTPResponse was already set"));
++ }
++ response = resp;
++ ready.signalAll();
++ } finally {
++ lock.unlock();
++ }
++ }
++
++ /**
++ * Get the HTTPResponse instance.
++ *
++ * @return HTTPResponse instance associated with the request.
++ */
++ HTTPResponse getHTTPResponse() {
++ lock.lock();
++ try {
++ while (response == null) {
++ try {
++ ready.await();
++ } catch (InterruptedException intx) {
++ LOG.log(Level.FINEST, "Interrupted", intx);
++ }
++ }
++ return response;
++ } finally {
++ lock.unlock();
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java
+new file mode 100644
+index 0000000..f1f301c
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPResponse.java
+@@ -0,0 +1,54 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * This class represents a complete HTTP response to a request made via
++ * a {@code HTTPSender} send request. Instances of this interface are
++ * intended to represent a deferred, future response, not necessarily a
++ * response which is immediately available.
++ */
++interface HTTPResponse {
++
++ /**
++ * Close out any resources still held by the original request. The
++ * conversation may need to be aborted if the session it was a part of
++ * gets abruptly terminated.
++ */
++ void abort();
++
++ /**
++ * Get the HTTP status code of the response (e.g., 200, 404, etc.). If
++ * the response has not yet been received from the remote server, this
++ * method should block until the response has arrived.
++ *
++ * @return HTTP status code
++ * @throws InterruptedException if interrupted while awaiting response
++ */
++ int getHTTPStatus() throws InterruptedException, BOSHException;
++
++ /**
++ * Get the HTTP response message body. If the response has not yet been
++ * received from the remote server, this method should block until the
++ * response has arrived.
++ *
++ * @return response message body
++ * @throws InterruptedException if interrupted while awaiting response
++ */
++ AbstractBody getBody() throws InterruptedException, BOSHException;
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java
+new file mode 100644
+index 0000000..486d274
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/HTTPSender.java
+@@ -0,0 +1,54 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++/**
++ * Interface used to represent code which can send a BOSH XML body over
++ * HTTP to a connection manager.
++ */
++interface HTTPSender {
++
++ /**
++ * Initialize the HTTP sender instance for use with the session provided.
++ * This method will be called once before use of the service instance.
++ *
++ * @param sessionCfg session configuration
++ */
++ void init(BOSHClientConfig sessionCfg);
++
++ /**
++ * Dispose of all resources used to provide the required services. This
++ * method will be called once when the service instance is no longer
++ * required.
++ */
++ void destroy();
++
++ /**
++ * Create a {@code Callable} instance which can be used to send the
++ * request specified to the connection manager. This method should
++ * return immediately, prior to doing any real work. The invocation
++ * of the returned {@code Callable} should send the request (if it has
++ * not already been sent by the time of the call), block while waiting
++ * for the response, and then return the response body.
++ *
++ * @param params CM session creation resopnse params
++ * @param body request body to send
++ * @return callable used to access the response
++ */
++ HTTPResponse send(CMSessionParams params, AbstractBody body);
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java b/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java
+new file mode 100644
+index 0000000..d395a06
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/QName.java
+@@ -0,0 +1,269 @@
++/*
++ * The Apache Software License, Version 1.1
++ *
++ *
++ * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
++ * reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * 1. Redistributions of source code must retain the above copyright
++ * notice, this list of conditions and the following disclaimer.
++ *
++ * 2. Redistributions in binary form must reproduce the above copyright
++ * notice, this list of conditions and the following disclaimer in
++ * the documentation and/or other materials provided with the
++ * distribution.
++ *
++ * 3. The end-user documentation included with the redistribution,
++ * if any, must include the following acknowledgment:
++ * "This product includes software developed by the
++ * Apache Software Foundation (http://www.apache.org/)."
++ * Alternately, this acknowledgment may appear in the software itself,
++ * if and wherever such third-party acknowledgments normally appear.
++ *
++ * 4. The names "Axis" and "Apache Software Foundation" must
++ * not be used to endorse or promote products derived from this
++ * software without prior written permission. For written
++ * permission, please contact apache@apache.org.
++ *
++ * 5. Products derived from this software may not be called "Apache",
++ * nor may "Apache" appear in their name, without prior written
++ * permission of the Apache Software Foundation.
++ *
++ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
++ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
++ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
++ * SUCH DAMAGE.
++ * ====================================================================
++ *
++ * This software consists of voluntary contributions made by many
++ * individuals on behalf of the Apache Software Foundation. For more
++ * information on the Apache Software Foundation, please see
++ * .
++ */
++package com.kenai.jbosh;
++
++import java.io.IOException;
++import java.io.ObjectInputStream;
++import java.io.Serializable;
++
++/**
++ * QName class represents the value of a qualified name
++ * as specified in XML
++ * Schema Part2: Datatypes specification.
++ *
++ * The value of a QName contains a namespaceURI, a localPart and a prefix.
++ * The localPart provides the local part of the qualified name. The
++ * namespaceURI is a URI reference identifying the namespace.
++ *
++ * @version 1.1
++ */
++public class QName implements Serializable {
++
++ /** comment/shared empty string */
++ private static final String emptyString = "".intern();
++
++ /** Field namespaceURI */
++ private String namespaceURI;
++
++ /** Field localPart */
++ private String localPart;
++
++ /** Field prefix */
++ private String prefix;
++
++ /**
++ * Constructor for the QName.
++ *
++ * @param localPart Local part of the QName
++ */
++ public QName(String localPart) {
++ this(emptyString, localPart, emptyString);
++ }
++
++ /**
++ * Constructor for the QName.
++ *
++ * @param namespaceURI Namespace URI for the QName
++ * @param localPart Local part of the QName.
++ */
++ public QName(String namespaceURI, String localPart) {
++ this(namespaceURI, localPart, emptyString);
++ }
++
++ /**
++ * Constructor for the QName.
++ *
++ * @param namespaceURI Namespace URI for the QName
++ * @param localPart Local part of the QName.
++ * @param prefix Prefix of the QName.
++ */
++ public QName(String namespaceURI, String localPart, String prefix) {
++ this.namespaceURI = (namespaceURI == null)
++ ? emptyString
++ : namespaceURI.intern();
++ if (localPart == null) {
++ throw new IllegalArgumentException("invalid QName local part");
++ } else {
++ this.localPart = localPart.intern();
++ }
++
++ if (prefix == null) {
++ throw new IllegalArgumentException("invalid QName prefix");
++ } else {
++ this.prefix = prefix.intern();
++ }
++ }
++
++ /**
++ * Gets the Namespace URI for this QName
++ *
++ * @return Namespace URI
++ */
++ public String getNamespaceURI() {
++ return namespaceURI;
++ }
++
++ /**
++ * Gets the Local part for this QName
++ *
++ * @return Local part
++ */
++ public String getLocalPart() {
++ return localPart;
++ }
++
++ /**
++ * Gets the Prefix for this QName
++ *
++ * @return Prefix
++ */
++ public String getPrefix() {
++ return prefix;
++ }
++
++ /**
++ * Returns a string representation of this QName
++ *
++ * @return a string representation of the QName
++ */
++ public String toString() {
++
++ return ((namespaceURI == emptyString)
++ ? localPart
++ : '{' + namespaceURI + '}' + localPart);
++ }
++
++ /**
++ * Tests this QName for equality with another object.
++ *
++ * If the given object is not a QName or is null then this method
++ * returns false.
++ *
++ * For two QNames to be considered equal requires that both
++ * localPart and namespaceURI must be equal. This method uses
++ * String.equals to check equality of localPart
++ * and namespaceURI. Any class that extends QName is required
++ * to satisfy this equality contract.
++ *
++ * This method satisfies the general contract of the Object.equals method.
++ *
++ * @param obj the reference object with which to compare
++ *
++ * @return true if the given object is identical to this
++ * QName: false otherwise.
++ */
++ public final boolean equals(Object obj) {
++
++ if (obj == this) {
++ return true;
++ }
++
++ if (!(obj instanceof QName)) {
++ return false;
++ }
++
++ if ((namespaceURI == ((QName) obj).namespaceURI)
++ && (localPart == ((QName) obj).localPart)) {
++ return true;
++ }
++
++ return false;
++ }
++
++ /**
++ * Returns a QName holding the value of the specified String.
++ *
++ * The string must be in the form returned by the QName.toString()
++ * method, i.e. "{namespaceURI}localPart", with the "{namespaceURI}"
++ * part being optional.
++ *
++ * This method doesn't do a full validation of the resulting QName.
++ * In particular, it doesn't check that the resulting namespace URI
++ * is a legal URI (per RFC 2396 and RFC 2732), nor that the resulting
++ * local part is a legal NCName per the XML Namespaces specification.
++ *
++ * @param s the string to be parsed
++ * @throws java.lang.IllegalArgumentException If the specified String cannot be parsed as a QName
++ * @return QName corresponding to the given String
++ */
++ public static QName valueOf(String s) {
++
++ if ((s == null) || s.equals("")) {
++ throw new IllegalArgumentException("invalid QName literal");
++ }
++
++ if (s.charAt(0) == '{') {
++ int i = s.indexOf('}');
++
++ if (i == -1) {
++ throw new IllegalArgumentException("invalid QName literal");
++ }
++
++ if (i == s.length() - 1) {
++ throw new IllegalArgumentException("invalid QName literal");
++ } else {
++ return new QName(s.substring(1, i), s.substring(i + 1));
++ }
++ } else {
++ return new QName(s);
++ }
++ }
++
++ /**
++ * Returns a hash code value for this QName object. The hash code
++ * is based on both the localPart and namespaceURI parts of the
++ * QName. This method satisfies the general contract of the
++ * Object.hashCode method.
++ *
++ * @return a hash code value for this Qname object
++ */
++ public final int hashCode() {
++ return namespaceURI.hashCode() ^ localPart.hashCode();
++ }
++
++ /**
++ * Ensure that deserialization properly interns the results.
++ * @param in the ObjectInputStream to be read
++ */
++ private void readObject(ObjectInputStream in) throws
++ IOException, ClassNotFoundException {
++ in.defaultReadObject();
++
++ namespaceURI = namespaceURI.intern();
++ localPart = localPart.intern();
++ prefix = prefix.intern();
++ }
++}
++
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java b/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java
+new file mode 100644
+index 0000000..14b1475
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/RequestIDSequence.java
+@@ -0,0 +1,120 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.security.SecureRandom;
++import java.util.concurrent.atomic.AtomicLong;
++import java.util.concurrent.locks.Lock;
++import java.util.concurrent.locks.ReentrantLock;
++
++/**
++ * Request ID sequence generator. This generator generates a random first
++ * RID and then manages the sequence from there on out.
++ */
++final class RequestIDSequence {
++
++ /**
++ * Maximum number of bits available for representing request IDs, according
++ * to the XEP-0124 spec.s
++ */
++ private static final int MAX_BITS = 53;
++
++ /**
++ * Bits devoted to incremented values.
++ */
++ private static final int INCREMENT_BITS = 32;
++
++ /**
++ * Minimum number of times the initial RID can be incremented before
++ * exceeding the maximum.
++ */
++ private static final long MIN_INCREMENTS = 1L << INCREMENT_BITS;
++
++ /**
++ * Max initial value.
++ */
++ private static final long MAX_INITIAL = (1L << MAX_BITS) - MIN_INCREMENTS;
++
++ /**
++ * Max bits mask.
++ */
++ private static final long MASK = ~(Long.MAX_VALUE << MAX_BITS);
++
++ /**
++ * Random number generator.
++ */
++ private static final SecureRandom RAND = new SecureRandom();
++
++ /**
++ * Internal lock.
++ */
++ private static final Lock LOCK = new ReentrantLock();
++
++ /**
++ * The last reqest ID used, or <= 0 if a new request ID needs to be
++ * generated.
++ */
++ private AtomicLong nextRequestID = new AtomicLong();
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent direct construction.
++ */
++ RequestIDSequence() {
++ nextRequestID = new AtomicLong(generateInitialValue());
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Public methods:
++
++ /**
++ * Calculates the next request ID value to use. This number must be
++ * initialized such that it is unlikely to ever exceed 2 ^ 53, according
++ * to XEP-0124.
++ *
++ * @return next request ID value
++ */
++ public long getNextRID() {
++ return nextRequestID.getAndIncrement();
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Generates an initial RID value by generating numbers until a number is
++ * found which is smaller than the maximum allowed value and greater
++ * than zero.
++ *
++ * @return random initial value
++ */
++ private long generateInitialValue() {
++ long result;
++ LOCK.lock();
++ try {
++ do {
++ result = RAND.nextLong() & MASK;
++ } while (result > MAX_INITIAL);
++ } finally {
++ LOCK.unlock();
++ }
++ return result;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java
+new file mode 100644
+index 0000000..07d0556
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ServiceLib.java
+@@ -0,0 +1,195 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.BufferedReader;
++import java.io.Closeable;
++import java.io.IOException;
++import java.io.InputStream;
++import java.io.InputStreamReader;
++import java.net.URL;
++import java.util.ArrayList;
++import java.util.List;
++import java.util.logging.Level;
++import java.util.logging.Logger;
++
++/**
++ * Utility library for use in loading services using the Jar Service
++ * Provider Interface (Jar SPI). This can be replaced once the minimum
++ * java rev moves beyond Java 5.
++ */
++final class ServiceLib {
++
++ /**
++ * Logger.
++ */
++ private static final Logger LOG =
++ Logger.getLogger(ServiceLib.class.getName());
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Prevent construction.
++ */
++ private ServiceLib() {
++ // Empty
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Package-private methods:
++
++ /**
++ * Probe for and select an implementation of the specified service
++ * type by using the a modified Jar SPI mechanism. Modified in that
++ * the system properties will be checked to see if there is a value
++ * set for the naem of the class to be loaded. If so, that value is
++ * treated as the class name of the first implementation class to be
++ * attempted to be loaded. This provides a (unsupported) mechanism
++ * to insert other implementations. Note that the supported mechanism
++ * is by properly ordering the classpath.
++ *
++ * @return service instance
++ * @throws IllegalStateException is no service implementations could be
++ * instantiated
++ */
++ static T loadService(Class ofType) {
++ List implClasses = loadServicesImplementations(ofType);
++ for (String implClass : implClasses) {
++ T result = attemptLoad(ofType, implClass);
++ if (result != null) {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Selected " + ofType.getSimpleName()
++ + " implementation: "
++ + result.getClass().getName());
++ }
++ return result;
++ }
++ }
++ throw(new IllegalStateException(
++ "Could not load " + ofType.getName() + " implementation"));
++ }
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Private methods:
++
++ /**
++ * Generates a list of implementation class names by using
++ * the Jar SPI technique. The order in which the class names occur
++ * in the service manifest is significant.
++ *
++ * @return list of all declared implementation class names
++ */
++ private static List loadServicesImplementations(
++ final Class ofClass) {
++ List result = new ArrayList();
++
++ // Allow a sysprop to specify the first candidate
++ String override = System.getProperty(ofClass.getName());
++ if (override != null) {
++ result.add(override);
++ }
++
++ ClassLoader loader = ServiceLib.class.getClassLoader();
++ URL url = loader.getResource("META-INF/services/" + ofClass.getName());
++ InputStream inStream = null;
++ InputStreamReader reader = null;
++ BufferedReader bReader = null;
++ try {
++ inStream = url.openStream();
++ reader = new InputStreamReader(inStream);
++ bReader = new BufferedReader(reader);
++ String line;
++ while ((line = bReader.readLine()) != null) {
++ if (!line.matches("\\s*(#.*)?")) {
++ // not a comment or blank line
++ result.add(line.trim());
++ }
++ }
++ } catch (IOException iox) {
++ LOG.log(Level.WARNING,
++ "Could not load services descriptor: " + url.toString(),
++ iox);
++ } finally {
++ finalClose(bReader);
++ finalClose(reader);
++ finalClose(inStream);
++ }
++ return result;
++ }
++
++ /**
++ * Attempts to load the specified implementation class.
++ * Attempts will fail if - for example - the implementation depends
++ * on a class not found on the classpath.
++ *
++ * @param className implementation class to attempt to load
++ * @return service instance, or {@code null} if the instance could not be
++ * loaded
++ */
++ private static T attemptLoad(
++ final Class ofClass,
++ final String className) {
++ if (LOG.isLoggable(Level.FINEST)) {
++ LOG.finest("Attempting service load: " + className);
++ }
++ Level level;
++ Exception thrown;
++ try {
++ Class clazz = Class.forName(className);
++ if (!ofClass.isAssignableFrom(clazz)) {
++ if (LOG.isLoggable(Level.WARNING)) {
++ LOG.warning(clazz.getName() + " is not assignable to "
++ + ofClass.getName());
++ }
++ return null;
++ }
++ return ofClass.cast(clazz.newInstance());
++ } catch (ClassNotFoundException ex) {
++ level = Level.FINEST;
++ thrown = ex;
++ } catch (InstantiationException ex) {
++ level = Level.WARNING;
++ thrown = ex;
++ } catch (IllegalAccessException ex) {
++ level = Level.WARNING;
++ thrown = ex;
++ }
++ LOG.log(level,
++ "Could not load " + ofClass.getSimpleName()
++ + " instance: " + className,
++ thrown);
++ return null;
++ }
++
++ /**
++ * Check and close a closeable object, trapping and ignoring any
++ * exception that might result.
++ *
++ * @param closeMe the thing to close
++ */
++ private static void finalClose(final Closeable closeMe) {
++ if (closeMe != null) {
++ try {
++ closeMe.close();
++ } catch (IOException iox) {
++ LOG.log(Level.FINEST, "Could not close: " + closeMe, iox);
++ }
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java b/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java
+new file mode 100644
+index 0000000..fe225fb
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/StaticBody.java
+@@ -0,0 +1,133 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.ByteArrayOutputStream;
++import java.io.IOException;
++import java.io.InputStream;
++import java.util.Collections;
++import java.util.Map;
++
++/**
++ * Implementation of the {@code AbstractBody} class which allows for the
++ * definition of messages from pre-existing message content. Instances of
++ * this class are based on the underlying data and therefore cannot be
++ * modified. In order to obtain the wrapper element namespace and
++ * attribute information, the body content is partially parsed.
++ *
++ * This class does only minimal syntactic and semantic checking with respect
++ * to what the generated XML will look like. It is up to the developer to
++ * protect against the definition of malformed XML messages when building
++ * instances of this class.
++ *
++ * Instances of this class are immutable and thread-safe.
++ */
++final class StaticBody extends AbstractBody {
++
++ /**
++ * Selected parser to be used to process raw XML messages.
++ */
++ private static final BodyParser PARSER =
++ new BodyParserXmlPull();
++
++ /**
++ * Size of the internal buffer when copying from a stream.
++ */
++ private static final int BUFFER_SIZE = 1024;
++
++ /**
++ * Map of all attributes to their values.
++ */
++ private final Map attrs;
++
++ /**
++ * This body message in raw XML form.
++ */
++ private final String raw;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent direct construction.
++ */
++ private StaticBody(
++ final Map attrMap,
++ final String rawXML) {
++ attrs = attrMap;
++ raw = rawXML;
++ }
++
++ /**
++ * Creates an instance which is initialized by reading a body
++ * message from the provided stream.
++ *
++ * @param inStream stream to read message XML from
++ * @return body instance
++ * @throws BOSHException on parse error
++ */
++ public static StaticBody fromStream(
++ final InputStream inStream)
++ throws BOSHException {
++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
++ try {
++ byte[] buffer = new byte[BUFFER_SIZE];
++ int read;
++ do {
++ read = inStream.read(buffer);
++ if (read > 0) {
++ byteOut.write(buffer, 0, read);
++ }
++ } while (read >= 0);
++ } catch (IOException iox) {
++ throw(new BOSHException(
++ "Could not read body data", iox));
++ }
++ return fromString(byteOut.toString());
++ }
++
++ /**
++ * Creates an instance which is initialized by reading a body
++ * message from the provided raw XML string.
++ *
++ * @param rawXML raw message XML in string form
++ * @return body instance
++ * @throws BOSHException on parse error
++ */
++ public static StaticBody fromString(
++ final String rawXML)
++ throws BOSHException {
++ BodyParserResults results = PARSER.parse(rawXML);
++ return new StaticBody(results.getAttributes(), rawXML);
++ }
++
++
++ /**
++ * {@inheritDoc}
++ */
++ public Map getAttributes() {
++ return Collections.unmodifiableMap(attrs);
++ }
++
++ /**
++ * {@inheritDoc}
++ */
++ public String toXML() {
++ return raw;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java b/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java
+new file mode 100644
+index 0000000..0aecfd8
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/TerminalBindingCondition.java
+@@ -0,0 +1,208 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.util.HashMap;
++import java.util.Map;
++
++/**
++ * Terminal binding conditions and their associated messages.
++ */
++final class TerminalBindingCondition {
++
++ /**
++ * Map of condition names to condition instances.
++ */
++ private static final Map
++ COND_TO_INSTANCE = new HashMap();
++
++ /**
++ * Map of HTTP response codes to condition instances.
++ */
++ private static final Map
++ CODE_TO_INSTANCE = new HashMap();
++
++ static final TerminalBindingCondition BAD_REQUEST =
++ createWithCode("bad-request", "The format of an HTTP header or "
++ + "binding element received from the client is unacceptable "
++ + "(e.g., syntax error).", Integer.valueOf(400));
++
++ static final TerminalBindingCondition HOST_GONE =
++ create("host-gone", "The target domain specified in the 'to' "
++ + "attribute or the target host or port specified in the 'route' "
++ + "attribute is no longer serviced by the connection manager.");
++
++ static final TerminalBindingCondition HOST_UNKNOWN =
++ create("host-unknown", "The target domain specified in the 'to' "
++ + "attribute or the target host or port specified in the 'route' "
++ + "attribute is unknown to the connection manager.");
++
++ static final TerminalBindingCondition IMPROPER_ADDRESSING =
++ create("improper-addressing", "The initialization element lacks a "
++ + "'to' or 'route' attribute (or the attribute has no value) but "
++ + "the connection manager requires one.");
++
++ static final TerminalBindingCondition INTERNAL_SERVER_ERROR =
++ create("internal-server-error", "The connection manager has "
++ + "experienced an internal error that prevents it from servicing "
++ + "the request.");
++
++ static final TerminalBindingCondition ITEM_NOT_FOUND =
++ createWithCode("item-not-found", "(1) 'sid' is not valid, (2) "
++ + "'stream' is not valid, (3) 'rid' is larger than the upper limit "
++ + "of the expected window, (4) connection manager is unable to "
++ + "resend response, (5) 'key' sequence is invalid.",
++ Integer.valueOf(404));
++
++ static final TerminalBindingCondition OTHER_REQUEST =
++ create("other-request", "Another request being processed at the "
++ + "same time as this request caused the session to terminate.");
++
++ static final TerminalBindingCondition POLICY_VIOLATION =
++ createWithCode("policy-violation", "The client has broken the "
++ + "session rules (polling too frequently, requesting too "
++ + "frequently, sending too many simultaneous requests).",
++ Integer.valueOf(403));
++
++ static final TerminalBindingCondition REMOTE_CONNECTION_FAILED =
++ create("remote-connection-failed", "The connection manager was "
++ + "unable to connect to, or unable to connect securely to, or has "
++ + "lost its connection to, the server.");
++
++ static final TerminalBindingCondition REMOTE_STREAM_ERROR =
++ create("remote-stream-error", "Encapsulated transport protocol "
++ + "error.");
++
++ static final TerminalBindingCondition SEE_OTHER_URI =
++ create("see-other-uri", "The connection manager does not operate "
++ + "at this URI (e.g., the connection manager accepts only SSL or "
++ + "TLS connections at some https: URI rather than the http: URI "
++ + "requested by the client).");
++
++ static final TerminalBindingCondition SYSTEM_SHUTDOWN =
++ create("system-shutdown", "The connection manager is being shut "
++ + "down. All active HTTP sessions are being terminated. No new "
++ + "sessions can be created.");
++
++ static final TerminalBindingCondition UNDEFINED_CONDITION =
++ create("undefined-condition", "Unknown or undefined error "
++ + "condition.");
++
++ /**
++ * Condition name.
++ */
++ private final String cond;
++
++ /**
++ * Descriptive message.
++ */
++ private final String msg;
++
++ /**
++ * Private constructor to pre
++ */
++ private TerminalBindingCondition(
++ final String condition,
++ final String message) {
++ cond = condition;
++ msg = message;
++ }
++
++ /**
++ * Helper method to call the helper method to add entries.
++ */
++ private static TerminalBindingCondition create(
++ final String condition,
++ final String message) {
++ return createWithCode(condition, message, null);
++ }
++
++ /**
++ * Helper method to add entries.
++ */
++ private static TerminalBindingCondition createWithCode(
++ final String condition,
++ final String message,
++ final Integer code) {
++ if (condition == null) {
++ throw(new IllegalArgumentException(
++ "condition may not be null"));
++ }
++ if (message == null) {
++ throw(new IllegalArgumentException(
++ "message may not be null"));
++ }
++ if (COND_TO_INSTANCE.get(condition) != null) {
++ throw(new IllegalStateException(
++ "Multiple definitions of condition: " + condition));
++ }
++ TerminalBindingCondition result =
++ new TerminalBindingCondition(condition, message);
++ COND_TO_INSTANCE.put(condition, result);
++ if (code != null) {
++ if (CODE_TO_INSTANCE.get(code) != null) {
++ throw(new IllegalStateException(
++ "Multiple definitions of code: " + code));
++ }
++ CODE_TO_INSTANCE.put(code, result);
++ }
++ return result;
++ }
++
++ /**
++ * Lookup the terminal binding condition instance with the condition
++ * name specified.
++ *
++ * @param condStr condition name
++ * @return terminal binding condition instance, or {@code null} if no
++ * instance is known by the name specified
++ */
++ static TerminalBindingCondition forString(final String condStr) {
++ return COND_TO_INSTANCE.get(condStr);
++ }
++
++ /**
++ * Lookup the terminal binding condition instance associated with the
++ * HTTP response code specified.
++ *
++ * @param httpRespCode HTTP response code
++ * @return terminal binding condition instance, or {@code null} if no
++ * instance is known by the response code specified
++ */
++ static TerminalBindingCondition forHTTPResponseCode(final int httpRespCode) {
++ return CODE_TO_INSTANCE.get(Integer.valueOf(httpRespCode));
++ }
++
++ /**
++ * Get the name of the condition.
++ *
++ * @return condition name
++ */
++ String getCondition() {
++ return cond;
++ }
++
++ /**
++ * Get the human readable error message associated with this condition.
++ *
++ * @return error message
++ */
++ String getMessage() {
++ return msg;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java b/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java
+new file mode 100644
+index 0000000..20844ad
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/ZLIBCodec.java
+@@ -0,0 +1,104 @@
++/*
++ * Copyright 2009 Mike Cumings
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package com.kenai.jbosh;
++
++import java.io.ByteArrayInputStream;
++import java.io.ByteArrayOutputStream;
++import java.io.IOException;
++import java.util.zip.DeflaterOutputStream;
++import java.util.zip.InflaterInputStream;
++
++/**
++ * Codec methods for compressing and uncompressing using ZLIB.
++ */
++final class ZLIBCodec {
++
++ /**
++ * Size of the internal buffer when decoding.
++ */
++ private static final int BUFFER_SIZE = 512;
++
++ ///////////////////////////////////////////////////////////////////////////
++ // Constructors:
++
++ /**
++ * Prevent construction.
++ */
++ private ZLIBCodec() {
++ // Empty
++ }
++
++ /**
++ * Returns the name of the codec.
++ *
++ * @return string name of the codec (i.e., "deflate")
++ */
++ public static String getID() {
++ return "deflate";
++ }
++
++ /**
++ * Compress/encode the data provided using the ZLIB format.
++ *
++ * @param data data to compress
++ * @return compressed data
++ * @throws IOException on compression failure
++ */
++ public static byte[] encode(final byte[] data) throws IOException {
++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
++ DeflaterOutputStream deflateOut = null;
++ try {
++ deflateOut = new DeflaterOutputStream(byteOut);
++ deflateOut.write(data);
++ deflateOut.close();
++ byteOut.close();
++ return byteOut.toByteArray();
++ } finally {
++ deflateOut.close();
++ byteOut.close();
++ }
++ }
++
++ /**
++ * Uncompress/decode the data provided using the ZLIB format.
++ *
++ * @param data data to uncompress
++ * @return uncompressed data
++ * @throws IOException on decompression failure
++ */
++ public static byte[] decode(final byte[] compressed) throws IOException {
++ ByteArrayInputStream byteIn = new ByteArrayInputStream(compressed);
++ ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
++ InflaterInputStream inflaterIn = null;
++ try {
++ inflaterIn = new InflaterInputStream(byteIn);
++ int read;
++ byte[] buffer = new byte[BUFFER_SIZE];
++ do {
++ read = inflaterIn.read(buffer);
++ if (read > 0) {
++ byteOut.write(buffer, 0, read);
++ }
++ } while (read >= 0);
++ return byteOut.toByteArray();
++ } finally {
++ inflaterIn.close();
++ byteOut.close();
++ }
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/com/kenai/jbosh/package.html b/external/asmack/build/src/trunk/com/kenai/jbosh/package.html
+new file mode 100644
+index 0000000..77a1924
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/kenai/jbosh/package.html
+@@ -0,0 +1,8 @@
++
++
++ Core classes of the JBOSH API.
++
++ Users of the client portion of the API should start by reading
++ up on the BOSHClient documentation.
++
++
+\ No newline at end of file
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java
+new file mode 100644
+index 0000000..90e6247
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestChallenge.java
+@@ -0,0 +1,393 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/DigestChallenge.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
++ *
++ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++import java.util.*;
++import org.apache.harmony.javax.security.sasl.*;
++
++/**
++ * Implements the DigestChallenge class which will be used by the
++ * DigestMD5SaslClient class
++ */
++class DigestChallenge extends Object
++{
++ public static final int QOP_AUTH = 0x01;
++ public static final int QOP_AUTH_INT = 0x02;
++ public static final int QOP_AUTH_CONF = 0x04;
++ public static final int QOP_UNRECOGNIZED = 0x08;
++
++ private static final int CIPHER_3DES = 0x01;
++ private static final int CIPHER_DES = 0x02;
++ private static final int CIPHER_RC4_40 = 0x04;
++ private static final int CIPHER_RC4 = 0x08;
++ private static final int CIPHER_RC4_56 = 0x10;
++ private static final int CIPHER_UNRECOGNIZED = 0x20;
++ private static final int CIPHER_RECOGNIZED_MASK =
++ CIPHER_3DES | CIPHER_DES | CIPHER_RC4_40 | CIPHER_RC4 | CIPHER_RC4_56;
++
++ private ArrayList m_realms;
++ private String m_nonce;
++ private int m_qop;
++ private boolean m_staleFlag;
++ private int m_maxBuf;
++ private String m_characterSet;
++ private String m_algorithm;
++ private int m_cipherOptions;
++
++ DigestChallenge(
++ byte[] challenge)
++ throws SaslException
++ {
++ m_realms = new ArrayList(5);
++ m_nonce = null;
++ m_qop = 0;
++ m_staleFlag = false;
++ m_maxBuf = -1;
++ m_characterSet = null;
++ m_algorithm = null;
++ m_cipherOptions = 0;
++
++ DirectiveList dirList = new DirectiveList(challenge);
++ try
++ {
++ dirList.parseDirectives();
++ checkSemantics(dirList);
++ }
++ catch (SaslException e)
++ {
++ }
++ }
++
++ /**
++ * Checks the semantics of the directives in the directive list as parsed
++ * from the digest challenge byte array.
++ *
++ * @param dirList the list of directives parsed from the digest challenge
++ *
++ * @exception SaslException If a semantic error occurs
++ */
++ void checkSemantics(
++ DirectiveList dirList) throws SaslException
++ {
++ Iterator directives = dirList.getIterator();
++ ParsedDirective directive;
++ String name;
++
++ while (directives.hasNext())
++ {
++ directive = (ParsedDirective)directives.next();
++ name = directive.getName();
++ if (name.equals("realm"))
++ handleRealm(directive);
++ else if (name.equals("nonce"))
++ handleNonce(directive);
++ else if (name.equals("qop"))
++ handleQop(directive);
++ else if (name.equals("maxbuf"))
++ handleMaxbuf(directive);
++ else if (name.equals("charset"))
++ handleCharset(directive);
++ else if (name.equals("algorithm"))
++ handleAlgorithm(directive);
++ else if (name.equals("cipher"))
++ handleCipher(directive);
++ else if (name.equals("stale"))
++ handleStale(directive);
++ }
++
++ /* post semantic check */
++ if (-1 == m_maxBuf)
++ m_maxBuf = 65536;
++
++ if (m_qop == 0)
++ m_qop = QOP_AUTH;
++ else if ( (m_qop & QOP_AUTH) != QOP_AUTH )
++ throw new SaslException("Only qop-auth is supported by client");
++ else if ( ((m_qop & QOP_AUTH_CONF) == QOP_AUTH_CONF) &&
++ (0 == (m_cipherOptions & CIPHER_RECOGNIZED_MASK)) )
++ throw new SaslException("Invalid cipher options");
++ else if (null == m_nonce)
++ throw new SaslException("Missing nonce directive");
++ else if (m_staleFlag)
++ throw new SaslException("Unexpected stale flag");
++ else if ( null == m_algorithm )
++ throw new SaslException("Missing algorithm directive");
++ }
++
++ /**
++ * This function implements the semenatics of the nonce directive.
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs due to too many nonce
++ * values
++ */
++ void handleNonce(
++ ParsedDirective pd) throws SaslException
++ {
++ if (null != m_nonce)
++ throw new SaslException("Too many nonce values.");
++
++ m_nonce = pd.getValue();
++ }
++
++ /**
++ * This function implements the semenatics of the realm directive.
++ *
++ * @param pd ParsedDirective
++ */
++ void handleRealm(
++ ParsedDirective pd)
++ {
++ m_realms.add(pd.getValue());
++ }
++
++ /**
++ * This function implements the semenatics of the qop (quality of protection)
++ * directive. The value of the qop directive is as defined below:
++ * qop-options = "qop" "=" <"> qop-list <">
++ * qop-list = 1#qop-value
++ * qop-value = "auth" | "auth-int" | "auth-conf" | token
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs due to too many qop
++ * directives
++ */
++ void handleQop(
++ ParsedDirective pd) throws SaslException
++ {
++ String token;
++ TokenParser parser;
++
++ if (m_qop != 0)
++ throw new SaslException("Too many qop directives.");
++
++ parser = new TokenParser(pd.getValue());
++ for (token = parser.parseToken();
++ token != null;
++ token = parser.parseToken())
++ {
++ if (token.equals("auth"))
++ m_qop |= QOP_AUTH;
++ else if (token.equals("auth-int"))
++ m_qop |= QOP_AUTH_INT;
++ else if (token.equals("auth-conf"))
++ m_qop |= QOP_AUTH_CONF;
++ else
++ m_qop |= QOP_UNRECOGNIZED;
++ }
++ }
++
++ /**
++ * This function implements the semenatics of the Maxbuf directive.
++ * the value is defined as: 1*DIGIT
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occur
++ */
++ void handleMaxbuf(
++ ParsedDirective pd) throws SaslException
++ {
++ if (-1 != m_maxBuf) /*it's initialized to -1 */
++ throw new SaslException("Too many maxBuf directives.");
++
++ m_maxBuf = Integer.parseInt(pd.getValue());
++
++ if (0 == m_maxBuf)
++ throw new SaslException("Max buf value must be greater than zero.");
++ }
++
++ /**
++ * This function implements the semenatics of the charset directive.
++ * the value is defined as: 1*DIGIT
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs dur to too many charset
++ * directives or Invalid character encoding
++ * directive
++ */
++ void handleCharset(
++ ParsedDirective pd) throws SaslException
++ {
++ if (null != m_characterSet)
++ throw new SaslException("Too many charset directives.");
++
++ m_characterSet = pd.getValue();
++
++ if (!m_characterSet.equals("utf-8"))
++ throw new SaslException("Invalid character encoding directive");
++ }
++
++ /**
++ * This function implements the semenatics of the charset directive.
++ * the value is defined as: 1*DIGIT
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs due to too many algorith
++ * directive or Invalid algorithm directive
++ * value
++ */
++ void handleAlgorithm(
++ ParsedDirective pd) throws SaslException
++ {
++ if (null != m_algorithm)
++ throw new SaslException("Too many algorithm directives.");
++
++ m_algorithm = pd.getValue();
++
++ if (!"md5-sess".equals(m_algorithm))
++ throw new SaslException("Invalid algorithm directive value: " +
++ m_algorithm);
++ }
++
++ /**
++ * This function implements the semenatics of the cipher-opts directive
++ * directive. The value of the qop directive is as defined below:
++ * qop-options = "qop" "=" <"> qop-list <">
++ * qop-list = 1#qop-value
++ * qop-value = "auth" | "auth-int" | "auth-conf" | token
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs due to Too many cipher
++ * directives
++ */
++ void handleCipher(
++ ParsedDirective pd) throws SaslException
++ {
++ String token;
++ TokenParser parser;
++
++ if (0 != m_cipherOptions)
++ throw new SaslException("Too many cipher directives.");
++
++ parser = new TokenParser(pd.getValue());
++ token = parser.parseToken();
++ for (token = parser.parseToken();
++ token != null;
++ token = parser.parseToken())
++ {
++ if ("3des".equals(token))
++ m_cipherOptions |= CIPHER_3DES;
++ else if ("des".equals(token))
++ m_cipherOptions |= CIPHER_DES;
++ else if ("rc4-40".equals(token))
++ m_cipherOptions |= CIPHER_RC4_40;
++ else if ("rc4".equals(token))
++ m_cipherOptions |= CIPHER_RC4;
++ else if ("rc4-56".equals(token))
++ m_cipherOptions |= CIPHER_RC4_56;
++ else
++ m_cipherOptions |= CIPHER_UNRECOGNIZED;
++ }
++
++ if (m_cipherOptions == 0)
++ m_cipherOptions = CIPHER_UNRECOGNIZED;
++ }
++
++ /**
++ * This function implements the semenatics of the stale directive.
++ *
++ * @param pd ParsedDirective
++ *
++ * @exception SaslException If an error occurs due to Too many stale
++ * directives or Invalid stale directive value
++ */
++ void handleStale(
++ ParsedDirective pd) throws SaslException
++ {
++ if (false != m_staleFlag)
++ throw new SaslException("Too many stale directives.");
++
++ if ("true".equals(pd.getValue()))
++ m_staleFlag = true;
++ else
++ throw new SaslException("Invalid stale directive value: " +
++ pd.getValue());
++ }
++
++ /**
++ * Return the list of the All the Realms
++ *
++ * @return List of all the realms
++ */
++ public ArrayList getRealms()
++ {
++ return m_realms;
++ }
++
++ /**
++ * @return Returns the Nonce
++ */
++ public String getNonce()
++ {
++ return m_nonce;
++ }
++
++ /**
++ * Return the quality-of-protection
++ *
++ * @return The quality-of-protection
++ */
++ public int getQop()
++ {
++ return m_qop;
++ }
++
++ /**
++ * @return The state of the Staleflag
++ */
++ public boolean getStaleFlag()
++ {
++ return m_staleFlag;
++ }
++
++ /**
++ * @return The Maximum Buffer value
++ */
++ public int getMaxBuf()
++ {
++ return m_maxBuf;
++ }
++
++ /**
++ * @return character set values as string
++ */
++ public String getCharacterSet()
++ {
++ return m_characterSet;
++ }
++
++ /**
++ * @return The String value of the algorithm
++ */
++ public String getAlgorithm()
++ {
++ return m_algorithm;
++ }
++
++ /**
++ * @return The cipher options
++ */
++ public int getCipherOptions()
++ {
++ return m_cipherOptions;
++ }
++}
++
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java
+new file mode 100644
+index 0000000..141c96b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DigestMD5SaslClient.java
+@@ -0,0 +1,820 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/DigestMD5SaslClient.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
++ *
++ * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++import org.apache.harmony.javax.security.sasl.*;
++import org.apache.harmony.javax.security.auth.callback.*;
++import java.security.SecureRandom;
++import java.security.MessageDigest;
++import java.security.NoSuchAlgorithmException;
++import java.io.UnsupportedEncodingException;
++import java.io.IOException;
++import java.util.*;
++
++/**
++ * Implements the Client portion of DigestMD5 Sasl mechanism.
++ */
++public class DigestMD5SaslClient implements SaslClient
++{
++ private String m_authorizationId = "";
++ private String m_protocol = "";
++ private String m_serverName = "";
++ private Map m_props;
++ private CallbackHandler m_cbh;
++ private int m_state;
++ private String m_qopValue = "";
++ private char[] m_HA1 = null;
++ private String m_digestURI;
++ private DigestChallenge m_dc;
++ private String m_clientNonce = "";
++ private String m_realm = "";
++ private String m_name = "";
++
++ private static final int STATE_INITIAL = 0;
++ private static final int STATE_DIGEST_RESPONSE_SENT = 1;
++ private static final int STATE_VALID_SERVER_RESPONSE = 2;
++ private static final int STATE_INVALID_SERVER_RESPONSE = 3;
++ private static final int STATE_DISPOSED = 4;
++
++ private static final int NONCE_BYTE_COUNT = 32;
++ private static final int NONCE_HEX_COUNT = 2*NONCE_BYTE_COUNT;
++
++ private static final String DIGEST_METHOD = "AUTHENTICATE";
++
++ /**
++ * Creates an DigestMD5SaslClient object using the parameters supplied.
++ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
++ * contained in props
++ *
++ * @param authorizationId The possibly null protocol-dependent
++ * identification to be used for authorization. If
++ * null or empty, the server derives an authorization
++ * ID from the client's authentication credentials.
++ * When the SASL authentication completes
++ * successfully, the specified entity is granted
++ * access.
++ *
++ * @param protocol The non-null string name of the protocol for which
++ * the authentication is being performed (e.g. "ldap")
++ *
++ * @param serverName The non-null fully qualified host name of the server
++ * to authenticate to
++ *
++ * @param props The possibly null set of properties used to select
++ * the SASL mechanism and to configure the
++ * authentication exchange of the selected mechanism.
++ * See the Sasl class for a list of standard properties.
++ * Other, possibly mechanism-specific, properties can
++ * be included. Properties not relevant to the selected
++ * mechanism are ignored.
++ *
++ * @param cbh The possibly null callback handler to used by the
++ * SASL mechanisms to get further information from the
++ * application/library to complete the authentication.
++ * For example, a SASL mechanism might require the
++ * authentication ID, password and realm from the
++ * caller. The authentication ID is requested by using
++ * a NameCallback. The password is requested by using
++ * a PasswordCallback. The realm is requested by using
++ * a RealmChoiceCallback if there is a list of realms
++ * to choose from, and by using a RealmCallback if the
++ * realm must be entered.
++ *
++ * @return A possibly null SaslClient created using the
++ * parameters supplied. If null, this factory cannot
++ * produce a SaslClient using the parameters supplied.
++ *
++ * @exception SaslException If a SaslClient instance cannot be created
++ * because of an error
++ */
++ public static SaslClient getClient(
++ String authorizationId,
++ String protocol,
++ String serverName,
++ Map props,
++ CallbackHandler cbh)
++ {
++ String desiredQOP = (String)props.get(Sasl.QOP);
++ String desiredStrength = (String)props.get(Sasl.STRENGTH);
++ String serverAuth = (String)props.get(Sasl.SERVER_AUTH);
++
++ //only support qop equal to auth
++ if ((desiredQOP != null) && !"auth".equals(desiredQOP))
++ return null;
++
++ //doesn't support server authentication
++ if ((serverAuth != null) && !"false".equals(serverAuth))
++ return null;
++
++ //need a callback handler to get the password
++ if (cbh == null)
++ return null;
++
++ return new DigestMD5SaslClient(authorizationId, protocol,
++ serverName, props, cbh);
++ }
++
++ /**
++ * Creates an DigestMD5SaslClient object using the parameters supplied.
++ * Assumes that the QOP, STRENGTH, and SERVER_AUTH properties are
++ * contained in props
++ *
++ * @param authorizationId The possibly null protocol-dependent
++ * identification to be used for authorization. If
++ * null or empty, the server derives an authorization
++ * ID from the client's authentication credentials.
++ * When the SASL authentication completes
++ * successfully, the specified entity is granted
++ * access.
++ *
++ * @param protocol The non-null string name of the protocol for which
++ * the authentication is being performed (e.g. "ldap")
++ *
++ * @param serverName The non-null fully qualified host name of the server
++ * to authenticate to
++ *
++ * @param props The possibly null set of properties used to select
++ * the SASL mechanism and to configure the
++ * authentication exchange of the selected mechanism.
++ * See the Sasl class for a list of standard properties.
++ * Other, possibly mechanism-specific, properties can
++ * be included. Properties not relevant to the selected
++ * mechanism are ignored.
++ *
++ * @param cbh The possibly null callback handler to used by the
++ * SASL mechanisms to get further information from the
++ * application/library to complete the authentication.
++ * For example, a SASL mechanism might require the
++ * authentication ID, password and realm from the
++ * caller. The authentication ID is requested by using
++ * a NameCallback. The password is requested by using
++ * a PasswordCallback. The realm is requested by using
++ * a RealmChoiceCallback if there is a list of realms
++ * to choose from, and by using a RealmCallback if the
++ * realm must be entered.
++ *
++ */
++ private DigestMD5SaslClient(
++ String authorizationId,
++ String protocol,
++ String serverName,
++ Map props,
++ CallbackHandler cbh)
++ {
++ m_authorizationId = authorizationId;
++ m_protocol = protocol;
++ m_serverName = serverName;
++ m_props = props;
++ m_cbh = cbh;
++
++ m_state = STATE_INITIAL;
++ }
++
++ /**
++ * Determines if this mechanism has an optional initial response. If true,
++ * caller should call evaluateChallenge() with an empty array to get the
++ * initial response.
++ *
++ * @return true if this mechanism has an initial response
++ */
++ public boolean hasInitialResponse()
++ {
++ return false;
++ }
++
++ /**
++ * Determines if the authentication exchange has completed. This method
++ * may be called at any time, but typically, it will not be called until
++ * the caller has received indication from the server (in a protocol-
++ * specific manner) that the exchange has completed.
++ *
++ * @return true if the authentication exchange has completed;
++ * false otherwise.
++ */
++ public boolean isComplete()
++ {
++ if ((m_state == STATE_VALID_SERVER_RESPONSE) ||
++ (m_state == STATE_INVALID_SERVER_RESPONSE) ||
++ (m_state == STATE_DISPOSED))
++ return true;
++ else
++ return false;
++ }
++
++ /**
++ * Unwraps a byte array received from the server. This method can be called
++ * only after the authentication exchange has completed (i.e., when
++ * isComplete() returns true) and only if the authentication exchange has
++ * negotiated integrity and/or privacy as the quality of protection;
++ * otherwise, an IllegalStateException is thrown.
++ *
++ * incoming is the contents of the SASL buffer as defined in RFC 2222
++ * without the leading four octet field that represents the length.
++ * offset and len specify the portion of incoming to use.
++ *
++ * @param incoming A non-null byte array containing the encoded bytes
++ * from the server
++ * @param offset The starting position at incoming of the bytes to use
++ *
++ * @param len The number of bytes from incoming to use
++ *
++ * @return A non-null byte array containing the decoded bytes
++ *
++ */
++ public byte[] unwrap(
++ byte[] incoming,
++ int offset,
++ int len)
++ throws SaslException
++ {
++ throw new IllegalStateException(
++ "unwrap: QOP has neither integrity nor privacy>");
++ }
++
++ /**
++ * Wraps a byte array to be sent to the server. This method can be called
++ * only after the authentication exchange has completed (i.e., when
++ * isComplete() returns true) and only if the authentication exchange has
++ * negotiated integrity and/or privacy as the quality of protection;
++ * otherwise, an IllegalStateException is thrown.
++ *
++ * The result of this method will make up the contents of the SASL buffer as
++ * defined in RFC 2222 without the leading four octet field that represents
++ * the length. offset and len specify the portion of outgoing to use.
++ *
++ * @param outgoing A non-null byte array containing the bytes to encode
++ * @param offset The starting position at outgoing of the bytes to use
++ * @param len The number of bytes from outgoing to use
++ *
++ * @return A non-null byte array containing the encoded bytes
++ *
++ * @exception SaslException if incoming cannot be successfully unwrapped.
++ *
++ * @exception IllegalStateException if the authentication exchange has
++ * not completed, or if the negotiated quality of
++ * protection has neither integrity nor privacy.
++ */
++ public byte[] wrap(
++ byte[] outgoing,
++ int offset,
++ int len)
++ throws SaslException
++ {
++ throw new IllegalStateException(
++ "wrap: QOP has neither integrity nor privacy>");
++ }
++
++ /**
++ * Retrieves the negotiated property. This method can be called only after
++ * the authentication exchange has completed (i.e., when isComplete()
++ * returns true); otherwise, an IllegalStateException is thrown.
++ *
++ * @param propName The non-null property name
++ *
++ * @return The value of the negotiated property. If null, the property was
++ * not negotiated or is not applicable to this mechanism.
++ *
++ * @exception IllegalStateException if this authentication exchange has
++ * not completed
++ */
++ public Object getNegotiatedProperty(
++ String propName)
++ {
++ if (m_state != STATE_VALID_SERVER_RESPONSE)
++ throw new IllegalStateException(
++ "getNegotiatedProperty: authentication exchange not complete.");
++
++ if (Sasl.QOP.equals(propName))
++ return "auth";
++ else
++ return null;
++ }
++
++ /**
++ * Disposes of any system resources or security-sensitive information the
++ * SaslClient might be using. Invoking this method invalidates the
++ * SaslClient instance. This method is idempotent.
++ *
++ * @exception SaslException if a problem was encountered while disposing
++ * of the resources
++ */
++ public void dispose()
++ throws SaslException
++ {
++ if (m_state != STATE_DISPOSED)
++ {
++ m_state = STATE_DISPOSED;
++ }
++ }
++
++ /**
++ * Evaluates the challenge data and generates a response. If a challenge
++ * is received from the server during the authentication process, this
++ * method is called to prepare an appropriate next response to submit to
++ * the server.
++ *
++ * @param challenge The non-null challenge sent from the server. The
++ * challenge array may have zero length.
++ *
++ * @return The possibly null reponse to send to the server. It is null
++ * if the challenge accompanied a "SUCCESS" status and the
++ * challenge only contains data for the client to update its
++ * state and no response needs to be sent to the server.
++ * The response is a zero-length byte array if the client is to
++ * send a response with no data.
++ *
++ * @exception SaslException If an error occurred while processing the
++ * challenge or generating a response.
++ */
++ public byte[] evaluateChallenge(
++ byte[] challenge)
++ throws SaslException
++ {
++ byte[] response = null;
++
++ //printState();
++ switch (m_state)
++ {
++ case STATE_INITIAL:
++ if (challenge.length == 0)
++ throw new SaslException("response = byte[0]");
++ else
++ try
++ {
++ response = createDigestResponse(challenge).
++ getBytes("UTF-8");
++ m_state = STATE_DIGEST_RESPONSE_SENT;
++ }
++ catch (java.io.UnsupportedEncodingException e)
++ {
++ throw new SaslException(
++ "UTF-8 encoding not suppported by platform", e);
++ }
++ break;
++ case STATE_DIGEST_RESPONSE_SENT:
++ if (checkServerResponseAuth(challenge))
++ m_state = STATE_VALID_SERVER_RESPONSE;
++ else
++ {
++ m_state = STATE_INVALID_SERVER_RESPONSE;
++ throw new SaslException("Could not validate response-auth " +
++ "value from server");
++ }
++ break;
++ case STATE_VALID_SERVER_RESPONSE:
++ case STATE_INVALID_SERVER_RESPONSE:
++ throw new SaslException("Authentication sequence is complete");
++ case STATE_DISPOSED:
++ throw new SaslException("Client has been disposed");
++ default:
++ throw new SaslException("Unknown client state.");
++ }
++
++ return response;
++ }
++
++ /**
++ * This function takes a 16 byte binary md5-hash value and creates a 32
++ * character (plus a terminating null character) hex-digit
++ * representation of binary data.
++ *
++ * @param hash 16 byte binary md5-hash value in bytes
++ *
++ * @return 32 character (plus a terminating null character) hex-digit
++ * representation of binary data.
++ */
++ char[] convertToHex(
++ byte[] hash)
++ {
++ int i;
++ byte j;
++ byte fifteen = 15;
++ char[] hex = new char[32];
++
++ for (i = 0; i < 16; i++)
++ {
++ //convert value of top 4 bits to hex char
++ hex[i*2] = getHexChar((byte)((hash[i] & 0xf0) >> 4));
++ //convert value of bottom 4 bits to hex char
++ hex[(i*2)+1] = getHexChar((byte)(hash[i] & 0x0f));
++ }
++
++ return hex;
++ }
++
++ /**
++ * Calculates the HA1 portion of the response
++ *
++ * @param algorithm Algorith to use.
++ * @param userName User being authenticated
++ * @param realm realm information
++ * @param password password of teh user
++ * @param nonce nonce value
++ * @param clientNonce Clients Nonce value
++ *
++ * @return HA1 portion of the response in a character array
++ *
++ * @exception SaslException If an error occurs
++ */
++ char[] DigestCalcHA1(
++ String algorithm,
++ String userName,
++ String realm,
++ String password,
++ String nonce,
++ String clientNonce) throws SaslException
++ {
++ byte[] hash;
++
++ try
++ {
++ MessageDigest md = MessageDigest.getInstance("MD5");
++
++ md.update(userName.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(realm.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(password.getBytes("UTF-8"));
++ hash = md.digest();
++
++ if ("md5-sess".equals(algorithm))
++ {
++ md.update(hash);
++ md.update(":".getBytes("UTF-8"));
++ md.update(nonce.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(clientNonce.getBytes("UTF-8"));
++ hash = md.digest();
++ }
++ }
++ catch(NoSuchAlgorithmException e)
++ {
++ throw new SaslException("No provider found for MD5 hash", e);
++ }
++ catch(UnsupportedEncodingException e)
++ {
++ throw new SaslException(
++ "UTF-8 encoding not supported by platform.", e);
++ }
++
++ return convertToHex(hash);
++ }
++
++
++ /**
++ * This function calculates the response-value of the response directive of
++ * the digest-response as documented in RFC 2831
++ *
++ * @param HA1 H(A1)
++ * @param serverNonce nonce from server
++ * @param nonceCount 8 hex digits
++ * @param clientNonce client nonce
++ * @param qop qop-value: "", "auth", "auth-int"
++ * @param method method from the request
++ * @param digestUri requested URL
++ * @param clientResponseFlag request-digest or response-digest
++ *
++ * @return Response-value of the response directive of the digest-response
++ *
++ * @exception SaslException If an error occurs
++ */
++ char[] DigestCalcResponse(
++ char[] HA1, /* H(A1) */
++ String serverNonce, /* nonce from server */
++ String nonceCount, /* 8 hex digits */
++ String clientNonce, /* client nonce */
++ String qop, /* qop-value: "", "auth", "auth-int" */
++ String method, /* method from the request */
++ String digestUri, /* requested URL */
++ boolean clientResponseFlag) /* request-digest or response-digest */
++ throws SaslException
++ {
++ byte[] HA2;
++ byte[] respHash;
++ char[] HA2Hex;
++
++ // calculate H(A2)
++ try
++ {
++ MessageDigest md = MessageDigest.getInstance("MD5");
++ if (clientResponseFlag)
++ md.update(method.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(digestUri.getBytes("UTF-8"));
++ if ("auth-int".equals(qop))
++ {
++ md.update(":".getBytes("UTF-8"));
++ md.update("00000000000000000000000000000000".getBytes("UTF-8"));
++ }
++ HA2 = md.digest();
++ HA2Hex = convertToHex(HA2);
++
++ // calculate response
++ md.update(new String(HA1).getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(serverNonce.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ if (qop.length() > 0)
++ {
++ md.update(nonceCount.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(clientNonce.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ md.update(qop.getBytes("UTF-8"));
++ md.update(":".getBytes("UTF-8"));
++ }
++ md.update(new String(HA2Hex).getBytes("UTF-8"));
++ respHash = md.digest();
++ }
++ catch(NoSuchAlgorithmException e)
++ {
++ throw new SaslException("No provider found for MD5 hash", e);
++ }
++ catch(UnsupportedEncodingException e)
++ {
++ throw new SaslException(
++ "UTF-8 encoding not supported by platform.", e);
++ }
++
++ return convertToHex(respHash);
++ }
++
++
++ /**
++ * Creates the intial response to be sent to the server.
++ *
++ * @param challenge Challenge in bytes recived form the Server
++ *
++ * @return Initial response to be sent to the server
++ */
++ private String createDigestResponse(
++ byte[] challenge)
++ throws SaslException
++ {
++ char[] response;
++ StringBuffer digestResponse = new StringBuffer(512);
++ int realmSize;
++
++ m_dc = new DigestChallenge(challenge);
++
++ m_digestURI = m_protocol + "/" + m_serverName;
++
++ if ((m_dc.getQop() & DigestChallenge.QOP_AUTH)
++ == DigestChallenge.QOP_AUTH )
++ m_qopValue = "auth";
++ else
++ throw new SaslException("Client only supports qop of 'auth'");
++
++ //get call back information
++ Callback[] callbacks = new Callback[3];
++ ArrayList realms = m_dc.getRealms();
++ realmSize = realms.size();
++ if (realmSize == 0)
++ {
++ callbacks[0] = new RealmCallback("Realm");
++ }
++ else if (realmSize == 1)
++ {
++ callbacks[0] = new RealmCallback("Realm", (String)realms.get(0));
++ }
++ else
++ {
++ callbacks[0] =
++ new RealmChoiceCallback(
++ "Realm",
++ (String[])realms.toArray(new String[realmSize]),
++ 0, //the default choice index
++ false); //no multiple selections
++ }
++
++ callbacks[1] = new PasswordCallback("Password", false);
++ //false = no echo
++
++ if (m_authorizationId == null || m_authorizationId.length() == 0)
++ callbacks[2] = new NameCallback("Name");
++ else
++ callbacks[2] = new NameCallback("Name", m_authorizationId);
++
++ try
++ {
++ m_cbh.handle(callbacks);
++ }
++ catch(UnsupportedCallbackException e)
++ {
++ throw new SaslException("Handler does not support" +
++ " necessary callbacks",e);
++ }
++ catch(IOException e)
++ {
++ throw new SaslException("IO exception in CallbackHandler.", e);
++ }
++
++ if (realmSize > 1)
++ {
++ int[] selections =
++ ((RealmChoiceCallback)callbacks[0]).getSelectedIndexes();
++
++ if (selections.length > 0)
++ m_realm =
++ ((RealmChoiceCallback)callbacks[0]).getChoices()[selections[0]];
++ else
++ m_realm = ((RealmChoiceCallback)callbacks[0]).getChoices()[0];
++ }
++ else
++ m_realm = ((RealmCallback)callbacks[0]).getText();
++
++ m_clientNonce = getClientNonce();
++
++ m_name = ((NameCallback)callbacks[2]).getName();
++ if (m_name == null)
++ m_name = ((NameCallback)callbacks[2]).getDefaultName();
++ if (m_name == null)
++ throw new SaslException("No user name was specified.");
++
++ m_HA1 = DigestCalcHA1(
++ m_dc.getAlgorithm(),
++ m_name,
++ m_realm,
++ new String(((PasswordCallback)callbacks[1]).getPassword()),
++ m_dc.getNonce(),
++ m_clientNonce);
++
++ response = DigestCalcResponse(m_HA1,
++ m_dc.getNonce(),
++ "00000001",
++ m_clientNonce,
++ m_qopValue,
++ "AUTHENTICATE",
++ m_digestURI,
++ true);
++
++ digestResponse.append("username=\"");
++ digestResponse.append(m_authorizationId);
++ if (0 != m_realm.length())
++ {
++ digestResponse.append("\",realm=\"");
++ digestResponse.append(m_realm);
++ }
++ digestResponse.append("\",cnonce=\"");
++ digestResponse.append(m_clientNonce);
++ digestResponse.append("\",nc=");
++ digestResponse.append("00000001"); //nounce count
++ digestResponse.append(",qop=");
++ digestResponse.append(m_qopValue);
++ digestResponse.append(",digest-uri=\"");
++ digestResponse.append(m_digestURI);
++ digestResponse.append("\",response=");
++ digestResponse.append(response);
++ digestResponse.append(",charset=utf-8,nonce=\"");
++ digestResponse.append(m_dc.getNonce());
++ digestResponse.append("\"");
++
++ return digestResponse.toString();
++ }
++
++
++ /**
++ * This function validates the server response. This step performs a
++ * modicum of mutual authentication by verifying that the server knows
++ * the user's password
++ *
++ * @param serverResponse Response recived form Server
++ *
++ * @return true if the mutual authentication succeeds;
++ * else return false
++ *
++ * @exception SaslException If an error occurs
++ */
++ boolean checkServerResponseAuth(
++ byte[] serverResponse) throws SaslException
++ {
++ char[] response;
++ ResponseAuth responseAuth = null;
++ String responseStr;
++
++ responseAuth = new ResponseAuth(serverResponse);
++
++ response = DigestCalcResponse(m_HA1,
++ m_dc.getNonce(),
++ "00000001",
++ m_clientNonce,
++ m_qopValue,
++ DIGEST_METHOD,
++ m_digestURI,
++ false);
++
++ responseStr = new String(response);
++
++ return responseStr.equals(responseAuth.getResponseValue());
++ }
++
++
++ /**
++ * This function returns hex character representing the value of the input
++ *
++ * @param value Input value in byte
++ *
++ * @return Hex value of the Input byte value
++ */
++ private static char getHexChar(
++ byte value)
++ {
++ switch (value)
++ {
++ case 0:
++ return '0';
++ case 1:
++ return '1';
++ case 2:
++ return '2';
++ case 3:
++ return '3';
++ case 4:
++ return '4';
++ case 5:
++ return '5';
++ case 6:
++ return '6';
++ case 7:
++ return '7';
++ case 8:
++ return '8';
++ case 9:
++ return '9';
++ case 10:
++ return 'a';
++ case 11:
++ return 'b';
++ case 12:
++ return 'c';
++ case 13:
++ return 'd';
++ case 14:
++ return 'e';
++ case 15:
++ return 'f';
++ default:
++ return 'Z';
++ }
++ }
++
++ /**
++ * Calculates the Nonce value of the Client
++ *
++ * @return Nonce value of the client
++ *
++ * @exception SaslException If an error Occurs
++ */
++ String getClientNonce() throws SaslException
++ {
++ byte[] nonceBytes = new byte[NONCE_BYTE_COUNT];
++ SecureRandom prng;
++ byte nonceByte;
++ char[] hexNonce = new char[NONCE_HEX_COUNT];
++
++ try
++ {
++ prng = SecureRandom.getInstance("SHA1PRNG");
++ prng.nextBytes(nonceBytes);
++ for(int i=0; i> 4));
++ }
++ return new String(hexNonce);
++ }
++ catch(NoSuchAlgorithmException e)
++ {
++ throw new SaslException("No random number generator available", e);
++ }
++ }
++
++ /**
++ * Returns the IANA-registered mechanism name of this SASL client.
++ * (e.g. "CRAM-MD5", "GSSAPI")
++ *
++ * @return "DIGEST-MD5"the IANA-registered mechanism name of this SASL
++ * client.
++ */
++ public String getMechanismName()
++ {
++ return "DIGEST-MD5";
++ }
++
++} //end class DigestMD5SaslClient
++
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java b/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java
+new file mode 100644
+index 0000000..fc26a6b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/DirectiveList.java
+@@ -0,0 +1,363 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/DirectiveList.java,v 1.4 2005/01/17 15:00:54 sunilk Exp $
++ *
++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++import java.util.*;
++import org.apache.harmony.javax.security.sasl.*;
++import java.io.UnsupportedEncodingException;
++
++/**
++ * Implements the DirectiveList class whihc will be used by the
++ * DigestMD5SaslClient class
++ */
++class DirectiveList extends Object
++{
++ private static final int STATE_LOOKING_FOR_FIRST_DIRECTIVE = 1;
++ private static final int STATE_LOOKING_FOR_DIRECTIVE = 2;
++ private static final int STATE_SCANNING_NAME = 3;
++ private static final int STATE_LOOKING_FOR_EQUALS = 4;
++ private static final int STATE_LOOKING_FOR_VALUE = 5;
++ private static final int STATE_LOOKING_FOR_COMMA = 6;
++ private static final int STATE_SCANNING_QUOTED_STRING_VALUE = 7;
++ private static final int STATE_SCANNING_TOKEN_VALUE = 8;
++ private static final int STATE_NO_UTF8_SUPPORT = 9;
++
++ private int m_curPos;
++ private int m_errorPos;
++ private String m_directives;
++ private int m_state;
++ private ArrayList m_directiveList;
++ private String m_curName;
++ private int m_scanStart;
++
++ /**
++ * Constructs a new DirectiveList.
++ */
++ DirectiveList(
++ byte[] directives)
++ {
++ m_curPos = 0;
++ m_state = STATE_LOOKING_FOR_FIRST_DIRECTIVE;
++ m_directiveList = new ArrayList(10);
++ m_scanStart = 0;
++ m_errorPos = -1;
++ try
++ {
++ m_directives = new String(directives, "UTF-8");
++ }
++ catch(UnsupportedEncodingException e)
++ {
++ m_state = STATE_NO_UTF8_SUPPORT;
++ }
++ }
++
++ /**
++ * This function takes a US-ASCII character string containing a list of comma
++ * separated directives, and parses the string into the individual directives
++ * and their values. A directive consists of a token specifying the directive
++ * name followed by an equal sign (=) and the directive value. The value is
++ * either a token or a quoted string
++ *
++ * @exception SaslException If an error Occurs
++ */
++ void parseDirectives() throws SaslException
++ {
++ char prevChar;
++ char currChar;
++ int rc = 0;
++ boolean haveQuotedPair = false;
++ String currentName = "";
++
++ if (m_state == STATE_NO_UTF8_SUPPORT)
++ throw new SaslException("No UTF-8 support on platform");
++
++ prevChar = 0;
++
++ while (m_curPos < m_directives.length())
++ {
++ currChar = m_directives.charAt(m_curPos);
++ switch (m_state)
++ {
++ case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
++ case STATE_LOOKING_FOR_DIRECTIVE:
++ if (isWhiteSpace(currChar))
++ {
++ break;
++ }
++ else if (isValidTokenChar(currChar))
++ {
++ m_scanStart = m_curPos;
++ m_state = STATE_SCANNING_NAME;
++ }
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Invalid name character");
++ }
++ break;
++
++ case STATE_SCANNING_NAME:
++ if (isValidTokenChar(currChar))
++ {
++ break;
++ }
++ else if (isWhiteSpace(currChar))
++ {
++ currentName = m_directives.substring(m_scanStart, m_curPos);
++ m_state = STATE_LOOKING_FOR_EQUALS;
++ }
++ else if ('=' == currChar)
++ {
++ currentName = m_directives.substring(m_scanStart, m_curPos);
++ m_state = STATE_LOOKING_FOR_VALUE;
++ }
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Invalid name character");
++ }
++ break;
++
++ case STATE_LOOKING_FOR_EQUALS:
++ if (isWhiteSpace(currChar))
++ {
++ break;
++ }
++ else if ('=' == currChar)
++ {
++ m_state = STATE_LOOKING_FOR_VALUE;
++ }
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Expected equals sign '='.");
++ }
++ break;
++
++ case STATE_LOOKING_FOR_VALUE:
++ if (isWhiteSpace(currChar))
++ {
++ break;
++ }
++ else if ('"' == currChar)
++ {
++ m_scanStart = m_curPos+1; /* don't include the quote */
++ m_state = STATE_SCANNING_QUOTED_STRING_VALUE;
++ }
++ else if (isValidTokenChar(currChar))
++ {
++ m_scanStart = m_curPos;
++ m_state = STATE_SCANNING_TOKEN_VALUE;
++ }
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Unexpected character");
++ }
++ break;
++
++ case STATE_SCANNING_TOKEN_VALUE:
++ if (isValidTokenChar(currChar))
++ {
++ break;
++ }
++ else if (isWhiteSpace(currChar))
++ {
++ addDirective(currentName, false);
++ m_state = STATE_LOOKING_FOR_COMMA;
++ }
++ else if (',' == currChar)
++ {
++ addDirective(currentName, false);
++ m_state = STATE_LOOKING_FOR_DIRECTIVE;
++ }
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Invalid value character");
++ }
++ break;
++
++ case STATE_SCANNING_QUOTED_STRING_VALUE:
++ if ('\\' == currChar)
++ haveQuotedPair = true;
++ if ( ('"' == currChar) &&
++ ('\\' != prevChar) )
++ {
++ addDirective(currentName, haveQuotedPair);
++ haveQuotedPair = false;
++ m_state = STATE_LOOKING_FOR_COMMA;
++ }
++ break;
++
++ case STATE_LOOKING_FOR_COMMA:
++ if (isWhiteSpace(currChar))
++ break;
++ else if (currChar == ',')
++ m_state = STATE_LOOKING_FOR_DIRECTIVE;
++ else
++ {
++ m_errorPos = m_curPos;
++ throw new SaslException("Parse error: Expected a comma.");
++ }
++ break;
++ }
++ if (0 != rc)
++ break;
++ prevChar = currChar;
++ m_curPos++;
++ } /* end while loop */
++
++
++ if (rc == 0)
++ {
++ /* check the ending state */
++ switch (m_state)
++ {
++ case STATE_SCANNING_TOKEN_VALUE:
++ addDirective(currentName, false);
++ break;
++
++ case STATE_LOOKING_FOR_FIRST_DIRECTIVE:
++ case STATE_LOOKING_FOR_COMMA:
++ break;
++
++ case STATE_LOOKING_FOR_DIRECTIVE:
++ throw new SaslException("Parse error: Trailing comma.");
++
++ case STATE_SCANNING_NAME:
++ case STATE_LOOKING_FOR_EQUALS:
++ case STATE_LOOKING_FOR_VALUE:
++ throw new SaslException("Parse error: Missing value.");
++
++ case STATE_SCANNING_QUOTED_STRING_VALUE:
++ throw new SaslException("Parse error: Missing closing quote.");
++ }
++ }
++
++ }
++
++ /**
++ * This function returns TRUE if the character is a valid token character.
++ *
++ * token = 1*
++ *
++ * separators = "(" | ")" | "<" | ">" | "@"
++ * | "," | ";" | ":" | "\" | <">
++ * | "/" | "[" | "]" | "?" | "="
++ * | "{" | "}" | SP | HT
++ *
++ * CTL =
++ *
++ * CHAR =
++ *
++ * @param c character to be tested
++ *
++ * @return Returns TRUE if the character is a valid token character.
++ */
++ boolean isValidTokenChar(
++ char c)
++ {
++ if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
++ ( (c >= '\u003a') && (c <= '\u0040') ) ||
++ ( (c >= '\u005b') && (c <= '\u005d') ) ||
++ ('\u002c' == c) ||
++ ('\u0025' == c) ||
++ ('\u0028' == c) ||
++ ('\u0029' == c) ||
++ ('\u007b' == c) ||
++ ('\u007d' == c) ||
++ ('\u007f' == c) )
++ return false;
++
++ return true;
++ }
++
++ /**
++ * This function returns TRUE if the character is linear white space (LWS).
++ * LWS = [CRLF] 1*( SP | HT )
++ * @param c Input charcter to be tested
++ *
++ * @return Returns TRUE if the character is linear white space (LWS)
++ */
++ boolean isWhiteSpace(
++ char c)
++ {
++ if ( ('\t' == c) || // HORIZONTAL TABULATION.
++ ('\n' == c) || // LINE FEED.
++ ('\r' == c) || // CARRIAGE RETURN.
++ ('\u0020' == c) )
++ return true;
++
++ return false;
++ }
++
++ /**
++ * This function creates a directive record and adds it to the list, the
++ * value will be added later after it is parsed.
++ *
++ * @param name Name
++ * @param haveQuotedPair true if quoted pair is there else false
++ */
++ void addDirective(
++ String name,
++ boolean haveQuotedPair)
++ {
++ String value;
++ int inputIndex;
++ int valueIndex;
++ char valueChar;
++ int type;
++
++ if (!haveQuotedPair)
++ {
++ value = m_directives.substring(m_scanStart, m_curPos);
++ }
++ else
++ { //copy one character at a time skipping backslash excapes.
++ StringBuffer valueBuf = new StringBuffer(m_curPos - m_scanStart);
++ valueIndex = 0;
++ inputIndex = m_scanStart;
++ while (inputIndex < m_curPos)
++ {
++ if ('\\' == (valueChar = m_directives.charAt(inputIndex)))
++ inputIndex++;
++ valueBuf.setCharAt(valueIndex, m_directives.charAt(inputIndex));
++ valueIndex++;
++ inputIndex++;
++ }
++ value = new String(valueBuf);
++ }
++
++ if (m_state == STATE_SCANNING_QUOTED_STRING_VALUE)
++ type = ParsedDirective.QUOTED_STRING_VALUE;
++ else
++ type = ParsedDirective.TOKEN_VALUE;
++ m_directiveList.add(new ParsedDirective(name, value, type));
++ }
++
++
++ /**
++ * Returns the List iterator.
++ *
++ * @return Returns the Iterator Object for the List.
++ */
++ Iterator getIterator()
++ {
++ return m_directiveList.iterator();
++ }
++}
++
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java b/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java
+new file mode 100644
+index 0000000..17bf70e
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/ParsedDirective.java
+@@ -0,0 +1,56 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/ParsedDirective.java,v 1.1 2003/08/21 10:06:26 kkanil Exp $
++ *
++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++/**
++ * Implements the ParsedDirective class which will be used in the
++ * DigestMD5SaslClient mechanism.
++ */
++class ParsedDirective
++{
++ public static final int QUOTED_STRING_VALUE = 1;
++ public static final int TOKEN_VALUE = 2;
++
++ private int m_valueType;
++ private String m_name;
++ private String m_value;
++
++ ParsedDirective(
++ String name,
++ String value,
++ int type)
++ {
++ m_name = name;
++ m_value = value;
++ m_valueType = type;
++ }
++
++ String getValue()
++ {
++ return m_value;
++ }
++
++ String getName()
++ {
++ return m_name;
++ }
++
++ int getValueType()
++ {
++ return m_valueType;
++ }
++
++}
++
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java b/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java
+new file mode 100644
+index 0000000..0aef955
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/ResponseAuth.java
+@@ -0,0 +1,83 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/ResponseAuth.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
++ *
++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++import java.util.*;
++import org.apache.harmony.javax.security.sasl.*;
++
++/**
++ * Implements the ResponseAuth class used by the DigestMD5SaslClient mechanism
++ */
++class ResponseAuth
++{
++
++ private String m_responseValue;
++
++ ResponseAuth(
++ byte[] responseAuth)
++ throws SaslException
++ {
++ m_responseValue = null;
++
++ DirectiveList dirList = new DirectiveList(responseAuth);
++ try
++ {
++ dirList.parseDirectives();
++ checkSemantics(dirList);
++ }
++ catch (SaslException e)
++ {
++ }
++ }
++
++ /**
++ * Checks the semantics of the directives in the directive list as parsed
++ * from the digest challenge byte array.
++ *
++ * @param dirList the list of directives parsed from the digest challenge
++ *
++ * @exception SaslException If a semantic error occurs
++ */
++ void checkSemantics(
++ DirectiveList dirList) throws SaslException
++ {
++ Iterator directives = dirList.getIterator();
++ ParsedDirective directive;
++ String name;
++
++ while (directives.hasNext())
++ {
++ directive = (ParsedDirective)directives.next();
++ name = directive.getName();
++ if (name.equals("rspauth"))
++ m_responseValue = directive.getValue();
++ }
++
++ /* post semantic check */
++ if (m_responseValue == null)
++ throw new SaslException("Missing response-auth directive.");
++ }
++
++ /**
++ * returns the ResponseValue
++ *
++ * @return the ResponseValue as a String.
++ */
++ public String getResponseValue()
++ {
++ return m_responseValue;
++ }
++}
++
+diff --git a/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java b/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java
+new file mode 100644
+index 0000000..3d3491d
+--- /dev/null
++++ b/external/asmack/build/src/trunk/com/novell/sasl/client/TokenParser.java
+@@ -0,0 +1,208 @@
++/* **************************************************************************
++ * $OpenLDAP: /com/novell/sasl/client/TokenParser.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
++ *
++ * Copyright (C) 2002 Novell, Inc. All Rights Reserved.
++ *
++ * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
++ * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
++ * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
++ * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
++ * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
++ * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
++ * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
++ * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
++ ******************************************************************************/
++package com.novell.sasl.client;
++
++import org.apache.harmony.javax.security.sasl.*;
++/**
++ * The TokenParser class will parse individual tokens from a list of tokens that
++ * are a directive value for a DigestMD5 authentication.The tokens are separated
++ * commas.
++ */
++class TokenParser extends Object
++{
++ private static final int STATE_LOOKING_FOR_FIRST_TOKEN = 1;
++ private static final int STATE_LOOKING_FOR_TOKEN = 2;
++ private static final int STATE_SCANNING_TOKEN = 3;
++ private static final int STATE_LOOKING_FOR_COMMA = 4;
++ private static final int STATE_PARSING_ERROR = 5;
++ private static final int STATE_DONE = 6;
++
++ private int m_curPos;
++ private int m_scanStart;
++ private int m_state;
++ private String m_tokens;
++
++
++ TokenParser(
++ String tokens)
++ {
++ m_tokens = tokens;
++ m_curPos = 0;
++ m_scanStart = 0;
++ m_state = STATE_LOOKING_FOR_FIRST_TOKEN;
++ }
++
++ /**
++ * This function parses the next token from the tokens string and returns
++ * it as a string. If there are no more tokens a null reference is returned.
++ *
++ * @return the parsed token or a null reference if there are no more
++ * tokens
++ *
++ * @exception SASLException if an error occurs while parsing
++ */
++ String parseToken() throws SaslException
++ {
++ char currChar;
++ String token = null;
++
++
++ if (m_state == STATE_DONE)
++ return null;
++
++ while (m_curPos < m_tokens.length() && (token == null))
++ {
++ currChar = m_tokens.charAt(m_curPos);
++ switch (m_state)
++ {
++ case STATE_LOOKING_FOR_FIRST_TOKEN:
++ case STATE_LOOKING_FOR_TOKEN:
++ if (isWhiteSpace(currChar))
++ {
++ break;
++ }
++ else if (isValidTokenChar(currChar))
++ {
++ m_scanStart = m_curPos;
++ m_state = STATE_SCANNING_TOKEN;
++ }
++ else
++ {
++ m_state = STATE_PARSING_ERROR;
++ throw new SaslException("Invalid token character at position " + m_curPos);
++ }
++ break;
++
++ case STATE_SCANNING_TOKEN:
++ if (isValidTokenChar(currChar))
++ {
++ break;
++ }
++ else if (isWhiteSpace(currChar))
++ {
++ token = m_tokens.substring(m_scanStart, m_curPos);
++ m_state = STATE_LOOKING_FOR_COMMA;
++ }
++ else if (',' == currChar)
++ {
++ token = m_tokens.substring(m_scanStart, m_curPos);
++ m_state = STATE_LOOKING_FOR_TOKEN;
++ }
++ else
++ {
++ m_state = STATE_PARSING_ERROR;
++ throw new SaslException("Invalid token character at position " + m_curPos);
++ }
++ break;
++
++
++ case STATE_LOOKING_FOR_COMMA:
++ if (isWhiteSpace(currChar))
++ break;
++ else if (currChar == ',')
++ m_state = STATE_LOOKING_FOR_TOKEN;
++ else
++ {
++ m_state = STATE_PARSING_ERROR;
++ throw new SaslException("Expected a comma, found '" +
++ currChar + "' at postion " +
++ m_curPos);
++ }
++ break;
++ }
++ m_curPos++;
++ } /* end while loop */
++
++ if (token == null)
++ { /* check the ending state */
++ switch (m_state)
++ {
++ case STATE_SCANNING_TOKEN:
++ token = m_tokens.substring(m_scanStart);
++ m_state = STATE_DONE;
++ break;
++
++ case STATE_LOOKING_FOR_FIRST_TOKEN:
++ case STATE_LOOKING_FOR_COMMA:
++ break;
++
++ case STATE_LOOKING_FOR_TOKEN:
++ throw new SaslException("Trialing comma");
++ }
++ }
++
++ return token;
++ }
++
++ /**
++ * This function returns TRUE if the character is a valid token character.
++ *
++ * token = 1*
++ *
++ * separators = "(" | ")" | "<" | ">" | "@"
++ * | "," | ";" | ":" | "\" | <">
++ * | "/" | "[" | "]" | "?" | "="
++ * | "{" | "}" | SP | HT
++ *
++ * CTL =
++ *
++ * CHAR =
++ *
++ * @param c character to be validated
++ *
++ * @return True if character is valid Token character else it returns
++ * false
++ */
++ boolean isValidTokenChar(
++ char c)
++ {
++ if ( ( (c >= '\u0000') && (c <='\u0020') ) ||
++ ( (c >= '\u003a') && (c <= '\u0040') ) ||
++ ( (c >= '\u005b') && (c <= '\u005d') ) ||
++ ('\u002c' == c) ||
++ ('\u0025' == c) ||
++ ('\u0028' == c) ||
++ ('\u0029' == c) ||
++ ('\u007b' == c) ||
++ ('\u007d' == c) ||
++ ('\u007f' == c) )
++ return false;
++
++ return true;
++ }
++
++ /**
++ * This function returns TRUE if the character is linear white space (LWS).
++ * LWS = [CRLF] 1*( SP | HT )
++ *
++ * @param c character to be validated
++ *
++ * @return True if character is liner whitespace else it returns false
++ */
++ boolean isWhiteSpace(
++ char c)
++ {
++ if ( ('\t' == c) || // HORIZONTAL TABULATION.
++ ('\n' == c) || // LINE FEED.
++ ('\r' == c) || // CARRIAGE RETURN.
++ ('\u0020' == c) )
++ return true;
++
++ return false;
++ }
++
++}
++
+diff --git a/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java b/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java
+new file mode 100644
+index 0000000..4dfc622
+--- /dev/null
++++ b/external/asmack/build/src/trunk/de/measite/smack/AndroidDebugger.java
+@@ -0,0 +1,185 @@
++package de.measite.smack;
++
++import org.jivesoftware.smack.debugger.SmackDebugger;
++import org.jivesoftware.smack.ConnectionListener;
++import org.jivesoftware.smack.PacketListener;
++import org.jivesoftware.smack.Connection;
++import org.jivesoftware.smack.packet.Packet;
++import org.jivesoftware.smack.util.*;
++
++import android.util.Log;
++
++import java.io.Reader;
++import java.io.Writer;
++import java.text.SimpleDateFormat;
++import java.util.Date;
++
++/**
++ * Very simple debugger that prints to the android log the sent and received stanzas. Use
++ * this debugger with caution since printing to the console is an expensive operation that may
++ * even block the thread since only one thread may print at a time.
++ *
++ * It is possible to not only print the raw sent and received stanzas but also the interpreted
++ * packets by Smack. By default interpreted packets won't be printed. To enable this feature
++ * just change the printInterpreted static variable to true.
++ *
++ * @author Gaston Dombiak
++ */
++public class AndroidDebugger implements SmackDebugger {
++
++ public static boolean printInterpreted = false;
++ private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss aaa");
++
++ private Connection connection = null;
++
++ private PacketListener listener = null;
++ private ConnectionListener connListener = null;
++
++ private Writer writer;
++ private Reader reader;
++ private ReaderListener readerListener;
++ private WriterListener writerListener;
++
++ public AndroidDebugger(Connection connection, Writer writer, Reader reader) {
++ this.connection = connection;
++ this.writer = writer;
++ this.reader = reader;
++ createDebug();
++ }
++
++ /**
++ * Creates the listeners that will print in the console when new activity is detected.
++ */
++ private void createDebug() {
++ // Create a special Reader that wraps the main Reader and logs data to the GUI.
++ ObservableReader debugReader = new ObservableReader(reader);
++ readerListener = new ReaderListener() {
++ public void read(String str) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " RCV (" + connection.hashCode() +
++ "): " +
++ str);
++ }
++ };
++ debugReader.addReaderListener(readerListener);
++
++ // Create a special Writer that wraps the main Writer and logs data to the GUI.
++ ObservableWriter debugWriter = new ObservableWriter(writer);
++ writerListener = new WriterListener() {
++ public void write(String str) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " SENT (" + connection.hashCode() +
++ "): " +
++ str);
++ }
++ };
++ debugWriter.addWriterListener(writerListener);
++
++ // Assign the reader/writer objects to use the debug versions. The packet reader
++ // and writer will use the debug versions when they are created.
++ reader = debugReader;
++ writer = debugWriter;
++
++ // Create a thread that will listen for all incoming packets and write them to
++ // the GUI. This is what we call "interpreted" packet data, since it's the packet
++ // data as Smack sees it and not as it's coming in as raw XML.
++ listener = new PacketListener() {
++ public void processPacket(Packet packet) {
++ if (printInterpreted) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " RCV PKT (" +
++ connection.hashCode() +
++ "): " +
++ packet.toXML());
++ }
++ }
++ };
++
++ connListener = new ConnectionListener() {
++ public void connectionClosed() {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " Connection closed (" +
++ connection.hashCode() +
++ ")");
++ }
++
++ public void connectionClosedOnError(Exception e) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) +
++ " Connection closed due to an exception (" +
++ connection.hashCode() +
++ ")");
++ e.printStackTrace();
++ }
++ public void reconnectionFailed(Exception e) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) +
++ " Reconnection failed due to an exception (" +
++ connection.hashCode() +
++ ")");
++ e.printStackTrace();
++ }
++ public void reconnectionSuccessful() {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " Connection reconnected (" +
++ connection.hashCode() +
++ ")");
++ }
++ public void reconnectingIn(int seconds) {
++ Log.d("SMACK",
++ dateFormatter.format(new Date()) + " Connection (" +
++ connection.hashCode() +
++ ") will reconnect in " + seconds);
++ }
++ };
++ }
++
++ public Reader newConnectionReader(Reader newReader) {
++ ((ObservableReader)reader).removeReaderListener(readerListener);
++ ObservableReader debugReader = new ObservableReader(newReader);
++ debugReader.addReaderListener(readerListener);
++ reader = debugReader;
++ return reader;
++ }
++
++ public Writer newConnectionWriter(Writer newWriter) {
++ ((ObservableWriter)writer).removeWriterListener(writerListener);
++ ObservableWriter debugWriter = new ObservableWriter(newWriter);
++ debugWriter.addWriterListener(writerListener);
++ writer = debugWriter;
++ return writer;
++ }
++
++ public void userHasLogged(String user) {
++ boolean isAnonymous = "".equals(StringUtils.parseName(user));
++ String title =
++ "User logged (" + connection.hashCode() + "): "
++ + (isAnonymous ? "" : StringUtils.parseBareAddress(user))
++ + "@"
++ + connection.getServiceName()
++ + ":"
++ + connection.getPort();
++ title += "/" + StringUtils.parseResource(user);
++ Log.d("SMACK", title);
++ // Add the connection listener to the connection so that the debugger can be notified
++ // whenever the connection is closed.
++ connection.addConnectionListener(connListener);
++ }
++
++ public Reader getReader() {
++ return reader;
++ }
++
++ public Writer getWriter() {
++ return writer;
++ }
++
++ public PacketListener getReaderListener() {
++ return listener;
++ }
++
++ public PacketListener getWriterListener() {
++ return null;
++ }
++}
++
+diff --git a/external/asmack/build/src/trunk/de/measite/smack/Sasl.java b/external/asmack/build/src/trunk/de/measite/smack/Sasl.java
+new file mode 100644
+index 0000000..a59135d
+--- /dev/null
++++ b/external/asmack/build/src/trunk/de/measite/smack/Sasl.java
+@@ -0,0 +1,108 @@
++/*
++ * Copyright 2009 Rene Treffer
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ *
++ */
++package de.measite.smack;
++
++import java.util.Enumeration;
++import java.util.Hashtable;
++import java.util.Map;
++
++import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
++import org.apache.harmony.javax.security.sasl.SaslClient;
++import org.apache.harmony.javax.security.sasl.SaslException;
++import org.apache.harmony.javax.security.sasl.SaslServer;
++import org.apache.harmony.javax.security.sasl.SaslServerFactory;
++
++public class Sasl {
++
++ // SaslClientFactory service name
++ private static final String CLIENTFACTORYSRV = "SaslClientFactory"; //$NON-NLS-1$
++
++ // SaslServerFactory service name
++ private static final String SERVERFACTORYSRV = "SaslServerFactory"; //$NON-NLS-1$
++
++ public static final String POLICY_NOPLAINTEXT = "javax.security.sasl.policy.noplaintext"; //$NON-NLS-1$
++
++ public static final String POLICY_NOACTIVE = "javax.security.sasl.policy.noactive"; //$NON-NLS-1$
++
++ public static final String POLICY_NODICTIONARY = "javax.security.sasl.policy.nodictionary"; //$NON-NLS-1$
++
++ public static final String POLICY_NOANONYMOUS = "javax.security.sasl.policy.noanonymous"; //$NON-NLS-1$
++
++ public static final String POLICY_FORWARD_SECRECY = "javax.security.sasl.policy.forward"; //$NON-NLS-1$
++
++ public static final String POLICY_PASS_CREDENTIALS = "javax.security.sasl.policy.credentials"; //$NON-NLS-1$
++
++ public static final String MAX_BUFFER = "javax.security.sasl.maxbuffer"; //$NON-NLS-1$
++
++ public static final String RAW_SEND_SIZE = "javax.security.sasl.rawsendsize"; //$NON-NLS-1$
++
++ public static final String REUSE = "javax.security.sasl.reuse"; //$NON-NLS-1$
++
++ public static final String QOP = "javax.security.sasl.qop"; //$NON-NLS-1$
++
++ public static final String STRENGTH = "javax.security.sasl.strength"; //$NON-NLS-1$
++
++ public static final String SERVER_AUTH = "javax.security.sasl.server.authentication"; //$NON-NLS-1$
++
++ public static Enumeration getSaslClientFactories() {
++ Hashtable factories = new Hashtable();
++ factories.put(new SaslClientFactory(), new Object());
++ return factories.keys();
++ }
++
++ public static Enumeration getSaslServerFactories() {
++ return org.apache.harmony.javax.security.sasl.Sasl.getSaslServerFactories();
++ }
++
++ public static SaslServer createSaslServer(String mechanism, String protocol,
++ String serverName, Map prop, CallbackHandler cbh) throws SaslException {
++ return org.apache.harmony.javax.security.sasl.Sasl.createSaslServer(mechanism, protocol, serverName, prop, cbh);
++ }
++
++ public static SaslClient createSaslClient(String[] mechanisms, String authanticationID,
++ String protocol, String serverName, Map prop, CallbackHandler cbh)
++ throws SaslException {
++ if (mechanisms == null) {
++ throw new NullPointerException("auth.33"); //$NON-NLS-1$
++ }
++ SaslClientFactory fact = getSaslClientFactories().nextElement();
++ String[] mech = fact.getMechanismNames(null);
++ boolean is = false;
++ if (mech != null) {
++ for (int j = 0; j < mech.length; j++) {
++ for (int n = 0; n < mechanisms.length; n++) {
++ if (mech[j].equals(mechanisms[n])) {
++ is = true;
++ break;
++ }
++ }
++ }
++ }
++ if (is) {
++ return fact.createSaslClient(
++ mechanisms,
++ authanticationID,
++ protocol,
++ serverName,
++ prop,
++ cbh
++ );
++ }
++ return null;
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java b/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java
+new file mode 100644
+index 0000000..2fa1ebd
+--- /dev/null
++++ b/external/asmack/build/src/trunk/de/measite/smack/SaslClientFactory.java
+@@ -0,0 +1,60 @@
++/*
++ * Copyright 2009 Rene Treffer
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ *
++ */
++package de.measite.smack;
++
++import java.util.Map;
++
++import com.novell.sasl.client.DigestMD5SaslClient;
++
++import org.apache.harmony.javax.security.auth.callback.CallbackHandler;
++import org.apache.harmony.javax.security.sasl.SaslClient;
++import org.apache.harmony.javax.security.sasl.SaslException;
++import org.apache.qpid.management.common.sasl.PlainSaslClient;
++
++public class SaslClientFactory implements
++ org.apache.harmony.javax.security.sasl.SaslClientFactory {
++
++ @Override
++ public SaslClient createSaslClient(String[] mechanisms,
++ String authorizationId, String protocol, String serverName,
++ Map props, CallbackHandler cbh) throws SaslException {
++ for (String mech: mechanisms) {
++ if ("PLAIN".equals(mech)) {
++ return new PlainSaslClient(authorizationId, cbh);
++ } else
++ if ("DIGEST-MD5".equals(mech)) {
++ return DigestMD5SaslClient.getClient(
++ authorizationId,
++ protocol,
++ serverName,
++ props,
++ cbh
++ );
++ }
++ }
++ return null;
++ }
++
++ @Override
++ public String[] getMechanismNames(Map props) {
++ return new String[]{
++ "PLAIN",
++ "DIGEST-MD5"
++ };
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java
+new file mode 100644
+index 0000000..bb12554
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/AuthPermission.java
+@@ -0,0 +1,97 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++import java.security.BasicPermission;
++
++
++
++/**
++ * Governs the use of methods in this package and also its subpackages. A
++ * target name of the permission specifies which methods are allowed
++ * without specifying the concrete action lists. Possible target names and
++ * associated authentication permissions are:
++ *
++ *
++ * doAs invoke Subject.doAs methods.
++ * doAsPrivileged invoke the Subject.doAsPrivileged methods.
++ * getSubject invoke Subject.getSubject().
++ * getSubjectFromDomainCombiner invoke SubjectDomainCombiner.getSubject().
++ * setReadOnly invoke Subject.setReadonly().
++ * modifyPrincipals modify the set of principals
++ * associated with a Subject.
++ * modifyPublicCredentials modify the set of public credentials
++ * associated with a Subject.
++ * modifyPrivateCredentials modify the set of private credentials
++ * associated with a Subject.
++ * refreshCredential invoke the refresh method on a credential of a
++ * refreshable credential class.
++ * destroyCredential invoke the destroy method on a credential of a
++ * destroyable credential class.
++ * createLoginContext.name instantiate a LoginContext with the
++ * specified name. The wildcard name ('*')
++ * allows to a LoginContext of any name.
++ * getLoginConfiguration invoke the getConfiguration method of
++ * javax.security.auth.login.Configuration.
++ * refreshLoginConfiguration Invoke the refresh method of
++ * javax.security.auth.login.Configuration.
++ *
++ */
++public final class AuthPermission extends BasicPermission {
++
++ private static final long serialVersionUID = 5806031445061587174L;
++
++ private static final String CREATE_LOGIN_CONTEXT = "createLoginContext"; //$NON-NLS-1$
++
++ private static final String CREATE_LOGIN_CONTEXT_ANY = "createLoginContext.*"; //$NON-NLS-1$
++
++ // inits permission name.
++ private static String init(String name) {
++
++ if (name == null) {
++ throw new NullPointerException("auth.13"); //$NON-NLS-1$
++ }
++
++ if (CREATE_LOGIN_CONTEXT.equals(name)) {
++ return CREATE_LOGIN_CONTEXT_ANY;
++ }
++ return name;
++ }
++
++ /**
++ * Creates an authentication permission with the specified target name.
++ *
++ * @param name
++ * the target name of this authentication permission.
++ */
++ public AuthPermission(String name) {
++ super(init(name));
++ }
++
++ /**
++ * Creates an authentication permission with the specified target name.
++ *
++ * @param name
++ * the target name of this authentication permission.
++ * @param actions
++ * this parameter is ignored and should be {@code null}.
++ */
++ public AuthPermission(String name, String actions) {
++ super(init(name), actions);
++ }
++}
+\ No newline at end of file
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java
+new file mode 100644
+index 0000000..7c7ea79
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/DestroyFailedException.java
+@@ -0,0 +1,44 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++/**
++ * Signals that the {@link Destroyable#destroy()} method failed.
++ */
++public class DestroyFailedException extends Exception {
++
++ private static final long serialVersionUID = -7790152857282749162L;
++
++ /**
++ * Creates an exception of type {@code DestroyFailedException}.
++ */
++ public DestroyFailedException() {
++ super();
++ }
++
++ /**
++ * Creates an exception of type {@code DestroyFailedException}.
++ *
++ * @param message
++ * A detail message that describes the reason for this exception.
++ */
++ public DestroyFailedException(String message) {
++ super(message);
++ }
++
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java
+new file mode 100644
+index 0000000..12a107b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Destroyable.java
+@@ -0,0 +1,43 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++/**
++ * Allows for special treatment of sensitive information, when it comes to
++ * destroying or clearing of the data.
++ */
++public interface Destroyable {
++
++ /**
++ * Erases the sensitive information. Once an object is destroyed any calls
++ * to its methods will throw an {@code IllegalStateException}. If it does
++ * not succeed a DestroyFailedException is thrown.
++ *
++ * @throws DestroyFailedException
++ * if the information cannot be erased.
++ */
++ void destroy() throws DestroyFailedException;
++
++ /**
++ * Returns {@code true} once an object has been safely destroyed.
++ *
++ * @return whether the object has been safely destroyed.
++ */
++ boolean isDestroyed();
++
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java
+new file mode 100644
+index 0000000..d62bb24
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/PrivateCredentialPermission.java
+@@ -0,0 +1,395 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++import java.io.IOException;
++import java.io.ObjectInputStream;
++import java.io.Serializable;
++import java.security.Permission;
++import java.security.PermissionCollection;
++import java.security.Principal;
++import java.util.Set;
++
++
++
++/**
++ * Protects private credential objects belonging to a {@code Subject}. It has
++ * only one action which is "read". The target name of this permission has a
++ * special syntax:
++ *
++ *
++ *
++ * First it states a credential class and is followed then by a list of one or
++ * more principals identifying the subject.
++ *
++ * The principals on their part are specified as the name of the {@code
++ * Principal} class followed by the principal name in quotes. For example, the
++ * following file may define permission to read the private credentials of a
++ * principal named "Bob": "com.sun.PrivateCredential com.sun.Principal \"Bob\""
++ *
++ * The syntax also allows the use of the wildcard "*" in place of {@code
++ * CredentialClass} or {@code PrincipalClass} and/or {@code PrincipalName}.
++ *
++ * @see Principal
++ */
++public final class PrivateCredentialPermission extends Permission {
++
++ private static final long serialVersionUID = 5284372143517237068L;
++
++ // allowed action
++ private static final String READ = "read"; //$NON-NLS-1$
++
++ private String credentialClass;
++
++ // current offset
++ private transient int offset;
++
++ // owners set
++ private transient CredOwner[] set;
++
++ /**
++ * Creates a new permission for private credentials specified by the target
++ * name {@code name} and an {@code action}. The action is always
++ * {@code "read"}.
++ *
++ * @param name
++ * the target name of the permission.
++ * @param action
++ * the action {@code "read"}.
++ */
++ public PrivateCredentialPermission(String name, String action) {
++ super(name);
++ if (READ.equalsIgnoreCase(action)) {
++ initTargetName(name);
++ } else {
++ throw new IllegalArgumentException("auth.11"); //$NON-NLS-1$
++ }
++ }
++
++ /**
++ * Creates a {@code PrivateCredentialPermission} from the {@code Credential}
++ * class and set of principals.
++ *
++ * @param credentialClass
++ * the credential class name.
++ * @param principals
++ * the set of principals.
++ */
++ PrivateCredentialPermission(String credentialClass, Set principals) {
++ super(credentialClass);
++ this.credentialClass = credentialClass;
++
++ set = new CredOwner[principals.size()];
++ for (Principal p : principals) {
++ CredOwner element = new CredOwner(p.getClass().getName(), p.getName());
++ // check for duplicate elements
++ boolean found = false;
++ for (int ii = 0; ii < offset; ii++) {
++ if (set[ii].equals(element)) {
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ set[offset++] = element;
++ }
++ }
++ }
++
++ /**
++ * Initialize a PrivateCredentialPermission object and checks that a target
++ * name has a correct format: CredentialClass 1*(PrincipalClass
++ * "PrincipalName")
++ */
++ private void initTargetName(String name) {
++
++ if (name == null) {
++ throw new NullPointerException("auth.0E"); //$NON-NLS-1$
++ }
++
++ // check empty string
++ name = name.trim();
++ if (name.length() == 0) {
++ throw new IllegalArgumentException("auth.0F"); //$NON-NLS-1$
++ }
++
++ // get CredentialClass
++ int beg = name.indexOf(' ');
++ if (beg == -1) {
++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
++ }
++ credentialClass = name.substring(0, beg);
++
++ // get a number of pairs: PrincipalClass "PrincipalName"
++ beg++;
++ int count = 0;
++ int nameLength = name.length();
++ for (int i, j = 0; beg < nameLength; beg = j + 2, count++) {
++ i = name.indexOf(' ', beg);
++ j = name.indexOf('"', i + 2);
++
++ if (i == -1 || j == -1 || name.charAt(i + 1) != '"') {
++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
++ }
++ }
++
++ // name MUST have one pair at least
++ if (count < 1) {
++ throw new IllegalArgumentException("auth.10"); //$NON-NLS-1$
++ }
++
++ beg = name.indexOf(' ');
++ beg++;
++
++ // populate principal set with instances of CredOwner class
++ String principalClass;
++ String principalName;
++
++ set = new CredOwner[count];
++ for (int index = 0, i, j; index < count; beg = j + 2, index++) {
++ i = name.indexOf(' ', beg);
++ j = name.indexOf('"', i + 2);
++
++ principalClass = name.substring(beg, i);
++ principalName = name.substring(i + 2, j);
++
++ CredOwner element = new CredOwner(principalClass, principalName);
++ // check for duplicate elements
++ boolean found = false;
++ for (int ii = 0; ii < offset; ii++) {
++ if (set[ii].equals(element)) {
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ set[offset++] = element;
++ }
++ }
++ }
++
++ private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
++ ois.defaultReadObject();
++ initTargetName(getName());
++ }
++
++ /**
++ * Returns the principal's classes and names associated with this {@code
++ * PrivateCredentialPermission} as a two dimensional array. The first
++ * dimension of the array corresponds to the number of principals. The
++ * second dimension defines either the name of the {@code PrincipalClass}
++ * [x][0] or the value of {@code PrincipalName} [x][1].
++ *
++ * This corresponds to the the target name's syntax:
++ *
++ *
++ *
++ * @return the principal classes and names associated with this {@code
++ * PrivateCredentialPermission}.
++ */
++ public String[][] getPrincipals() {
++
++ String[][] s = new String[offset][2];
++
++ for (int i = 0; i < s.length; i++) {
++ s[i][0] = set[i].principalClass;
++ s[i][1] = set[i].principalName;
++ }
++ return s;
++ }
++
++ @Override
++ public String getActions() {
++ return READ;
++ }
++
++ /**
++ * Returns the class name of the credential associated with this permission.
++ *
++ * @return the class name of the credential associated with this permission.
++ */
++ public String getCredentialClass() {
++ return credentialClass;
++ }
++
++ @Override
++ public int hashCode() {
++ int hash = 0;
++ for (int i = 0; i < offset; i++) {
++ hash = hash + set[i].hashCode();
++ }
++ return getCredentialClass().hashCode() + hash;
++ }
++
++ @Override
++ public boolean equals(Object obj) {
++ if (obj == this) {
++ return true;
++ }
++
++ if (obj == null || this.getClass() != obj.getClass()) {
++ return false;
++ }
++
++ PrivateCredentialPermission that = (PrivateCredentialPermission) obj;
++
++ return credentialClass.equals(that.credentialClass) && (offset == that.offset)
++ && sameMembers(set, that.set, offset);
++ }
++
++ @Override
++ public boolean implies(Permission permission) {
++
++ if (permission == null || this.getClass() != permission.getClass()) {
++ return false;
++ }
++
++ PrivateCredentialPermission that = (PrivateCredentialPermission) permission;
++
++ if (!("*".equals(credentialClass) || credentialClass //$NON-NLS-1$
++ .equals(that.getCredentialClass()))) {
++ return false;
++ }
++
++ if (that.offset == 0) {
++ return true;
++ }
++
++ CredOwner[] thisCo = set;
++ CredOwner[] thatCo = that.set;
++ int thisPrincipalsSize = offset;
++ int thatPrincipalsSize = that.offset;
++ for (int i = 0, j; i < thisPrincipalsSize; i++) {
++ for (j = 0; j < thatPrincipalsSize; j++) {
++ if (thisCo[i].implies(thatCo[j])) {
++ break;
++ }
++ }
++ if (j == thatCo.length) {
++ return false;
++ }
++ }
++ return true;
++ }
++
++ @Override
++ public PermissionCollection newPermissionCollection() {
++ return null;
++ }
++
++ /**
++ * Returns true if the two arrays have the same length, and every member of
++ * one array is contained in another array
++ */
++ private boolean sameMembers(Object[] ar1, Object[] ar2, int length) {
++ if (ar1 == null && ar2 == null) {
++ return true;
++ }
++ if (ar1 == null || ar2 == null) {
++ return false;
++ }
++ boolean found;
++ for (int i = 0; i < length; i++) {
++ found = false;
++ for (int j = 0; j < length; j++) {
++ if (ar1[i].equals(ar2[j])) {
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ return false;
++ }
++ }
++ return true;
++ }
++
++ private static final class CredOwner implements Serializable {
++
++ private static final long serialVersionUID = -5607449830436408266L;
++
++ String principalClass;
++
++ String principalName;
++
++ // whether class name contains wildcards
++ private transient boolean isClassWildcard;
++
++ // whether pname contains wildcards
++ private transient boolean isPNameWildcard;
++
++ // Creates a new CredOwner with the specified Principal Class and Principal Name
++ CredOwner(String principalClass, String principalName) {
++ super();
++ if ("*".equals(principalClass)) { //$NON-NLS-1$
++ isClassWildcard = true;
++ }
++
++ if ("*".equals(principalName)) { //$NON-NLS-1$
++ isPNameWildcard = true;
++ }
++
++ if (isClassWildcard && !isPNameWildcard) {
++ throw new IllegalArgumentException("auth.12"); //$NON-NLS-1$
++ }
++
++ this.principalClass = principalClass;
++ this.principalName = principalName;
++ }
++
++ // Checks if this CredOwner implies the specified Object.
++ boolean implies(Object obj) {
++ if (obj == this) {
++ return true;
++ }
++
++ CredOwner co = (CredOwner) obj;
++
++ if (isClassWildcard || principalClass.equals(co.principalClass)) {
++ if (isPNameWildcard || principalName.equals(co.principalName)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ // Checks two CredOwner objects for equality.
++ @Override
++ public boolean equals(Object obj) {
++ if (obj == this) {
++ return true;
++ }
++ if (obj instanceof CredOwner) {
++ CredOwner that = (CredOwner) obj;
++ return principalClass.equals(that.principalClass)
++ && principalName.equals(that.principalName);
++ }
++ return false;
++ }
++
++ // Returns the hash code value for this object.
++ @Override
++ public int hashCode() {
++ return principalClass.hashCode() + principalName.hashCode();
++ }
++ }
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java
+new file mode 100644
+index 0000000..71bcc6b
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/RefreshFailedException.java
+@@ -0,0 +1,31 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++public class RefreshFailedException extends Exception {
++
++ private static final long serialVersionUID = 5058444488565265840L;
++
++ public RefreshFailedException() {
++ super();
++ }
++
++ public RefreshFailedException(String message) {
++ super(message);
++ }
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java
+new file mode 100644
+index 0000000..90b00cb
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Refreshable.java
+@@ -0,0 +1,26 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++public interface Refreshable {
++
++ void refresh() throws RefreshFailedException;
++
++ boolean isCurrent();
++
++}
+diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java
+new file mode 100644
+index 0000000..142686e
+--- /dev/null
++++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/Subject.java
+@@ -0,0 +1,782 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one or more
++ * contributor license agreements. See the NOTICE file distributed with
++ * this work for additional information regarding copyright ownership.
++ * The ASF licenses this file to You under the Apache License, Version 2.0
++ * (the "License"); you may not use this file except in compliance with
++ * the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.harmony.javax.security.auth;
++
++import java.io.IOException;
++import java.io.ObjectInputStream;
++import java.io.ObjectOutputStream;
++import java.io.Serializable;
++import java.security.AccessControlContext;
++import java.security.AccessController;
++import java.security.DomainCombiner;
++import java.security.Permission;
++import java.security.Principal;
++import java.security.PrivilegedAction;
++import java.security.PrivilegedActionException;
++import java.security.PrivilegedExceptionAction;
++import java.security.ProtectionDomain;
++import java.util.AbstractSet;
++import java.util.Collection;
++import java.util.Iterator;
++import java.util.LinkedList;
++import java.util.Set;
++
++
++
++/**
++ * The central class of the {@code javax.security.auth} package representing an
++ * authenticated user or entity (both referred to as "subject"). IT defines also
++ * the static methods that allow code to be run, and do modifications according
++ * to the subject's permissions.
++ *
++ * A subject has the following features:
++ *
++ *
A set of {@code Principal} objects specifying the identities bound to a
++ * {@code Subject} that distinguish it.
++ *
Credentials (public and private) such as certificates, keys, or
++ * authentication proofs such as tickets
++ *
++ */
++public final class Subject implements Serializable {
++
++ private static final long serialVersionUID = -8308522755600156056L;
++
++ private static final AuthPermission _AS = new AuthPermission("doAs"); //$NON-NLS-1$
++
++ private static final AuthPermission _AS_PRIVILEGED = new AuthPermission(
++ "doAsPrivileged"); //$NON-NLS-1$
++
++ private static final AuthPermission _SUBJECT = new AuthPermission(
++ "getSubject"); //$NON-NLS-1$
++
++ private static final AuthPermission _PRINCIPALS = new AuthPermission(
++ "modifyPrincipals"); //$NON-NLS-1$
++
++ private static final AuthPermission _PRIVATE_CREDENTIALS = new AuthPermission(
++ "modifyPrivateCredentials"); //$NON-NLS-1$
++
++ private static final AuthPermission _PUBLIC_CREDENTIALS = new AuthPermission(
++ "modifyPublicCredentials"); //$NON-NLS-1$
++
++ private static final AuthPermission _READ_ONLY = new AuthPermission(
++ "setReadOnly"); //$NON-NLS-1$
++
++ private final Set principals;
++
++ private boolean readOnly;
++
++ // set of private credentials
++ private transient SecureSet