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: + *

+ * BOSHClientConfig cfg = BOSHClientConfig.Builder.create(
+ *         "http://server:1234/httpbind", "jabber.org")
+ *     .setFrom("user@jabber.org")
+ *     .build();
+ * BOSHClient client = BOSHClient.create(cfg);
+ * 
+ * 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: + *

+ * client.send(ComposableBody.builder().build());
+ * 
+ * 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(" 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: + * + *
+ * targetName = CredentialClass {PrincipalClass "PrincipalName"}*
+ * 
+ * + * 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: + * + *

+     * targetName = CredentialClass {PrincipalClass "PrincipalName"}*
+     * 
+ * + * @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: + *

+ */ +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 privateCredentials; + + // set of public credentials + private transient SecureSet publicCredentials; + + /** + * The default constructor initializing the sets of public and private + * credentials and principals with the empty set. + */ + public Subject() { + super(); + principals = new SecureSet(_PRINCIPALS); + publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); + privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); + + readOnly = false; + } + + /** + * The constructor for the subject, setting its public and private + * credentials and principals according to the arguments. + * + * @param readOnly + * {@code true} if this {@code Subject} is read-only, thus + * preventing any modifications to be done. + * @param subjPrincipals + * the set of Principals that are attributed to this {@code + * Subject}. + * @param pubCredentials + * the set of public credentials that distinguish this {@code + * Subject}. + * @param privCredentials + * the set of private credentials that distinguish this {@code + * Subject}. + */ + public Subject(boolean readOnly, Set subjPrincipals, + Set pubCredentials, Set privCredentials) { + + if (subjPrincipals == null || pubCredentials == null || privCredentials == null) { + throw new NullPointerException(); + } + + principals = new SecureSet(_PRINCIPALS, subjPrincipals); + publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS, pubCredentials); + privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS, privCredentials); + + this.readOnly = readOnly; + } + + /** + * Runs the code defined by {@code action} using the permissions granted to + * the {@code Subject} itself and to the code as well. + * + * @param subject + * the distinguished {@code Subject}. + * @param action + * the code to be run. + * @return the {@code Object} returned when running the {@code action}. + */ + @SuppressWarnings("unchecked") + public static Object doAs(Subject subject, PrivilegedAction action) { + + checkPermission(_AS); + + return doAs_PrivilegedAction(subject, action, AccessController.getContext()); + } + + /** + * Run the code defined by {@code action} using the permissions granted to + * the {@code Subject} and to the code itself, additionally providing a more + * specific context. + * + * @param subject + * the distinguished {@code Subject}. + * @param action + * the code to be run. + * @param context + * the specific context in which the {@code action} is invoked. + * if {@code null} a new {@link AccessControlContext} is + * instantiated. + * @return the {@code Object} returned when running the {@code action}. + */ + @SuppressWarnings("unchecked") + public static Object doAsPrivileged(Subject subject, PrivilegedAction action, + AccessControlContext context) { + + checkPermission(_AS_PRIVILEGED); + + if (context == null) { + return doAs_PrivilegedAction(subject, action, new AccessControlContext( + new ProtectionDomain[0])); + } + return doAs_PrivilegedAction(subject, action, context); + } + + // instantiates a new context and passes it to AccessController + @SuppressWarnings("unchecked") + private static Object doAs_PrivilegedAction(Subject subject, PrivilegedAction action, + final AccessControlContext context) { + + AccessControlContext newContext; + + final SubjectDomainCombiner combiner; + if (subject == null) { + // performance optimization + // if subject is null there is nothing to combine + combiner = null; + } else { + combiner = new SubjectDomainCombiner(subject); + } + + PrivilegedAction dccAction = new PrivilegedAction() { + public Object run() { + + return new AccessControlContext(context, combiner); + } + }; + + newContext = (AccessControlContext) AccessController.doPrivileged(dccAction); + + return AccessController.doPrivileged(action, newContext); + } + + /** + * Runs the code defined by {@code action} using the permissions granted to + * the subject and to the code itself. + * + * @param subject + * the distinguished {@code Subject}. + * @param action + * the code to be run. + * @return the {@code Object} returned when running the {@code action}. + * @throws PrivilegedActionException + * if running the {@code action} throws an exception. + */ + @SuppressWarnings("unchecked") + public static Object doAs(Subject subject, PrivilegedExceptionAction action) + throws PrivilegedActionException { + + checkPermission(_AS); + + return doAs_PrivilegedExceptionAction(subject, action, AccessController.getContext()); + } + + /** + * Runs the code defined by {@code action} using the permissions granted to + * the subject and to the code itself, additionally providing a more + * specific context. + * + * @param subject + * the distinguished {@code Subject}. + * @param action + * the code to be run. + * @param context + * the specific context in which the {@code action} is invoked. + * if {@code null} a new {@link AccessControlContext} is + * instantiated. + * @return the {@code Object} returned when running the {@code action}. + * @throws PrivilegedActionException + * if running the {@code action} throws an exception. + */ + @SuppressWarnings("unchecked") + public static Object doAsPrivileged(Subject subject, + PrivilegedExceptionAction action, AccessControlContext context) + throws PrivilegedActionException { + + checkPermission(_AS_PRIVILEGED); + + if (context == null) { + return doAs_PrivilegedExceptionAction(subject, action, + new AccessControlContext(new ProtectionDomain[0])); + } + return doAs_PrivilegedExceptionAction(subject, action, context); + } + + // instantiates a new context and passes it to AccessController + @SuppressWarnings("unchecked") + private static Object doAs_PrivilegedExceptionAction(Subject subject, + PrivilegedExceptionAction action, final AccessControlContext context) + throws PrivilegedActionException { + + AccessControlContext newContext; + + final SubjectDomainCombiner combiner; + if (subject == null) { + // performance optimization + // if subject is null there is nothing to combine + combiner = null; + } else { + combiner = new SubjectDomainCombiner(subject); + } + + PrivilegedAction dccAction = new PrivilegedAction() { + public AccessControlContext run() { + return new AccessControlContext(context, combiner); + } + }; + + newContext = AccessController.doPrivileged(dccAction); + + return AccessController.doPrivileged(action, newContext); + } + + /** + * Checks two Subjects for equality. More specifically if the principals, + * public and private credentials are equal, equality for two {@code + * Subjects} is implied. + * + * @param obj + * the {@code Object} checked for equality with this {@code + * Subject}. + * @return {@code true} if the specified {@code Subject} is equal to this + * one. + */ + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + + if (obj == null || this.getClass() != obj.getClass()) { + return false; + } + + Subject that = (Subject) obj; + + if (principals.equals(that.principals) + && publicCredentials.equals(that.publicCredentials) + && privateCredentials.equals(that.privateCredentials)) { + return true; + } + return false; + } + + /** + * Returns this {@code Subject}'s {@link Principal}. + * + * @return this {@code Subject}'s {@link Principal}. + */ + public Set getPrincipals() { + return principals; + } + + + /** + * Returns this {@code Subject}'s {@link Principal} which is a subclass of + * the {@code Class} provided. + * + * @param c + * the {@code Class} as a criteria which the {@code Principal} + * returned must satisfy. + * @return this {@code Subject}'s {@link Principal}. Modifications to the + * returned set of {@code Principal}s do not affect this {@code + * Subject}'s set. + */ + public Set getPrincipals(Class c) { + return ((SecureSet) principals).get(c); + } + + /** + * Returns the private credentials associated with this {@code Subject}. + * + * @return the private credentials associated with this {@code Subject}. + */ + public Set getPrivateCredentials() { + return privateCredentials; + } + + /** + * Returns this {@code Subject}'s private credentials which are a subclass + * of the {@code Class} provided. + * + * @param c + * the {@code Class} as a criteria which the private credentials + * returned must satisfy. + * @return this {@code Subject}'s private credentials. Modifications to the + * returned set of credentials do not affect this {@code Subject}'s + * credentials. + */ + public Set getPrivateCredentials(Class c) { + return privateCredentials.get(c); + } + + /** + * Returns the public credentials associated with this {@code Subject}. + * + * @return the public credentials associated with this {@code Subject}. + */ + public Set getPublicCredentials() { + return publicCredentials; + } + + + /** + * Returns this {@code Subject}'s public credentials which are a subclass of + * the {@code Class} provided. + * + * @param c + * the {@code Class} as a criteria which the public credentials + * returned must satisfy. + * @return this {@code Subject}'s public credentials. Modifications to the + * returned set of credentials do not affect this {@code Subject}'s + * credentials. + */ + public Set getPublicCredentials(Class c) { + return publicCredentials.get(c); + } + + /** + * Returns a hash code of this {@code Subject}. + * + * @return a hash code of this {@code Subject}. + */ + @Override + public int hashCode() { + return principals.hashCode() + privateCredentials.hashCode() + + publicCredentials.hashCode(); + } + + /** + * Prevents from modifications being done to the credentials and {@link + * Principal} sets. After setting it to read-only this {@code Subject} can + * not be made writable again. The destroy method on the credentials still + * works though. + */ + public void setReadOnly() { + checkPermission(_READ_ONLY); + + readOnly = true; + } + + /** + * Returns whether this {@code Subject} is read-only or not. + * + * @return whether this {@code Subject} is read-only or not. + */ + public boolean isReadOnly() { + return readOnly; + } + + /** + * Returns a {@code String} representation of this {@code Subject}. + * + * @return a {@code String} representation of this {@code Subject}. + */ + @Override + public String toString() { + + StringBuilder buf = new StringBuilder("Subject:\n"); //$NON-NLS-1$ + + Iterator it = principals.iterator(); + while (it.hasNext()) { + buf.append("\tPrincipal: "); //$NON-NLS-1$ + buf.append(it.next()); + buf.append('\n'); + } + + it = publicCredentials.iterator(); + while (it.hasNext()) { + buf.append("\tPublic Credential: "); //$NON-NLS-1$ + buf.append(it.next()); + buf.append('\n'); + } + + int offset = buf.length() - 1; + it = privateCredentials.iterator(); + try { + while (it.hasNext()) { + buf.append("\tPrivate Credential: "); //$NON-NLS-1$ + buf.append(it.next()); + buf.append('\n'); + } + } catch (SecurityException e) { + buf.delete(offset, buf.length()); + buf.append("\tPrivate Credentials: no accessible information\n"); //$NON-NLS-1$ + } + return buf.toString(); + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + + in.defaultReadObject(); + + publicCredentials = new SecureSet(_PUBLIC_CREDENTIALS); + privateCredentials = new SecureSet(_PRIVATE_CREDENTIALS); + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + } + + /** + * Returns the {@code Subject} that was last associated with the {@code + * context} provided as argument. + * + * @param context + * the {@code context} that was associated with the + * {@code Subject}. + * @return the {@code Subject} that was last associated with the {@code + * context} provided as argument. + */ + public static Subject getSubject(final AccessControlContext context) { + checkPermission(_SUBJECT); + if (context == null) { + throw new NullPointerException("auth.09"); //$NON-NLS-1$ + } + PrivilegedAction action = new PrivilegedAction() { + public DomainCombiner run() { + return context.getDomainCombiner(); + } + }; + DomainCombiner combiner = AccessController.doPrivileged(action); + + if ((combiner == null) || !(combiner instanceof SubjectDomainCombiner)) { + return null; + } + return ((SubjectDomainCombiner) combiner).getSubject(); + } + + // checks passed permission + private static void checkPermission(Permission p) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(p); + } + } + + // FIXME is used only in two places. remove? + private void checkState() { + if (readOnly) { + throw new IllegalStateException("auth.0A"); //$NON-NLS-1$ + } + } + + private final class SecureSet extends AbstractSet implements Serializable { + + /** + * Compatibility issue: see comments for setType variable + */ + private static final long serialVersionUID = 7911754171111800359L; + + private LinkedList elements; + + /* + * Is used to define a set type for serialization. + * + * A type can be principal, priv. or pub. credential set. The spec. + * doesn't clearly says that priv. and pub. credential sets can be + * serialized and what classes they are. It is only possible to figure + * out from writeObject method comments that priv. credential set is + * serializable and it is an instance of SecureSet class. So pub. + * credential was implemented by analogy + * + * Compatibility issue: the class follows its specified serial form. + * Also according to the serialization spec. adding new field is a + * compatible change. So is ok for principal set (because the default + * value for integer is zero). But priv. or pub. credential set it is + * not compatible because most probably other implementations resolve + * this issue in other way + */ + private int setType; + + // Defines principal set for serialization. + private static final int SET_Principal = 0; + + // Defines private credential set for serialization. + private static final int SET_PrivCred = 1; + + // Defines public credential set for serialization. + private static final int SET_PubCred = 2; + + // permission required to modify set + private transient AuthPermission permission; + + protected SecureSet(AuthPermission perm) { + permission = perm; + elements = new LinkedList(); + } + + // creates set from specified collection with specified permission + // all collection elements are verified before adding + protected SecureSet(AuthPermission perm, Collection s) { + this(perm); + + // Subject's constructor receives a Set, we can trusts if a set is from bootclasspath, + // and not to check whether it contains duplicates or not + boolean trust = s.getClass().getClassLoader() == null; + + Iterator it = s.iterator(); + while (it.hasNext()) { + SST o = it.next(); + verifyElement(o); + if (trust || !elements.contains(o)) { + elements.add(o); + } + } + } + + // verifies new set element + private void verifyElement(Object o) { + + if (o == null) { + throw new NullPointerException(); + } + if (permission == _PRINCIPALS && !(Principal.class.isAssignableFrom(o.getClass()))) { + throw new IllegalArgumentException("auth.0B"); //$NON-NLS-1$ + } + } + + /* + * verifies specified element, checks set state, and security permission + * to modify set before adding new element + */ + @Override + public boolean add(SST o) { + + verifyElement(o); + + checkState(); + checkPermission(permission); + + if (!elements.contains(o)) { + elements.add(o); + return true; + } + return false; + } + + // returns an instance of SecureIterator + @Override + public Iterator iterator() { + + if (permission == _PRIVATE_CREDENTIALS) { + /* + * private credential set requires iterator with additional + * security check (PrivateCredentialPermission) + */ + return new SecureIterator(elements.iterator()) { + /* + * checks permission to access next private credential moves + * to the next element even SecurityException was thrown + */ + @Override + public SST next() { + SST obj = iterator.next(); + checkPermission(new PrivateCredentialPermission(obj + .getClass().getName(), principals)); + return obj; + } + }; + } + return new SecureIterator(elements.iterator()); + } + + @Override + public boolean retainAll(Collection c) { + + if (c == null) { + throw new NullPointerException(); + } + return super.retainAll(c); + } + + @Override + public int size() { + return elements.size(); + } + + /** + * return set with elements that are instances or subclasses of the + * specified class + */ + protected final Set get(final Class c) { + + if (c == null) { + throw new NullPointerException(); + } + + AbstractSet s = new AbstractSet() { + private LinkedList elements = new LinkedList(); + + @Override + public boolean add(E o) { + + if (!c.isAssignableFrom(o.getClass())) { + throw new IllegalArgumentException( + "auth.0C " + c.getName()); //$NON-NLS-1$ + } + + if (elements.contains(o)) { + return false; + } + elements.add(o); + return true; + } + + @Override + public Iterator iterator() { + return elements.iterator(); + } + + @Override + public boolean retainAll(Collection c) { + + if (c == null) { + throw new NullPointerException(); + } + return super.retainAll(c); + } + + @Override + public int size() { + return elements.size(); + } + }; + + // FIXME must have permissions for requested priv. credentials + for (Iterator it = iterator(); it.hasNext();) { + SST o = it.next(); + if (c.isAssignableFrom(o.getClass())) { + s.add(c.cast(o)); + } + } + return s; + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + + switch (setType) { + case SET_Principal: + permission = _PRINCIPALS; + break; + case SET_PrivCred: + permission = _PRIVATE_CREDENTIALS; + break; + case SET_PubCred: + permission = _PUBLIC_CREDENTIALS; + break; + default: + throw new IllegalArgumentException(); + } + + Iterator it = elements.iterator(); + while (it.hasNext()) { + verifyElement(it.next()); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + + if (permission == _PRIVATE_CREDENTIALS) { + // does security check for each private credential + for (Iterator it = iterator(); it.hasNext();) { + it.next(); + } + setType = SET_PrivCred; + } else if (permission == _PRINCIPALS) { + setType = SET_Principal; + } else { + setType = SET_PubCred; + } + + out.defaultWriteObject(); + } + + /** + * Represents iterator for subject's secure set + */ + private class SecureIterator implements Iterator { + protected Iterator iterator; + + protected SecureIterator(Iterator iterator) { + this.iterator = iterator; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public SST next() { + return iterator.next(); + } + + /** + * checks set state, and security permission to modify set before + * removing current element + */ + public void remove() { + checkState(); + checkPermission(permission); + iterator.remove(); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java new file mode 100644 index 0000000..edbb672 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/SubjectDomainCombiner.java @@ -0,0 +1,123 @@ +/* + * 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.DomainCombiner; +import java.security.Principal; +import java.security.ProtectionDomain; +import java.util.Set; + +/** + * Merges permissions based on code source and code signers with permissions + * granted to the specified {@link Subject}. + */ +public class SubjectDomainCombiner implements DomainCombiner { + + // subject to be associated + private Subject subject; + + // permission required to get a subject object + private static final AuthPermission _GET = new AuthPermission( + "getSubjectFromDomainCombiner"); //$NON-NLS-1$ + + /** + * Creates a domain combiner for the entity provided in {@code subject}. + * + * @param subject + * the entity to which this domain combiner is associated. + */ + public SubjectDomainCombiner(Subject subject) { + super(); + if (subject == null) { + throw new NullPointerException(); + } + this.subject = subject; + } + + /** + * Returns the entity to which this domain combiner is associated. + * + * @return the entity to which this domain combiner is associated. + */ + public Subject getSubject() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(_GET); + } + + return subject; + } + + /** + * Merges the {@code ProtectionDomain} with the {@code Principal}s + * associated with the subject of this {@code SubjectDomainCombiner}. + * + * @param currentDomains + * the {@code ProtectionDomain}s associated with the context of + * the current thread. The domains must be sorted according to + * the execution order, the most recent residing at the + * beginning. + * @param assignedDomains + * the {@code ProtectionDomain}s from the parent thread based on + * code source and signers. + * @return a single {@code ProtectionDomain} array computed from the two + * provided arrays, or {@code null}. + * @see ProtectionDomain + */ + public ProtectionDomain[] combine(ProtectionDomain[] currentDomains, + ProtectionDomain[] assignedDomains) { + // get array length for combining protection domains + int len = 0; + if (currentDomains != null) { + len += currentDomains.length; + } + if (assignedDomains != null) { + len += assignedDomains.length; + } + if (len == 0) { + return null; + } + + ProtectionDomain[] pd = new ProtectionDomain[len]; + + // for each current domain substitute set of principal with subject's + int cur = 0; + if (currentDomains != null) { + + Set s = subject.getPrincipals(); + Principal[] p = s.toArray(new Principal[s.size()]); + + for (cur = 0; cur < currentDomains.length; cur++) { + if (currentDomains[cur] != null) { + ProtectionDomain newPD; + newPD = new ProtectionDomain(currentDomains[cur].getCodeSource(), + currentDomains[cur].getPermissions(), currentDomains[cur] + .getClassLoader(), p); + pd[cur] = newPD; + } + } + } + + // copy assigned domains + if (assignedDomains != null) { + System.arraycopy(assignedDomains, 0, pd, cur, assignedDomains.length); + } + + return pd; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java new file mode 100644 index 0000000..8fd745c --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/Callback.java @@ -0,0 +1,25 @@ +/* + * 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.callback; + +/** + * Defines an empty base interface for all {@code Callback}s used during + * authentication. + */ +public interface Callback { +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java new file mode 100644 index 0000000..d09fafa --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/CallbackHandler.java @@ -0,0 +1,54 @@ +/* + * 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.callback; + +import java.io.IOException; + +/** + * Needs to be implemented by classes that want to handle authentication + * {@link Callback}s. A single method {@link #handle(Callback[])} must be + * provided that checks the type of the incoming {@code Callback}s and reacts + * accordingly. {@code CallbackHandler}s can be installed per application. It is + * also possible to configure a system-default {@code CallbackHandler} by + * setting the {@code auth.login.defaultCallbackHandler} property in the + * standard {@code security.properties} file. + */ +public interface CallbackHandler { + + /** + * Handles the actual {@link Callback}. A {@code CallbackHandler} needs to + * implement this method. In the method, it is free to select which {@code + * Callback}s it actually wants to handle and in which way. For example, a + * console-based {@code CallbackHandler} might choose to sequentially ask + * the user for login and password, if it implements these {@code Callback} + * s, whereas a GUI-based one might open a single dialog window for both + * values. If a {@code CallbackHandler} is not able to handle a specific + * {@code Callback}, it needs to throw an + * {@link UnsupportedCallbackException}. + * + * @param callbacks + * the array of {@code Callback}s that need handling + * @throws IOException + * if an I/O related error occurs + * @throws UnsupportedCallbackException + * if the {@code CallbackHandler} is not able to handle a + * specific {@code Callback} + */ + void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException; + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java new file mode 100644 index 0000000..1e53fb6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ChoiceCallback.java @@ -0,0 +1,109 @@ +/* + * 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.callback; + +import java.io.Serializable; + + + +public class ChoiceCallback implements Callback, Serializable { + + private static final long serialVersionUID = -3975664071579892167L; + + private int defaultChoice; + + private String prompt; + + private boolean multipleSelectionsAllowed; + + private String[] choices; + + private int[] selections; + + private void setChoices(String[] choices) { + if (choices == null || choices.length == 0) { + throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$ + } + for (int i = 0; i < choices.length; i++) { + if (choices[i] == null || choices[i].length() == 0) { + throw new IllegalArgumentException("auth.1C"); //$NON-NLS-1$ + } + } + //FIXME: System.arraycopy(choices, 0 , new String[choices.length], 0, choices.length); + this.choices = choices; + + } + + private void setPrompt(String prompt) { + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + this.prompt = prompt; + } + + private void setDefaultChoice(int defaultChoice) { + if (0 > defaultChoice || defaultChoice >= choices.length) { + throw new IllegalArgumentException("auth.1D"); //$NON-NLS-1$ + } + this.defaultChoice = defaultChoice; + } + + public ChoiceCallback(String prompt, String[] choices, int defaultChoice, + boolean multipleSelectionsAllowed) { + super(); + setPrompt(prompt); + setChoices(choices); + setDefaultChoice(defaultChoice); + this.multipleSelectionsAllowed = multipleSelectionsAllowed; + } + + public boolean allowMultipleSelections() { + return multipleSelectionsAllowed; + } + + public String[] getChoices() { + return choices; + } + + public int getDefaultChoice() { + return defaultChoice; + } + + public String getPrompt() { + return prompt; + } + + public int[] getSelectedIndexes() { + return selections; + } + + public void setSelectedIndex(int selection) { + this.selections = new int[1]; + this.selections[0] = selection; + } + + public void setSelectedIndexes(int[] selections) { + if (!multipleSelectionsAllowed) { + throw new UnsupportedOperationException(); + } + this.selections = selections; + //FIXME: + // this.selections = new int[selections.length] + //System.arraycopy(selections, 0, this.selections, 0, this.selections.length); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java new file mode 100644 index 0000000..a1893f3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/ConfirmationCallback.java @@ -0,0 +1,234 @@ +/* + * 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.callback; + +import java.io.Serializable; + + + +public class ConfirmationCallback implements Callback, Serializable { + + private static final long serialVersionUID = -9095656433782481624L; + + public static final int YES = 0; // default options + + public static final int NO = 1; + + public static final int CANCEL = 2; + + public static final int OK = 3; + + public static final int YES_NO_OPTION = 0; // options type + + public static final int YES_NO_CANCEL_OPTION = 1; + + public static final int OK_CANCEL_OPTION = 2; + + public static final int UNSPECIFIED_OPTION = -1; + + public static final int INFORMATION = 0; // messages type + + public static final int WARNING = 1; + + public static final int ERROR = 2; + + private String prompt; + + private int messageType; + + private int optionType = UNSPECIFIED_OPTION; + + private int defaultOption; + + private String[] options; + + private int selection; + + public ConfirmationCallback(int messageType, int optionType, int defaultOption) { + super(); + if (messageType > ERROR || messageType < INFORMATION) { + throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ + } + + switch (optionType) { + case YES_NO_OPTION: + if (defaultOption != YES && defaultOption != NO) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + case YES_NO_CANCEL_OPTION: + if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + case OK_CANCEL_OPTION: + if (defaultOption != OK && defaultOption != CANCEL) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + default: + throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$ + } + this.messageType = messageType; + this.optionType = optionType; + this.defaultOption = defaultOption; + } + + public ConfirmationCallback(int messageType, String[] options, int defaultOption) { + super(); + if (messageType > ERROR || messageType < INFORMATION) { + throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ + } + + if (options == null || options.length == 0) { + throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ + } + for (int i = 0; i < options.length; i++) { + if (options[i] == null || options[i].length() == 0) { + throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ + } + } + if (0 > defaultOption || defaultOption >= options.length) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + // FIXME:System.arraycopy(options, 0 , new String[this.options.length], + // 0, this.options.length); + this.options = options; + this.defaultOption = defaultOption; + this.messageType = messageType; + } + + public ConfirmationCallback(String prompt, int messageType, int optionType, + int defaultOption) { + super(); + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + + if (messageType > ERROR || messageType < INFORMATION) { + throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ + } + + switch (optionType) { + case YES_NO_OPTION: + if (defaultOption != YES && defaultOption != NO) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + case YES_NO_CANCEL_OPTION: + if (defaultOption != YES && defaultOption != NO && defaultOption != CANCEL) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + case OK_CANCEL_OPTION: + if (defaultOption != OK && defaultOption != CANCEL) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + break; + default: + throw new IllegalArgumentException("auth.18"); //$NON-NLS-1$ + } + this.prompt = prompt; + this.messageType = messageType; + this.optionType = optionType; + this.defaultOption = defaultOption; + } + + public ConfirmationCallback(String prompt, int messageType, String[] options, + int defaultOption) { + super(); + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + + if (messageType > ERROR || messageType < INFORMATION) { + throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ + } + + if (options == null || options.length == 0) { + throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ + } + for (int i = 0; i < options.length; i++) { + if (options[i] == null || options[i].length() == 0) { + throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ + } + } + if (0 > defaultOption || defaultOption >= options.length) { + throw new IllegalArgumentException("auth.17"); //$NON-NLS-1$ + } + // FIXME:System.arraycopy(options, 0 , new String[this.options.length], + // 0, this.options.length); + this.options = options; + this.defaultOption = defaultOption; + this.messageType = messageType; + this.prompt = prompt; + } + + public String getPrompt() { + return prompt; + } + + public int getMessageType() { + return messageType; + } + + public int getDefaultOption() { + return defaultOption; + } + + public String[] getOptions() { + return options; + } + + public int getOptionType() { + return optionType; + } + + public int getSelectedIndex() { + return selection; + } + + public void setSelectedIndex(int selection) { + if (options != null) { + if (0 <= selection && selection <= options.length) { + this.selection = selection; + } else { + throw new ArrayIndexOutOfBoundsException("auth.1B"); //$NON-NLS-1$ + } + } else { + switch (optionType) { + case YES_NO_OPTION: + if (selection != YES && selection != NO) { + throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ + } + break; + case YES_NO_CANCEL_OPTION: + if (selection != YES && selection != NO && selection != CANCEL) { + throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ + } + break; + case OK_CANCEL_OPTION: + if (selection != OK && selection != CANCEL) { + throw new IllegalArgumentException("auth.19"); //$NON-NLS-1$ + } + break; + } + this.selection = selection; + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java new file mode 100644 index 0000000..729bb49 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/LanguageCallback.java @@ -0,0 +1,41 @@ +/* + * 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.callback; + +import java.io.Serializable; +import java.util.Locale; + +public class LanguageCallback implements Callback, Serializable { + + private static final long serialVersionUID = 2019050433478903213L; + + private Locale locale; + + public LanguageCallback() { + super(); + } + + public Locale getLocale() { + return locale; + } + + public void setLocale(Locale locale) { + this.locale = locale; + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java new file mode 100644 index 0000000..97264ef --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/NameCallback.java @@ -0,0 +1,74 @@ +/* + * 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.callback; + +import java.io.Serializable; + + + +public class NameCallback implements Callback, Serializable { + + private static final long serialVersionUID = 3770938795909392253L; + + private String prompt; + + private String defaultName; + + private String inputName; + + private void setPrompt(String prompt) { + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + this.prompt = prompt; + } + + private void setDefaultName(String defaultName) { + if (defaultName == null || defaultName.length() == 0) { + throw new IllegalArgumentException("auth.1E"); //$NON-NLS-1$ + } + this.defaultName = defaultName; + } + + public NameCallback(String prompt) { + super(); + setPrompt(prompt); + } + + public NameCallback(String prompt, String defaultName) { + super(); + setPrompt(prompt); + setDefaultName(defaultName); + } + + public String getPrompt() { + return prompt; + } + + public String getDefaultName() { + return defaultName; + } + + public void setName(String name) { + this.inputName = name; + } + + public String getName() { + return inputName; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java new file mode 100644 index 0000000..bd142fc --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/PasswordCallback.java @@ -0,0 +1,124 @@ +/* + * 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.callback; + +import java.io.Serializable; +import java.util.Arrays; + + + +/** + * Is used in conjunction with a {@link CallbackHandler} to retrieve a password + * when needed. + */ +public class PasswordCallback implements Callback, Serializable { + + private static final long serialVersionUID = 2267422647454909926L; + + private String prompt; + + boolean echoOn; + + private char[] inputPassword; + + private void setPrompt(String prompt) throws IllegalArgumentException { + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + this.prompt = prompt; + } + + /** + * Creates a new {@code PasswordCallback} instance. + * + * @param prompt + * the message that should be displayed to the user + * @param echoOn + * determines whether the user input should be echoed + */ + public PasswordCallback(String prompt, boolean echoOn) { + super(); + setPrompt(prompt); + this.echoOn = echoOn; + } + + /** + * Returns the prompt that was specified when creating this {@code + * PasswordCallback} + * + * @return the prompt + */ + public String getPrompt() { + return prompt; + } + + /** + * Queries whether this {@code PasswordCallback} expects user input to be + * echoed, which is specified during the creation of the object. + * + * @return {@code true} if (and only if) user input should be echoed + */ + public boolean isEchoOn() { + return echoOn; + } + + /** + * Sets the password. The {@link CallbackHandler} that performs the actual + * provisioning or input of the password needs to call this method to hand + * back the password to the security service that requested it. + * + * @param password + * the password. A copy of this is stored, so subsequent changes + * to the input array do not affect the {@code PasswordCallback}. + */ + public void setPassword(char[] password) { + if (password == null) { + this.inputPassword = password; + } else { + inputPassword = new char[password.length]; + System.arraycopy(password, 0, inputPassword, 0, inputPassword.length); + } + } + + /** + * Returns the password. The security service that needs the password + * usually calls this method once the {@link CallbackHandler} has finished + * its work. + * + * @return the password. A copy of the internal password is created and + * returned, so subsequent changes to the internal password do not + * affect the result. + */ + public char[] getPassword() { + if (inputPassword != null) { + char[] tmp = new char[inputPassword.length]; + System.arraycopy(inputPassword, 0, tmp, 0, tmp.length); + return tmp; + } + return null; + } + + /** + * Clears the password stored in this {@code PasswordCallback}. + */ + public void clearPassword() { + if (inputPassword != null) { + Arrays.fill(inputPassword, '\u0000'); + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java new file mode 100644 index 0000000..c7de222 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextInputCallback.java @@ -0,0 +1,74 @@ +/* + * 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.callback; + +import java.io.Serializable; + + + +public class TextInputCallback implements Callback, Serializable { + + private static final long serialVersionUID = -8064222478852811804L; + + private String defaultText; + + private String prompt; + + private String inputText; + + private void setPrompt(String prompt) { + if (prompt == null || prompt.length() == 0) { + throw new IllegalArgumentException("auth.14"); //$NON-NLS-1$ + } + this.prompt = prompt; + } + + private void setDefaultText(String defaultText) { + if (defaultText == null || defaultText.length() == 0) { + throw new IllegalArgumentException("auth.15"); //$NON-NLS-1$ + } + this.defaultText = defaultText; + } + + public TextInputCallback(String prompt) { + super(); + setPrompt(prompt); + } + + public TextInputCallback(String prompt, String defaultText) { + super(); + setPrompt(prompt); + setDefaultText(defaultText); + } + + public String getDefaultText() { + return defaultText; + } + + public String getPrompt() { + return prompt; + } + + public String getText() { + return inputText; + } + + public void setText(String text) { + this.inputText = text; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java new file mode 100644 index 0000000..23a72fa --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/TextOutputCallback.java @@ -0,0 +1,56 @@ +/* + * 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.callback; + +import java.io.Serializable; + + + +public class TextOutputCallback implements Callback, Serializable { + + private static final long serialVersionUID = 1689502495511663102L; + + public static final int INFORMATION = 0; + + public static final int WARNING = 1; + + public static final int ERROR = 2; + + private String message; + + private int messageType; + + public TextOutputCallback(int messageType, String message) { + if (messageType > ERROR || messageType < INFORMATION) { + throw new IllegalArgumentException("auth.16"); //$NON-NLS-1$ + } + if (message == null || message.length() == 0) { + throw new IllegalArgumentException("auth.1F"); //$NON-NLS-1$ + } + this.messageType = messageType; + this.message = message; + } + + public String getMessage() { + return message; + } + + public int getMessageType() { + return messageType; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java new file mode 100644 index 0000000..19f6e40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/callback/UnsupportedCallbackException.java @@ -0,0 +1,64 @@ +/* + * 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.callback; + +/** + * Thrown when a {@link CallbackHandler} does not support a particular {@link + * Callback}. + */ +public class UnsupportedCallbackException extends Exception { + + private static final long serialVersionUID = -6873556327655666839L; + + private Callback callback; + + /** + * Creates a new exception instance and initializes it with just the + * unsupported {@code Callback}, but no error message. + * + * @param callback + * the {@code Callback} + */ + public UnsupportedCallbackException(Callback callback) { + super(); + this.callback = callback; + } + + /** + * Creates a new exception instance and initializes it with both the + * unsupported {@code Callback} and an error message. + * + * @param callback + * the {@code Callback} + * @param message + * the error message + */ + public UnsupportedCallbackException(Callback callback, String message) { + super(message); + this.callback = callback; + } + + /** + * Returns the unsupported {@code Callback} that triggered this exception. + * + * @return the {@code Callback} + */ + public Callback getCallback() { + return callback; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.java new file mode 100644 index 0000000..c86e801 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountException.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.login; + +public class AccountException extends LoginException { + + private static final long serialVersionUID = -2112878680072211787L; + + public AccountException() { + super(); + } + + public AccountException(String message) { + super(message); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.java new file mode 100644 index 0000000..1e0ce4d --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountExpiredException.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.login; + +public class AccountExpiredException extends AccountException { + + private static final long serialVersionUID = -6064064890162661560L; + + public AccountExpiredException() { + super(); + } + + public AccountExpiredException(String message) { + super(message); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java new file mode 100644 index 0000000..47913a5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountLockedException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class AccountLockedException extends AccountException { + + private static final long serialVersionUID = 8280345554014066334L; + + public AccountLockedException() { + super(); + } + + public AccountLockedException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java new file mode 100644 index 0000000..8ca9b96 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AccountNotFoundException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class AccountNotFoundException extends AccountException { + + private static final long serialVersionUID = 1498349563916294614L; + + public AccountNotFoundException() { + super(); + } + + public AccountNotFoundException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java new file mode 100644 index 0000000..2a735dc --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/AppConfigurationEntry.java @@ -0,0 +1,95 @@ +/* + * 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.login; + +import java.util.Collections; +import java.util.Map; + + + +public class AppConfigurationEntry { + + // the login module options + private final Map options; + + // the control flag + private final AppConfigurationEntry.LoginModuleControlFlag controlFlag; + + // the login module name + private final String loginModuleName; + + public AppConfigurationEntry(String loginModuleName, + AppConfigurationEntry.LoginModuleControlFlag controlFlag, Map options) { + + if (loginModuleName == null || loginModuleName.length() == 0) { + throw new IllegalArgumentException("auth.26"); //$NON-NLS-1$ + } + + if (controlFlag == null) { + throw new IllegalArgumentException("auth.27"); //$NON-NLS-1$ + } + + if (options == null) { + throw new IllegalArgumentException("auth.1A"); //$NON-NLS-1$ + } + + this.loginModuleName = loginModuleName; + this.controlFlag = controlFlag; + this.options = Collections.unmodifiableMap(options); + } + + public String getLoginModuleName() { + return loginModuleName; + } + + public LoginModuleControlFlag getControlFlag() { + return controlFlag; + } + + public Map getOptions() { + return options; + } + + public static class LoginModuleControlFlag { + + // the control flag + private final String flag; + + public static final LoginModuleControlFlag REQUIRED = new LoginModuleControlFlag( + "LoginModuleControlFlag: required"); //$NON-NLS-1$ + + public static final LoginModuleControlFlag REQUISITE = new LoginModuleControlFlag( + "LoginModuleControlFlag: requisite"); //$NON-NLS-1$ + + public static final LoginModuleControlFlag OPTIONAL = new LoginModuleControlFlag( + "LoginModuleControlFlag: optional"); //$NON-NLS-1$ + + public static final LoginModuleControlFlag SUFFICIENT = new LoginModuleControlFlag( + "LoginModuleControlFlag: sufficient"); //$NON-NLS-1$ + + // Creates the LoginModuleControlFlag object with specified a flag + private LoginModuleControlFlag(String flag) { + this.flag = flag; + } + + @Override + public String toString() { + return flag; + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java new file mode 100644 index 0000000..74c371f --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/Configuration.java @@ -0,0 +1,102 @@ +/* + * 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.login; + +import java.security.AccessController; +import org.apache.harmony.javax.security.auth.AuthPermission; + +public abstract class Configuration { + + // the current configuration + private static Configuration configuration; + + // creates a AuthPermission object with a specify property + private static final AuthPermission GET_LOGIN_CONFIGURATION = new AuthPermission( + "getLoginConfiguration"); //$NON-NLS-1$ + + // creates a AuthPermission object with a specify property + private static final AuthPermission SET_LOGIN_CONFIGURATION = new AuthPermission( + "setLoginConfiguration"); //$NON-NLS-1$ + + // Key to security properties, defining default configuration provider. + private static final String LOGIN_CONFIGURATION_PROVIDER = "login.configuration.provider"; //$NON-NLS-1$ + + protected Configuration() { + super(); + } + + public static Configuration getConfiguration() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(GET_LOGIN_CONFIGURATION); + } + return getAccessibleConfiguration(); + } + + /** + * Reads name of default configuration provider from security.properties, + * loads the class and instantiates the provider.
In case of any + * exception, wraps it with SecurityException and throws further. + */ + private static final Configuration getDefaultProvider() { + return new Configuration() { + + @Override + public void refresh() { + } + + @Override + public AppConfigurationEntry[] getAppConfigurationEntry( + String applicationName) { + return new AppConfigurationEntry[0]; + } + }; + } + + /** + * Shortcut accessor for friendly classes, to skip security checks. + * If active configuration was set to null, tries to load a default + * provider, so this method never returns null.
+ * This method is synchronized with setConfiguration() + */ + static Configuration getAccessibleConfiguration() { + Configuration current = configuration; + if (current == null) { + synchronized (Configuration.class) { + if (configuration == null) { + configuration = getDefaultProvider(); + } + return configuration; + } + } + return current; + } + + public static void setConfiguration(Configuration configuration) { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(SET_LOGIN_CONFIGURATION); + } + Configuration.configuration = configuration; + } + + public abstract AppConfigurationEntry[] getAppConfigurationEntry(String applicationName); + + public abstract void refresh(); + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java new file mode 100644 index 0000000..e74e866 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class CredentialException extends LoginException { + + private static final long serialVersionUID = -4772893876810601859L; + + public CredentialException() { + super(); + } + + public CredentialException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java new file mode 100644 index 0000000..3ca3ad7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialExpiredException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class CredentialExpiredException extends CredentialException { + + private static final long serialVersionUID = -5344739593859737937L; + + public CredentialExpiredException() { + super(); + } + + public CredentialExpiredException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java new file mode 100644 index 0000000..ffd529f --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/CredentialNotFoundException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class CredentialNotFoundException extends CredentialException { + + private static final long serialVersionUID = -7779934467214319475L; + + public CredentialNotFoundException() { + super(); + } + + public CredentialNotFoundException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java new file mode 100644 index 0000000..f689d99 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/FailedLoginException.java @@ -0,0 +1,32 @@ +/* + * 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.login; + +public class FailedLoginException extends LoginException { + + private static final long serialVersionUID = 802556922354616286L; + + public FailedLoginException() { + super(); + } + + public FailedLoginException(String message) { + super(message); + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java new file mode 100644 index 0000000..7d46278 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginContext.java @@ -0,0 +1,548 @@ +/* + * 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.login; + +import java.io.IOException; +import java.security.AccessController; +import java.security.AccessControlContext; +import java.security.PrivilegedExceptionAction; +import java.security.PrivilegedActionException; + +import java.security.Security; +import java.util.HashMap; +import java.util.Map; + +import org.apache.harmony.javax.security.auth.Subject; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.auth.spi.LoginModule; +import org.apache.harmony.javax.security.auth.AuthPermission; + +import org.apache.harmony.javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag; + + + +public class LoginContext { + + private static final String DEFAULT_CALLBACK_HANDLER_PROPERTY = "auth.login.defaultCallbackHandler"; //$NON-NLS-1$ + + /* + * Integer constants which serve as a replacement for the corresponding + * LoginModuleControlFlag.* constants. These integers are used later as + * index in the arrays - see loginImpl() and logoutImpl() methods + */ + private static final int OPTIONAL = 0; + + private static final int REQUIRED = 1; + + private static final int REQUISITE = 2; + + private static final int SUFFICIENT = 3; + + // Subject to be used for this LoginContext's operations + private Subject subject; + + /* + * Shows whether the subject was specified by user (true) or was created by + * this LoginContext itself (false). + */ + private boolean userProvidedSubject; + + // Shows whether we use installed or user-provided Configuration + private boolean userProvidedConfig; + + // An user's AccessControlContext, used when user specifies + private AccessControlContext userContext; + + /* + * Either a callback handler passed by the user or a wrapper for the user's + * specified handler - see init() below. + */ + private CallbackHandler callbackHandler; + + /* + * An array which keeps the instantiated and init()-ialized login modules + * and their states + */ + private Module[] modules; + + // Stores a shared state + private Map sharedState; + + // A context class loader used to load [mainly] LoginModules + private ClassLoader contextClassLoader; + + // Shows overall status - whether this LoginContext was successfully logged + private boolean loggedIn; + + public LoginContext(String name) throws LoginException { + super(); + init(name, null, null, null); + } + + public LoginContext(String name, CallbackHandler cbHandler) throws LoginException { + super(); + if (cbHandler == null) { + throw new LoginException("auth.34"); //$NON-NLS-1$ + } + init(name, null, cbHandler, null); + } + + public LoginContext(String name, Subject subject) throws LoginException { + super(); + if (subject == null) { + throw new LoginException("auth.03"); //$NON-NLS-1$ + } + init(name, subject, null, null); + } + + public LoginContext(String name, Subject subject, CallbackHandler cbHandler) + throws LoginException { + super(); + if (subject == null) { + throw new LoginException("auth.03"); //$NON-NLS-1$ + } + if (cbHandler == null) { + throw new LoginException("auth.34"); //$NON-NLS-1$ + } + init(name, subject, cbHandler, null); + } + + public LoginContext(String name, Subject subject, CallbackHandler cbHandler, + Configuration config) throws LoginException { + super(); + init(name, subject, cbHandler, config); + } + + // Does all the machinery needed for the initialization. + private void init(String name, Subject subject, final CallbackHandler cbHandler, + Configuration config) throws LoginException { + userProvidedSubject = (this.subject = subject) != null; + + // + // Set config + // + if (name == null) { + throw new LoginException("auth.00"); //$NON-NLS-1$ + } + + if (config == null) { + config = Configuration.getAccessibleConfiguration(); + } else { + userProvidedConfig = true; + } + + SecurityManager sm = System.getSecurityManager(); + + if (sm != null && !userProvidedConfig) { + sm.checkPermission(new AuthPermission("createLoginContext." + name));//$NON-NLS-1$ + } + + AppConfigurationEntry[] entries = config.getAppConfigurationEntry(name); + if (entries == null) { + if (sm != null && !userProvidedConfig) { + sm.checkPermission(new AuthPermission("createLoginContext.other")); //$NON-NLS-1$ + } + entries = config.getAppConfigurationEntry("other"); //$NON-NLS-1$ + if (entries == null) { + throw new LoginException("auth.35 " + name); //$NON-NLS-1$ + } + } + + modules = new Module[entries.length]; + for (int i = 0; i < modules.length; i++) { + modules[i] = new Module(entries[i]); + } + // + // Set CallbackHandler and this.contextClassLoader + // + + /* + * as some of the operations to be executed (i.e. get*ClassLoader, + * getProperty, class loading) are security-checked, then combine all of + * them into a single doPrivileged() call. + */ + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Void run() throws Exception { + // First, set the 'contextClassLoader' + contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader == null) { + contextClassLoader = ClassLoader.getSystemClassLoader(); + } + // then, checks whether the cbHandler is set + if (cbHandler == null) { + // well, let's try to find it + String klassName = Security + .getProperty(DEFAULT_CALLBACK_HANDLER_PROPERTY); + if (klassName == null || klassName.length() == 0) { + return null; + } + Class klass = Class.forName(klassName, true, contextClassLoader); + callbackHandler = (CallbackHandler) klass.newInstance(); + } else { + callbackHandler = cbHandler; + } + return null; + } + }); + } catch (PrivilegedActionException ex) { + Throwable cause = ex.getCause(); + throw (LoginException) new LoginException("auth.36").initCause(cause);//$NON-NLS-1$ + } + + if (userProvidedConfig) { + userContext = AccessController.getContext(); + } else if (callbackHandler != null) { + userContext = AccessController.getContext(); + callbackHandler = new ContextedCallbackHandler(callbackHandler); + } + } + + public Subject getSubject() { + if (userProvidedSubject || loggedIn) { + return subject; + } + return null; + } + + /** + * Warning: calling the method more than once may result in undefined + * behaviour if logout() method is not invoked before. + */ + public void login() throws LoginException { + PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + public Void run() throws LoginException { + loginImpl(); + return null; + } + }; + try { + if (userProvidedConfig) { + AccessController.doPrivileged(action, userContext); + } else { + AccessController.doPrivileged(action); + } + } catch (PrivilegedActionException ex) { + throw (LoginException) ex.getException(); + } + } + + /** + * The real implementation of login() method whose calls are wrapped into + * appropriate doPrivileged calls in login(). + */ + private void loginImpl() throws LoginException { + if (subject == null) { + subject = new Subject(); + } + + if (sharedState == null) { + sharedState = new HashMap(); + } + + // PHASE 1: Calling login()-s + Throwable firstProblem = null; + + int[] logged = new int[4]; + int[] total = new int[4]; + + for (Module module : modules) { + try { + // if a module fails during Class.forName(), then it breaks overall + // attempt - see catch() below + module.create(subject, callbackHandler, sharedState); + + if (module.module.login()) { + ++total[module.getFlag()]; + ++logged[module.getFlag()]; + if (module.getFlag() == SUFFICIENT) { + break; + } + } + } catch (Throwable ex) { + if (firstProblem == null) { + firstProblem = ex; + } + if (module.klass == null) { + /* + * an exception occurred during class lookup - overall + * attempt must fail a little trick: increase the REQUIRED's + * number - this will look like a failed REQUIRED module + * later, so overall attempt will fail + */ + ++total[REQUIRED]; + break; + } + ++total[module.getFlag()]; + // something happened after the class was loaded + if (module.getFlag() == REQUISITE) { + // ... and no need to walk down anymore + break; + } + } + } + // end of PHASE1, + + // Let's decide whether we have either overall success or a total failure + boolean fail = true; + + /* + * Note: 'failed[xxx]!=0' is not enough to check. + * + * Use 'logged[xx] != total[xx]' instead. This is because some modules + * might not be counted as 'failed' if an exception occurred during + * preload()/Class.forName()-ing. But, such modules still get counted in + * the total[]. + */ + + // if any REQ* module failed - then it's failure + if (logged[REQUIRED] != total[REQUIRED] || logged[REQUISITE] != total[REQUISITE]) { + // fail = true; + } else { + if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { + // neither REQUIRED nor REQUISITE was configured. + // must have at least one SUFFICIENT or OPTIONAL + if (logged[OPTIONAL] != 0 || logged[SUFFICIENT] != 0) { + fail = false; + } + //else { fail = true; } + } else { + fail = false; + } + } + + int commited[] = new int[4]; + // clear it + total[0] = total[1] = total[2] = total[3] = 0; + if (!fail) { + // PHASE 2: + + for (Module module : modules) { + if (module.klass != null) { + ++total[module.getFlag()]; + try { + module.module.commit(); + ++commited[module.getFlag()]; + } catch (Throwable ex) { + if (firstProblem == null) { + firstProblem = ex; + } + } + } + } + } + + // need to decide once again + fail = true; + if (commited[REQUIRED] != total[REQUIRED] || commited[REQUISITE] != total[REQUISITE]) { + //fail = true; + } else { + if (total[REQUIRED] == 0 && total[REQUISITE] == 0) { + /* + * neither REQUIRED nor REQUISITE was configured. must have at + * least one SUFFICIENT or OPTIONAL + */ + if (commited[OPTIONAL] != 0 || commited[SUFFICIENT] != 0) { + fail = false; + } else { + //fail = true; + } + } else { + fail = false; + } + } + + if (fail) { + // either login() or commit() failed. aborting... + + for (Module module : modules) { + try { + module.module.abort(); + } catch ( /*LoginException*/Throwable ex) { + if (firstProblem == null) { + firstProblem = ex; + } + } + } + if (firstProblem instanceof PrivilegedActionException + && firstProblem.getCause() != null) { + firstProblem = firstProblem.getCause(); + } + if (firstProblem instanceof LoginException) { + throw (LoginException) firstProblem; + } + throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ + } + loggedIn = true; + } + + public void logout() throws LoginException { + PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + public Void run() throws LoginException { + logoutImpl(); + return null; + } + }; + try { + if (userProvidedConfig) { + AccessController.doPrivileged(action, userContext); + } else { + AccessController.doPrivileged(action); + } + } catch (PrivilegedActionException ex) { + throw (LoginException) ex.getException(); + } + } + + /** + * The real implementation of logout() method whose calls are wrapped into + * appropriate doPrivileged calls in logout(). + */ + private void logoutImpl() throws LoginException { + if (subject == null) { + throw new LoginException("auth.38"); //$NON-NLS-1$ + } + loggedIn = false; + Throwable firstProblem = null; + int total = 0; + for (Module module : modules) { + try { + module.module.logout(); + ++total; + } catch (Throwable ex) { + if (firstProblem == null) { + firstProblem = ex; + } + } + } + if (firstProblem != null || total == 0) { + if (firstProblem instanceof PrivilegedActionException + && firstProblem.getCause() != null) { + firstProblem = firstProblem.getCause(); + } + if (firstProblem instanceof LoginException) { + throw (LoginException) firstProblem; + } + throw (LoginException) new LoginException("auth.37").initCause(firstProblem); //$NON-NLS-1$ + } + } + + /** + *

A class that servers as a wrapper for the CallbackHandler when we use + * installed Configuration, but not a passed one. See API docs on the + * LoginContext.

+ * + *

Simply invokes the given handler with the given AccessControlContext.

+ */ + private class ContextedCallbackHandler implements CallbackHandler { + private final CallbackHandler hiddenHandlerRef; + + ContextedCallbackHandler(CallbackHandler handler) { + super(); + this.hiddenHandlerRef = handler; + } + + public void handle(final Callback[] callbacks) throws IOException, + UnsupportedCallbackException { + try { + AccessController.doPrivileged(new PrivilegedExceptionAction() { + public Void run() throws IOException, UnsupportedCallbackException { + hiddenHandlerRef.handle(callbacks); + return null; + } + }, userContext); + } catch (PrivilegedActionException ex) { + if (ex.getCause() instanceof UnsupportedCallbackException) { + throw (UnsupportedCallbackException) ex.getCause(); + } + throw (IOException) ex.getCause(); + } + } + } + + /** + * A private class that stores an instantiated LoginModule. + */ + private final class Module { + + // An initial info about the module to be used + AppConfigurationEntry entry; + + // A mapping of LoginModuleControlFlag onto a simple int constant + int flag; + + // The LoginModule itself + LoginModule module; + + // A class of the module + Class klass; + + Module(AppConfigurationEntry entry) { + this.entry = entry; + LoginModuleControlFlag flg = entry.getControlFlag(); + if (flg == LoginModuleControlFlag.OPTIONAL) { + flag = OPTIONAL; + } else if (flg == LoginModuleControlFlag.REQUISITE) { + flag = REQUISITE; + } else if (flg == LoginModuleControlFlag.SUFFICIENT) { + flag = SUFFICIENT; + } else { + flag = REQUIRED; + //if(flg!=LoginModuleControlFlag.REQUIRED) throw new Error() + } + } + + int getFlag() { + return flag; + } + + /** + * Loads class of the LoginModule, instantiates it and then calls + * initialize(). + */ + void create(Subject subject, CallbackHandler callbackHandler, Map sharedState) + throws LoginException { + String klassName = entry.getLoginModuleName(); + if (klass == null) { + try { + klass = Class.forName(klassName, false, contextClassLoader); + } catch (ClassNotFoundException ex) { + throw (LoginException) new LoginException( + "auth.39 " + klassName).initCause(ex); //$NON-NLS-1$ + } + } + + if (module == null) { + try { + module = (LoginModule) klass.newInstance(); + } catch (IllegalAccessException ex) { + throw (LoginException) new LoginException( + "auth.3A " + klassName) //$NON-NLS-1$ + .initCause(ex); + } catch (InstantiationException ex) { + throw (LoginException) new LoginException( + "auth.3A" + klassName) //$NON-NLS-1$ + .initCause(ex); + } + module.initialize(subject, callbackHandler, sharedState, entry.getOptions()); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java new file mode 100644 index 0000000..e9ea566 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/login/LoginException.java @@ -0,0 +1,45 @@ +/* + * 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.login; + +import java.security.GeneralSecurityException; + +/** + * Base class for exceptions that are thrown when a login error occurs. + */ +public class LoginException extends GeneralSecurityException { + + private static final long serialVersionUID = -4679091624035232488L; + + /** + * Creates a new exception instance and initializes it with default values. + */ + public LoginException() { + super(); + } + + /** + * Creates a new exception instance and initializes it with a given message. + * + * @param message the error message + */ + public LoginException(String message) { + super(message); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java new file mode 100644 index 0000000..3ed9eb2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/auth/spi/LoginModule.java @@ -0,0 +1,38 @@ +/* + * 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.spi; + +import java.util.Map; + +import org.apache.harmony.javax.security.auth.Subject; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.login.LoginException; + +public interface LoginModule { + + void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options); + + boolean login() throws LoginException; + + boolean commit() throws LoginException; + + boolean abort() throws LoginException; + + boolean logout() throws LoginException; +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java new file mode 100644 index 0000000..38703ef --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthenticationException.java @@ -0,0 +1,35 @@ +/* + * 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.sasl; + +public class AuthenticationException extends SaslException { + + private static final long serialVersionUID = -3579708765071815007L; + + public AuthenticationException() { + super(); + } + + public AuthenticationException(String detail) { + super(detail); + } + + public AuthenticationException(String detail, Throwable ex) { + super(detail, ex); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java new file mode 100644 index 0000000..2ba90a2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/AuthorizeCallback.java @@ -0,0 +1,79 @@ +/* + * 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.sasl; + +import java.io.Serializable; +import org.apache.harmony.javax.security.auth.callback.Callback; + +public class AuthorizeCallback implements Callback, Serializable { + + private static final long serialVersionUID = -2353344186490470805L; + + /** + * Serialized field for storing authenticationID. + */ + private final String authenticationID; + + /** + * Serialized field for storing authorizationID. + */ + private final String authorizationID; + + /** + * Serialized field for storing authorizedID. + */ + private String authorizedID; + + /** + * Store authorized Serialized field. + */ + private boolean authorized; + + public AuthorizeCallback(String authnID, String authzID) { + super(); + authenticationID = authnID; + authorizationID = authzID; + authorizedID = authzID; + } + + public String getAuthenticationID() { + return authenticationID; + } + + public String getAuthorizationID() { + return authorizationID; + } + + public String getAuthorizedID() { + return (authorized ? authorizedID : null); + } + + public boolean isAuthorized() { + return authorized; + } + + public void setAuthorized(boolean ok) { + authorized = ok; + } + + public void setAuthorizedID(String id) { + if (id != null) { + authorizedID = id; + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java new file mode 100644 index 0000000..65b5d15 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmCallback.java @@ -0,0 +1,33 @@ +/* + * 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.sasl; + +import org.apache.harmony.javax.security.auth.callback.TextInputCallback; + +public class RealmCallback extends TextInputCallback { + + private static final long serialVersionUID = -4342673378785456908L; + + public RealmCallback(String prompt) { + super(prompt); + } + + public RealmCallback(String prompt, String defaultRealmInfo) { + super(prompt, defaultRealmInfo); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java new file mode 100644 index 0000000..079ea07 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/RealmChoiceCallback.java @@ -0,0 +1,30 @@ +/* + * 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.sasl; + +import org.apache.harmony.javax.security.auth.callback.ChoiceCallback; + +public class RealmChoiceCallback extends ChoiceCallback { + + private static final long serialVersionUID = -8588141348846281332L; + + public RealmChoiceCallback(String prompt, String[] choices, int defaultChoice, + boolean multiple) { + super(prompt, choices, defaultChoice, multiple); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java new file mode 100644 index 0000000..4d827f8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/Sasl.java @@ -0,0 +1,204 @@ +/* + * 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.sasl; + +import java.security.Provider; +import java.security.Security; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; + + + +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Map; +import java.util.HashSet; +import java.util.Iterator; + +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$ + + // Default public constructor is overridden + private Sasl() { + super(); + } + + // Forms new instance of factory + private static Object newInstance(String factoryName, Provider prv) throws SaslException { + String msg = "auth.31"; //$NON-NLS-1$ + Object factory; + ClassLoader cl = prv.getClass().getClassLoader(); + if (cl == null) { + cl = ClassLoader.getSystemClassLoader(); + } + try { + factory = (Class.forName(factoryName, true, cl)).newInstance(); + return factory; + } catch (IllegalAccessException e) { + throw new SaslException(msg + factoryName, e); + } catch (ClassNotFoundException e) { + throw new SaslException(msg + factoryName, e); + } catch (InstantiationException e) { + throw new SaslException(msg + factoryName, e); + } + } + + /** + * This method forms the list of SaslClient/SaslServer factories which are + * implemented in used providers + */ + private static Collection findFactories(String service) { + HashSet fact = new HashSet(); + Provider[] pp = Security.getProviders(); + if ((pp == null) || (pp.length == 0)) { + return fact; + } + HashSet props = new HashSet(); + for (int i = 0; i < pp.length; i++) { + String prName = pp[i].getName(); + Enumeration keys = pp[i].keys(); + while (keys.hasMoreElements()) { + String s = (String) keys.nextElement(); + if (s.startsWith(service)) { + String prop = pp[i].getProperty(s); + try { + if (props.add(prName.concat(prop))) { + fact.add(newInstance(prop, pp[i])); + } + } catch (SaslException e) { + // ignore this factory + e.printStackTrace(); + } + } + } + } + return fact; + } + + @SuppressWarnings("unchecked") + public static Enumeration getSaslClientFactories() { + Collection res = (Collection) findFactories(CLIENTFACTORYSRV); + return Collections.enumeration(res); + + } + + @SuppressWarnings("unchecked") + public static Enumeration getSaslServerFactories() { + Collection res = (Collection) findFactories(SERVERFACTORYSRV); + return Collections.enumeration(res); + } + + public static SaslServer createSaslServer(String mechanism, String protocol, + String serverName, Map prop, CallbackHandler cbh) throws SaslException { + if (mechanism == null) { + throw new NullPointerException("auth.32"); //$NON-NLS-1$ + } + Collection res = findFactories(SERVERFACTORYSRV); + if (res.isEmpty()) { + return null; + } + + Iterator iter = res.iterator(); + while (iter.hasNext()) { + SaslServerFactory fact = (SaslServerFactory) iter.next(); + String[] mech = fact.getMechanismNames(null); + boolean is = false; + if (mech != null) { + for (int j = 0; j < mech.length; j++) { + if (mech[j].equals(mechanism)) { + is = true; + break; + } + } + } + if (is) { + SaslServer saslS = fact.createSaslServer(mechanism, protocol, serverName, prop, + cbh); + if (saslS != null) { + return saslS; + } + } + } + return null; + } + + 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$ + } + Collection res = findFactories(CLIENTFACTORYSRV); + if (res.isEmpty()) { + return null; + } + + Iterator iter = res.iterator(); + while (iter.hasNext()) { + SaslClientFactory fact = (SaslClientFactory) iter.next(); + 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) { + SaslClient saslC = fact.createSaslClient(mechanisms, authanticationID, + protocol, serverName, prop, cbh); + if (saslC != null) { + return saslC; + } + } + } + return null; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java new file mode 100644 index 0000000..e07ff53 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClient.java @@ -0,0 +1,37 @@ +/* + * 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.sasl; + +public interface SaslClient { + + void dispose() throws SaslException; + + byte[] evaluateChallenge(byte[] challenge) throws SaslException; + + String getMechanismName(); + + Object getNegotiatedProperty(String propName); + + boolean hasInitialResponse(); + + boolean isComplete(); + + byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException; + + byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException; +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java new file mode 100644 index 0000000..e567ed3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslClientFactory.java @@ -0,0 +1,30 @@ +/* + * 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.sasl; + +import java.util.Map; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; + +public interface SaslClientFactory { + + SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, + String serverName, Map props, CallbackHandler cbh) throws SaslException; + + String[] getMechanismNames(Map props); + +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java new file mode 100644 index 0000000..1ab7b12 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslException.java @@ -0,0 +1,69 @@ +/* + * 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.sasl; + +import java.io.IOException; + +public class SaslException extends IOException { + + private static final long serialVersionUID = 4579784287983423626L; + + /** + * Serialized field for storing initial cause + */ + private Throwable _exception; + + public SaslException() { + super(); + } + + public SaslException(String detail) { + super(detail); + } + + public SaslException(String detail, Throwable ex) { + super(detail); + if (ex != null) { + super.initCause(ex); + _exception = ex; + } + } + + @Override + public Throwable getCause() { + return _exception; + } + + @Override + public Throwable initCause(Throwable cause) { + super.initCause(cause); + _exception = cause; + return this; + } + + @Override + public String toString() { + if (_exception == null) { + return super.toString(); + } + StringBuilder sb = new StringBuilder(super.toString()); + sb.append(", caused by: "); //$NON-NLS-1$ + sb.append(_exception.toString()); + return sb.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java new file mode 100644 index 0000000..f057a4b --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServer.java @@ -0,0 +1,37 @@ +/* + * 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.sasl; + +public interface SaslServer { + + void dispose() throws SaslException; + + byte[] evaluateResponse(byte[] response) throws SaslException; + + String getAuthorizationID(); + + String getMechanismName(); + + Object getNegotiatedProperty(String propName); + + boolean isComplete(); + + byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException; + + byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException; +} diff --git a/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java new file mode 100644 index 0000000..d59530e --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/harmony/javax/security/sasl/SaslServerFactory.java @@ -0,0 +1,30 @@ +/* + * 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.sasl; + +import java.util.Map; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; + +public interface SaslServerFactory { + + SaslServer createSaslServer(String mechanisms, String protocol, String serverName, + Map props, CallbackHandler cbh) throws SaslException; + + String[] getMechanismNames(Map props); + +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java new file mode 100644 index 0000000..5c33e40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/CRAMMD5HashedSaslClientFactory.java @@ -0,0 +1,59 @@ +/* + * + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import de.measite.smack.Sasl; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslClientFactory; +import org.apache.harmony.javax.security.sasl.SaslException; +import java.util.Map; + +public class CRAMMD5HashedSaslClientFactory implements SaslClientFactory +{ + /** The name of this mechanism */ + public static final String MECHANISM = "CRAM-MD5-HASHED"; + + public SaslClient createSaslClient(String[] mechanisms, String authorizationId, String protocol, + String serverName, Map props, CallbackHandler cbh) + throws SaslException + { + for (int i = 0; i < mechanisms.length; i++) + { + if (mechanisms[i].equals(MECHANISM)) + { + if (cbh == null) + { + throw new SaslException("CallbackHandler must not be null"); + } + + String[] mechs = {"CRAM-MD5"}; + return Sasl.createSaslClient(mechs, authorizationId, protocol, serverName, props, cbh); + } + } + return null; + } + + public String[] getMechanismNames(Map props) + { + return new String[]{MECHANISM}; + } +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java new file mode 100644 index 0000000..19162d8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/ClientSaslFactory.java @@ -0,0 +1,53 @@ +/* + * + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslClientFactory; +import org.apache.harmony.javax.security.sasl.SaslException; +import java.util.Map; + +public class ClientSaslFactory implements SaslClientFactory +{ + public SaslClient createSaslClient(String[] mechs, String authorizationId, String protocol, + String serverName, Map props, CallbackHandler cbh) + throws SaslException + { + for (int i = 0; i < mechs.length; i++) + { + if (mechs[i].equals("PLAIN")) + { + return new PlainSaslClient(authorizationId, cbh); + } + } + return null; + } + + /** + * Simple-minded implementation that ignores props + */ + public String[] getMechanismNames(Map props) + { + return new String[]{"PLAIN"}; + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java new file mode 100644 index 0000000..31010ba --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/Constants.java @@ -0,0 +1,33 @@ +/* + * + * 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.qpid.management.common.sasl; + +public class Constants +{ + + public final static String MECH_CRAMMD5 = "CRAM-MD5"; + public final static String MECH_PLAIN = "PLAIN"; + public final static String SASL_CRAMMD5 = "SASL/CRAM-MD5"; + public final static String SASL_PLAIN = "SASL/PLAIN"; + +} + diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java new file mode 100644 index 0000000..5793dae --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/JCAProvider.java @@ -0,0 +1,55 @@ +/* + * + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.sasl.SaslClientFactory; +import java.security.Provider; +import java.util.Map; + +public class JCAProvider extends Provider +{ + private static final long serialVersionUID = 1L; + + /** + * Creates the security provider with a map from SASL mechanisms to implementing factories. + * + * @param providerMap The map from SASL mechanims to implementing factory classes. + */ + public JCAProvider(Map> providerMap) + { + super("AMQSASLProvider", 1.0, "A JCA provider that registers all " + + "AMQ SASL providers that want to be registered"); + register(providerMap); + } + + /** + * Registers client factory classes for a map of mechanism names to client factory classes. + * + * @param providerMap The map from SASL mechanims to implementing factory classes. + */ + private void register(Map> providerMap) + { + for (Map.Entry> me : providerMap.entrySet()) + { + put("SaslClientFactory." + me.getKey(), me.getValue().getName()); + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java new file mode 100644 index 0000000..99a1d43 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/PlainSaslClient.java @@ -0,0 +1,210 @@ +/* + * + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import de.measite.smack.Sasl; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; + +public class PlainSaslClient implements SaslClient +{ + + private boolean completed; + private CallbackHandler cbh; + private String authorizationID; + private String authenticationID; + private byte password[]; + private static byte SEPARATOR = 0; + + public PlainSaslClient(String authorizationID, CallbackHandler cbh) throws SaslException + { + completed = false; + this.cbh = cbh; + Object[] userInfo = getUserInfo(); + this.authorizationID = authorizationID; + this.authenticationID = (String) userInfo[0]; + this.password = (byte[]) userInfo[1]; + if (authenticationID == null || password == null) + { + throw new SaslException("PLAIN: authenticationID and password must be specified"); + } + } + + public byte[] evaluateChallenge(byte[] challenge) throws SaslException + { + if (completed) + { + throw new IllegalStateException("PLAIN: authentication already " + + "completed"); + } + completed = true; + try + { + byte authzid[] = + authorizationID == null ? null : authorizationID.getBytes("UTF8"); + byte authnid[] = authenticationID.getBytes("UTF8"); + byte response[] = + new byte[ + password.length + + authnid.length + + 2 + // SEPARATOR + (authzid != null ? authzid.length : 0) + ]; + int size = 0; + if (authzid != null) { + System.arraycopy(authzid, 0, response, 0, authzid.length); + size = authzid.length; + } + response[size++] = SEPARATOR; + System.arraycopy(authnid, 0, response, size, authnid.length); + size += authnid.length; + response[size++] = SEPARATOR; + System.arraycopy(password, 0, response, size, password.length); + clearPassword(); + return response; + } catch (UnsupportedEncodingException e) { + throw new SaslException("PLAIN: Cannot get UTF-8 encoding of ids", + e); + } + } + + public String getMechanismName() + { + return "PLAIN"; + } + + public boolean hasInitialResponse() + { + return true; + } + + public boolean isComplete() + { + return completed; + } + + public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException + { + if (completed) { + throw new IllegalStateException("PLAIN: this mechanism supports " + + "neither integrity nor privacy"); + } else { + throw new IllegalStateException("PLAIN: authentication not " + + "completed"); + } + } + + public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException + { + if (completed) + { + throw new IllegalStateException("PLAIN: this mechanism supports " + + "neither integrity nor privacy"); + } + else + { + throw new IllegalStateException("PLAIN: authentication not " + + "completed"); + } + } + + public Object getNegotiatedProperty(String propName) + { + if (completed) + { + if (propName.equals(Sasl.QOP)) + { + return "auth"; + } + else + { + return null; + } + } + else + { + throw new IllegalStateException("PLAIN: authentication not " + + "completed"); + } + } + + private void clearPassword() + { + if (password != null) + { + for (int i = 0 ; i < password.length ; i++) + { + password[i] = 0; + } + password = null; + } + } + + public void dispose() throws SaslException + { + clearPassword(); + } + + protected void finalize() + { + clearPassword(); + } + + private Object[] getUserInfo() throws SaslException + { + try + { + final String userPrompt = "PLAIN authentication id: "; + final String pwPrompt = "PLAIN password: "; + NameCallback nameCb = new NameCallback(userPrompt); + PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false); + cbh.handle(new Callback[] { nameCb, passwordCb }); + String userid = nameCb.getName(); + char pwchars[] = passwordCb.getPassword(); + byte pwbytes[]; + if (pwchars != null) + { + pwbytes = (new String(pwchars)).getBytes("UTF8"); + passwordCb.clearPassword(); + } + else + { + pwbytes = null; + } + return (new Object[] { userid, pwbytes }); + } + catch (IOException e) + { + throw new SaslException("Cannot get password", e); + } + catch (UnsupportedCallbackException e) + { + throw new SaslException("Cannot get userid/password", e); + } + } +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java new file mode 100644 index 0000000..1eb44e3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/SaslProvider.java @@ -0,0 +1,35 @@ +/* + * + * 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.qpid.management.common.sasl; + +import java.security.Provider; + +public class SaslProvider extends Provider +{ + private static final long serialVersionUID = -6978096016899676466L; + + public SaslProvider() + { + super("SaslClientFactory", 1.0, "SASL PLAIN CLIENT MECHANISM"); + put("SaslClientFactory.PLAIN", "ClientSaslFactory"); + } + +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java new file mode 100644 index 0000000..a7886cf --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UserPasswordCallbackHandler.java @@ -0,0 +1,77 @@ +/* + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; + +public class UserPasswordCallbackHandler implements CallbackHandler +{ + private String user; + private char[] pwchars; + + public UserPasswordCallbackHandler(String user, String password) + { + this.user = user; + this.pwchars = password.toCharArray(); + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + if (callbacks[i] instanceof NameCallback) + { + NameCallback ncb = (NameCallback) callbacks[i]; + ncb.setName(user); + } + else if (callbacks[i] instanceof PasswordCallback) + { + PasswordCallback pcb = (PasswordCallback) callbacks[i]; + pcb.setPassword(pwchars); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + private void clearPassword() + { + if (pwchars != null) + { + for (int i = 0 ; i < pwchars.length ; i++) + { + pwchars[i] = 0; + } + pwchars = null; + } + } + + protected void finalize() + { + clearPassword(); + } +} diff --git a/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java new file mode 100644 index 0000000..54d7374 --- /dev/null +++ b/external/asmack/build/src/trunk/org/apache/qpid/management/common/sasl/UsernameHashedPasswordCallbackHandler.java @@ -0,0 +1,107 @@ +/* + * + * 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.qpid.management.common.sasl; + +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +public class UsernameHashedPasswordCallbackHandler implements CallbackHandler +{ + private String user; + private char[] pwchars; + + public UsernameHashedPasswordCallbackHandler(String user, String password) throws Exception + { + this.user = user; + this.pwchars = getHash(password); + } + + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException + { + for (int i = 0; i < callbacks.length; i++) + { + if (callbacks[i] instanceof NameCallback) + { + NameCallback ncb = (NameCallback) callbacks[i]; + ncb.setName(user); + } + else if (callbacks[i] instanceof PasswordCallback) + { + PasswordCallback pcb = (PasswordCallback) callbacks[i]; + pcb.setPassword(pwchars); + } + else + { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + + private void clearPassword() + { + if (pwchars != null) + { + for (int i = 0 ; i < pwchars.length ; i++) + { + pwchars[i] = 0; + } + pwchars = null; + } + } + + protected void finalize() + { + clearPassword(); + } + + public static char[] getHash(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException + { + byte[] data = text.getBytes("utf-8"); + + MessageDigest md = MessageDigest.getInstance("MD5"); + + for (byte b : data) + { + md.update(b); + } + + byte[] digest = md.digest(); + + char[] hash = new char[digest.length ]; + + int index = 0; + for (byte b : digest) + { + hash[index++] = (char) b; + } + + return hash; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java new file mode 100644 index 0000000..69acf90 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AbstractConnectionListener.java @@ -0,0 +1,46 @@ +/** + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * The AbstractConnectionListener class provides an empty implementation for all + * methods defined by the {@link ConnectionListener} interface. This is a + * convenience class which should be used in case you do not need to implement + * all methods. + * + * @author Henning Staib + */ +public class AbstractConnectionListener implements ConnectionListener { + + public void connectionClosed() { + // do nothing + } + + public void connectionClosedOnError(Exception e) { + // do nothing + } + + public void reconnectingIn(int seconds) { + // do nothing + } + + public void reconnectionFailed(Exception e) { + // do nothing + } + + public void reconnectionSuccessful() { + // do nothing + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java new file mode 100644 index 0000000..4d9faa5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AccountManager.java @@ -0,0 +1,337 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Registration; +import org.jivesoftware.smack.util.StringUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Allows creation and management of accounts on an XMPP server. + * + * @see Connection#getAccountManager() + * @author Matt Tucker + */ +public class AccountManager { + + private Connection connection; + private Registration info = null; + + /** + * Flag that indicates whether the server supports In-Band Registration. + * In-Band Registration may be advertised as a stream feature. If no stream feature + * was advertised from the server then try sending an IQ packet to discover if In-Band + * Registration is available. + */ + private boolean accountCreationSupported = false; + + /** + * Creates a new AccountManager instance. + * + * @param connection a connection to a XMPP server. + */ + public AccountManager(Connection connection) { + this.connection = connection; + } + + /** + * Sets whether the server supports In-Band Registration. In-Band Registration may be + * advertised as a stream feature. If no stream feature was advertised from the server + * then try sending an IQ packet to discover if In-Band Registration is available. + * + * @param accountCreationSupported true if the server supports In-Band Registration. + */ + void setSupportsAccountCreation(boolean accountCreationSupported) { + this.accountCreationSupported = accountCreationSupported; + } + + /** + * Returns true if the server supports creating new accounts. Many servers require + * that you not be currently authenticated when creating new accounts, so the safest + * behavior is to only create new accounts before having logged in to a server. + * + * @return true if the server support creating new accounts. + */ + public boolean supportsAccountCreation() { + // Check if we already know that the server supports creating new accounts + if (accountCreationSupported) { + return true; + } + // No information is known yet (e.g. no stream feature was received from the server + // indicating that it supports creating new accounts) so send an IQ packet as a way + // to discover if this feature is supported + try { + if (info == null) { + getRegistrationInfo(); + accountCreationSupported = info.getType() != IQ.Type.ERROR; + } + return accountCreationSupported; + } + catch (XMPPException xe) { + return false; + } + } + + /** + * Returns an unmodifiable collection of the names of the required account attributes. + * All attributes must be set when creating new accounts. The standard set of possible + * attributes are as follows:
    + *
  • name -- the user's name. + *
  • first -- the user's first name. + *
  • last -- the user's last name. + *
  • email -- the user's email address. + *
  • city -- the user's city. + *
  • state -- the user's state. + *
  • zip -- the user's ZIP code. + *
  • phone -- the user's phone number. + *
  • url -- the user's website. + *
  • date -- the date the registration took place. + *
  • misc -- other miscellaneous information to associate with the account. + *
  • text -- textual information to associate with the account. + *
  • remove -- empty flag to remove account. + *

+ * + * Typically, servers require no attributes when creating new accounts, or just + * the user's email address. + * + * @return the required account attributes. + */ + public Collection getAccountAttributes() { + try { + if (info == null) { + getRegistrationInfo(); + } + Map attributes = info.getAttributes(); + if (attributes != null) { + return Collections.unmodifiableSet(attributes.keySet()); + } + } + catch (XMPPException xe) { + xe.printStackTrace(); + } + return Collections.emptySet(); + } + + /** + * Returns the value of a given account attribute or null if the account + * attribute wasn't found. + * + * @param name the name of the account attribute to return its value. + * @return the value of the account attribute or null if an account + * attribute wasn't found for the requested name. + */ + public String getAccountAttribute(String name) { + try { + if (info == null) { + getRegistrationInfo(); + } + return info.getAttributes().get(name); + } + catch (XMPPException xe) { + xe.printStackTrace(); + } + return null; + } + + /** + * Returns the instructions for creating a new account, or null if there + * are no instructions. If present, instructions should be displayed to the end-user + * that will complete the registration process. + * + * @return the account creation instructions, or null if there are none. + */ + public String getAccountInstructions() { + try { + if (info == null) { + getRegistrationInfo(); + } + return info.getInstructions(); + } + catch (XMPPException xe) { + return null; + } + } + + /** + * Creates a new account using the specified username and password. The server may + * require a number of extra account attributes such as an email address and phone + * number. In that case, Smack will attempt to automatically set all required + * attributes with blank values, which may or may not be accepted by the server. + * Therefore, it's recommended to check the required account attributes and to let + * the end-user populate them with real values instead. + * + * @param username the username. + * @param password the password. + * @throws XMPPException if an error occurs creating the account. + */ + public void createAccount(String username, String password) throws XMPPException { + if (!supportsAccountCreation()) { + throw new XMPPException("Server does not support account creation."); + } + // Create a map for all the required attributes, but give them blank values. + Map attributes = new HashMap(); + for (String attributeName : getAccountAttributes()) { + attributes.put(attributeName, ""); + } + createAccount(username, password, attributes); + } + + /** + * Creates a new account using the specified username, password and account attributes. + * The attributes Map must contain only String name/value pairs and must also have values + * for all required attributes. + * + * @param username the username. + * @param password the password. + * @param attributes the account attributes. + * @throws XMPPException if an error occurs creating the account. + * @see #getAccountAttributes() + */ + public void createAccount(String username, String password, Map attributes) + throws XMPPException + { + if (!supportsAccountCreation()) { + throw new XMPPException("Server does not support account creation."); + } + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + reg.setTo(connection.getServiceName()); + attributes.put("username",username); + attributes.put("password",password); + reg.setAttributes(attributes); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + /** + * Changes the password of the currently logged-in account. This operation can only + * be performed after a successful login operation has been completed. Not all servers + * support changing passwords; an XMPPException will be thrown when that is the case. + * + * @throws IllegalStateException if not currently logged-in to the server. + * @throws XMPPException if an error occurs when changing the password. + */ + public void changePassword(String newPassword) throws XMPPException { + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + reg.setTo(connection.getServiceName()); + Map map = new HashMap(); + map.put("username",StringUtils.parseName(connection.getUser())); + map.put("password",newPassword); + reg.setAttributes(map); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + /** + * Deletes the currently logged-in account from the server. This operation can only + * be performed after a successful login operation has been completed. Not all servers + * support deleting accounts; an XMPPException will be thrown when that is the case. + * + * @throws IllegalStateException if not currently logged-in to the server. + * @throws XMPPException if an error occurs when deleting the account. + */ + public void deleteAccount() throws XMPPException { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to delete a account."); + } + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + reg.setTo(connection.getServiceName()); + Map attributes = new HashMap(); + // To delete an account, we add a single attribute, "remove", that is blank. + attributes.put("remove", ""); + reg.setAttributes(attributes); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + /** + * Gets the account registration info from the server. + * + * @throws XMPPException if an error occurs. + */ + private synchronized void getRegistrationInfo() throws XMPPException { + Registration reg = new Registration(); + reg.setTo(connection.getServiceName()); + PacketFilter filter = new AndFilter(new PacketIDFilter(reg.getPacketID()), + new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + else { + info = (Registration)result; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java new file mode 100644 index 0000000..a4cbacc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/AndroidConnectionConfiguration.java @@ -0,0 +1,86 @@ +package org.jivesoftware.smack; + +import org.jivesoftware.smack.proxy.ProxyInfo; +import org.jivesoftware.smack.util.DNSUtil; + +/** + * This class wraps DNS SRV lookups for a new ConnectionConfiguration in a + * new thread, since Android API >= 11 (Honeycomb) does not allow network + * activity in the main thread. + * + * @author Florian Schmaus fschmaus@gmail.com + * + */ +public class AndroidConnectionConfiguration extends ConnectionConfiguration { + private static final int DEFAULT_TIMEOUT = 1000; + + /** + * Creates a new ConnectionConfiguration for the specified service name. + * A DNS SRV lookup will be performed to find out the actual host address + * and port to use for the connection. + * + * @param serviceName the name of the service provided by an XMPP server. + */ + public AndroidConnectionConfiguration(String serviceName) throws XMPPException { + super(); + AndroidInit(serviceName, DEFAULT_TIMEOUT); + } + + /** + * + * @param serviceName + * @param timeout + * @throws XMPPException + */ + public AndroidConnectionConfiguration(String serviceName, int timeout) throws XMPPException { + super(); + AndroidInit(serviceName, timeout); + } + + /** + * + * @param serviceName + * @param timeout + * @throws XMPPException + */ + private void AndroidInit(String serviceName, int timeout) throws XMPPException { + class DnsSrvLookupRunnable implements Runnable { + String serviceName; + volatile DNSUtil.HostAddress address; + + public DnsSrvLookupRunnable(String serviceName) { + this.serviceName = serviceName; + } + + @Override + public void run() { + // Perform DNS lookup to get host and port to use + address = DNSUtil.resolveXMPPDomain(serviceName); + } + + public DNSUtil.HostAddress getHostAddress() { + return address; + } + } + + DnsSrvLookupRunnable dnsSrv = new DnsSrvLookupRunnable(serviceName); + Thread t = new Thread(dnsSrv, "dns-srv-lookup"); + t.start(); + try { + t.join(timeout); + } catch (InterruptedException e) { + throw new XMPPException("DNS lookup timeout after " + timeout + "ms", e); + } + + DNSUtil.HostAddress address = dnsSrv.getHostAddress(); + if (address == null) { + throw new XMPPException("DNS lookup failure"); + } + + String host = address.getHost(); + int port = address.getPort(); + ProxyInfo proxy = ProxyInfo.forDefaultProxy(); + + init(host, port, serviceName, proxy); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java new file mode 100644 index 0000000..0b033b4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConfiguration.java @@ -0,0 +1,124 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2009 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.net.URI; +import java.net.URISyntaxException; + +import org.jivesoftware.smack.ConnectionConfiguration; +import org.jivesoftware.smack.proxy.ProxyInfo; + +/** + * Configuration to use while establishing the connection to the XMPP server via + * HTTP binding. + * + * @see BOSHConnection + * @author Guenther Niess + */ +public class BOSHConfiguration extends ConnectionConfiguration { + + private boolean ssl; + private String file; + + public BOSHConfiguration(String xmppDomain) { + super(xmppDomain, 7070); + setSASLAuthenticationEnabled(true); + ssl = false; + file = "/http-bind/"; + } + + public BOSHConfiguration(String xmppDomain, int port) { + super(xmppDomain, port); + setSASLAuthenticationEnabled(true); + ssl = false; + file = "/http-bind/"; + } + + /** + * Create a HTTP Binding configuration. + * + * @param https true if you want to use SSL + * (e.g. false for http://domain.lt:7070/http-bind). + * @param host the hostname or IP address of the connection manager + * (e.g. domain.lt for http://domain.lt:7070/http-bind). + * @param port the port of the connection manager + * (e.g. 7070 for http://domain.lt:7070/http-bind). + * @param filePath the file which is described by the URL + * (e.g. /http-bind for http://domain.lt:7070/http-bind). + * @param xmppDomain the XMPP service name + * (e.g. domain.lt for the user alice@domain.lt) + */ + public BOSHConfiguration(boolean https, String host, int port, String filePath, String xmppDomain) { + super(host, port, xmppDomain); + setSASLAuthenticationEnabled(true); + ssl = https; + file = (filePath != null ? filePath : "/"); + } + + /** + * Create a HTTP Binding configuration. + * + * @param https true if you want to use SSL + * (e.g. false for http://domain.lt:7070/http-bind). + * @param host the hostname or IP address of the connection manager + * (e.g. domain.lt for http://domain.lt:7070/http-bind). + * @param port the port of the connection manager + * (e.g. 7070 for http://domain.lt:7070/http-bind). + * @param filePath the file which is described by the URL + * (e.g. /http-bind for http://domain.lt:7070/http-bind). + * @param proxy the configuration of a proxy server. + * @param xmppDomain the XMPP service name + * (e.g. domain.lt for the user alice@domain.lt) + */ + public BOSHConfiguration(boolean https, String host, int port, String filePath, ProxyInfo proxy, String xmppDomain) { + super(host, port, xmppDomain, proxy); + setSASLAuthenticationEnabled(true); + ssl = https; + file = (filePath != null ? filePath : "/"); + } + + public boolean isProxyEnabled() { + return (proxy != null && proxy.getProxyType() != ProxyInfo.ProxyType.NONE); + } + + public ProxyInfo getProxyInfo() { + return proxy; + } + + public String getProxyAddress() { + return (proxy != null ? proxy.getProxyAddress() : null); + } + + public int getProxyPort() { + return (proxy != null ? proxy.getProxyPort() : 8080); + } + + public boolean isUsingSSL() { + return ssl; + } + + public URI getURI() throws URISyntaxException { + if (file.charAt(0) != '/') { + file = '/' + file; + } + return new URI((ssl ? "https://" : "http://") + getHost() + ":" + getPort() + file); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java new file mode 100644 index 0000000..403b89a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHConnection.java @@ -0,0 +1,780 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2009 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.io.IOException; +import java.io.PipedReader; +import java.io.PipedWriter; +import java.io.Writer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; + +import com.kenai.jbosh.BOSHClient; +import com.kenai.jbosh.BOSHClientConfig; +import com.kenai.jbosh.BOSHClientConnEvent; +import com.kenai.jbosh.BOSHClientConnListener; +import com.kenai.jbosh.BOSHClientRequestListener; +import com.kenai.jbosh.BOSHClientResponseListener; +import com.kenai.jbosh.BOSHException; +import com.kenai.jbosh.BOSHMessageEvent; +import com.kenai.jbosh.BodyQName; +import com.kenai.jbosh.ComposableBody; + +/** + * Creates a connection to a XMPP server via HTTP binding. + * This is specified in the XEP-0206: XMPP Over BOSH. + * + * @see Connection + * @author Guenther Niess + */ +public class BOSHConnection extends Connection { + + /** + * The XMPP Over Bosh namespace. + */ + public static final String XMPP_BOSH_NS = "urn:xmpp:xbosh"; + + /** + * The BOSH namespace from XEP-0124. + */ + public static final String BOSH_URI = "http://jabber.org/protocol/httpbind"; + + /** + * The used BOSH client from the jbosh library. + */ + private BOSHClient client; + + /** + * Holds the initial configuration used while creating the connection. + */ + private final BOSHConfiguration config; + + // Some flags which provides some info about the current state. + private boolean connected = false; + private boolean authenticated = false; + private boolean anonymous = false; + private boolean isFirstInitialization = true; + private boolean wasAuthenticated = false; + private boolean done = false; + + /** + * The Thread environment for sending packet listeners. + */ + private ExecutorService listenerExecutor; + + // The readerPipe and consumer thread are used for the debugger. + private PipedWriter readerPipe; + private Thread readerConsumer; + + /** + * The BOSH equivalent of the stream ID which is used for DIGEST authentication. + */ + protected String authID = null; + + /** + * The session ID for the BOSH session with the connection manager. + */ + protected String sessionID = null; + + /** + * The full JID of the authenticated user. + */ + private String user = null; + + /** + * The roster maybe also called buddy list holds the list of the users contacts. + */ + private Roster roster = null; + + + /** + * Create a HTTP Binding connection to a XMPP server. + * + * @param https true if you want to use SSL + * (e.g. false for http://domain.lt:7070/http-bind). + * @param host the hostname or IP address of the connection manager + * (e.g. domain.lt for http://domain.lt:7070/http-bind). + * @param port the port of the connection manager + * (e.g. 7070 for http://domain.lt:7070/http-bind). + * @param filePath the file which is described by the URL + * (e.g. /http-bind for http://domain.lt:7070/http-bind). + * @param xmppDomain the XMPP service name + * (e.g. domain.lt for the user alice@domain.lt) + */ + public BOSHConnection(boolean https, String host, int port, String filePath, String xmppDomain) { + super(new BOSHConfiguration(https, host, port, filePath, xmppDomain)); + this.config = (BOSHConfiguration) getConfiguration(); + } + + /** + * Create a HTTP Binding connection to a XMPP server. + * + * @param config The configuration which is used for this connection. + */ + public BOSHConnection(BOSHConfiguration config) { + super(config); + this.config = config; + } + + public void connect() throws XMPPException { + if (connected) { + throw new IllegalStateException("Already connected to a server."); + } + done = false; + try { + // Ensure a clean starting state + if (client != null) { + client.close(); + client = null; + } + saslAuthentication.init(); + sessionID = null; + authID = null; + + // Initialize BOSH client + BOSHClientConfig.Builder cfgBuilder = BOSHClientConfig.Builder + .create(config.getURI(), config.getServiceName()); + if (config.isProxyEnabled()) { + cfgBuilder.setProxy(config.getProxyAddress(), config.getProxyPort()); + } + client = BOSHClient.create(cfgBuilder.build()); + + // Create an executor to deliver incoming packets to listeners. + // We'll use a single thread with an unbounded queue. + listenerExecutor = Executors + .newSingleThreadExecutor(new ThreadFactory() { + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, + "Smack Listener Processor (" + + connectionCounterValue + ")"); + thread.setDaemon(true); + return thread; + } + }); + client.addBOSHClientConnListener(new BOSHConnectionListener(this)); + client.addBOSHClientResponseListener(new BOSHPacketReader(this)); + + // Initialize the debugger + if (config.isDebuggerEnabled()) { + initDebugger(); + if (isFirstInitialization) { + if (debugger.getReaderListener() != null) { + addPacketListener(debugger.getReaderListener(), null); + } + if (debugger.getWriterListener() != null) { + addPacketSendingListener(debugger.getWriterListener(), null); + } + } + } + + // Send the session creation request + client.send(ComposableBody.builder() + .setNamespaceDefinition("xmpp", XMPP_BOSH_NS) + .setAttribute(BodyQName.createWithPrefix(XMPP_BOSH_NS, "version", "xmpp"), "1.0") + .build()); + } catch (Exception e) { + throw new XMPPException("Can't connect to " + getServiceName(), e); + } + + // Wait for the response from the server + synchronized (this) { + long endTime = System.currentTimeMillis() + + SmackConfiguration.getPacketReplyTimeout() * 6; + while ((!connected) && (System.currentTimeMillis() < endTime)) { + try { + wait(Math.abs(endTime - System.currentTimeMillis())); + } + catch (InterruptedException e) {} + } + } + + // If there is no feedback, throw an remote server timeout error + if (!connected && !done) { + done = true; + String errorMessage = "Timeout reached for the connection to " + + getHost() + ":" + getPort() + "."; + throw new XMPPException( + errorMessage, + new XMPPError(XMPPError.Condition.remote_server_timeout, errorMessage)); + } + } + + public String getConnectionID() { + if (!connected) { + return null; + } else if (authID != null) { + return authID; + } else { + return sessionID; + } + } + + public Roster getRoster() { + if (roster == null) { + return null; + } + if (!config.isRosterLoadedAtLogin()) { + roster.reload(); + } + // If this is the first time the user has asked for the roster after calling + // login, we want to wait for the server to send back the user's roster. + // This behavior shields API users from having to worry about the fact that + // roster operations are asynchronous, although they'll still have to listen + // for changes to the roster. Note: because of this waiting logic, internal + // Smack code should be wary about calling the getRoster method, and may + // need to access the roster object directly. + if (!roster.rosterInitialized) { + try { + synchronized (roster) { + long waitTime = SmackConfiguration.getPacketReplyTimeout(); + long start = System.currentTimeMillis(); + while (!roster.rosterInitialized) { + if (waitTime <= 0) { + break; + } + roster.wait(waitTime); + long now = System.currentTimeMillis(); + waitTime -= now - start; + start = now; + } + } + } catch (InterruptedException ie) { + // Ignore. + } + } + return roster; + } + + public String getUser() { + return user; + } + + public boolean isAnonymous() { + return anonymous; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public boolean isConnected() { + return connected; + } + + public boolean isSecureConnection() { + // TODO: Implement SSL usage + return false; + } + + public boolean isUsingCompression() { + // TODO: Implement compression + return false; + } + + public void login(String username, String password, String resource) + throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + // Do partial version of nameprep on the username. + username = username.toLowerCase().trim(); + + String response; + if (config.isSASLAuthenticationEnabled() + && saslAuthentication.hasNonAnonymousAuthentication()) { + // Authenticate using SASL + if (password != null) { + response = saslAuthentication.authenticate(username, password, resource); + } else { + response = saslAuthentication.authenticate(username, resource, config.getCallbackHandler()); + } + } else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticate(username, password, resource); + } + + // Set the user. + if (response != null) { + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + } else { + this.user = username + "@" + getServiceName(); + if (resource != null) { + this.user += "/" + resource; + } + } + + // Create the roster if it is not a reconnection. + if (this.roster == null) { + if(this.rosterStorage==null){ + this.roster = new Roster(this); + } + else{ + this.roster = new Roster(this,rosterStorage); + } + } + if (config.isRosterLoadedAtLogin()) { + this.roster.reload(); + } + + // Set presence to online. + if (config.isSendPresence()) { + sendPacket(new Presence(Presence.Type.available)); + } + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = false; + + // Stores the autentication for future reconnection + config.setLoginInfo(username, password, resource); + + // If debugging is enabled, change the the debug window title to include + // the + // name we are now logged-in as.l + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + public void loginAnonymously() throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + + String response; + if (config.isSASLAuthenticationEnabled() && + saslAuthentication.hasAnonymousAuthentication()) { + response = saslAuthentication.authenticateAnonymously(); + } + else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticateAnonymously(); + } + + // Set the user value. + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + + // Anonymous users can't have a roster. + roster = null; + + // Set presence to online. + if (config.isSendPresence()) { + sendPacket(new Presence(Presence.Type.available)); + } + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = true; + + // If debugging is enabled, change the the debug window title to include the + // name we are now logged-in as. + // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger + // will be null + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + public void sendPacket(Packet packet) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (packet == null) { + throw new NullPointerException("Packet is null."); + } + if (!done) { + // Invoke interceptors for the new packet that is about to be sent. + // Interceptors + // may modify the content of the packet. + firePacketInterceptors(packet); + + try { + send(ComposableBody.builder().setPayloadXML(packet.toXML()) + .build()); + } catch (BOSHException e) { + e.printStackTrace(); + return; + } + + // Process packet writer listeners. Note that we're using the + // sending + // thread so it's expected that listeners are fast. + firePacketSendingListeners(packet); + } + } + + public void disconnect(Presence unavailablePresence) { + if (!connected) { + return; + } + shutdown(unavailablePresence); + + // Cleanup + if (roster != null) { + roster.cleanup(); + roster = null; + } + sendListeners.clear(); + recvListeners.clear(); + collectors.clear(); + interceptors.clear(); + + // Reset the connection flags + wasAuthenticated = false; + isFirstInitialization = true; + + // Notify connection listeners of the connection closing if done hasn't already been set. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.connectionClosed(); + } + catch (Exception e) { + // Catch and print any exception so we can recover + // from a faulty listener and finish the shutdown process + e.printStackTrace(); + } + } + } + + /** + * Closes the connection by setting presence to unavailable and closing the + * HTTP client. The shutdown logic will be used during a planned disconnection or when + * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's + * BOSH packet reader and {@link Roster} will not be removed; thus + * connection's state is kept. + * + * @param unavailablePresence the presence packet to send during shutdown. + */ + protected void shutdown(Presence unavailablePresence) { + setWasAuthenticated(authenticated); + authID = null; + sessionID = null; + done = true; + authenticated = false; + connected = false; + isFirstInitialization = false; + + try { + client.disconnect(ComposableBody.builder() + .setNamespaceDefinition("xmpp", XMPP_BOSH_NS) + .setPayloadXML(unavailablePresence.toXML()) + .build()); + // Wait 150 ms for processes to clean-up, then shutdown. + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + // Close down the readers and writers. + if (readerPipe != null) { + try { + readerPipe.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + // Shut down the listener executor. + if (listenerExecutor != null) { + listenerExecutor.shutdown(); + } + readerConsumer = null; + } + + /** + * Sets whether the connection has already logged in the server. + * + * @param wasAuthenticated true if the connection has already been authenticated. + */ + private void setWasAuthenticated(boolean wasAuthenticated) { + if (!this.wasAuthenticated) { + this.wasAuthenticated = wasAuthenticated; + } + } + + /** + * Send a HTTP request to the connection manager with the provided body element. + * + * @param body the body which will be sent. + */ + protected void send(ComposableBody body) throws BOSHException { + if (!connected) { + throw new IllegalStateException("Not connected to a server!"); + } + if (body == null) { + throw new NullPointerException("Body mustn't be null!"); + } + if (sessionID != null) { + body = body.rebuild().setAttribute( + BodyQName.create(BOSH_URI, "sid"), sessionID).build(); + } + client.send(body); + } + + /** + * Processes a packet after it's been fully parsed by looping through the + * installed packet collectors and listeners and letting them examine the + * packet to see if they are a match with the filter. + * + * @param packet the packet to process. + */ + protected void processPacket(Packet packet) { + if (packet == null) { + return; + } + + // Loop through all collectors and notify the appropriate ones. + for (PacketCollector collector : getPacketCollectors()) { + collector.processPacket(packet); + } + + // Deliver the incoming packet to listeners. + listenerExecutor.submit(new ListenerNotification(packet)); + } + + /** + * Initialize the SmackDebugger which allows to log and debug XML traffic. + */ + protected void initDebugger() { + // TODO: Maybe we want to extend the SmackDebugger for simplification + // and a performance boost. + + // Initialize a empty writer which discards all data. + writer = new Writer() { + public void write(char[] cbuf, int off, int len) { /* ignore */} + public void close() { /* ignore */ } + public void flush() { /* ignore */ } + }; + + // Initialize a pipe for received raw data. + try { + readerPipe = new PipedWriter(); + reader = new PipedReader(readerPipe); + } + catch (IOException e) { + // Ignore + } + + // Call the method from the parent class which initializes the debugger. + super.initDebugger(); + + // Add listeners for the received and sent raw data. + client.addBOSHClientResponseListener(new BOSHClientResponseListener() { + public void responseReceived(BOSHMessageEvent event) { + if (event.getBody() != null) { + try { + readerPipe.write(event.getBody().toXML()); + readerPipe.flush(); + } catch (Exception e) { + // Ignore + } + } + } + }); + client.addBOSHClientRequestListener(new BOSHClientRequestListener() { + public void requestSent(BOSHMessageEvent event) { + if (event.getBody() != null) { + try { + writer.write(event.getBody().toXML()); + } catch (Exception e) { + // Ignore + } + } + } + }); + + // Create and start a thread which discards all read data. + readerConsumer = new Thread() { + private Thread thread = this; + private int bufferLength = 1024; + + public void run() { + try { + char[] cbuf = new char[bufferLength]; + while (readerConsumer == thread && !done) { + reader.read(cbuf, 0, bufferLength); + } + } catch (IOException e) { + // Ignore + } + } + }; + readerConsumer.setDaemon(true); + readerConsumer.start(); + } + + /** + * Sends out a notification that there was an error with the connection + * and closes the connection. + * + * @param e the exception that causes the connection close event. + */ + protected void notifyConnectionError(Exception e) { + // Closes the connection temporary. A reconnection is possible + shutdown(new Presence(Presence.Type.unavailable)); + // Print the stack trace to help catch the problem + e.printStackTrace(); + // Notify connection listeners of the error. + for (ConnectionListener listener : getConnectionListeners()) { + try { + listener.connectionClosedOnError(e); + } + catch (Exception e2) { + // Catch and print any exception so we can recover + // from a faulty listener + e2.printStackTrace(); + } + } + } + + + /** + * A listener class which listen for a successfully established connection + * and connection errors and notifies the BOSHConnection. + * + * @author Guenther Niess + */ + private class BOSHConnectionListener implements BOSHClientConnListener { + + private final BOSHConnection connection; + + public BOSHConnectionListener(BOSHConnection connection) { + this.connection = connection; + } + + /** + * Notify the BOSHConnection about connection state changes. + * Process the connection listeners and try to login if the + * connection was formerly authenticated and is now reconnected. + */ + public void connectionEvent(BOSHClientConnEvent connEvent) { + try { + if (connEvent.isConnected()) { + connected = true; + if (isFirstInitialization) { + isFirstInitialization = false; + for (ConnectionCreationListener listener : getConnectionCreationListeners()) { + listener.connectionCreated(connection); + } + } + else { + try { + if (wasAuthenticated) { + connection.login( + config.getUsername(), + config.getPassword(), + config.getResource()); + } + for (ConnectionListener listener : getConnectionListeners()) { + listener.reconnectionSuccessful(); + } + } + catch (XMPPException e) { + for (ConnectionListener listener : getConnectionListeners()) { + listener.reconnectionFailed(e); + } + } + } + } + else { + if (connEvent.isError()) { + try { + connEvent.getCause(); + } + catch (Exception e) { + notifyConnectionError(e); + } + } + connected = false; + } + } + finally { + synchronized (connection) { + connection.notifyAll(); + } + } + } + } + + /** + * This class notifies all listeners that a packet was received. + */ + private class ListenerNotification implements Runnable { + + private Packet packet; + + public ListenerNotification(Packet packet) { + this.packet = packet; + } + + public void run() { + for (ListenerWrapper listenerWrapper : recvListeners.values()) { + listenerWrapper.notifyListener(packet); + } + } + } + + @Override + public void setRosterStorage(RosterStorage storage) + throws IllegalStateException { + if(this.roster!=null){ + throw new IllegalStateException("Roster is already initialized"); + } + this.rosterStorage = storage; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java new file mode 100644 index 0000000..c86d756 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/BOSHPacketReader.java @@ -0,0 +1,169 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2009 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.io.StringReader; + +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; +import org.jivesoftware.smack.sasl.SASLMechanism.Failure; +import org.jivesoftware.smack.sasl.SASLMechanism.Success; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; + +import com.kenai.jbosh.AbstractBody; +import com.kenai.jbosh.BOSHClientResponseListener; +import com.kenai.jbosh.BOSHMessageEvent; +import com.kenai.jbosh.BodyQName; +import com.kenai.jbosh.ComposableBody; + +/** + * Listens for XML traffic from the BOSH connection manager and parses it into + * packet objects. + * + * @author Guenther Niess + */ +public class BOSHPacketReader implements BOSHClientResponseListener { + + private BOSHConnection connection; + + /** + * Create a packet reader which listen on a BOSHConnection for received + * HTTP responses, parse the packets and notifies the connection. + * + * @param connection the corresponding connection for the received packets. + */ + public BOSHPacketReader(BOSHConnection connection) { + this.connection = connection; + } + + /** + * Parse the received packets and notify the corresponding connection. + * + * @param event the BOSH client response which includes the received packet. + */ + public void responseReceived(BOSHMessageEvent event) { + AbstractBody body = event.getBody(); + if (body != null) { + try { + if (connection.sessionID == null) { + connection.sessionID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "sid")); + } + if (connection.authID == null) { + connection.authID = body.getAttribute(BodyQName.create(BOSHConnection.BOSH_URI, "authid")); + } + final XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, + true); + parser.setInput(new StringReader(body.toXML())); + int eventType = parser.getEventType(); + do { + eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("body")) { + // ignore the container root element + } else if (parser.getName().equals("message")) { + connection.processPacket(PacketParserUtils.parseMessage(parser)); + } else if (parser.getName().equals("iq")) { + connection.processPacket(PacketParserUtils.parseIQ(parser, connection)); + } else if (parser.getName().equals("presence")) { + connection.processPacket(PacketParserUtils.parsePresence(parser)); + } else if (parser.getName().equals("challenge")) { + // The server is challenging the SASL authentication + // made by the client + final String challengeData = parser.nextText(); + connection.getSASLAuthentication() + .challengeReceived(challengeData); + connection.processPacket(new Challenge( + challengeData)); + } else if (parser.getName().equals("success")) { + connection.send(ComposableBody.builder() + .setNamespaceDefinition("xmpp", BOSHConnection.XMPP_BOSH_NS) + .setAttribute( + BodyQName.createWithPrefix(BOSHConnection.XMPP_BOSH_NS, "restart", "xmpp"), + "true") + .setAttribute( + BodyQName.create(BOSHConnection.BOSH_URI, "to"), + connection.getServiceName()) + .build()); + connection.getSASLAuthentication().authenticated(); + connection.processPacket(new Success(parser.nextText())); + } else if (parser.getName().equals("features")) { + parseFeatures(parser); + } else if (parser.getName().equals("failure")) { + if ("urn:ietf:params:xml:ns:xmpp-sasl".equals(parser.getNamespace(null))) { + final Failure failure = PacketParserUtils.parseSASLFailure(parser); + connection.getSASLAuthentication().authenticationFailed(); + connection.processPacket(failure); + } + } else if (parser.getName().equals("error")) { + throw new XMPPException(PacketParserUtils.parseStreamError(parser)); + } + } + } while (eventType != XmlPullParser.END_DOCUMENT); + } + catch (Exception e) { + if (connection.isConnected()) { + connection.notifyConnectionError(e); + } + } + } + } + + /** + * Parse and setup the XML stream features. + * + * @param parser the XML parser, positioned at the start of a message packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + private void parseFeatures(XmlPullParser parser) throws Exception { + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("mechanisms")) { + // The server is reporting available SASL mechanisms. Store + // this information + // which will be used later while logging (i.e. + // authenticating) into + // the server + connection.getSASLAuthentication().setAvailableSASLMethods( + PacketParserUtils.parseMechanisms(parser)); + } else if (parser.getName().equals("bind")) { + // The server requires the client to bind a resource to the + // stream + connection.getSASLAuthentication().bindingRequired(); + } else if (parser.getName().equals("session")) { + // The server supports sessions + connection.getSASLAuthentication().sessionsSupported(); + } else if (parser.getName().equals("register")) { + connection.getAccountManager().setSupportsAccountCreation( + true); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("features")) { + done = true; + } + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java new file mode 100644 index 0000000..5ee5fa9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Chat.java @@ -0,0 +1,190 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Message; + +import java.util.Set; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * A chat is a series of messages sent between two users. Each chat has a unique + * thread ID, which is used to track which messages are part of a particular + * conversation. Some messages are sent without a thread ID, and some clients + * don't send thread IDs at all. Therefore, if a message without a thread ID + * arrives it is routed to the most recently created Chat with the message + * sender. + * + * @author Matt Tucker + */ +public class Chat { + + private ChatManager chatManager; + private String threadID; + private String participant; + private final Set listeners = new CopyOnWriteArraySet(); + + /** + * Creates a new chat with the specified user and thread ID. + * + * @param chatManager the chatManager the chat will use. + * @param participant the user to chat with. + * @param threadID the thread ID to use. + */ + Chat(ChatManager chatManager, String participant, String threadID) { + this.chatManager = chatManager; + this.participant = participant; + this.threadID = threadID; + } + + /** + * Returns the thread id associated with this chat, which corresponds to the + * thread field of XMPP messages. This method may return null + * if there is no thread ID is associated with this Chat. + * + * @return the thread ID of this chat. + */ + public String getThreadID() { + return threadID; + } + + /** + * Returns the name of the user the chat is with. + * + * @return the name of the user the chat is occuring with. + */ + public String getParticipant() { + return participant; + } + + /** + * Generate a message including setting recipient, type and body. + * + * @param text the body + * @return the new message + */ + public Message generateMessage(String text) { + Message message = new Message(participant, Message.Type.chat); + message.setThread(threadID); + message.setBody(text); + return message; + } + + /** + * Sends the specified text as a message to the other chat participant. + * This is a convenience method for: + * + *

+     *     Message message = chat.createMessage();
+     *     message.setBody(messageText);
+     *     chat.sendMessage(message);
+     * 
+ * + * @param text the text to send. + * @throws XMPPException if sending the message fails. + */ + public void sendMessage(String text) throws XMPPException { + chatManager.sendMessage(this, generateMessage(text)); + } + + /** + * Sends a message to the other chat participant. The thread ID, recipient, + * and message type of the message will automatically set to those of this chat. + * + * @param message the message to send. + * @throws XMPPException if an error occurs sending the message. + */ + public void sendMessage(Message message) throws XMPPException { + // Force the recipient, message type, and thread ID since the user elected + // to send the message through this chat object. + message.setTo(participant); + message.setType(Message.Type.chat); + message.setThread(threadID); + chatManager.sendMessage(this, message); + } + + /** + * Adds a packet listener that will be notified of any new messages in the + * chat. + * + * @param listener a packet listener. + */ + public void addMessageListener(MessageListener listener) { + if(listener == null) { + return; + } + // TODO these references should be weak. + listeners.add(listener); + } + + public void removeMessageListener(MessageListener listener) { + listeners.remove(listener); + } + + /** + * Returns an unmodifiable collection of all of the listeners registered with this chat. + * + * @return an unmodifiable collection of all of the listeners registered with this chat. + */ + public Collection getListeners() { + return Collections.unmodifiableCollection(listeners); + } + + /** + * Creates a {@link org.jivesoftware.smack.PacketCollector} which will accumulate the Messages + * for this chat. Always cancel PacketCollectors when finished with them as they will accumulate + * messages indefinitely. + * + * @return the PacketCollector which returns Messages for this chat. + */ + public PacketCollector createCollector() { + return chatManager.createPacketCollector(this); + } + + /** + * Delivers a message directly to this chat, which will add the message + * to the collector and deliver it to all listeners registered with the + * Chat. This is used by the Connection class to deliver messages + * without a thread ID. + * + * @param message the message. + */ + void deliver(Message message) { + // Because the collector and listeners are expecting a thread ID with + // a specific value, set the thread ID on the message even though it + // probably never had one. + message.setThread(threadID); + + for (MessageListener listener : listeners) { + listener.processMessage(this, message); + } + } + + + @Override + public boolean equals(Object obj) { + return obj instanceof Chat + && threadID.equals(((Chat)obj).getThreadID()) + && participant.equals(((Chat)obj).getParticipant()); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java new file mode 100644 index 0000000..22dc3f9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManager.java @@ -0,0 +1,284 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.ThreadFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.collections.ReferenceMap; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * The chat manager keeps track of references to all current chats. It will not hold any references + * in memory on its own so it is neccesary to keep a reference to the chat object itself. To be + * made aware of new chats, register a listener by calling {@link #addChatListener(ChatManagerListener)}. + * + * @author Alexander Wenckus + */ +public class ChatManager { + + /** + * Returns the next unique id. Each id made up of a short alphanumeric + * prefix along with a unique numeric value. + * + * @return the next id. + */ + private static synchronized String nextID() { + return prefix + Long.toString(id++); + } + + /** + * A prefix helps to make sure that ID's are unique across mutliple instances. + */ + private static String prefix = StringUtils.randomString(5); + + /** + * Keeps track of the current increment, which is appended to the prefix to + * forum a unique ID. + */ + private static long id = 0; + + /** + * Maps thread ID to chat. + */ + private Map threadChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, + ReferenceMap.WEAK)); + + /** + * Maps jids to chats + */ + private Map jidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, + ReferenceMap.WEAK)); + + /** + * Maps base jids to chats + */ + private Map baseJidChats = Collections.synchronizedMap(new ReferenceMap(ReferenceMap.HARD, + ReferenceMap.WEAK)); + + private Set chatManagerListeners + = new CopyOnWriteArraySet(); + + private Map interceptors + = new WeakHashMap(); + + private Connection connection; + + ChatManager(Connection connection) { + this.connection = connection; + + PacketFilter filter = new PacketFilter() { + public boolean accept(Packet packet) { + if (!(packet instanceof Message)) { + return false; + } + Message.Type messageType = ((Message) packet).getType(); + return messageType != Message.Type.groupchat && + messageType != Message.Type.headline; + } + }; + // Add a listener for all message packets so that we can deliver errant + // messages to the best Chat instance available. + connection.addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + Chat chat; + if (message.getThread() == null) { + chat = getUserChat(message.getFrom()); + } + else { + chat = getThreadChat(message.getThread()); + if (chat == null) { + // Try to locate the chat based on the sender of the message + chat = getUserChat(message.getFrom()); + } + } + + if(chat == null) { + chat = createChat(message); + } + deliverMessage(chat, message); + } + }, filter); + } + + /** + * Creates a new chat and returns it. + * + * @param userJID the user this chat is with. + * @param listener the listener which will listen for new messages from this chat. + * @return the created chat. + */ + public Chat createChat(String userJID, MessageListener listener) { + String threadID; + do { + threadID = nextID(); + } while (threadChats.get(threadID) != null); + + return createChat(userJID, threadID, listener); + } + + /** + * Creates a new chat using the specified thread ID, then returns it. + * + * @param userJID the jid of the user this chat is with + * @param thread the thread of the created chat. + * @param listener the listener to add to the chat + * @return the created chat. + */ + public Chat createChat(String userJID, String thread, MessageListener listener) { + if(thread == null) { + thread = nextID(); + } + Chat chat = threadChats.get(thread); + if(chat != null) { + throw new IllegalArgumentException("ThreadID is already used"); + } + chat = createChat(userJID, thread, true); + chat.addMessageListener(listener); + return chat; + } + + private Chat createChat(String userJID, String threadID, boolean createdLocally) { + Chat chat = new Chat(this, userJID, threadID); + threadChats.put(threadID, chat); + jidChats.put(userJID, chat); + baseJidChats.put(StringUtils.parseBareAddress(userJID), chat); + + for(ChatManagerListener listener : chatManagerListeners) { + listener.chatCreated(chat, createdLocally); + } + + return chat; + } + + private Chat createChat(Message message) { + String threadID = message.getThread(); + if(threadID == null) { + threadID = nextID(); + } + String userJID = message.getFrom(); + + return createChat(userJID, threadID, false); + } + + /** + * Try to get a matching chat for the given user JID. Try the full + * JID map first, the try to match on the base JID if no match is + * found. + * + * @param userJID + * @return + */ + private Chat getUserChat(String userJID) { + Chat match = jidChats.get(userJID); + + if (match == null) { + match = baseJidChats.get(StringUtils.parseBareAddress(userJID)); + } + return match; + } + + public Chat getThreadChat(String thread) { + return threadChats.get(thread); + } + + /** + * Register a new listener with the ChatManager to recieve events related to chats. + * + * @param listener the listener. + */ + public void addChatListener(ChatManagerListener listener) { + chatManagerListeners.add(listener); + } + + /** + * Removes a listener, it will no longer be notified of new events related to chats. + * + * @param listener the listener that is being removed + */ + public void removeChatListener(ChatManagerListener listener) { + chatManagerListeners.remove(listener); + } + + /** + * Returns an unmodifiable collection of all chat listeners currently registered with this + * manager. + * + * @return an unmodifiable collection of all chat listeners currently registered with this + * manager. + */ + public Collection getChatListeners() { + return Collections.unmodifiableCollection(chatManagerListeners); + } + + private void deliverMessage(Chat chat, Message message) { + // Here we will run any interceptors + chat.deliver(message); + } + + void sendMessage(Chat chat, Message message) { + for(Map.Entry interceptor : interceptors.entrySet()) { + PacketFilter filter = interceptor.getValue(); + if(filter != null && filter.accept(message)) { + interceptor.getKey().interceptPacket(message); + } + } + // Ensure that messages being sent have a proper FROM value + if (message.getFrom() == null) { + message.setFrom(connection.getUser()); + } + connection.sendPacket(message); + } + + PacketCollector createPacketCollector(Chat chat) { + return connection.createPacketCollector(new AndFilter(new ThreadFilter(chat.getThreadID()), + new FromContainsFilter(chat.getParticipant()))); + } + + /** + * Adds an interceptor which intercepts any messages sent through chats. + * + * @param packetInterceptor the interceptor. + */ + public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor) { + addOutgoingMessageInterceptor(packetInterceptor, null); + } + + public void addOutgoingMessageInterceptor(PacketInterceptor packetInterceptor, PacketFilter filter) { + if (packetInterceptor != null) { + interceptors.put(packetInterceptor, filter); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java new file mode 100644 index 0000000..d7d5ab7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ChatManagerListener.java @@ -0,0 +1,37 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * A listener for chat related events. + * + * @author Alexander Wenckus + */ +public interface ChatManagerListener { + + /** + * Event fired when a new chat is created. + * + * @param chat the chat that was created. + * @param createdLocally true if the chat was created by the local user and false if it wasn't. + */ + void chatCreated(Chat chat, boolean createdLocally); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java new file mode 100644 index 0000000..4357d65 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java @@ -0,0 +1,891 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2009 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jivesoftware.smack.debugger.SmackDebugger; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; + +/** + * The abstract Connection class provides an interface for connections to a + * XMPP server and implements shared methods which are used by the + * different types of connections (e.g. XMPPConnection or BoshConnection). + * + * To create a connection to a XMPP server a simple usage of this API might + * look like the following: + *
+ * // Create a connection to the igniterealtime.org XMPP server.
+ * Connection con = new XMPPConnection("igniterealtime.org");
+ * // Connect to the server
+ * con.connect();
+ * // Most servers require you to login before performing other tasks.
+ * con.login("jsmith", "mypass");
+ * // Start a new conversation with John Doe and send him a message.
+ * Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
+ * 

+ * public void processMessage(Chat chat, Message message) { + * // Print out any messages we get back to standard out. + * System.out.println("Received message: " + message); + * } + * }); + * chat.sendMessage("Howdy!"); + * // Disconnect from the server + * con.disconnect(); + *

+ *

+ * Connections can be reused between connections. This means that an Connection + * may be connected, disconnected and then connected again. Listeners of the Connection + * will be retained accross connections.

+ *

+ * If a connected Connection gets disconnected abruptly then it will try to reconnect + * again. To stop the reconnection process, use {@link #disconnect()}. Once stopped + * you can use {@link #connect()} to manually connect to the server. + * + * @see XMPPConnection + * @author Matt Tucker + * @author Guenther Niess + */ +public abstract class Connection { + + /** + * Counter to uniquely identify connections that are created. + */ + private final static AtomicInteger connectionCounter = new AtomicInteger(0); + + /** + * A set of listeners which will be invoked if a new connection is created. + */ + private final static Set connectionEstablishedListeners = + new CopyOnWriteArraySet(); + + /** + * Value that indicates whether debugging is enabled. When enabled, a debug + * window will apear for each new connection that will contain the following + * information:

    + *
  • Client Traffic -- raw XML traffic generated by Smack and sent to the server. + *
  • Server Traffic -- raw XML traffic sent by the server to the client. + *
  • Interpreted Packets -- shows XML packets from the server as parsed by Smack. + *
+ *

+ * Debugging can be enabled by setting this field to true, or by setting the Java system + * property smack.debugEnabled to true. The system property can be set on the + * command line such as "java SomeApp -Dsmack.debugEnabled=true". + */ + public static boolean DEBUG_ENABLED = false; + + static { + // Use try block since we may not have permission to get a system + // property (for example, when an applet). + try { + DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); + } + catch (Exception e) { + // Ignore. + } + // Ensure the SmackConfiguration class is loaded by calling a method in it. + SmackConfiguration.getVersion(); + } + + /** + * A collection of ConnectionListeners which listen for connection closing + * and reconnection events. + */ + protected final Collection connectionListeners = + new CopyOnWriteArrayList(); + + /** + * A collection of PacketCollectors which collects packets for a specified filter + * and perform blocking and polling operations on the result queue. + */ + protected final Collection collectors = new ConcurrentLinkedQueue(); + + /** + * List of PacketListeners that will be notified when a new packet was received. + */ + protected final Map recvListeners = + new ConcurrentHashMap(); + + /** + * List of PacketListeners that will be notified when a new packet was sent. + */ + protected final Map sendListeners = + new ConcurrentHashMap(); + + /** + * List of PacketInterceptors that will be notified when a new packet is about to be + * sent to the server. These interceptors may modify the packet before it is being + * actually sent to the server. + */ + protected final Map interceptors = + new ConcurrentHashMap(); + + /** + * The AccountManager allows creation and management of accounts on an XMPP server. + */ + private AccountManager accountManager = null; + + /** + * The ChatManager keeps track of references to all current chats. + */ + protected ChatManager chatManager = null; + + /** + * The SmackDebugger allows to log and debug XML traffic. + */ + protected SmackDebugger debugger = null; + + /** + * The Reader which is used for the {@see debugger}. + */ + protected Reader reader; + + /** + * The Writer which is used for the {@see debugger}. + */ + protected Writer writer; + + /** + * The permanent storage for the roster + */ + protected RosterStorage rosterStorage; + + + /** + * The SASLAuthentication manager that is responsible for authenticating with the server. + */ + protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); + + /** + * A number to uniquely identify connections that are created. This is distinct from the + * connection ID, which is a value sent by the server once a connection is made. + */ + protected final int connectionCounterValue = connectionCounter.getAndIncrement(); + + /** + * Holds the initial configuration used while creating the connection. + */ + protected final ConnectionConfiguration config; + + protected String connectionID = null; + + /** + * Create a new Connection to a XMPP server. + * + * @param configuration The configuration which is used to establish the connection. + */ + protected Connection(ConnectionConfiguration configuration) { + config = configuration; + } + + /** + * Returns the configuration used to connect to the server. + * + * @return the configuration used to connect to the server. + */ + protected ConnectionConfiguration getConfiguration() { + return config; + } + + /** + * Returns the name of the service provided by the XMPP server for this connection. + * This is also called XMPP domain of the connected server. After + * authenticating with the server the returned value may be different. + * + * @return the name of the service provided by the XMPP server. + */ + public String getServiceName() { + return config.getServiceName(); + } + + /** + * Returns the host name of the server where the XMPP server is running. This would be the + * IP address of the server or a name that may be resolved by a DNS server. + * + * @return the host name of the server where the XMPP server is running. + */ + public String getHost() { + return config.getHost(); + } + + public String getCapsNode(){ + return config.getCapsNode(); + } + + /** + * Returns the port number of the XMPP server for this connection. The default port + * for normal connections is 5222. The default port for SSL connections is 5223. + * + * @return the port number of the XMPP server. + */ + public int getPort() { + return config.getPort(); + } + + /** + * Returns the full XMPP address of the user that is logged in to the connection or + * null if not logged in yet. An XMPP address is in the form + * username@server/resource. + * + * @return the full XMPP address of the user logged in. + */ + public abstract String getUser(); + + /** + * Returns the connection ID for this connection, which is the value set by the server + * when opening a XMPP stream. If the server does not set a connection ID, this value + * will be null. This value will be null if not connected to the server. + * + * @return the ID of this connection returned from the XMPP server or null if + * not connected to the server. + */ + public abstract String getConnectionID(); + + /** + * Returns true if currently connected to the XMPP server. + * + * @return true if connected. + */ + public abstract boolean isConnected(); + + /** + * Returns true if currently authenticated by successfully calling the login method. + * + * @return true if authenticated. + */ + public abstract boolean isAuthenticated(); + + /** + * Returns true if currently authenticated anonymously. + * + * @return true if authenticated anonymously. + */ + public abstract boolean isAnonymous(); + + /** + * Returns true if the connection to the server has successfully negotiated encryption. + * + * @return true if a secure connection to the server. + */ + public abstract boolean isSecureConnection(); + + /** + * Returns if the reconnection mechanism is allowed to be used. By default + * reconnection is allowed. + * + * @return true if the reconnection mechanism is allowed to be used. + */ + protected boolean isReconnectionAllowed() { + return config.isReconnectionAllowed(); + } + + /** + * Returns true if network traffic is being compressed. When using stream compression network + * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow + * speed network connection. However, the server will need to use more CPU time in order to + * un/compress network data so under high load the server performance might be affected. + * + * @return true if network traffic is being compressed. + */ + public abstract boolean isUsingCompression(); + + /** + * Establishes a connection to the XMPP server and performs an automatic login + * only if the previous connection state was logged (authenticated). It basically + * creates and maintains a connection to the server.

+ *

+ * Listeners will be preserved from a previous connection if the reconnection + * occurs after an abrupt termination. + * + * @throws XMPPException if an error occurs while trying to establish the connection. + */ + public abstract void connect() throws XMPPException; + + /** + * Logs in to the server using the strongest authentication mode supported by + * the server, then sets presence to available. If the server supports SASL authentication + * then the user will be authenticated using SASL if not Non-SASL authentication will + * be tried. If more than five seconds (default timeout) elapses in each step of the + * authentication process without a response from the server, or if an error occurs, a + * XMPPException will be thrown.

+ * + * Before logging in (i.e. authenticate) to the server the connection must be connected. + * + * It is possible to log in without sending an initial available presence by using + * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is + * not interested in loading its roster upon login then use + * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. + * Finally, if you want to not pass a password and instead use a more advanced mechanism + * while using SASL then you may be interested in using + * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. + * For more advanced login settings see {@link ConnectionConfiguration}. + * + * @param username the username. + * @param password the password or null if using a CallbackHandler. + * @throws XMPPException if an error occurs. + */ + public void login(String username, String password) throws XMPPException { + login(username, password, "Smack"); + } + + /** + * Logs in to the server using the strongest authentication mode supported by + * the server, then sets presence to available. If the server supports SASL authentication + * then the user will be authenticated using SASL if not Non-SASL authentication will + * be tried. If more than five seconds (default timeout) elapses in each step of the + * authentication process without a response from the server, or if an error occurs, a + * XMPPException will be thrown.

+ * + * Before logging in (i.e. authenticate) to the server the connection must be connected. + * + * It is possible to log in without sending an initial available presence by using + * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is + * not interested in loading its roster upon login then use + * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. + * Finally, if you want to not pass a password and instead use a more advanced mechanism + * while using SASL then you may be interested in using + * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. + * For more advanced login settings see {@link ConnectionConfiguration}. + * + * @param username the username. + * @param password the password or null if using a CallbackHandler. + * @param resource the resource. + * @throws XMPPException if an error occurs. + * @throws IllegalStateException if not connected to the server, or already logged in + * to the serrver. + */ + public abstract void login(String username, String password, String resource) throws XMPPException; + + /** + * Logs in to the server anonymously. Very few servers are configured to support anonymous + * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login + * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or + * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). + * + * @throws XMPPException if an error occurs or anonymous logins are not supported by the server. + * @throws IllegalStateException if not connected to the server, or already logged in + * to the serrver. + */ + public abstract void loginAnonymously() throws XMPPException; + + /** + * Sends the specified packet to the server. + * + * @param packet the packet to send. + */ + public abstract void sendPacket(Packet packet); + + /** + * Returns an account manager instance for this connection. + * + * @return an account manager for this connection. + */ + public AccountManager getAccountManager() { + if (accountManager == null) { + accountManager = new AccountManager(this); + } + return accountManager; + } + + /** + * Returns a chat manager instance for this connection. The ChatManager manages all incoming and + * outgoing chats on the current connection. + * + * @return a chat manager instance for this connection. + */ + public synchronized ChatManager getChatManager() { + if (this.chatManager == null) { + this.chatManager = new ChatManager(this); + } + return this.chatManager; + } + + /** + * Returns the roster for the user. + *

+ * This method will never return null, instead if the user has not yet logged into + * the server or is logged in anonymously all modifying methods of the returned roster object + * like {@link Roster#createEntry(String, String, String[])}, + * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing + * {@link RosterListener}s will throw an IllegalStateException. + * + * @return the user's roster. + */ + public abstract Roster getRoster(); + + /** + * Set the store for the roster of this connection. If you set the roster storage + * of a connection you enable support for XEP-0237 (RosterVersioning) + * @param store the store used for roster versioning + * @throws IllegalStateException if you add a roster store when roster is initializied + */ + public abstract void setRosterStorage(RosterStorage storage) throws IllegalStateException; + + /** + * Returns the SASLAuthentication manager that is responsible for authenticating with + * the server. + * + * @return the SASLAuthentication manager that is responsible for authenticating with + * the server. + */ + public SASLAuthentication getSASLAuthentication() { + return saslAuthentication; + } + + /** + * Closes the connection by setting presence to unavailable then closing the connection to + * the XMPP server. The Connection can still be used for connecting to the server + * again.

+ *

+ * This method cleans up all resources used by the connection. Therefore, the roster, + * listeners and other stateful objects cannot be re-used by simply calling connect() + * on this connection again. This is unlike the behavior during unexpected disconnects + * (and subsequent connections). In that case, all state is preserved to allow for + * more seamless error recovery. + */ + public void disconnect() { + disconnect(new Presence(Presence.Type.unavailable)); + } + + /** + * Closes the connection. A custom unavailable presence is sent to the server, followed + * by closing the stream. The Connection can still be used for connecting to the server + * again. A custom unavilable presence is useful for communicating offline presence + * information such as "On vacation". Typically, just the status text of the presence + * packet is set with online information, but most XMPP servers will deliver the full + * presence packet with whatever data is set.

+ *

+ * This method cleans up all resources used by the connection. Therefore, the roster, + * listeners and other stateful objects cannot be re-used by simply calling connect() + * on this connection again. This is unlike the behavior during unexpected disconnects + * (and subsequent connections). In that case, all state is preserved to allow for + * more seamless error recovery. + * + * @param unavailablePresence the presence packet to send during shutdown. + */ + public abstract void disconnect(Presence unavailablePresence); + + /** + * Adds a new listener that will be notified when new Connections are created. Note + * that newly created connections will not be actually connected to the server. + * + * @param connectionCreationListener a listener interested on new connections. + */ + public static void addConnectionCreationListener( + ConnectionCreationListener connectionCreationListener) { + connectionEstablishedListeners.add(connectionCreationListener); + } + + /** + * Removes a listener that was interested in connection creation events. + * + * @param connectionCreationListener a listener interested on new connections. + */ + public static void removeConnectionCreationListener( + ConnectionCreationListener connectionCreationListener) { + connectionEstablishedListeners.remove(connectionCreationListener); + } + + /** + * Get the collection of listeners that are interested in connection creation events. + * + * @return a collection of listeners interested on new connections. + */ + protected static Collection getConnectionCreationListeners() { + return Collections.unmodifiableCollection(connectionEstablishedListeners); + } + + /** + * Adds a connection listener to this connection that will be notified when + * the connection closes or fails. The connection needs to already be connected + * or otherwise an IllegalStateException will be thrown. + * + * @param connectionListener a connection listener. + */ + public void addConnectionListener(ConnectionListener connectionListener) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (connectionListener == null) { + return; + } + if (!connectionListeners.contains(connectionListener)) { + connectionListeners.add(connectionListener); + } + } + + /** + * Removes a connection listener from this connection. + * + * @param connectionListener a connection listener. + */ + public void removeConnectionListener(ConnectionListener connectionListener) { + connectionListeners.remove(connectionListener); + } + + /** + * Get the collection of listeners that are interested in connection events. + * + * @return a collection of listeners interested on connection events. + */ + protected Collection getConnectionListeners() { + return connectionListeners; + } + + /** + * Creates a new packet collector for this connection. A packet filter determines + * which packets will be accumulated by the collector. A PacketCollector is + * more suitable to use than a {@link PacketListener} when you need to wait for + * a specific result. + * + * @param packetFilter the packet filter to use. + * @return a new packet collector. + */ + public PacketCollector createPacketCollector(PacketFilter packetFilter) { + PacketCollector collector = new PacketCollector(this, packetFilter); + // Add the collector to the list of active collectors. + collectors.add(collector); + return collector; + } + + /** + * Remove a packet collector of this connection. + * + * @param collector a packet collectors which was created for this connection. + */ + protected void removePacketCollector(PacketCollector collector) { + collectors.remove(collector); + } + + /** + * Get the collection of all packet collectors for this connection. + * + * @return a collection of packet collectors for this connection. + */ + protected Collection getPacketCollectors() { + return collectors; + } + + /** + * Registers a packet listener with this connection. A packet filter determines + * which packets will be delivered to the listener. If the same packet listener + * is added again with a different filter, only the new filter will be used. + * + * @param packetListener the packet listener to notify of new received packets. + * @param packetFilter the packet filter to use. + */ + public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { + if (packetListener == null) { + throw new NullPointerException("Packet listener is null."); + } + ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); + recvListeners.put(packetListener, wrapper); + } + + /** + * Removes a packet listener for received packets from this connection. + * + * @param packetListener the packet listener to remove. + */ + public void removePacketListener(PacketListener packetListener) { + recvListeners.remove(packetListener); + } + + /** + * Get a map of all packet listeners for received packets of this connection. + * + * @return a map of all packet listeners for received packets. + */ + protected Map getPacketListeners() { + return recvListeners; + } + + /** + * Registers a packet listener with this connection. The listener will be + * notified of every packet that this connection sends. A packet filter determines + * which packets will be delivered to the listener. Note that the thread + * that writes packets will be used to invoke the listeners. Therefore, each + * packet listener should complete all operations quickly or use a different + * thread for processing. + * + * @param packetListener the packet listener to notify of sent packets. + * @param packetFilter the packet filter to use. + */ + public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { + if (packetListener == null) { + throw new NullPointerException("Packet listener is null."); + } + ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); + sendListeners.put(packetListener, wrapper); + } + + /** + * Removes a packet listener for sending packets from this connection. + * + * @param packetListener the packet listener to remove. + */ + public void removePacketSendingListener(PacketListener packetListener) { + sendListeners.remove(packetListener); + } + + /** + * Get a map of all packet listeners for sending packets of this connection. + * + * @return a map of all packet listeners for sent packets. + */ + protected Map getPacketSendingListeners() { + return sendListeners; + } + + + /** + * Process all packet listeners for sending packets. + * + * @param packet the packet to process. + */ + protected void firePacketSendingListeners(Packet packet) { + // Notify the listeners of the new sent packet + for (ListenerWrapper listenerWrapper : sendListeners.values()) { + listenerWrapper.notifyListener(packet); + } + } + + /** + * Registers a packet interceptor with this connection. The interceptor will be + * invoked every time a packet is about to be sent by this connection. Interceptors + * may modify the packet to be sent. A packet filter determines which packets + * will be delivered to the interceptor. + * + * @param packetInterceptor the packet interceptor to notify of packets about to be sent. + * @param packetFilter the packet filter to use. + */ + public void addPacketInterceptor(PacketInterceptor packetInterceptor, + PacketFilter packetFilter) { + if (packetInterceptor == null) { + throw new NullPointerException("Packet interceptor is null."); + } + interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); + } + + /** + * Removes a packet interceptor. + * + * @param packetInterceptor the packet interceptor to remove. + */ + public void removePacketInterceptor(PacketInterceptor packetInterceptor) { + interceptors.remove(packetInterceptor); + } + + public boolean isSendPresence() { + return config.isSendPresence(); + } + + /** + * Get a map of all packet interceptors for sending packets of this connection. + * + * @return a map of all packet interceptors for sending packets. + */ + protected Map getPacketInterceptors() { + return interceptors; + } + + /** + * Process interceptors. Interceptors may modify the packet that is about to be sent. + * Since the thread that requested to send the packet will invoke all interceptors, it + * is important that interceptors perform their work as soon as possible so that the + * thread does not remain blocked for a long period. + * + * @param packet the packet that is going to be sent to the server + */ + protected void firePacketInterceptors(Packet packet) { + if (packet != null) { + for (InterceptorWrapper interceptorWrapper : interceptors.values()) { + interceptorWrapper.notifyListener(packet); + } + } + } + + /** + * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} + * by setup the system property smack.debuggerClass to the implementation. + * + * @throws IllegalStateException if the reader or writer isn't yet initialized. + * @throws IllegalArgumentException if the SmackDebugger can't be loaded. + */ + protected void initDebugger() { + if (reader == null || writer == null) { + throw new NullPointerException("Reader or writer isn't initialized."); + } + // If debugging is enabled, we open a window and write out all network traffic. + if (config.isDebuggerEnabled()) { + if (debugger == null) { + // Detect the debugger class to use. + String className = null; + // Use try block since we may not have permission to get a system + // property (for example, when an applet). + try { + className = System.getProperty("smack.debuggerClass"); + } + catch (Throwable t) { + // Ignore. + } + Class debuggerClass = null; + if (className != null) { + try { + debuggerClass = Class.forName(className); + } + catch (Exception e) { + e.printStackTrace(); + } + } + if (debuggerClass == null) { + try { + debuggerClass = + Class.forName("de.measite.smack.AndroidDebugger"); + } + catch (Exception ex) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smack.debugger.ConsoleDebugger"); + } + catch (Exception ex2) { + ex2.printStackTrace(); + } + } + } + // Create a new debugger instance. If an exception occurs then disable the debugging + // option + try { + Constructor constructor = debuggerClass + .getConstructor(Connection.class, Writer.class, Reader.class); + debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); + reader = debugger.getReader(); + writer = debugger.getWriter(); + } + catch (Exception e) { + throw new IllegalArgumentException("Can't initialize the configured debugger!", e); + } + } + else { + // Obtain new reader and writer from the existing debugger + reader = debugger.newConnectionReader(reader); + writer = debugger.newConnectionWriter(writer); + } + } + + } + + + + /** + * A wrapper class to associate a packet filter with a listener. + */ + protected static class ListenerWrapper { + + private PacketListener packetListener; + private PacketFilter packetFilter; + + /** + * Create a class which associates a packet filter with a listener. + * + * @param packetListener the packet listener. + * @param packetFilter the associated filter or null if it listen for all packets. + */ + public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { + this.packetListener = packetListener; + this.packetFilter = packetFilter; + } + + /** + * Notify and process the packet listener if the filter matches the packet. + * + * @param packet the packet which was sent or received. + */ + public void notifyListener(Packet packet) { + if (packetFilter == null || packetFilter.accept(packet)) { + packetListener.processPacket(packet); + } + } + } + + /** + * A wrapper class to associate a packet filter with an interceptor. + */ + protected static class InterceptorWrapper { + + private PacketInterceptor packetInterceptor; + private PacketFilter packetFilter; + + /** + * Create a class which associates a packet filter with an interceptor. + * + * @param packetInterceptor the interceptor. + * @param packetFilter the associated filter or null if it intercepts all packets. + */ + public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { + this.packetInterceptor = packetInterceptor; + this.packetFilter = packetFilter; + } + + public boolean equals(Object object) { + if (object == null) { + return false; + } + if (object instanceof InterceptorWrapper) { + return ((InterceptorWrapper) object).packetInterceptor + .equals(this.packetInterceptor); + } + else if (object instanceof PacketInterceptor) { + return object.equals(this.packetInterceptor); + } + return false; + } + + /** + * Notify and process the packet interceptor if the filter matches the packet. + * + * @param packet the packet which will be sent. + */ + public void notifyListener(Packet packet) { + if (packetFilter == null || packetFilter.accept(packet)) { + packetInterceptor.interceptPacket(packet); + } + } + } + + public boolean isAddFrom() { + // TODO Auto-generated method stub + return false; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig new file mode 100644 index 0000000..270ba33 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Connection.java.orig @@ -0,0 +1,891 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2009 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.jivesoftware.smack.debugger.SmackDebugger; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; + +/** + * The abstract Connection class provides an interface for connections to a + * XMPP server and implements shared methods which are used by the + * different types of connections (e.g. XMPPConnection or BoshConnection). + * + * To create a connection to a XMPP server a simple usage of this API might + * look like the following: + *

+ * // Create a connection to the igniterealtime.org XMPP server.
+ * Connection con = new XMPPConnection("igniterealtime.org");
+ * // Connect to the server
+ * con.connect();
+ * // Most servers require you to login before performing other tasks.
+ * con.login("jsmith", "mypass");
+ * // Start a new conversation with John Doe and send him a message.
+ * Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
+ * 

+ * public void processMessage(Chat chat, Message message) { + * // Print out any messages we get back to standard out. + * System.out.println("Received message: " + message); + * } + * }); + * chat.sendMessage("Howdy!"); + * // Disconnect from the server + * con.disconnect(); + *

+ *

+ * Connections can be reused between connections. This means that an Connection + * may be connected, disconnected and then connected again. Listeners of the Connection + * will be retained accross connections.

+ *

+ * If a connected Connection gets disconnected abruptly then it will try to reconnect + * again. To stop the reconnection process, use {@link #disconnect()}. Once stopped + * you can use {@link #connect()} to manually connect to the server. + * + * @see XMPPConnection + * @author Matt Tucker + * @author Guenther Niess + */ +public abstract class Connection { + + /** + * Counter to uniquely identify connections that are created. + */ + private final static AtomicInteger connectionCounter = new AtomicInteger(0); + + /** + * A set of listeners which will be invoked if a new connection is created. + */ + private final static Set connectionEstablishedListeners = + new CopyOnWriteArraySet(); + + /** + * Value that indicates whether debugging is enabled. When enabled, a debug + * window will apear for each new connection that will contain the following + * information:

    + *
  • Client Traffic -- raw XML traffic generated by Smack and sent to the server. + *
  • Server Traffic -- raw XML traffic sent by the server to the client. + *
  • Interpreted Packets -- shows XML packets from the server as parsed by Smack. + *
+ *

+ * Debugging can be enabled by setting this field to true, or by setting the Java system + * property smack.debugEnabled to true. The system property can be set on the + * command line such as "java SomeApp -Dsmack.debugEnabled=true". + */ + public static boolean DEBUG_ENABLED = false; + + static { + // Use try block since we may not have permission to get a system + // property (for example, when an applet). + try { + DEBUG_ENABLED = Boolean.getBoolean("smack.debugEnabled"); + } + catch (Exception e) { + // Ignore. + } + // Ensure the SmackConfiguration class is loaded by calling a method in it. + SmackConfiguration.getVersion(); + } + + /** + * A collection of ConnectionListeners which listen for connection closing + * and reconnection events. + */ + protected final Collection connectionListeners = + new CopyOnWriteArrayList(); + + /** + * A collection of PacketCollectors which collects packets for a specified filter + * and perform blocking and polling operations on the result queue. + */ + protected final Collection collectors = new ConcurrentLinkedQueue(); + + /** + * List of PacketListeners that will be notified when a new packet was received. + */ + protected final Map recvListeners = + new ConcurrentHashMap(); + + /** + * List of PacketListeners that will be notified when a new packet was sent. + */ + protected final Map sendListeners = + new ConcurrentHashMap(); + + /** + * List of PacketInterceptors that will be notified when a new packet is about to be + * sent to the server. These interceptors may modify the packet before it is being + * actually sent to the server. + */ + protected final Map interceptors = + new ConcurrentHashMap(); + + /** + * The AccountManager allows creation and management of accounts on an XMPP server. + */ + private AccountManager accountManager = null; + + /** + * The ChatManager keeps track of references to all current chats. + */ + protected ChatManager chatManager = null; + + /** + * The SmackDebugger allows to log and debug XML traffic. + */ + protected SmackDebugger debugger = null; + + /** + * The Reader which is used for the {@see debugger}. + */ + protected Reader reader; + + /** + * The Writer which is used for the {@see debugger}. + */ + protected Writer writer; + + /** + * The permanent storage for the roster + */ + protected RosterStorage rosterStorage; + + + /** + * The SASLAuthentication manager that is responsible for authenticating with the server. + */ + protected SASLAuthentication saslAuthentication = new SASLAuthentication(this); + + /** + * A number to uniquely identify connections that are created. This is distinct from the + * connection ID, which is a value sent by the server once a connection is made. + */ + protected final int connectionCounterValue = connectionCounter.getAndIncrement(); + + /** + * Holds the initial configuration used while creating the connection. + */ + protected final ConnectionConfiguration config; + + protected String connectionID = null; + + /** + * Create a new Connection to a XMPP server. + * + * @param configuration The configuration which is used to establish the connection. + */ + protected Connection(ConnectionConfiguration configuration) { + config = configuration; + } + + /** + * Returns the configuration used to connect to the server. + * + * @return the configuration used to connect to the server. + */ + protected ConnectionConfiguration getConfiguration() { + return config; + } + + /** + * Returns the name of the service provided by the XMPP server for this connection. + * This is also called XMPP domain of the connected server. After + * authenticating with the server the returned value may be different. + * + * @return the name of the service provided by the XMPP server. + */ + public String getServiceName() { + return config.getServiceName(); + } + + /** + * Returns the host name of the server where the XMPP server is running. This would be the + * IP address of the server or a name that may be resolved by a DNS server. + * + * @return the host name of the server where the XMPP server is running. + */ + public String getHost() { + return config.getHost(); + } + + public String getCapsNode(){ + return config.getCapsNode(); + } + + /** + * Returns the port number of the XMPP server for this connection. The default port + * for normal connections is 5222. The default port for SSL connections is 5223. + * + * @return the port number of the XMPP server. + */ + public int getPort() { + return config.getPort(); + } + + /** + * Returns the full XMPP address of the user that is logged in to the connection or + * null if not logged in yet. An XMPP address is in the form + * username@server/resource. + * + * @return the full XMPP address of the user logged in. + */ + public abstract String getUser(); + + /** + * Returns the connection ID for this connection, which is the value set by the server + * when opening a XMPP stream. If the server does not set a connection ID, this value + * will be null. This value will be null if not connected to the server. + * + * @return the ID of this connection returned from the XMPP server or null if + * not connected to the server. + */ + public abstract String getConnectionID(); + + /** + * Returns true if currently connected to the XMPP server. + * + * @return true if connected. + */ + public abstract boolean isConnected(); + + /** + * Returns true if currently authenticated by successfully calling the login method. + * + * @return true if authenticated. + */ + public abstract boolean isAuthenticated(); + + /** + * Returns true if currently authenticated anonymously. + * + * @return true if authenticated anonymously. + */ + public abstract boolean isAnonymous(); + + /** + * Returns true if the connection to the server has successfully negotiated encryption. + * + * @return true if a secure connection to the server. + */ + public abstract boolean isSecureConnection(); + + /** + * Returns if the reconnection mechanism is allowed to be used. By default + * reconnection is allowed. + * + * @return true if the reconnection mechanism is allowed to be used. + */ + protected boolean isReconnectionAllowed() { + return config.isReconnectionAllowed(); + } + + /** + * Returns true if network traffic is being compressed. When using stream compression network + * traffic can be reduced up to 90%. Therefore, stream compression is ideal when using a slow + * speed network connection. However, the server will need to use more CPU time in order to + * un/compress network data so under high load the server performance might be affected. + * + * @return true if network traffic is being compressed. + */ + public abstract boolean isUsingCompression(); + + /** + * Establishes a connection to the XMPP server and performs an automatic login + * only if the previous connection state was logged (authenticated). It basically + * creates and maintains a connection to the server.

+ *

+ * Listeners will be preserved from a previous connection if the reconnection + * occurs after an abrupt termination. + * + * @throws XMPPException if an error occurs while trying to establish the connection. + */ + public abstract void connect() throws XMPPException; + + /** + * Logs in to the server using the strongest authentication mode supported by + * the server, then sets presence to available. If the server supports SASL authentication + * then the user will be authenticated using SASL if not Non-SASL authentication will + * be tried. If more than five seconds (default timeout) elapses in each step of the + * authentication process without a response from the server, or if an error occurs, a + * XMPPException will be thrown.

+ * + * Before logging in (i.e. authenticate) to the server the connection must be connected. + * + * It is possible to log in without sending an initial available presence by using + * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is + * not interested in loading its roster upon login then use + * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. + * Finally, if you want to not pass a password and instead use a more advanced mechanism + * while using SASL then you may be interested in using + * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. + * For more advanced login settings see {@link ConnectionConfiguration}. + * + * @param username the username. + * @param password the password or null if using a CallbackHandler. + * @throws XMPPException if an error occurs. + */ + public void login(String username, String password) throws XMPPException { + login(username, password, "Smack"); + } + + /** + * Logs in to the server using the strongest authentication mode supported by + * the server, then sets presence to available. If the server supports SASL authentication + * then the user will be authenticated using SASL if not Non-SASL authentication will + * be tried. If more than five seconds (default timeout) elapses in each step of the + * authentication process without a response from the server, or if an error occurs, a + * XMPPException will be thrown.

+ * + * Before logging in (i.e. authenticate) to the server the connection must be connected. + * + * It is possible to log in without sending an initial available presence by using + * {@link ConnectionConfiguration#setSendPresence(boolean)}. If this connection is + * not interested in loading its roster upon login then use + * {@link ConnectionConfiguration#setRosterLoadedAtLogin(boolean)}. + * Finally, if you want to not pass a password and instead use a more advanced mechanism + * while using SASL then you may be interested in using + * {@link ConnectionConfiguration#setCallbackHandler(javax.security.auth.callback.CallbackHandler)}. + * For more advanced login settings see {@link ConnectionConfiguration}. + * + * @param username the username. + * @param password the password or null if using a CallbackHandler. + * @param resource the resource. + * @throws XMPPException if an error occurs. + * @throws IllegalStateException if not connected to the server, or already logged in + * to the serrver. + */ + public abstract void login(String username, String password, String resource) throws XMPPException; + + /** + * Logs in to the server anonymously. Very few servers are configured to support anonymous + * authentication, so it's fairly likely logging in anonymously will fail. If anonymous login + * does succeed, your XMPP address will likely be in the form "123ABC@server/789XYZ" or + * "server/123ABC" (where "123ABC" and "789XYZ" is a random value generated by the server). + * + * @throws XMPPException if an error occurs or anonymous logins are not supported by the server. + * @throws IllegalStateException if not connected to the server, or already logged in + * to the serrver. + */ + public abstract void loginAnonymously() throws XMPPException; + + /** + * Sends the specified packet to the server. + * + * @param packet the packet to send. + */ + public abstract void sendPacket(Packet packet); + + /** + * Returns an account manager instance for this connection. + * + * @return an account manager for this connection. + */ + public AccountManager getAccountManager() { + if (accountManager == null) { + accountManager = new AccountManager(this); + } + return accountManager; + } + + /** + * Returns a chat manager instance for this connection. The ChatManager manages all incoming and + * outgoing chats on the current connection. + * + * @return a chat manager instance for this connection. + */ + public synchronized ChatManager getChatManager() { + if (this.chatManager == null) { + this.chatManager = new ChatManager(this); + } + return this.chatManager; + } + + /** + * Returns the roster for the user. + *

+ * This method will never return null, instead if the user has not yet logged into + * the server or is logged in anonymously all modifying methods of the returned roster object + * like {@link Roster#createEntry(String, String, String[])}, + * {@link Roster#removeEntry(RosterEntry)} , etc. except adding or removing + * {@link RosterListener}s will throw an IllegalStateException. + * + * @return the user's roster. + */ + public abstract Roster getRoster(); + + /** + * Set the store for the roster of this connection. If you set the roster storage + * of a connection you enable support for XEP-0237 (RosterVersioning) + * @param store the store used for roster versioning + * @throws IllegalStateException if you add a roster store when roster is initializied + */ + public abstract void setRosterStorage(RosterStorage storage) throws IllegalStateException; + + /** + * Returns the SASLAuthentication manager that is responsible for authenticating with + * the server. + * + * @return the SASLAuthentication manager that is responsible for authenticating with + * the server. + */ + public SASLAuthentication getSASLAuthentication() { + return saslAuthentication; + } + + /** + * Closes the connection by setting presence to unavailable then closing the connection to + * the XMPP server. The Connection can still be used for connecting to the server + * again.

+ *

+ * This method cleans up all resources used by the connection. Therefore, the roster, + * listeners and other stateful objects cannot be re-used by simply calling connect() + * on this connection again. This is unlike the behavior during unexpected disconnects + * (and subsequent connections). In that case, all state is preserved to allow for + * more seamless error recovery. + */ + public void disconnect() { + disconnect(new Presence(Presence.Type.unavailable)); + } + + /** + * Closes the connection. A custom unavailable presence is sent to the server, followed + * by closing the stream. The Connection can still be used for connecting to the server + * again. A custom unavilable presence is useful for communicating offline presence + * information such as "On vacation". Typically, just the status text of the presence + * packet is set with online information, but most XMPP servers will deliver the full + * presence packet with whatever data is set.

+ *

+ * This method cleans up all resources used by the connection. Therefore, the roster, + * listeners and other stateful objects cannot be re-used by simply calling connect() + * on this connection again. This is unlike the behavior during unexpected disconnects + * (and subsequent connections). In that case, all state is preserved to allow for + * more seamless error recovery. + * + * @param unavailablePresence the presence packet to send during shutdown. + */ + public abstract void disconnect(Presence unavailablePresence); + + /** + * Adds a new listener that will be notified when new Connections are created. Note + * that newly created connections will not be actually connected to the server. + * + * @param connectionCreationListener a listener interested on new connections. + */ + public static void addConnectionCreationListener( + ConnectionCreationListener connectionCreationListener) { + connectionEstablishedListeners.add(connectionCreationListener); + } + + /** + * Removes a listener that was interested in connection creation events. + * + * @param connectionCreationListener a listener interested on new connections. + */ + public static void removeConnectionCreationListener( + ConnectionCreationListener connectionCreationListener) { + connectionEstablishedListeners.remove(connectionCreationListener); + } + + /** + * Get the collection of listeners that are interested in connection creation events. + * + * @return a collection of listeners interested on new connections. + */ + protected static Collection getConnectionCreationListeners() { + return Collections.unmodifiableCollection(connectionEstablishedListeners); + } + + /** + * Adds a connection listener to this connection that will be notified when + * the connection closes or fails. The connection needs to already be connected + * or otherwise an IllegalStateException will be thrown. + * + * @param connectionListener a connection listener. + */ + public void addConnectionListener(ConnectionListener connectionListener) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (connectionListener == null) { + return; + } + if (!connectionListeners.contains(connectionListener)) { + connectionListeners.add(connectionListener); + } + } + + /** + * Removes a connection listener from this connection. + * + * @param connectionListener a connection listener. + */ + public void removeConnectionListener(ConnectionListener connectionListener) { + connectionListeners.remove(connectionListener); + } + + /** + * Get the collection of listeners that are interested in connection events. + * + * @return a collection of listeners interested on connection events. + */ + protected Collection getConnectionListeners() { + return connectionListeners; + } + + /** + * Creates a new packet collector for this connection. A packet filter determines + * which packets will be accumulated by the collector. A PacketCollector is + * more suitable to use than a {@link PacketListener} when you need to wait for + * a specific result. + * + * @param packetFilter the packet filter to use. + * @return a new packet collector. + */ + public PacketCollector createPacketCollector(PacketFilter packetFilter) { + PacketCollector collector = new PacketCollector(this, packetFilter); + // Add the collector to the list of active collectors. + collectors.add(collector); + return collector; + } + + /** + * Remove a packet collector of this connection. + * + * @param collector a packet collectors which was created for this connection. + */ + protected void removePacketCollector(PacketCollector collector) { + collectors.remove(collector); + } + + /** + * Get the collection of all packet collectors for this connection. + * + * @return a collection of packet collectors for this connection. + */ + protected Collection getPacketCollectors() { + return collectors; + } + + /** + * Registers a packet listener with this connection. A packet filter determines + * which packets will be delivered to the listener. If the same packet listener + * is added again with a different filter, only the new filter will be used. + * + * @param packetListener the packet listener to notify of new received packets. + * @param packetFilter the packet filter to use. + */ + public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) { + if (packetListener == null) { + throw new NullPointerException("Packet listener is null."); + } + ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); + recvListeners.put(packetListener, wrapper); + } + + /** + * Removes a packet listener for received packets from this connection. + * + * @param packetListener the packet listener to remove. + */ + public void removePacketListener(PacketListener packetListener) { + recvListeners.remove(packetListener); + } + + /** + * Get a map of all packet listeners for received packets of this connection. + * + * @return a map of all packet listeners for received packets. + */ + protected Map getPacketListeners() { + return recvListeners; + } + + /** + * Registers a packet listener with this connection. The listener will be + * notified of every packet that this connection sends. A packet filter determines + * which packets will be delivered to the listener. Note that the thread + * that writes packets will be used to invoke the listeners. Therefore, each + * packet listener should complete all operations quickly or use a different + * thread for processing. + * + * @param packetListener the packet listener to notify of sent packets. + * @param packetFilter the packet filter to use. + */ + public void addPacketSendingListener(PacketListener packetListener, PacketFilter packetFilter) { + if (packetListener == null) { + throw new NullPointerException("Packet listener is null."); + } + ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter); + sendListeners.put(packetListener, wrapper); + } + + /** + * Removes a packet listener for sending packets from this connection. + * + * @param packetListener the packet listener to remove. + */ + public void removePacketSendingListener(PacketListener packetListener) { + sendListeners.remove(packetListener); + } + + /** + * Get a map of all packet listeners for sending packets of this connection. + * + * @return a map of all packet listeners for sent packets. + */ + protected Map getPacketSendingListeners() { + return sendListeners; + } + + + /** + * Process all packet listeners for sending packets. + * + * @param packet the packet to process. + */ + protected void firePacketSendingListeners(Packet packet) { + // Notify the listeners of the new sent packet + for (ListenerWrapper listenerWrapper : sendListeners.values()) { + listenerWrapper.notifyListener(packet); + } + } + + /** + * Registers a packet interceptor with this connection. The interceptor will be + * invoked every time a packet is about to be sent by this connection. Interceptors + * may modify the packet to be sent. A packet filter determines which packets + * will be delivered to the interceptor. + * + * @param packetInterceptor the packet interceptor to notify of packets about to be sent. + * @param packetFilter the packet filter to use. + */ + public void addPacketInterceptor(PacketInterceptor packetInterceptor, + PacketFilter packetFilter) { + if (packetInterceptor == null) { + throw new NullPointerException("Packet interceptor is null."); + } + interceptors.put(packetInterceptor, new InterceptorWrapper(packetInterceptor, packetFilter)); + } + + /** + * Removes a packet interceptor. + * + * @param packetInterceptor the packet interceptor to remove. + */ + public void removePacketInterceptor(PacketInterceptor packetInterceptor) { + interceptors.remove(packetInterceptor); + } + + public boolean isSendPresence() { + return config.isSendPresence(); + } + + /** + * Get a map of all packet interceptors for sending packets of this connection. + * + * @return a map of all packet interceptors for sending packets. + */ + protected Map getPacketInterceptors() { + return interceptors; + } + + /** + * Process interceptors. Interceptors may modify the packet that is about to be sent. + * Since the thread that requested to send the packet will invoke all interceptors, it + * is important that interceptors perform their work as soon as possible so that the + * thread does not remain blocked for a long period. + * + * @param packet the packet that is going to be sent to the server + */ + protected void firePacketInterceptors(Packet packet) { + if (packet != null) { + for (InterceptorWrapper interceptorWrapper : interceptors.values()) { + interceptorWrapper.notifyListener(packet); + } + } + } + + /** + * Initialize the {@link #debugger}. You can specify a customized {@link SmackDebugger} + * by setup the system property smack.debuggerClass to the implementation. + * + * @throws IllegalStateException if the reader or writer isn't yet initialized. + * @throws IllegalArgumentException if the SmackDebugger can't be loaded. + */ + protected void initDebugger() { + if (reader == null || writer == null) { + throw new NullPointerException("Reader or writer isn't initialized."); + } + // If debugging is enabled, we open a window and write out all network traffic. + if (config.isDebuggerEnabled()) { + if (debugger == null) { + // Detect the debugger class to use. + String className = null; + // Use try block since we may not have permission to get a system + // property (for example, when an applet). + try { + className = System.getProperty("smack.debuggerClass"); + } + catch (Throwable t) { + // Ignore. + } + Class debuggerClass = null; + if (className != null) { + try { + debuggerClass = Class.forName(className); + } + catch (Exception e) { + e.printStackTrace(); + } + } + if (debuggerClass == null) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); + } + catch (Exception ex) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); + } + catch (Exception ex2) { + ex2.printStackTrace(); + } + } + } + // Create a new debugger instance. If an exception occurs then disable the debugging + // option + try { + Constructor constructor = debuggerClass + .getConstructor(Connection.class, Writer.class, Reader.class); + debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); + reader = debugger.getReader(); + writer = debugger.getWriter(); + } + catch (Exception e) { + throw new IllegalArgumentException("Can't initialize the configured debugger!", e); + } + } + else { + // Obtain new reader and writer from the existing debugger + reader = debugger.newConnectionReader(reader); + writer = debugger.newConnectionWriter(writer); + } + } + + } + + + + /** + * A wrapper class to associate a packet filter with a listener. + */ + protected static class ListenerWrapper { + + private PacketListener packetListener; + private PacketFilter packetFilter; + + /** + * Create a class which associates a packet filter with a listener. + * + * @param packetListener the packet listener. + * @param packetFilter the associated filter or null if it listen for all packets. + */ + public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { + this.packetListener = packetListener; + this.packetFilter = packetFilter; + } + + /** + * Notify and process the packet listener if the filter matches the packet. + * + * @param packet the packet which was sent or received. + */ + public void notifyListener(Packet packet) { + if (packetFilter == null || packetFilter.accept(packet)) { + packetListener.processPacket(packet); + } + } + } + + /** + * A wrapper class to associate a packet filter with an interceptor. + */ + protected static class InterceptorWrapper { + + private PacketInterceptor packetInterceptor; + private PacketFilter packetFilter; + + /** + * Create a class which associates a packet filter with an interceptor. + * + * @param packetInterceptor the interceptor. + * @param packetFilter the associated filter or null if it intercepts all packets. + */ + public InterceptorWrapper(PacketInterceptor packetInterceptor, PacketFilter packetFilter) { + this.packetInterceptor = packetInterceptor; + this.packetFilter = packetFilter; + } + + public boolean equals(Object object) { + if (object == null) { + return false; + } + if (object instanceof InterceptorWrapper) { + return ((InterceptorWrapper) object).packetInterceptor + .equals(this.packetInterceptor); + } + else if (object instanceof PacketInterceptor) { + return object.equals(this.packetInterceptor); + } + return false; + } + + /** + * Notify and process the packet interceptor if the filter matches the packet. + * + * @param packet the packet which will be sent. + */ + public void notifyListener(Packet packet) { + if (packetFilter == null || packetFilter.accept(packet)) { + packetInterceptor.interceptPacket(packet); + } + } + } + + public boolean isAddFrom() { + // TODO Auto-generated method stub + return false; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java new file mode 100644 index 0000000..2d617fd --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionConfiguration.java @@ -0,0 +1,788 @@ +/** + * $RCSfile$ + * $Revision: 3306 $ + * $Date: 2006-01-16 14:34:56 -0300 (Mon, 16 Jan 2006) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.proxy.ProxyInfo; +import org.jivesoftware.smack.util.DNSUtil; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import java.io.File; + +/** + * Configuration to use while establishing the connection to the server. It is possible to + * configure the path to the trustore file that keeps the trusted CA root certificates and + * enable or disable all or some of the checkings done while verifying server certificates.

+ * + * It is also possible to configure if TLS, SASL, and compression are used or not. + * + * @author Gaston Dombiak + */ +public class ConnectionConfiguration implements Cloneable { + + /** + * Hostname of the XMPP server. Usually servers use the same service name as the name + * of the server. However, there are some servers like google where host would be + * talk.google.com and the serviceName would be gmail.com. + */ + private String serviceName; + + private String host; + private int port; + + private String truststorePath; + private String truststoreType; + private String truststorePassword; + private String keystorePath; + private String keystoreType; + private String pkcs11Library; + private boolean verifyChainEnabled = false; + private boolean verifyRootCAEnabled = false; + private boolean selfSignedCertificateEnabled = false; + private boolean expiredCertificatesCheckEnabled = false; + private boolean notMatchingDomainCheckEnabled = false; + private boolean isRosterVersioningAvailable = false; + private boolean enableEntityCaps = true; + private String capsNode = null; + private SSLContext customSSLContext; + + private boolean compressionEnabled = false; + + private boolean saslAuthenticationEnabled = true; + /** + * Used to get information from the user + */ + private CallbackHandler callbackHandler; + + private boolean debuggerEnabled = Connection.DEBUG_ENABLED; + + // Flag that indicates if a reconnection should be attempted when abruptly disconnected + private boolean reconnectionAllowed = true; + + // Holds the socket factory that is used to generate the socket in the connection + private SocketFactory socketFactory; + + // Holds the authentication information for future reconnections + private String username; + private String password; + private String resource; + private boolean sendPresence = true; + private boolean rosterLoadedAtLogin = true; + private SecurityMode securityMode = SecurityMode.enabled; + + // Holds the proxy information (such as proxyhost, proxyport, username, password etc) + protected ProxyInfo proxy; + + /** + * Creates a new ConnectionConfiguration for the specified service name. + * A DNS SRV lookup will be performed to find out the actual host address + * and port to use for the connection. + * + * @param serviceName the name of the service provided by an XMPP server. + */ + public ConnectionConfiguration(String serviceName) { + // Perform DNS lookup to get host and port to use + DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); + init(address.getHost(), address.getPort(), serviceName, + ProxyInfo.forDefaultProxy()); + } + + /** + * + */ + protected ConnectionConfiguration() { + /* Does nothing */ + } + + /** + * Creates a new ConnectionConfiguration for the specified service name + * with specified proxy. + * A DNS SRV lookup will be performed to find out the actual host address + * and port to use for the connection. + * + * @param serviceName the name of the service provided by an XMPP server. + * @param proxy the proxy through which XMPP is to be connected + */ + public ConnectionConfiguration(String serviceName,ProxyInfo proxy) { + // Perform DNS lookup to get host and port to use + DNSUtil.HostAddress address = DNSUtil.resolveXMPPDomain(serviceName); + init(address.getHost(), address.getPort(), serviceName, proxy); + } + + /** + * Creates a new ConnectionConfiguration using the specified host, port and + * service name. This is useful for manually overriding the DNS SRV lookup + * process that's used with the {@link #ConnectionConfiguration(String)} + * constructor. For example, say that an XMPP server is running at localhost + * in an internal network on port 5222 but is configured to think that it's + * "example.com" for testing purposes. This constructor is necessary to connect + * to the server in that case since a DNS SRV lookup for example.com would not + * point to the local testing server. + * + * @param host the host where the XMPP server is running. + * @param port the port where the XMPP is listening. + * @param serviceName the name of the service provided by an XMPP server. + */ + public ConnectionConfiguration(String host, int port, String serviceName) { + init(host, port, serviceName, ProxyInfo.forDefaultProxy()); + } + + /** + * Creates a new ConnectionConfiguration using the specified host, port and + * service name. This is useful for manually overriding the DNS SRV lookup + * process that's used with the {@link #ConnectionConfiguration(String)} + * constructor. For example, say that an XMPP server is running at localhost + * in an internal network on port 5222 but is configured to think that it's + * "example.com" for testing purposes. This constructor is necessary to connect + * to the server in that case since a DNS SRV lookup for example.com would not + * point to the local testing server. + * + * @param host the host where the XMPP server is running. + * @param port the port where the XMPP is listening. + * @param serviceName the name of the service provided by an XMPP server. + * @param proxy the proxy through which XMPP is to be connected + */ + public ConnectionConfiguration(String host, int port, String serviceName, ProxyInfo proxy) { + init(host, port, serviceName, proxy); + } + + /** + * Creates a new ConnectionConfiguration for a connection that will connect + * to the desired host and port. + * + * @param host the host where the XMPP server is running. + * @param port the port where the XMPP is listening. + */ + public ConnectionConfiguration(String host, int port) { + init(host, port, host, ProxyInfo.forDefaultProxy()); + } + + /** + * Creates a new ConnectionConfiguration for a connection that will connect + * to the desired host and port with desired proxy. + * + * @param host the host where the XMPP server is running. + * @param port the port where the XMPP is listening. + * @param proxy the proxy through which XMPP is to be connected + */ + public ConnectionConfiguration(String host, int port, ProxyInfo proxy) { + init(host, port, host, proxy); + } + + protected void init(String host, int port, String serviceName, ProxyInfo proxy) { + this.host = host; + this.port = port; + this.serviceName = serviceName; + this.proxy = proxy; + + // Build the default path to the cacert truststore file. By default we are + // going to use the file located in $JREHOME/lib/security/cacerts. + String javaHome = System.getProperty("java.home"); + StringBuilder buffer = new StringBuilder(); + buffer.append(javaHome).append(File.separator).append("lib"); + buffer.append(File.separator).append("security"); + buffer.append(File.separator).append("cacerts"); + truststorePath = buffer.toString(); + // Set the default store type + truststoreType = "jks"; + // Set the default password of the cacert file that is "changeit" + truststorePassword = "changeit"; + keystorePath = System.getProperty("javax.net.ssl.keyStore"); + keystoreType = "jks"; + pkcs11Library = "pkcs11.config"; + + //Setting the SocketFactory according to proxy supplied + socketFactory = proxy.getSocketFactory(); + } + + /** + * Sets the server name, also known as XMPP domain of the target server. + * + * @param serviceName the XMPP domain of the target server. + */ + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + /** + * Returns the server name of the target server. + * + * @return the server name of the target server. + */ + public String getServiceName() { + return serviceName; + } + + /** + * Returns the host to use when establishing the connection. The host and port to use + * might have been resolved by a DNS lookup as specified by the XMPP spec (and therefore + * may not match the {@link #getServiceName service name}. + * + * @return the host to use when establishing the connection. + */ + public String getHost() { + return host; + } + + /** + * Returns the port to use when establishing the connection. The host and port to use + * might have been resolved by a DNS lookup as specified by the XMPP spec. + * + * @return the port to use when establishing the connection. + */ + public int getPort() { + return port; + } + + /** + * Returns the TLS security mode used when making the connection. By default, + * the mode is {@link SecurityMode#enabled}. + * + * @return the security mode. + */ + public SecurityMode getSecurityMode() { + return securityMode; + } + + /** + * Sets the TLS security mode used when making the connection. By default, + * the mode is {@link SecurityMode#enabled}. + * + * @param securityMode the security mode. + */ + public void setSecurityMode(SecurityMode securityMode) { + this.securityMode = securityMode; + } + + /** + * Retuns the path to the trust store file. The trust store file contains the root + * certificates of several well known CAs. By default, will attempt to use the + * the file located in $JREHOME/lib/security/cacerts. + * + * @return the path to the truststore file. + */ + public String getTruststorePath() { + return truststorePath; + } + + /** + * Sets the path to the trust store file. The truststore file contains the root + * certificates of several well?known CAs. By default Smack is going to use + * the file located in $JREHOME/lib/security/cacerts. + * + * @param truststorePath the path to the truststore file. + */ + public void setTruststorePath(String truststorePath) { + this.truststorePath = truststorePath; + } + + /** + * Returns the trust store type, or null if it's not set. + * + * @return the trust store type. + */ + public String getTruststoreType() { + return truststoreType; + } + + /** + * Sets the trust store type. + * + * @param truststoreType the trust store type. + */ + public void setTruststoreType(String truststoreType) { + this.truststoreType = truststoreType; + } + + /** + * Returns the password to use to access the trust store file. It is assumed that all + * certificates share the same password in the trust store. + * + * @return the password to use to access the truststore file. + */ + public String getTruststorePassword() { + return truststorePassword; + } + + /** + * Sets the password to use to access the trust store file. It is assumed that all + * certificates share the same password in the trust store. + * + * @param truststorePassword the password to use to access the truststore file. + */ + public void setTruststorePassword(String truststorePassword) { + this.truststorePassword = truststorePassword; + } + + /** + * Retuns the path to the keystore file. The key store file contains the + * certificates that may be used to authenticate the client to the server, + * in the event the server requests or requires it. + * + * @return the path to the keystore file. + */ + public String getKeystorePath() { + return keystorePath; + } + + /** + * Sets the path to the keystore file. The key store file contains the + * certificates that may be used to authenticate the client to the server, + * in the event the server requests or requires it. + * + * @param keystorePath the path to the keystore file. + */ + public void setKeystorePath(String keystorePath) { + this.keystorePath = keystorePath; + } + + /** + * Returns the keystore type, or null if it's not set. + * + * @return the keystore type. + */ + public String getKeystoreType() { + return keystoreType; + } + + /** + * Sets the keystore type. + * + * @param keystoreType the keystore type. + */ + public void setKeystoreType(String keystoreType) { + this.keystoreType = keystoreType; + } + + + /** + * Returns the PKCS11 library file location, needed when the + * Keystore type is PKCS11. + * + * @return the path to the PKCS11 library file + */ + public String getPKCS11Library() { + return pkcs11Library; + } + + /** + * Sets the PKCS11 library file location, needed when the + * Keystore type is PKCS11 + * + * @param pkcs11Library the path to the PKCS11 library file + */ + public void setPKCS11Library(String pkcs11Library) { + this.pkcs11Library = pkcs11Library; + } + + /** + * Returns true if the whole chain of certificates presented by the server are going to + * be checked. By default the certificate chain is not verified. + * + * @return true if the whole chaing of certificates presented by the server are going to + * be checked. + */ + public boolean isVerifyChainEnabled() { + return verifyChainEnabled; + } + + /** + * Sets if the whole chain of certificates presented by the server are going to + * be checked. By default the certificate chain is not verified. + * + * @param verifyChainEnabled if the whole chaing of certificates presented by the server + * are going to be checked. + */ + public void setVerifyChainEnabled(boolean verifyChainEnabled) { + this.verifyChainEnabled = verifyChainEnabled; + } + + /** + * Returns true if root CA checking is going to be done. By default checking is disabled. + * + * @return true if root CA checking is going to be done. + */ + public boolean isVerifyRootCAEnabled() { + return verifyRootCAEnabled; + } + + /** + * Sets if root CA checking is going to be done. By default checking is disabled. + * + * @param verifyRootCAEnabled if root CA checking is going to be done. + */ + public void setVerifyRootCAEnabled(boolean verifyRootCAEnabled) { + this.verifyRootCAEnabled = verifyRootCAEnabled; + } + + /** + * Returns true if self-signed certificates are going to be accepted. By default + * this option is disabled. + * + * @return true if self-signed certificates are going to be accepted. + */ + public boolean isSelfSignedCertificateEnabled() { + return selfSignedCertificateEnabled; + } + + /** + * Sets if self-signed certificates are going to be accepted. By default + * this option is disabled. + * + * @param selfSignedCertificateEnabled if self-signed certificates are going to be accepted. + */ + public void setSelfSignedCertificateEnabled(boolean selfSignedCertificateEnabled) { + this.selfSignedCertificateEnabled = selfSignedCertificateEnabled; + } + + /** + * Returns true if certificates presented by the server are going to be checked for their + * validity. By default certificates are not verified. + * + * @return true if certificates presented by the server are going to be checked for their + * validity. + */ + public boolean isExpiredCertificatesCheckEnabled() { + return expiredCertificatesCheckEnabled; + } + + /** + * Sets if certificates presented by the server are going to be checked for their + * validity. By default certificates are not verified. + * + * @param expiredCertificatesCheckEnabled if certificates presented by the server are going + * to be checked for their validity. + */ + public void setExpiredCertificatesCheckEnabled(boolean expiredCertificatesCheckEnabled) { + this.expiredCertificatesCheckEnabled = expiredCertificatesCheckEnabled; + } + + /** + * Returns true if certificates presented by the server are going to be checked for their + * domain. By default certificates are not verified. + * + * @return true if certificates presented by the server are going to be checked for their + * domain. + */ + public boolean isNotMatchingDomainCheckEnabled() { + return notMatchingDomainCheckEnabled; + } + + /** + * Sets if certificates presented by the server are going to be checked for their + * domain. By default certificates are not verified. + * + * @param notMatchingDomainCheckEnabled if certificates presented by the server are going + * to be checked for their domain. + */ + public void setNotMatchingDomainCheckEnabled(boolean notMatchingDomainCheckEnabled) { + this.notMatchingDomainCheckEnabled = notMatchingDomainCheckEnabled; + } + + /** + * Gets the custom SSLContext for SSL sockets. This is null by default. + * + * @return the SSLContext previously set with setCustomSSLContext() or null. + */ + public SSLContext getCustomSSLContext() { + return this.customSSLContext; + } + + /** + * Sets a custom SSLContext for creating SSL sockets. A custom Context causes all other + * SSL/TLS realted settings to be ignored. + * + * @param context the custom SSLContext for new sockets; null to reset default behaviour. + */ + public void setCustomSSLContext(SSLContext context) { + this.customSSLContext = context; + } + + /** + * Returns true if the connection is going to use stream compression. Stream compression + * will be requested after TLS was established (if TLS was enabled) and only if the server + * offered stream compression. With stream compression network traffic can be reduced + * up to 90%. By default compression is disabled. + * + * @return true if the connection is going to use stream compression. + */ + public boolean isCompressionEnabled() { + return compressionEnabled; + } + + /** + * Sets if the connection is going to use stream compression. Stream compression + * will be requested after TLS was established (if TLS was enabled) and only if the server + * offered stream compression. With stream compression network traffic can be reduced + * up to 90%. By default compression is disabled. + * + * @param compressionEnabled if the connection is going to use stream compression. + */ + public void setCompressionEnabled(boolean compressionEnabled) { + this.compressionEnabled = compressionEnabled; + } + + /** + * Returns true if the client is going to use SASL authentication when logging into the + * server. If SASL authenticatin fails then the client will try to use non-sasl authentication. + * By default SASL is enabled. + * + * @return true if the client is going to use SASL authentication when logging into the + * server. + */ + public boolean isSASLAuthenticationEnabled() { + return saslAuthenticationEnabled; + } + + /** + * Sets whether the client will use SASL authentication when logging into the + * server. If SASL authenticatin fails then the client will try to use non-sasl authentication. + * By default, SASL is enabled. + * + * @param saslAuthenticationEnabled if the client is going to use SASL authentication when + * logging into the server. + */ + public void setSASLAuthenticationEnabled(boolean saslAuthenticationEnabled) { + this.saslAuthenticationEnabled = saslAuthenticationEnabled; + } + + /** + * Returns true if the new connection about to be establish is going to be debugged. By + * default the value of {@link Connection#DEBUG_ENABLED} is used. + * + * @return true if the new connection about to be establish is going to be debugged. + */ + public boolean isDebuggerEnabled() { + return debuggerEnabled; + } + + /** + * Sets if the new connection about to be establish is going to be debugged. By + * default the value of {@link Connection#DEBUG_ENABLED} is used. + * + * @param debuggerEnabled if the new connection about to be establish is going to be debugged. + */ + public void setDebuggerEnabled(boolean debuggerEnabled) { + this.debuggerEnabled = debuggerEnabled; + } + + /** + * Sets if the reconnection mechanism is allowed to be used. By default + * reconnection is allowed. + * + * @param isAllowed if the reconnection mechanism is allowed to use. + */ + public void setReconnectionAllowed(boolean isAllowed) { + this.reconnectionAllowed = isAllowed; + } + /** + * Returns if the reconnection mechanism is allowed to be used. By default + * reconnection is allowed. + * + * @return if the reconnection mechanism is allowed to be used. + */ + public boolean isReconnectionAllowed() { + return this.reconnectionAllowed; + } + + /** + * Sets the socket factory used to create new xmppConnection sockets. + * This is useful when connecting through SOCKS5 proxies. + * + * @param socketFactory used to create new sockets. + */ + public void setSocketFactory(SocketFactory socketFactory) { + this.socketFactory = socketFactory; + } + + /** + * Sets if an initial available presence will be sent to the server. By default + * an available presence will be sent to the server indicating that this presence + * is not online and available to receive messages. If you want to log in without + * being 'noticed' then pass a false value. + * + * @param sendPresence true if an initial available presence will be sent while logging in. + */ + public void setSendPresence(boolean sendPresence) { + this.sendPresence = sendPresence; + } + + /** + * Returns true if the roster will be loaded from the server when logging in. This + * is the common behaviour for clients but sometimes clients may want to differ this + * or just never do it if not interested in rosters. + * + * @return true if the roster will be loaded from the server when logging in. + */ + public boolean isRosterLoadedAtLogin() { + return rosterLoadedAtLogin; + } + + /** + * Sets if the roster will be loaded from the server when logging in. This + * is the common behaviour for clients but sometimes clients may want to differ this + * or just never do it if not interested in rosters. + * + * @param rosterLoadedAtLogin if the roster will be loaded from the server when logging in. + */ + public void setRosterLoadedAtLogin(boolean rosterLoadedAtLogin) { + this.rosterLoadedAtLogin = rosterLoadedAtLogin; + } + + /** + * Returns a CallbackHandler to obtain information, such as the password or + * principal information during the SASL authentication. A CallbackHandler + * will be used ONLY if no password was specified during the login while + * using SASL authentication. + * + * @return a CallbackHandler to obtain information, such as the password or + * principal information during the SASL authentication. + */ + public CallbackHandler getCallbackHandler() { + return callbackHandler; + } + + /** + * Sets a CallbackHandler to obtain information, such as the password or + * principal information during the SASL authentication. A CallbackHandler + * will be used ONLY if no password was specified during the login while + * using SASL authentication. + * + * @param callbackHandler to obtain information, such as the password or + * principal information during the SASL authentication. + */ + public void setCallbackHandler(CallbackHandler callbackHandler) { + this.callbackHandler = callbackHandler; + } + + /** + * Returns the socket factory used to create new xmppConnection sockets. + * This is useful when connecting through SOCKS5 proxies. + * + * @return socketFactory used to create new sockets. + */ + public SocketFactory getSocketFactory() { + return this.socketFactory; + } + + /** + * An enumeration for TLS security modes that are available when making a connection + * to the XMPP server. + */ + public static enum SecurityMode { + + /** + * Securirty via TLS encryption is required in order to connect. If the server + * does not offer TLS or if the TLS negotiaton fails, the connection to the server + * will fail. + */ + required, + + /** + * Security via TLS encryption is used whenever it's available. This is the + * default setting. + */ + enabled, + + /** + * Security via TLS encryption is disabled and only un-encrypted connections will + * be used. If only TLS encryption is available from the server, the connection + * will fail. + */ + disabled + } + + /** + * Returns the username to use when trying to reconnect to the server. + * + * @return the username to use when trying to reconnect to the server. + */ + String getUsername() { + return this.username; + } + + /** + * Returns the password to use when trying to reconnect to the server. + * + * @return the password to use when trying to reconnect to the server. + */ + String getPassword() { + return this.password; + } + + /** + * Returns the resource to use when trying to reconnect to the server. + * + * @return the resource to use when trying to reconnect to the server. + */ + String getResource() { + return resource; + } + + boolean isRosterVersioningAvailable(){ + return isRosterVersioningAvailable; + } + + void setRosterVersioningAvailable(boolean enabled){ + isRosterVersioningAvailable = enabled; + } + + boolean isEntityCapsEnabled() { + return enableEntityCaps; + } + + /** + * Enable or disable Entity Capabilities (XEP-115) + * http://xmpp.org/extensions/xep-0115.html + * + * Only available on XMPPConnection + * default is enabled + * + * @param enabled + */ + void setEntityCaps(boolean enabled) { + enableEntityCaps = enabled; + } + + void setCapsNode(String node){ + capsNode = node; + } + + String getCapsNode(){ + return capsNode; + } + + /** + * Returns true if an available presence should be sent when logging in while reconnecting. + * + * @return true if an available presence should be sent when logging in while reconnecting + */ + boolean isSendPresence() { + return sendPresence; + } + + void setLoginInfo(String username, String password, String resource) { + this.username = username; + this.password = password; + this.resource = resource; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java new file mode 100644 index 0000000..7cbda18 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionCreationListener.java @@ -0,0 +1,41 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * Implementors of this interface will be notified when a new {@link Connection} + * has been created. The newly created connection will not be actually connected to + * the server. Use {@link Connection#addConnectionCreationListener(ConnectionCreationListener)} + * to add new listeners. + * + * @author Gaston Dombiak + */ +public interface ConnectionCreationListener { + + /** + * Notification that a new connection has been created. The new connection + * will not yet be connected to the server. + * + * @param connection the newly created connection. + */ + public void connectionCreated(Connection connection); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java new file mode 100644 index 0000000..a7ceef1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ConnectionListener.java @@ -0,0 +1,69 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * Interface that allows for implementing classes to listen for connection closing + * and reconnection events. Listeners are registered with Connection objects. + * + * @see Connection#addConnectionListener + * @see Connection#removeConnectionListener + * + * @author Matt Tucker + */ +public interface ConnectionListener { + + /** + * Notification that the connection was closed normally or that the reconnection + * process has been aborted. + */ + public void connectionClosed(); + + /** + * Notification that the connection was closed due to an exception. When + * abruptly disconnected it is possible for the connection to try reconnecting + * to the server. + * + * @param e the exception. + */ + public void connectionClosedOnError(Exception e); + + /** + * The connection will retry to reconnect in the specified number of seconds. + * + * @param seconds remaining seconds before attempting a reconnection. + */ + public void reconnectingIn(int seconds); + + /** + * The connection has reconnected successfully to the server. Connections will + * reconnect to the server when the previous socket connection was abruptly closed. + */ + public void reconnectionSuccessful(); + + /** + * An attempt to connect to the server has failed. The connection will keep trying + * reconnecting to the server in a moment. + * + * @param e the exception that caused the reconnection to fail. + */ + public void reconnectionFailed(Exception e); +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java new file mode 100644 index 0000000..c084acb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSPresenceDiscoverer.java @@ -0,0 +1,131 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.util.Tuple; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceEvent; +import org.jmdns.ServiceListener; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.LinkedList; + + +/** + * An implementation of LLPresenceDiscoverer using JmDNS. + * + * @author Jonas Ã…dahl + */ +class JmDNSPresenceDiscoverer extends LLPresenceDiscoverer { + protected static final int SERVICE_REQUEST_TIMEOUT = 10000; + protected static JmDNS jmdns; + + JmDNSPresenceDiscoverer() throws XMPPException { + jmdns = JmDNSService.jmdns; + if (jmdns == null) + throw new XMPPException("Failed to fully initiate mDNS daemon."); + + jmdns.addServiceListener(JmDNSService.SERVICE_TYPE, new PresenceServiceListener()); + } + + /** + * Convert raw TXT fields to a list of strings. + * The raw TXT fields are encoded as follows: + *

    + *
  • Byte 0 specifies the length of the first field (which starts at byte 1).
  • + *
  • If the last byte of the previous field is the last byte of the array, + * all TXT fields has been read.
  • + *
  • If there are more bytes following, the next byte after the last of the + * previous field specifies the length of the next field.
  • + *
+ * + * @param bytes raw TXT fields as an array of bytes. + * @return TXT fields as a list of strings. + */ + private static List TXTToList(byte[] bytes) { + List list = new LinkedList(); + int size_i = 0; + while (size_i < bytes.length) { + int s = (int)(bytes[size_i]); + try { + list.add(new String(bytes, ++size_i, s, "UTF-8")); + } catch (UnsupportedEncodingException uee) { + // ignore + } + size_i += s; + } + return list; + } + + /** + * Convert a TXT field list bundled with a '_presence._tcp' service to a + * String,String tuple. The TXT field list looks as following: + * "key=value" which is converted into the tuple (key, value). + * + * @param strings the TXT fields. + * @return a list of key,value tuples. + */ + private static List> TXTListToXMPPRecords(List strings) { + // records :: [(String, String)] + List> records = new LinkedList>(); + for (String s : strings) { + String[] record = s.split("=", 2); + // check if valid + if (record.length == 2) + records.add(new Tuple(record[0], record[1])); + } + return records; + } + + /** + * Implementation of a JmDNS ServiceListener. Listens to service resolved and + * service information resolved events. + */ + private class PresenceServiceListener implements ServiceListener { + public void serviceAdded(ServiceEvent event) { + // XXX + // To reduce network usage, we should only request information + // when needed. + new RequestInfoThread(event).start(); + } + public void serviceRemoved(ServiceEvent event) { + presenceRemoved(event.getName()); + } + public void serviceResolved(ServiceEvent event) { + presenceInfoAdded(event.getName(), + new LLPresence(event.getName(), + event.getInfo().getAddress(), event.getInfo().getPort(), + TXTListToXMPPRecords(TXTToList(event.getInfo().getTextBytes())))); + } + + private class RequestInfoThread extends Thread { + ServiceEvent event; + + RequestInfoThread(ServiceEvent event) { + this.event = event; + } + + public void run() { + jmdns.requestServiceInfo(event.getType(), event.getName(), + true, SERVICE_REQUEST_TIMEOUT); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java new file mode 100644 index 0000000..3ab73e5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/JmDNSService.java @@ -0,0 +1,195 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.util.Tuple; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceInfo; +import org.jmdns.ServiceNameListener; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.DNSCache; +import org.jmdns.impl.DNSEntry; + +import java.util.LinkedList; +import java.net.InetAddress; +import java.io.IOException; +import java.util.Hashtable; + +/** + * Implements a LLService using JmDNS. + * + * @author Jonas Ã…dahl + */ +public class JmDNSService extends LLService implements ServiceNameListener { + static JmDNS jmdns = null; + static JmDNSPresenceDiscoverer presenceDiscoverer = null; + private ServiceInfo serviceInfo; + static final String SERVICE_TYPE = "_presence._tcp.local."; + + private JmDNSService(LLPresence presence, LLPresenceDiscoverer presenceDiscoverer) { + super(presence, presenceDiscoverer); + } + + /** + * Instantiate a new JmDNSService and start to listen for connections. + * + * @param presence the mDNS presence information that should be used. + */ + public static LLService create(LLPresence presence) throws XMPPException { + return create(presence, null); + } + + /** + * Instantiate a new JmDNSService and start to listen for connections. + * + * @param presence the mDNS presence information that should be used. + * @param addr the INET Address to use. + */ + public static LLService create(LLPresence presence, InetAddress addr) throws XMPPException { + // Start the JmDNS daemon. + initJmDNS(addr); + + // Start the presence discoverer + JmDNSPresenceDiscoverer presenceDiscoverer = new JmDNSPresenceDiscoverer(); + + // Start the presence service + JmDNSService service = new JmDNSService(presence, presenceDiscoverer); + + return service; + } + + @Override + public void close() { + super.close(); + jmdns.close(); + jmdns = null; + } + + /** + * Start the JmDNS daemon. + */ + private static void initJmDNS(InetAddress addr) throws XMPPException { + try { + if (jmdns == null) { + if (addr == null) { + jmdns = JmDNS.create(); + } + else { + jmdns = JmDNS.create(addr); + } + } + } + catch (IOException ioe) { + throw new XMPPException(ioe); + } + } + + protected void updateText() { + Hashtable ht = new Hashtable(); + + for (Tuple t : presence.toList()) { + if (t.a != null && t.b != null) { + ht.put(t.a, t.b); + } + } + + serviceInfo.setText(ht); + } + + /** + * Register the DNS-SD service with the daemon. + */ + protected void registerService() throws XMPPException { + Hashtable ht = new Hashtable(); + + for (Tuple t : presence.toList()) { + if (t.a != null && t.b != null) + ht.put(t.a, t.b); + } + serviceInfo = ServiceInfo.create(SERVICE_TYPE, + presence.getServiceName(), presence.getPort(), 0, 0, ht); + serviceInfo.addServiceNameListener(this); + + try { + String originalName = serviceInfo.getQualifiedName(); + jmdns.registerService(serviceInfo); + presence.setServiceName(serviceInfo.getName()); + + if (!originalName.equals(serviceInfo.getQualifiedName())) { + // Update presence service name + // Name collision occured, lets remove confusing elements + // from cache in case something goes wrong + JmDNSImpl jmdnsimpl = (JmDNSImpl) jmdns; + DNSCache.CacheNode n = jmdnsimpl.getCache().find(originalName); + + LinkedList toRemove = new LinkedList(); + while (n != null) { + DNSEntry e = n.getValue(); + if (e != null) + toRemove.add(e); + + n = n.next(); + } + + // Remove the DNSEntry's one by one + for (DNSEntry e : toRemove) { + jmdnsimpl.getCache().remove(e); + } + } + } + catch (IOException ioe) { + throw new XMPPException(ioe); + } + } + + /** + * Reregister the DNS-SD service with the daemon. + */ + protected void reannounceService() throws XMPPException { + try { + jmdns.reannounceService(serviceInfo); + } + catch (IOException ioe) { + throw new XMPPException("Exception occured when reannouncing mDNS presence.", ioe); + } + } + + public void serviceNameChanged(String newName, String oldName) { + try { + super.serviceNameChanged(newName, oldName); + } + catch (Throwable t) { + // ignore + } + } + + /** + * Unregister the DNS-SD service, making the client unavailable. + */ + public void makeUnavailable() { + jmdns.unregisterService(serviceInfo); + serviceInfo = null; + } + + + @Override + public void spam() { + super.spam(); + System.out.println("Service name: " + serviceInfo.getName()); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java new file mode 100644 index 0000000..2df8d1a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChat.java @@ -0,0 +1,114 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Message; + +import java.util.Set; +import java.util.List; +import java.util.LinkedList; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Keeps track of a chat session between two link-local clients. + */ +public class LLChat { + private String serviceName; + private LLService service; + + private Set listeners = new CopyOnWriteArraySet(); + + // Queue for storing messages in case no listener is available when a + // message is to be delivered. + private List messageQueue = new LinkedList(); + + + LLChat(LLService service, LLPresence presence) throws XMPPException { + this.service = service;; + serviceName = presence.getServiceName(); + } + + /** + * Get the service name of the remote client of this chat session. + * + * @return the service name of the remote client of this chat session + */ + public String getServiceName() { + return serviceName; + } + + /** + * Deliver a message to the message listeners. + * + * @param message the message to be delivered. + */ + void deliver(Message message) { + // if no listeners are available, queue the messages for later. + synchronized (listeners) { + if (listeners.isEmpty()) + messageQueue.add(message); + else { + for (LLMessageListener listener : listeners) { + listener.processMessage(this, message); + } + } + } + } + + /** + * Send a message packet to the remote client. + * + * @param message the message to be sent. + * @throws XMPPException if an exception occurs during transmission. + */ + public void sendMessage(Message message) throws XMPPException { + message.setTo(serviceName); + message.setType(Message.Type.chat); + service.sendMessage(message); + } + + public Message generateMessage(String text) { + Message message = new Message(serviceName, Message.Type.chat); + message.setBody(text); + return message; + } + + /** + * Send a message to the remote client. + * + * @param message the message to be sent. + * @throws XMPPException if an exception occurs during transmission. + */ + public void sendMessage(String text) throws XMPPException { + service.sendMessage(generateMessage(text)); + } + + /** + * Add a message listener. The message listener will be notified when new + * messages are received. If there was no listener when messages was to be + * delivered, the first listener to be added will receive all of the queued + * messages. + */ + public void addMessageListener(LLMessageListener listener) { + synchronized (listeners) { + listeners.add(listener); + for (Message message : messageQueue) + listener.processMessage(this, message); + messageQueue.clear(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java new file mode 100644 index 0000000..8f2c0c6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLChatListener.java @@ -0,0 +1,36 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * Notification about new Link-local chat sessions. + */ +public interface LLChatListener { + + /** + * New chat has been created. + * + * @param chat the newly created chat. + */ + public void newChat(LLChat chat); + + /** + * Called when a chat session is invalidated (due to service + * name changes. + */ + public void chatInvalidated(LLChat chat); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java new file mode 100644 index 0000000..8dbc75b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionConfiguration.java @@ -0,0 +1,107 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import javax.net.SocketFactory; +import java.net.Socket; + +/** + * Link-local connection configuration settings. Two general cases exists, + * one where the we want to connect to a remote peer, and one when o remote + * peer has connected to us. + */ +public class LLConnectionConfiguration extends ConnectionConfiguration implements Cloneable { + private LLPresence remotePresence; + private LLPresence localPresence; + private Socket socket; + + private boolean debuggerEnabled = Connection.DEBUG_ENABLED; + + // Holds the socket factory that is used to generate the socket in the connection + private SocketFactory socketFactory; + + /** + * Configuration used for connecting to remote peer. + * @param local LLPresence for the local user + * @param remote LLPresence for the remote user + */ + LLConnectionConfiguration(LLPresence local, LLPresence remote) { + this.localPresence = local; + this.remotePresence = remote; + } + + /** + * Instantiate a link-local configuration when the connection is acting as + * the host. + * + * @param local the local link-local presence class. + * @param remoteSocket the socket which the new connection is assigned to. + */ + LLConnectionConfiguration(LLPresence local, Socket remoteSocket) { + this.localPresence = local; + this.socket = remoteSocket; + } + + /** + * Tells if the connection is the initiating one. + * @return true if this configuration is for the connecting connection. + */ + public boolean isInitiator() { + return socket == null; + } + + /** + * Return the service name of the remote peer. + * @return the remote peer's service name. + */ + public String getRemoteServiceName() { + return remotePresence.getServiceName(); + } + + /** + * Return the service name of this client. + * @return this clients service name. + */ + public String getLocalServiceName() { + return localPresence.getServiceName(); + } + + /** + * Return this clients link-local presence information. + * @return this clients link-local presence information. + */ + public LLPresence getLocalPresence() { + return localPresence; + } + + /** + * Return the remote client's link-local presence information. + * @return the remote client's link-local presence information. + */ + public LLPresence getRemotePresence() { + return remotePresence; + } + + /** + * Return the socket which has been established when the + * remote client connected. + * @return the socket established when the remote client connected. + */ + public Socket getSocket() { + return socket; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java new file mode 100644 index 0000000..c4f4681 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLConnectionListener.java @@ -0,0 +1,30 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * Notification about when new Link-local connections has been established. + */ +public interface LLConnectionListener { + + /** + * A new link-local connection has been established. + * + * @param connection the new established connection. + */ + public void connectionCreated(XMPPLLConnection connection); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java new file mode 100644 index 0000000..fb97c74 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLMessageListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Message; + +/** + * Notification when messages are being delivered to a chat. + */ +public interface LLMessageListener { + /** + * New message in chat. + * + * @param chat the chat session which the message was delivered to. + * @param message the message being delivered. + */ + void processMessage(LLChat chat, Message message); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java new file mode 100644 index 0000000..bb88e5e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPacketReader.java @@ -0,0 +1,244 @@ +/** + * $RCSfile$ + * $Revision: 7232 $ + * $Date: 2007-02-20 16:57:31 -0800 (Tue, 20 Feb 2007) $ + * + * Copyright 2003-2007 Jive Software. 2008-2009 Jonas Ã…dahl + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.*; +import java.util.concurrent.*; + +/** + * Listens for XML traffic from a remote XMPP client and parses it into packet objects. + * The packet reader also manages all packet listeners and collectors.

+ * + * @see PacketCollector + * @see PacketListener + * @author Matt Tucker + */ +public class LLPacketReader extends PacketReader { + + private XMPPLLConnection connection; + private LLService service; + + public LLPacketReader(final LLService service, final XMPPLLConnection connection) { + super(connection); + this.service = service; + this.connection = connection; + this.init(); + } + + /** + * Parse top-level packets in order to process them further. + * + * @param thread the thread that is being used by the reader to parse incoming packets. + */ + protected void parsePackets(Thread thread) { + try { + int eventType = parser.getEventType(); + do { + connection.updateLastActivity(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("message")) { + processPacket(PacketParserUtils.parseMessage(parser)); + } + else if (parser.getName().equals("iq")) { + processPacket(parseIQ(parser)); + } + else if (parser.getName().equals("presence")) { + processPacket(PacketParserUtils.parsePresence(parser)); + } + // We found an opening stream. Record information about it, then notify + // the connectionID lock so that the packet reader startup can finish. + else if (parser.getName().equals("stream")) { + // Ensure the correct jabber:client namespace is being used. + if ("jabber:client".equals(parser.getNamespace(null))) { + // Get the connection id. + for (int i=0; i rest = + new ConcurrentHashMap(); + + public static enum Mode { + avail, away, dnd + } + + // Host details + private int port = 0; + private String host; + private String host6; // Currently unused + private String serviceName; + + public LLPresence(String serviceName) { + this.serviceName = serviceName; + } + + public LLPresence(String serviceName, InetAddress host, int port) { + this.serviceName = serviceName; + if (host instanceof Inet4Address) { + this.host = host.getHostAddress(); + } else if (host instanceof Inet6Address) { + this.host6 = host.getHostAddress(); + } else { + throw new UnsupportedOperationException(); + } + this.port = port; + } + + public LLPresence(String serviceName, InetAddress host, int port, + List> records) { + this(serviceName, host, port); + + // Parse the tuple list (originating from the TXT fields) and put them + // in variables + for (Tuple t : records) { + if (t.a.equals("1st")) + setFirstName(t.b); + else if (t.a.equals("last")) + setLastName(t.b); + else if (t.a.equals("email")) + setEMail(t.b); + else if (t.a.equals("jid")) + setJID(t.b); + else if (t.a.equals("nick")) + setNick(t.b); + else if (t.a.equals("hash")) + setHash(t.b); + else if (t.a.equals("node")) + setNode(t.b); + else if (t.a.equals("ver")) + setVer(t.b); + else if (t.a.equals("status")) { + try { + setStatus(Mode.valueOf(t.b)); + } + catch (IllegalArgumentException iae) { + System.err.println("Found invalid presence status (" + + t.b + ") in TXT entry."); + } + } + else if (t.a.equals("msg")) + setMsg(t.b); + else { + // Unknown key + if (!rest.containsKey(t.a)) + rest.put(t.a, t.b); + } + } + } + + public List> toList() { + LinkedList> list = new LinkedList>(); + list.add(new Tuple("txtvers", "1")); + list.add(new Tuple("1st", firstName)); + list.add(new Tuple("last", lastName)); + list.add(new Tuple("email", email)); + list.add(new Tuple("jid", jid)); + list.add(new Tuple("nick", nick)); + list.add(new Tuple("status", status.toString())); + list.add(new Tuple("msg", msg)); + list.add(new Tuple("hash", hash)); + list.add(new Tuple("node", node)); + list.add(new Tuple("ver", ver)); + list.add(new Tuple("port.p2ppj", new Integer(port).toString())); + + for (Map.Entry e : rest.entrySet()) { + list.add(new Tuple(e.getKey(), e.getValue())); + } + + return list; + } + + /** + * Update all the values of the presence. + */ + void update(LLPresence p) { + setFirstName(p.getFirstName()); + setLastName(p.getLastName()); + setEMail(p.getEMail()); + setMsg(p.getMsg()); + setNick(p.getNick()); + setStatus(p.getStatus()); + setJID(p.getJID()); + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public void setFirstName(String name) { + firstName = name; + } + + public void setLastName(String name) { + lastName = name; + } + + public void setEMail(String email) { + this.email = email; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public void setNick(String nick) { + this.nick = nick; + } + + public void setStatus(Mode status) { + this.status = status; + } + + public void setJID(String jid) { + this.jid = jid; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public void setNode(String node) { + this.node = node; + } + + public void setVer(String ver) { + this.ver = ver; + } + + void setPort(int port) { + this.port = port; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getEMail() { + return email; + } + + public String getMsg() { + return msg; + } + + public String getNick() { + return nick; + } + + public Mode getStatus() { + return status; + } + + public String getJID() { + return jid; + } + + public String getServiceName() { + return serviceName; + } + + public String getHost() { + return host; + } + + public String getHash() { + return hash; + } + + public String getNode() { + return node; + } + + public String getVer() { + return ver; + } + + public int getPort() { + return port; + } + + public String getValue(String key) { + return rest.get(key); + } + + public void putValue(String key, String value) { + rest.put(key, value); + } + + public boolean equals(Object o) { + if (o instanceof LLPresence) { + LLPresence p = (LLPresence)o; + return p.serviceName == serviceName && + p.host == host; + } + return false; + } + + public int hashCode() { + return serviceName.hashCode(); + } + + /** Update from an old presence, in case it has host information that we are missing (e.g. ipv4 address) */ + public void updateFrom(LLPresence other) { + if (other == null) + return; + + if (host6 == null) { + host6 = other.host6; + } + + if (host == null) { + host = other.host; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java new file mode 100644 index 0000000..56bd013 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceDiscoverer.java @@ -0,0 +1,108 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import java.util.Set; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Link-local presence discoverer. XEP-0174 describes how to use mDNS/DNS-SD. + * This class in an abstract representation of the basic functionality of + * handeling presences discovering. + */ +public abstract class LLPresenceDiscoverer { + // Listeners to be notified about changes. + protected Set listeners = new CopyOnWriteArraySet(); + // Map of service name -> Link-local presence + private Map presences = new ConcurrentHashMap(); + + /** + * Add listener which will be notified when new presences are discovered, + * presence information changed or presences goes offline. + * @param listener the listener to be notified. + */ + public void addPresenceListener(LLPresenceListener listener) { + listeners.add(listener); + for (LLPresence presence : presences.values()) + listener.presenceNew(presence); + } + + /** + * Remove presence listener. + * @param listener listener to be removed. + */ + public void removePresenceListener(LLPresenceListener listener) { + listeners.remove(listener); + } + + /** + * Return a collection of presences known. + * @return all known presences. + */ + public Collection getPresences() { + return presences.values(); + } + + /** + * Return the presence with the specified service name. + * + * @param name service name of the presence. + * @return the presence information with the given service name. + */ + public LLPresence getPresence(String name) { + return presences.get(name); + } + + /** + * Used by the class extending this one to tell when new + * presence is added. + * + * @param name service name of the presence. + */ + protected void presenceAdded(String name) { + presences.put(name, null); + } + + /** + * Used by the class extending this one to tell when new + * presence information is added. + * + * @param name service name of the presence. + * @param presence presence information. + */ + protected void presenceInfoAdded(String name, LLPresence presence) { + presence.updateFrom(presences.get(name)); + presences.put(name, presence); + for (LLPresenceListener l : listeners) + l.presenceNew(presence); + } + + /** + * Used by the class extending htis one to tell when a presence + * goes offline. + * + * @param name service name of the presence going offline. + */ + protected void presenceRemoved(String name) { + LLPresence presence = presences.remove(name); + for (LLPresenceListener l : listeners) + l.presenceRemove(presence); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java new file mode 100644 index 0000000..c486468 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLPresenceListener.java @@ -0,0 +1,35 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +/** + * Interface for receiving notifications about presence changes. + */ +public interface LLPresenceListener { + /** + * New link-local presence has been discovered. + * + * @param presence information about the new presence + */ + + public void presenceNew(LLPresence presence); + /** + * A link-local presence has gone offline. + * @param presence the presence which went offline. + */ + public void presenceRemove(LLPresence presence); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java new file mode 100644 index 0000000..21e2daf --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLService.java @@ -0,0 +1,995 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; + + +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.Map; +import java.util.LinkedList; +import java.util.ArrayList; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ConcurrentHashMap; + +/** + * LLService acts as an abstract interface to a Link-local XMPP service + * according to XEP-0174. XEP-0174 describes how this is achieved using + * mDNS/DNS-SD, and this class creates an implementation unspecific + * interface for doing this. + * + * The mDNS/DNS-SD is for example implemented by JmDNSService (using JmDNS). + * + * There is only one instance of LLService possible at one time. + * + * Tasks taken care of here are: + *

    + *
  • Connection Management + *
      + *
    • Keep track of active connections from and to the local client
    • + *
    • Listen for connections on a semi randomized port announced by the + * mDNS/DNS-SD daemon
    • + *
    • Establish new connections when there is none to use and packets are + * to be sent
    • + *
        + *
      • Chat Management - Keep track of messaging sessions between users
      • + *
      + * + * @author Jonas Ã…dahl + */ +public abstract class LLService { + private LLService service = null; + + // Listeners for new services + private static Set serviceCreatedListeners = + new CopyOnWriteArraySet(); + + static final int DEFAULT_MIN_PORT = 2300; + static final int DEFAULT_MAX_PORT = 2400; + protected LLPresence presence; + private boolean done = false; + private Thread listenerThread; + + private boolean initiated = false; + + private Map chats = + new ConcurrentHashMap(); + + private Map ingoing = + new ConcurrentHashMap(); + private Map outgoing = + new ConcurrentHashMap(); + + // Listeners for state updates, such as LLService closed down + private Set stateListeners = + new CopyOnWriteArraySet(); + + // Listeners for XMPPLLConnections associated with this service + private Set llServiceConnectionListeners = + new CopyOnWriteArraySet(); + + // Listeners for packets coming from this Link-local service + private final Map listeners = + new ConcurrentHashMap(); + + // Presence discoverer, notifies of changes in presences on the network. + private LLPresenceDiscoverer presenceDiscoverer; + + // chat listeners gets notified when new chats are created + private Set chatListeners = new CopyOnWriteArraySet(); + + // Set of Packet collector wrappers + private Set collectorWrappers = + new CopyOnWriteArraySet(); + + // Set of associated connections. + private Set associatedConnections = + new HashSet(); + + private ServerSocket socket; + + static { + SmackConfiguration.getVersion(); + } + + /** + * Spam stdout with some debug information. + */ + public void spam() { + System.out.println("Number of ingoing connection in map: " + ingoing.size()); + System.out.println("Number of outgoing connection in map: " + outgoing.size()); + + System.out.println("Active chats:"); + for (LLChat chat : chats.values()) { + System.out.println(" * " + chat.getServiceName()); + } + + System.out.println("Known presences:"); + for (LLPresence presence : presenceDiscoverer.getPresences()) { + System.out.println(" * " + presence.getServiceName() + "(" + presence.getStatus() + ", " + presence.getHost() + ":" + presence.getPort() + ")"); + } + Thread.currentThread().getThreadGroup().list(); + } + + protected LLService(LLPresence presence, LLPresenceDiscoverer discoverer) { + this.presence = presence; + presenceDiscoverer = discoverer; + service = this; + + XMPPLLConnection.addLLConnectionListener(new LLConnectionListener() { + public void connectionCreated(XMPPLLConnection connection) { + // We only care about this connection if we were the one + // creating it + if (isAssociatedConnection(connection)) { + if (connection.isInitiator()) { + addOutgoingConnection(connection); + } + else { + addIngoingConnection(connection); + } + + connection.addConnectionListener(new ConnectionActivityListener(connection)); + + // Notify listeners that a new connection associated with this + // service has been created. + notifyNewServiceConnection(connection); + + // add message listener. filter logic: + // type = msg ^ (msg.type = chat v msg.type = normal v msg.type = error) + connection.addPacketListener(new MessageListener(), + new AndFilter( + new PacketTypeFilter(Message.class), + new OrFilter( + new MessageTypeFilter(Message.Type.chat), + new OrFilter( + new MessageTypeFilter(Message.Type.normal), + new MessageTypeFilter(Message.Type.error))))); + + // add other existing packet filters associated with this service + for (ListenerWrapper wrapper : listeners.values()) { + connection.addPacketListener(wrapper.getPacketListener(), + wrapper.getPacketFilter()); + } + + // add packet collectors + for (CollectorWrapper cw : collectorWrappers) { + cw.createPacketCollector(connection); + } + } + } + }); + + notifyServiceListeners(this); + } + + /** + * Add a LLServiceListener. The LLServiceListener is notified when a new + * Link-local service is created. + * + * @param listener the LLServiceListener + */ + public static void addLLServiceListener(LLServiceListener listener) { + serviceCreatedListeners.add(listener); + } + + /** + * Remove a LLServiceListener. + */ + public static void removeLLServiceListener(LLServiceListener listener) { + serviceCreatedListeners.remove(listener); + } + + /** + * Notify LLServiceListeners about a new Link-local service. + * + * @param service the new service. + */ + public static void notifyServiceListeners(LLService service) { + for (LLServiceListener listener : serviceCreatedListeners) { + listener.serviceCreated(service); + } + } + + /** + * Returns the running mDNS/DNS-SD XMPP instance. There can only be one + * instance at a time. + * + * @return the active LLService instance. + * @throws XMPPException if the LLService hasn't been instantiated. + */ + /*public static LLService getServiceInstance() throws XMPPException { + if (service == null) + throw new XMPPException("Link-local service not initiated."); + return service; + }*/ + + /** + * Registers the service to the mDNS/DNS-SD daemon. + * Should be implemented by the class extending this, for mDNS/DNS-SD library specific calls. + */ + protected abstract void registerService() throws XMPPException; + + /** + * Re-announce the presence information by using the mDNS/DNS-SD daemon. + */ + protected abstract void reannounceService() throws XMPPException; + + /** + * Make the client unavailabe. Equivalent to sending unavailable-presence. + */ + public abstract void makeUnavailable(); + + /** + * Update the text field information. Used for setting new presence information. + */ + protected abstract void updateText(); + + public void init() throws XMPPException { + // allocate a new port for remote clients to connect to + socket = bindRange(DEFAULT_MIN_PORT, DEFAULT_MAX_PORT); + presence.setPort(socket.getLocalPort()); + + // register service on the allocated port + registerService(); + + // start to listen for new connections + listenerThread = new Thread() { + public void run() { + try { + // Listen for connections + listenForConnections(); + + // If listen for connections returns with no exception, + // the service has closed down + for (LLServiceStateListener listener : stateListeners) + listener.serviceClosed(); + } catch (XMPPException e) { + for (LLServiceStateListener listener : stateListeners) + listener.serviceClosedOnError(e); + } + } + }; + listenerThread.setName("Smack Link-local Service Listener"); + listenerThread.setDaemon(true); + listenerThread.start(); + + initiated = true; + } + + public void close() { + done = true; + + // close incoming connections + for (XMPPLLConnection connection : ingoing.values()) { + try { + connection.shutdown(); + } catch (Exception e) { + // ignore + } + } + + // close outgoing connections + for (XMPPLLConnection connection : outgoing.values()) { + try { + connection.shutdown(); + } catch (Exception e) { + // ignore + } + } + try { + socket.close(); + } catch (IOException ioe) { + // ignore + } + } + + /** + * Listen for new connections on socket, and spawn XMPPLLConnections + * when new connections are established. + * + * @throws XMPPException whenever an exception occurs + */ + private void listenForConnections() throws XMPPException { + while (!done) { + try { + // wait for new connection + Socket s = socket.accept(); + + LLConnectionConfiguration config = + new LLConnectionConfiguration(presence, s); + XMPPLLConnection connection = new XMPPLLConnection(this, config); + + // Associate the new connection with this service + addAssociatedConnection(connection); + + // Spawn new thread to handle the connecting. + // The reason for spawning a new thread is to let two remote clients + // be able to connect at the same time. + Thread connectionInitiatorThread = + new ConnectionInitiatorThread(connection); + connectionInitiatorThread.setName("Smack Link-local Connection Initiator"); + connectionInitiatorThread.setDaemon(true); + connectionInitiatorThread.start(); + } + catch (SocketException se) { + // If we are closing down, it's probably closed socket exception. + if (!done) { + throw new XMPPException("Link-local service unexpectedly closed down.", se); + } + } + catch (IOException ioe) { + throw new XMPPException("Link-local service unexpectedly closed down.", ioe); + } + } + } + + /** + * Bind one socket to any port within a given range. + * + * @param min the minimum port number allowed + * @param max hte maximum port number allowed + * @throws XMPPException if binding failed on all allowed ports. + */ + private static ServerSocket bindRange(int min, int max) throws XMPPException { + int port = 0; + for (int try_port = min; try_port <= max; try_port++) { + try { + ServerSocket socket = new ServerSocket(try_port); + return socket; + } + catch (IOException e) { + // failed to bind, try next + } + } + throw new XMPPException("Unable to bind port, no ports available."); + } + + protected void unknownOriginMessage(Message message) { + for (LLServiceStateListener listener : stateListeners) { + listener.unknownOriginMessage(message); + } + } + + protected void serviceNameChanged(String newName, String oldName) { + // update our own presence with the new name, for future connections + presence.setServiceName(newName); + + // Cleanup chats (remove tho two affected chats) + LLChat c1 = removeLLChat(newName); + LLChat c2 = removeLLChat(oldName); + for (LLChatListener listener : chatListeners) { + if (c1 != null) + listener.chatInvalidated(c1); + if (c2 != null) + listener.chatInvalidated(c1); + } + + // clean up connections + XMPPLLConnection c; + c = getConnectionTo(oldName); + if (c != null) + c.disconnect(); + c = getConnectionTo(newName); + if (c != null) + c.disconnect(); + + // notify listeners + for (LLServiceStateListener listener : stateListeners) { + listener.serviceNameChanged(newName, oldName); + } + } + + /** + * Adds a listener that are notified when a new link-local connection + * has been established. + * + * @param listener A class implementing the LLConnectionListener interface. + */ + public void addLLServiceConnectionListener(LLServiceConnectionListener listener) { + llServiceConnectionListeners.add(listener); + } + + /** + * Removes a listener from the new connection listener list. + * + * @param listener The class implementing the LLConnectionListener interface that + * is to be removed. + */ + public void removeLLServiceConnectionListener(LLServiceConnectionListener listener) { + llServiceConnectionListeners.remove(listener); + } + + private void notifyNewServiceConnection(XMPPLLConnection connection) { + for (LLServiceConnectionListener listener : llServiceConnectionListeners) { + listener.connectionCreated(connection); + } + } + + /** + * Add the given connection to the list of associated connections. + * An associated connection means it's a Link-Local connection managed + * by this service. + * + * @param connection the connection to be associated + */ + private void addAssociatedConnection(XMPPLLConnection connection) { + synchronized (associatedConnections) { + associatedConnections.add(connection); + } + } + + /** + * Remove the given connection from the list of associated connections. + * + * @param connection the connection to be removed. + */ + private void removeAssociatedConnection(XMPPLLConnection connection) { + synchronized (associatedConnections) { + associatedConnections.remove(connection); + } + } + + /** + * Return true if the given connection is associated / managed by this + * service. + * + * @param connection the connection to be checked + * @return true if the connection is associated with this service or false + * if it is not associated with this service. + */ + private boolean isAssociatedConnection(XMPPLLConnection connection) { + synchronized (associatedConnections) { + return associatedConnections.contains(connection); + } + } + + /** + * Add a packet listener. + * + * @param listener the PacketListener + * @param filter the Filter + */ + public void addPacketListener(PacketListener listener, PacketFilter filter) { + ListenerWrapper wrapper = new ListenerWrapper(listener, filter); + listeners.put(listener, wrapper); + + // Also add to existing connections + synchronized (ingoing) { + synchronized (outgoing) { + for (XMPPLLConnection c : getConnections()) { + c.addPacketListener(listener, filter); + } + } + } + } + + /** + * Remove a packet listener. + */ + public void removePacketListener(PacketListener listener) { + listeners.remove(listener); + + // Also add to existing connections + synchronized (ingoing) { + synchronized (outgoing) { + for (XMPPLLConnection c : getConnections()) { + c.removePacketListener(listener); + } + } + } + } + + /** + * Add service state listener. + * + * @param listener the service state listener to be added. + */ + public void addServiceStateListener(LLServiceStateListener listener) { + stateListeners.add(listener); + } + + /** + * Remove service state listener. + * + * @param listener the service state listener to be removed. + */ + public void removeServiceStateListener(LLServiceStateListener listener) { + stateListeners.remove(listener); + } + + /** + * Add Link-local chat session listener. The chat session listener will + * be notified when new link-local chat sessions are created. + * + * @param listener the listener to be added. + */ + public void addLLChatListener(LLChatListener listener) { + chatListeners.add(listener); + } + + /** + * Remove Link-local chat session listener. + * + * @param listener the listener to be removed. + */ + public void removeLLChatListener(LLChatListener listener) { + chatListeners.remove(listener); + } + + /** + * Add presence listener. A presence listener will be notified of new + * presences, presences going offline, and changes in presences. + * + * @param listener the listener to be added. + */ + public void addPresenceListener(LLPresenceListener listener) { + presenceDiscoverer.addPresenceListener(listener); + } + + /** + * Remove presence listener. + * + * @param listener presence listener to be removed. + */ + public void removePresenceListener(LLPresenceListener listener) { + presenceDiscoverer.removePresenceListener(listener); + } + + /** + * Get the presence information associated with the given service name. + * + * @param serviceName the service name which information should be returned. + * @return the service information. + */ + public LLPresence getPresenceByServiceName(String serviceName) { + return presenceDiscoverer.getPresence(serviceName); + } + + public CollectorWrapper createPacketCollector(PacketFilter filter) { + CollectorWrapper wrapper = new CollectorWrapper(filter); + collectorWrappers.add(wrapper); + return wrapper; + } + + /** + * Return a collection of all active connections. This may be used if the + * user wants to change a property on all connections, such as add a service + * discovery feature or other. + * + * @return a colllection of all active connections. + */ + public Collection getConnections() { + Collection connections = + new ArrayList(outgoing.values()); + connections.addAll(ingoing.values()); + return connections; + } + + /** + * Returns a connection to a given service name. + * First checks for an outgoing connection, if noone exists, + * try ingoing. + * + * @param serviceName the service name + * @return a connection associated with the service name or null if no + * connection is available. + */ + XMPPLLConnection getConnectionTo(String serviceName) { + XMPPLLConnection connection = outgoing.get(serviceName); + if (connection != null) + return connection; + return ingoing.get(serviceName); + } + + void addIngoingConnection(XMPPLLConnection connection) { + ingoing.put(connection.getServiceName(), connection); + } + + void removeIngoingConnection(XMPPLLConnection connection) { + ingoing.remove(connection.getServiceName()); + } + + void addOutgoingConnection(XMPPLLConnection connection) { + outgoing.put(connection.getServiceName(), connection); + } + + void removeOutgoingConnection(XMPPLLConnection connection) { + outgoing.remove(connection.getServiceName()); + } + + LLChat removeLLChat(String serviceName) { + return chats.remove(serviceName); + } + + void newLLChat(LLChat chat) { + chats.put(chat.getServiceName(), chat); + for (LLChatListener listener : chatListeners) { + listener.newChat(chat); + } + } + + /** + * Get a LLChat associated with a given service name. + * If no LLChat session is available, a new one is created. + * + * @param serviceName the service name + * @return a chat session instance associated with the given service name. + */ + public LLChat getChat(String serviceName) throws XMPPException { + LLChat chat = chats.get(serviceName); + if (chat == null) { + LLPresence presence = getPresenceByServiceName(serviceName); + if (presence == null) + throw new XMPPException("Can't initiate new chat to '" + + serviceName + "': mDNS presence unknown."); + chat = new LLChat(this, presence); + newLLChat(chat); + } + return chat; + } + + /** + * Returns a XMPPLLConnection to the serviceName. + * If no established connection exists, a new connection is created. + * + * @param serviceName Service name of the remote client. + * @return A connection to the given service name. + */ + public XMPPLLConnection getConnection(String serviceName) throws XMPPException { + // If a connection exists, return it. + XMPPLLConnection connection = getConnectionTo(serviceName); + if (connection != null) + return connection; + + // If no connection exists, look up the presence and connect according to. + LLPresence remotePresence = getPresenceByServiceName(serviceName); + + if (remotePresence == null) { + throw new XMPPException("Can't initiate connection, remote peer is not available."); + } + + LLConnectionConfiguration config = + new LLConnectionConfiguration(presence, remotePresence); + connection = new XMPPLLConnection(this, config); + // Associate the new connection with this service + addAssociatedConnection(connection); + connection.connect(); + addOutgoingConnection(connection); + + return connection; + } + + /** + * Send a message to the remote peer. + * + * @param message the message to be sent. + * @throws XMPPException if the message cannot be sent. + */ + void sendMessage(Message message) throws XMPPException { + sendPacket(message); + } + + + /** + * Send a packet to the remote peer. + * + * @param packet the packet to be sent. + * @throws XMPPException if the packet cannot be sent. + */ + public void sendPacket(Packet packet) throws XMPPException { + getConnection(packet.getTo()).sendPacket(packet); + } + + /** + * Send an IQ set or get and wait for the response. This function works + * different from a normal one-connection IQ request where a packet + * collector is created and added to the connection. This function + * takes care of (at least) two cases when this doesn't work: + *
        + *
      • Consider client A requests something from B. This is done by + * A connecting to B (no existing connection is available), then + * sending an IQ request to B using the new connection and starts + * waiting for a reply. However the connection between them may be + * terminated due to inactivity, and for B to reply, it have to + * establish a new connection. This function takes care of this + * by listening for the packets on all new connections.
      • + *
      • Consider client A and client B concurrently establishes + * connections between them. This will result in two parallell + * connections between the two entities and the two clients may + * choose whatever connection to use when communicating. This + * function takes care of the possibility that if A requests + * something from B using connection #1 and B replies using + * connection #2, the packet will still be collected.
      • + *
      + */ + public IQ getIQResponse(IQ request) throws XMPPException { + XMPPLLConnection connection = getConnection(request.getTo()); + + // Create a packet collector to listen for a response. + // Filter: req.id == rpl.id ^ (rp.iqtype in (result, error)) + CollectorWrapper collector = createPacketCollector( + new AndFilter( + new PacketIDFilter(request.getPacketID()), + new OrFilter( + new IQTypeFilter(IQ.Type.RESULT), + new IQTypeFilter(IQ.Type.ERROR)))); + + connection.sendPacket(request); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the remote host."); + } + + return result; + } + + /** + * Update the presence information announced by the mDNS/DNS-SD daemon. + * The presence object stored in the LLService class will be updated + * with the new information and the daemon will reannounce the changes. + * + * @param presence the new presence information + * @throws XMPPException if an error occurs + */ + public void updatePresence(LLPresence presence) throws XMPPException { + this.presence.update(presence); + + if (initiated) { + updateText(); + reannounceService(); + } + } + + /** + * Get current Link-local presence. + */ + public LLPresence getLocalPresence() { + return presence; + } + + /** + * ConnectionActivityListener listens for link-local connection activity + * such as closed connection and broken connection, and keeps record of + * what active connections exist up to date. + */ + private class ConnectionActivityListener implements ConnectionListener { + private XMPPLLConnection connection; + + ConnectionActivityListener(XMPPLLConnection connection) { + this.connection = connection; + } + + public void connectionClosed() { + removeConnectionRecord(); + } + + public void connectionClosedOnError(Exception e) { + removeConnectionRecord(); + } + + public void reconnectingIn(int seconds) { + } + + public void reconnectionSuccessful() { + } + + public void reconnectionFailed(Exception e) { + } + + private void removeConnectionRecord() { + if (connection.isInitiator()) + removeOutgoingConnection(connection); + else + removeIngoingConnection(connection); + + removeAssociatedConnection(connection); + } + } + + /** + * MessageListener listenes for messages from connections and delivers them + * to the corresponding chat session. If no session is available, a new one + * is created. + */ + private class MessageListener implements PacketListener { + public void processPacket(Packet packet) { + // handle message + if (packet instanceof Message) { + Message message = (Message)packet; + String remoteServiceName = message.getFrom(); + LLPresence presence = getPresenceByServiceName(remoteServiceName); + + // Get existing chat instance or create a new one and deliver + // the message. + try { + getChat(remoteServiceName).deliver(message); + } + catch (XMPPException xe) { + // If getChat throws an exception, it's because no + // presence could be found, thus known origin. + service.unknownOriginMessage(message); + } + } + } + } + + /** + * Initiates a connection in a seperate thread, controlling + * it was established correctly and stream was initiated. + */ + private class ConnectionInitiatorThread extends Thread { + XMPPLLConnection connection; + + ConnectionInitiatorThread(XMPPLLConnection connection) { + this.connection = connection; + } + + public void run() { + try { + connection.initListen(); + } + catch (XMPPException xe) { + // ignore, since its an incoming connection + // there is nothing to save + } + } + } + + /** + * A wrapper class to associate a packet filter with a listener. + */ + private static class ListenerWrapper { + + private PacketListener packetListener; + private PacketFilter packetFilter; + + public ListenerWrapper(PacketListener packetListener, PacketFilter packetFilter) { + this.packetListener = packetListener; + this.packetFilter = packetFilter; + } + + public void notifyListener(Packet packet) { + if (packetFilter == null || packetFilter.accept(packet)) { + packetListener.processPacket(packet); + } + } + + public PacketListener getPacketListener() { + return packetListener; + } + + public PacketFilter getPacketFilter() { + return packetFilter; + } + } + + /** + * Packet Collector Wrapper which is used for collecting packages + * from multiple connections as well as newly established connections (works + * together with LLService constructor. + */ + public class CollectorWrapper { + // Existing collectors. + private Set collectors = + new CopyOnWriteArraySet(); + + // Packet filter for all the collectors. + private PacketFilter packetFilter; + + // A common object used for shared locking between + // the collectors. + private Object lock = new Object(); + + private CollectorWrapper(PacketFilter packetFilter) { + this.packetFilter = packetFilter; + + // Apply to all active connections + for (XMPPLLConnection connection : getConnections()) { + createPacketCollector(connection); + } + } + + /** + * Create a new per-connection packet collector. + * + * @param connection the connection the collector should be added to. + */ + private void createPacketCollector(XMPPLLConnection connection) { + synchronized (connection) { + PacketCollector collector = + connection.createPacketCollector(packetFilter); + collector.setLock(lock); + collectors.add(collector); + } + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available or the timeout has elapased. If the + * timeout elapses without a result, null will be returned. + * + * @param timeout the amount of time to wait for the next packet + * (in milleseconds). + * @return the next available packet. + */ + public synchronized Packet nextResult(long timeout) { + Packet packet; + long waitTime = timeout; + long start = System.currentTimeMillis(); + + try { + while (true) { + for (PacketCollector c : collectors) { + if (c.isCanceled()) + collectors.remove(c); + else { + packet = c.pollResult(); + if (packet != null) + return packet; + } + } + + if (waitTime <= 0) { + break; + } + + // wait + synchronized (lock) { + lock.wait(waitTime); + } + long now = System.currentTimeMillis(); + waitTime -= (now - start); + } + } + catch (InterruptedException ie) { + // ignore + } + + return null; + } + + public void cancel() { + for (PacketCollector c : collectors) { + c.cancel(); + } + collectorWrappers.remove(this); + } + } +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java new file mode 100644 index 0000000..7d50d87 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceConnectionListener.java @@ -0,0 +1,15 @@ +package org.jivesoftware.smack; + +/** + * Notification about when new Link-local connections associated with a + * specific Link-local service has been established. + */ +public interface LLServiceConnectionListener { + + /** + * A new link-local connection has been established. + * + * @param connection the new established connection. + */ + public void connectionCreated(XMPPLLConnection connection); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java new file mode 100644 index 0000000..febaf36 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceListener.java @@ -0,0 +1,14 @@ +package org.jivesoftware.smack; + +/** + * Notification for new Link-local services created. + */ +public interface LLServiceListener { + + /** + * The function called when a new Link-local service is created. + * + * @param service the new service + */ + public void serviceCreated(LLService service); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java new file mode 100644 index 0000000..936fbc0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/LLServiceStateListener.java @@ -0,0 +1,39 @@ +package org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Message; + +/** + * Interface for handeling link-local service events such as + * service closing, service crashes and other events. + */ +public interface LLServiceStateListener { + + /** + * Notification that the service name was changed. + * + * @param newName the new service name + * @param oldName the previous service name + */ + public void serviceNameChanged(String newName, String oldName); + + /** + * Notification that the connection was closed normally. + */ + public void serviceClosed(); + + /** + * Notification that the connection was closed due to an exception. + * + * @param e the exception. + */ + public void serviceClosedOnError(Exception e); + + /** + * Notification that a message with unknown presence was received. + * This could be someone being invisible, meaning no presece is + * announced. + * + * @param e the exception. + */ + public void unknownOriginMessage(Message e); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java new file mode 100644 index 0000000..187c56c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/MessageListener.java @@ -0,0 +1,30 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Message; + +/** + * + */ +public interface MessageListener { + void processMessage(Chat chat, Message message); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java new file mode 100644 index 0000000..c1fe12d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/NonSASLAuthentication.java @@ -0,0 +1,143 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.Authentication; +import org.jivesoftware.smack.packet.IQ; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.auth.callback.Callback; + +/** + * Implementation of JEP-0078: Non-SASL Authentication. Follow the following + * link to obtain more + * information about the JEP. + * + * @author Gaston Dombiak + */ +class NonSASLAuthentication implements UserAuthentication { + + private Connection connection; + + public NonSASLAuthentication(Connection connection) { + super(); + this.connection = connection; + } + + public String authenticate(String username, String resource, CallbackHandler cbh) throws XMPPException { + //Use the callback handler to determine the password, and continue on. + PasswordCallback pcb = new PasswordCallback("Password: ",false); + try { + cbh.handle(new Callback[]{pcb}); + return authenticate(username, String.valueOf(pcb.getPassword()),resource); + } catch (Exception e) { + throw new XMPPException("Unable to determine password.",e); + } + } + + public String authenticate(String username, String password, String resource) throws + XMPPException { + // If we send an authentication packet in "get" mode with just the username, + // the server will return the list of authentication protocols it supports. + Authentication discoveryAuth = new Authentication(); + discoveryAuth.setType(IQ.Type.GET); + discoveryAuth.setUsername(username); + + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(discoveryAuth.getPacketID())); + // Send the packet + connection.sendPacket(discoveryAuth); + // Wait up to a certain number of seconds for a response from the server. + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + // Otherwise, no error so continue processing. + Authentication authTypes = (Authentication) response; + collector.cancel(); + + // Now, create the authentication packet we'll send to the server. + Authentication auth = new Authentication(); + auth.setUsername(username); + + // Figure out if we should use digest or plain text authentication. + if (authTypes.getDigest() != null) { + auth.setDigest(connection.getConnectionID(), password); + } + else if (authTypes.getPassword() != null) { + auth.setPassword(password); + } + else { + throw new XMPPException("Server does not support compatible authentication mechanism."); + } + + auth.setResource(resource); + + collector = connection.createPacketCollector(new PacketIDFilter(auth.getPacketID())); + // Send the packet. + connection.sendPacket(auth); + // Wait up to a certain number of seconds for a response from the server. + response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + if (response == null) { + throw new XMPPException("Authentication failed."); + } + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + // We're done with the collector, so explicitly cancel it. + collector.cancel(); + + return response.getTo(); + } + + public String authenticateAnonymously() throws XMPPException { + // Create the authentication packet we'll send to the server. + Authentication auth = new Authentication(); + + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(auth.getPacketID())); + // Send the packet. + connection.sendPacket(auth); + // Wait up to a certain number of seconds for a response from the server. + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + if (response == null) { + throw new XMPPException("Anonymous login failed."); + } + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + // We're done with the collector, so explicitly cancel it. + collector.cancel(); + + if (response.getTo() != null) { + return response.getTo(); + } + else { + return connection.getServiceName() + "/" + ((Authentication) response).getResource(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java new file mode 100644 index 0000000..223bec6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/OpenTrustManager.java @@ -0,0 +1,49 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * Dummy trust manager that trust all certificates presented by the server. This class + * is used during old SSL connections. + * + * @author Gaston Dombiak + */ +class OpenTrustManager implements X509TrustManager { + + public OpenTrustManager() { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java new file mode 100644 index 0000000..d07f8eb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketCollector.java @@ -0,0 +1,239 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; + +import java.util.LinkedList; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * Provides a mechanism to collect packets into a result queue that pass a + * specified filter. The collector lets you perform blocking and polling + * operations on the result queue. So, a PacketCollector is more suitable to + * use than a {@link PacketListener} when you need to wait for a specific + * result.

      + * + * Each packet collector will queue up a configured number of packets for processing before + * older packets are automatically dropped. The default number is retrieved by + * {@link SmackConfiguration#getPacketCollectorSize()}. + * + * @see Connection#createPacketCollector(PacketFilter) + * @author Matt Tucker + */ +public class PacketCollector { + + /** + * Max number of packets that any one collector can hold. After the max is + * reached, older packets will be automatically dropped from the queue as + * new packets are added. + */ + private int maxPackets = SmackConfiguration.getPacketCollectorSize(); + + private PacketFilter packetFilter; + private LinkedList resultQueue; + private Connection conection; + private boolean cancelled = false; + private Object lock = new Object(); + + /** + * Creates a new packet collector. If the packet filter is null, then + * all packets will match this collector. + * + * @param conection the connection the collector is tied to. + * @param packetFilter determines which packets will be returned by this collector. + */ + protected PacketCollector(Connection conection, PacketFilter packetFilter) { + this.conection = conection; + this.packetFilter = packetFilter; + this.resultQueue = new LinkedList(); + } + + /** + * Set the lock used for notifying about new packages. + * + * @param lock the new lock object. + */ + public synchronized void setLock(Object lock) { + Object oldLock = this.lock; + this.lock = lock; + + // release the threads waiting for a notify on the old lock + synchronized (oldLock) { + oldLock.notifyAll(); + } + } + + /** + * Creates a new packet collector. If the packet filter is null, then + * all packets will match this collector. + * + * @param conection the connection the collector is tied to. + * @param packetFilter determines which packets will be returned by this collector. + * @param maxSize the maximum number of packets that will be stored in the collector. + */ + protected PacketCollector(Connection conection, PacketFilter packetFilter, int maxSize) { + this(conection, packetFilter); + maxPackets = maxSize; + } + + /** + * Explicitly cancels the packet collector so that no more results are + * queued up. Once a packet collector has been cancelled, it cannot be + * re-enabled. Instead, a new packet collector must be created. + */ + public void cancel() { + // If the packet collector has already been cancelled, do nothing. + if (!cancelled) { + cancelled = true; + conection.removePacketCollector(this); + } + } + + /** + * Returns true if the packet collector is canceled. + * + * @return true if canceled, false if still active. + */ + public boolean isCanceled() { + return cancelled; + } + + /** + * Returns the packet filter associated with this packet collector. The packet + * filter is used to determine what packets are queued as results. + * + * @return the packet filter. + */ + public PacketFilter getPacketFilter() { + return packetFilter; + } + + /** + * Polls to see if a packet is currently available and returns it, or + * immediately returns null if no packets are currently in the + * result queue. + * + * @return the next packet result, or null if there are no more + * results. + */ + public synchronized Packet pollResult() { + if (resultQueue.isEmpty()) { + return null; + } + else { + return resultQueue.removeLast(); + } + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available. + * + * @return the next available packet. + */ + public Packet nextResult() { + // Wait indefinitely until there is a result to return. + while (resultQueue.isEmpty()) { + try { + synchronized (lock) { + lock.wait(); + } + } + catch (InterruptedException ie) { + // Ignore. + } + } + return resultQueue.removeLast(); + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available or the timeout has elapased. If the + * timeout elapses without a result, null will be returned. + * + * @param timeout the amount of time to wait for the next packet (in milleseconds). + * @return the next available packet. + */ + public Packet nextResult(long timeout) { + // Wait up to the specified amount of time for a result. + if (resultQueue.isEmpty()) { + long waitTime = timeout; + long start = System.currentTimeMillis(); + try { + // Keep waiting until the specified amount of time has elapsed, or + // a packet is available to return. + while (resultQueue.isEmpty()) { + if (waitTime <= 0) { + break; + } + synchronized (lock) { + lock.wait(waitTime); + } + long now = System.currentTimeMillis(); + waitTime -= (now - start); + start = now; + } + } + catch (InterruptedException ie) { + // Ignore. + } + // Still haven't found a result, so return null. + if (resultQueue.isEmpty()) { + return null; + } + // Return the packet that was found. + else { + return resultQueue.removeLast(); + } + } + // There's already a packet waiting, so return it. + else { + return resultQueue.removeLast(); + } + } + + /** + * Processes a packet to see if it meets the criteria for this packet collector. + * If so, the packet is added to the result queue. + * + * @param packet the packet to process. + */ + protected synchronized void processPacket(Packet packet) { + if (packet == null) { + return; + } + if (packetFilter == null || packetFilter.accept(packet)) { + // If the max number of packets has been reached, remove the oldest one. + if (resultQueue.size() == maxPackets) { + resultQueue.removeLast(); + } + // Add the new packet. + resultQueue.addFirst(packet); + // Notify waiting threads a result is available. + synchronized (lock) { + lock.notifyAll(); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java new file mode 100644 index 0000000..bd89031 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketInterceptor.java @@ -0,0 +1,49 @@ +/** + * $Revision: 2408 $ + * $Date: 2004-11-02 20:53:30 -0300 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Provides a mechanism to intercept and modify packets that are going to be + * sent to the server. PacketInterceptors are added to the {@link Connection} + * together with a {@link org.jivesoftware.smack.filter.PacketFilter} so that only + * certain packets are intercepted and processed by the interceptor.

      + * + * This allows event-style programming -- every time a new packet is found, + * the {@link #interceptPacket(Packet)} method will be called. + * + * @see Connection#addPacketInterceptor(PacketInterceptor, org.jivesoftware.smack.filter.PacketFilter) + * @author Gaston Dombiak + */ +public interface PacketInterceptor { + + /** + * Process the packet that is about to be sent to the server. The intercepted + * packet can be modified by the interceptor.

      + * + * Interceptors are invoked using the same thread that requested the packet + * to be sent, so it's very important that implementations of this method + * not block for any extended period of time. + * + * @param packet the packet to is going to be sent to the server. + */ + public void interceptPacket(Packet packet); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java new file mode 100644 index 0000000..4bc83aa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketListener.java @@ -0,0 +1,48 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Provides a mechanism to listen for packets that pass a specified filter. + * This allows event-style programming -- every time a new packet is found, + * the {@link #processPacket(Packet)} method will be called. This is the + * opposite approach to the functionality provided by a {@link PacketCollector} + * which lets you block while waiting for results. + * + * @see Connection#addPacketListener(PacketListener, org.jivesoftware.smack.filter.PacketFilter) + * @author Matt Tucker + */ +public interface PacketListener { + + /** + * Process the next packet sent to this packet listener.

      + * + * A single thread is responsible for invoking all listeners, so + * it's very important that implementations of this method not block + * for any extended period of time. + * + * @param packet the packet to process. + */ + public void processPacket(Packet packet); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java new file mode 100644 index 0000000..ba79d40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketReader.java @@ -0,0 +1,493 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.Connection.ListenerWrapper; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.sasl.SASLMechanism.Challenge; +import org.jivesoftware.smack.sasl.SASLMechanism.Failure; +import org.jivesoftware.smack.sasl.SASLMechanism.Success; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.util.concurrent.*; + +/** + * Listens for XML traffic from the XMPP server and parses it into packet objects. + * The packet reader also invokes all packet listeners and collectors.

      + * + * @see Connection#createPacketCollector + * @see Connection#addPacketListener + * @author Matt Tucker + */ +class PacketReader { + + protected Thread readerThread; + private ExecutorService listenerExecutor; + + private XMPPConnection connection; + protected XmlPullParser parser; + protected boolean done; + + protected String connectionID = null; + private Semaphore connectionSemaphore; + + protected PacketReader(final XMPPConnection connection) { + this.connection = connection; + this.init(); + } + + /** + * Initializes the reader in order to be used. The reader is initialized during the + * first connection and when reconnecting due to an abruptly disconnection. + */ + protected void init() { + done = false; + connectionID = null; + + readerThread = new Thread() { + public void run() { + parsePackets(this); + } + }; + readerThread.setName("Smack Packet Reader (" + connection.connectionCounterValue + ")"); + readerThread.setDaemon(true); + + // Create an executor to deliver incoming packets to listeners. We'll use a single + // thread with an unbounded queue. + listenerExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() { + + public Thread newThread(Runnable runnable) { + Thread thread = new Thread(runnable, + "Smack Listener Processor (" + connection.connectionCounterValue + ")"); + thread.setDaemon(true); + return thread; + } + }); + + resetParser(); + } + + /** + * Starts the packet reader thread and returns once a connection to the server + * has been established. A connection will be attempted for a maximum of five + * seconds. An XMPPException will be thrown if the connection fails. + * + * @throws XMPPException if the server fails to send an opening stream back + * for more than five seconds. + */ + public void startup() throws XMPPException { + connectionSemaphore = new Semaphore(1); + + readerThread.start(); + // Wait for stream tag before returing. We'll wait a couple of seconds before + // giving up and throwing an error. + try { + connectionSemaphore.acquire(); + + // A waiting thread may be woken up before the wait time or a notify + // (although this is a rare thing). Therefore, we continue waiting + // until either a connectionID has been set (and hence a notify was + // made) or the total wait time has elapsed. + int waitTime = SmackConfiguration.getPacketReplyTimeout(); + connectionSemaphore.tryAcquire(3 * waitTime, TimeUnit.MILLISECONDS); + } + catch (InterruptedException ie) { + // Ignore. + } + if (connectionID == null) { + throw new XMPPException("Connection failed. No response from server."); + } + else { + connection.connectionID = connectionID; + } + } + + /** + * Shuts the packet reader down. + */ + public void shutdown() { + // Notify connection listeners of the connection closing if done hasn't already been set. + if (!done) { + for (ConnectionListener listener : connection.getConnectionListeners()) { + try { + listener.connectionClosed(); + } + catch (Exception e) { + // Catch and print any exception so we can recover + // from a faulty listener and finish the shutdown process + e.printStackTrace(); + } + } + } + done = true; + + // Shut down the listener executor. + listenerExecutor.shutdown(); + } + + /** + * Cleans up all resources used by the packet reader. + */ + void cleanup() { + connection.recvListeners.clear(); + connection.collectors.clear(); + } + + /** + * Sends out a notification that there was an error with the connection + * and closes the connection. + * + * @param e the exception that causes the connection close event. + */ + void notifyConnectionError(Exception e) { + done = true; + // Closes the connection temporary. A reconnection is possible + connection.quickShutdown(); + // Print the stack trace to help catch the problem + e.printStackTrace(); + // Notify connection listeners of the error. + for (ConnectionListener listener : connection.getConnectionListeners()) { + try { + listener.connectionClosedOnError(e); + } + catch (Exception e2) { + // Catch and print any exception so we can recover + // from a faulty listener + e2.printStackTrace(); + } + } + } + + /** + * Sends a notification indicating that the connection was reconnected successfully. + */ + protected void notifyReconnection() { + // Notify connection listeners of the reconnection. + for (ConnectionListener listener : connection.getConnectionListeners()) { + try { + listener.reconnectionSuccessful(); + } + catch (Exception e) { + // Catch and print any exception so we can recover + // from a faulty listener + e.printStackTrace(); + } + } + } + + /** + * Resets the parser using the latest connection's reader. Reseting the parser is necessary + * when the plain connection has been secured or when a new opening stream element is going + * to be sent by the server. + */ + private void resetParser() { + try { + parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(connection.reader); + } + catch (XmlPullParserException xppe) { + xppe.printStackTrace(); + } + } + + /** + * Parse top-level packets in order to process them further. + * + * @param thread the thread that is being used by the reader to parse incoming packets. + */ + protected void parsePackets(Thread thread) { + try { + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("message")) { + processPacket(PacketParserUtils.parseMessage(parser)); + } + else if (parser.getName().equals("iq")) { + processPacket(PacketParserUtils.parseIQ(parser, connection)); + } + else if (parser.getName().equals("presence")) { + processPacket(PacketParserUtils.parsePresence(parser)); + } + // We found an opening stream. Record information about it, then notify + // the connectionID lock so that the packet reader startup can finish. + else if (parser.getName().equals("stream")) { + // Ensure the correct jabber:client namespace is being used. + if ("jabber:client".equals(parser.getNamespace(null))) { + // Get the connection id. + for (int i=0; i + * + * 1) An opening stream was sent from a non XMPP 1.0 compliant server + * 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS + * 3) TLS negotiation was successful + * + */ + protected void releaseConnectionIDLock() { + connectionSemaphore.release(); + } + + /** + * Processes a packet after it's been fully parsed by looping through the installed + * packet collectors and listeners and letting them examine the packet to see if + * they are a match with the filter. + * + * @param packet the packet to process. + */ + protected void processPacket(Packet packet) { + if (packet == null) { + return; + } + + // Loop through all collectors and notify the appropriate ones. + for (PacketCollector collector: connection.getPacketCollectors()) { + collector.processPacket(packet); + } + + // Deliver the incoming packet to listeners. + listenerExecutor.submit(new ListenerNotification(packet)); + } + + private void parseFeatures(XmlPullParser parser) throws Exception { + boolean startTLSReceived = false; + boolean startTLSRequired = false; + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("starttls")) { + startTLSReceived = true; + } + else if (parser.getName().equals("mechanisms")) { + // The server is reporting available SASL mechanisms. Store this information + // which will be used later while logging (i.e. authenticating) into + // the server + connection.getSASLAuthentication() + .setAvailableSASLMethods(PacketParserUtils.parseMechanisms(parser)); + } + else if (parser.getName().equals("bind")) { + // The server requires the client to bind a resource to the stream + connection.getSASLAuthentication().bindingRequired(); + } + else if(parser.getName().equals("ver")){ + connection.getConfiguration().setRosterVersioningAvailable(true); + } + else if(parser.getName().equals("c")){ + String node = parser.getAttributeValue(null, "node"); + String ver = parser.getAttributeValue(null, "ver"); + String capsNode = node+"#"+ver; + connection.getConfiguration().setCapsNode(capsNode); + } + else if (parser.getName().equals("session")) { + // The server supports sessions + connection.getSASLAuthentication().sessionsSupported(); + } + else if (parser.getName().equals("compression")) { + // The server supports stream compression + connection.setAvailableCompressionMethods(PacketParserUtils.parseCompressionMethods(parser)); + } + else if (parser.getName().equals("register")) { + connection.getAccountManager().setSupportsAccountCreation(true); + } + else { + try { + UnknownPacket packet = (UnknownPacket) PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser); + processPacket(packet); + } + catch (ClassCastException ex) { + // ignore + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("starttls")) { + // Confirm the server that we want to use TLS + connection.startTLSReceived(startTLSRequired); + } + else if (parser.getName().equals("required") && startTLSReceived) { + startTLSRequired = true; + } + else if (parser.getName().equals("features")) { + done = true; + } + } + } + + // If TLS is required but the server doesn't offer it, disconnect + // from the server and throw an error. First check if we've already negotiated TLS + // and are secure, however (features get parsed a second time after TLS is established). + if (!connection.isSecureConnection()) { + if (!startTLSReceived && connection.getConfiguration().getSecurityMode() == + ConnectionConfiguration.SecurityMode.required) + { + throw new XMPPException("Server does not support security (TLS), " + + "but security required by connection configuration.", + new XMPPError(XMPPError.Condition.forbidden)); + } + } + + // Release the lock after TLS has been negotiated or we are not insterested in TLS + if (!startTLSReceived || connection.getConfiguration().getSecurityMode() == + ConnectionConfiguration.SecurityMode.disabled) + { + releaseConnectionIDLock(); + } + } + + /** + * A runnable to notify all listeners of a packet. + */ + private class ListenerNotification implements Runnable { + + private Packet packet; + + public ListenerNotification(Packet packet) { + this.packet = packet; + } + + public void run() { + for (ListenerWrapper listenerWrapper : connection.recvListeners.values()) { + listenerWrapper.notifyListener(packet); + } + } + } + + public void quickShutdown() { + done = true; + + // Shut down the listener executor. + listenerExecutor.shutdown(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java new file mode 100644 index 0000000..d0721f8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PacketWriter.java @@ -0,0 +1,320 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Packet; + +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +/** + * Writes packets to a XMPP server. Packets are sent using a dedicated thread. Packet + * interceptors can be registered to dynamically modify packets before they're actually + * sent. Packet listeners can be registered to listen for all outgoing packets. + * + * @see Connection#addPacketInterceptor + * @see Connection#addPacketSendingListener + * + * @author Matt Tucker + */ +class PacketWriter { + + private Thread writerThread; + private Thread keepAliveThread; + private Writer writer; + private XMPPConnection connection; + private final BlockingQueue queue; + private boolean done; + + /** + * Timestamp when the last stanza was sent to the server. This information is used + * by the keep alive process to only send heartbeats when the connection has been idle. + */ + private long lastActive = System.currentTimeMillis(); + + /** + * Creates a new packet writer with the specified connection. + * + * @param connection the connection. + */ + protected PacketWriter(XMPPConnection connection) { + this.queue = new ArrayBlockingQueue(500, true); + this.connection = connection; + init(); + } + + /** + * Initializes the writer in order to be used. It is called at the first connection and also + * is invoked if the connection is disconnected by an error. + */ + protected void init() { + this.writer = connection.writer; + done = false; + + writerThread = new Thread() { + public void run() { + writePackets(this); + } + }; + writerThread.setName("Smack Packet Writer (" + connection.connectionCounterValue + ")"); + writerThread.setDaemon(true); + } + + /** + * Sends the specified packet to the server. + * + * @param packet the packet to send. + */ + public void sendPacket(Packet packet) { + if (!done) { + // Invoke interceptors for the new packet that is about to be sent. Interceptors + // may modify the content of the packet. + connection.firePacketInterceptors(packet); + + try { + queue.put(packet); + } + catch (InterruptedException ie) { + ie.printStackTrace(); + return; + } + synchronized (queue) { + queue.notifyAll(); + } + + // Process packet writer listeners. Note that we're using the sending + // thread so it's expected that listeners are fast. + connection.firePacketSendingListeners(packet); + } + } + + /** + * Starts the packet writer thread and opens a connection to the server. The + * packet writer will continue writing packets until {@link #shutdown} or an + * error occurs. + */ + public void startup() { + writerThread.start(); + } + + /** + * Starts the keep alive process. A white space (aka heartbeat) is going to be + * sent to the server every 30 seconds (by default) since the last stanza was sent + * to the server. + */ + void startKeepAliveProcess() { + // Schedule a keep-alive task to run if the feature is enabled. will write + // out a space character each time it runs to keep the TCP/IP connection open. + int keepAliveInterval = SmackConfiguration.getKeepAliveInterval(); + if (keepAliveInterval > 0) { + KeepAliveTask task = new KeepAliveTask(keepAliveInterval); + keepAliveThread = new Thread(task); + task.setThread(keepAliveThread); + keepAliveThread.setDaemon(true); + keepAliveThread.setName("Smack Keep Alive (" + connection.connectionCounterValue + ")"); + keepAliveThread.start(); + } + } + + void setWriter(Writer writer) { + this.writer = writer; + } + + /** + * Shuts down the packet writer. Once this method has been called, no further + * packets will be written to the server. + */ + public void shutdown() { + done = true; + synchronized (queue) { + queue.notifyAll(); + } + // Interrupt the keep alive thread if one was created + if (keepAliveThread != null) + keepAliveThread.interrupt(); + } + + /** + * Cleans up all resources used by the packet writer. + */ + void cleanup() { + connection.interceptors.clear(); + connection.sendListeners.clear(); + } + + /** + * Returns the next available packet from the queue for writing. + * + * @return the next packet for writing. + */ + private Packet nextPacket() { + Packet packet = null; + // Wait until there's a packet or we're done. + while (!done && (packet = queue.poll()) == null) { + try { + synchronized (queue) { + queue.wait(); + } + } + catch (InterruptedException ie) { + // Do nothing + } + } + return packet; + } + + private void writePackets(Thread thisThread) { + try { + // Open the stream. + openStream(); + // Write out packets from the queue. + while (!done && (writerThread == thisThread)) { + Packet packet = nextPacket(); + if (packet != null) { + synchronized (writer) { + writer.write(packet.toXML()); + writer.flush(); + // Keep track of the last time a stanza was sent to the server + lastActive = System.currentTimeMillis(); + } + } + } + // Flush out the rest of the queue. If the queue is extremely large, it's possible + // we won't have time to entirely flush it before the socket is forced closed + // by the shutdown process. + try { + synchronized (writer) { + while (!queue.isEmpty()) { + Packet packet = queue.remove(); + writer.write(packet.toXML()); + } + writer.flush(); + } + } + catch (Exception e) { + e.printStackTrace(); + } + + // Delete the queue contents (hopefully nothing is left). + queue.clear(); + + // Close the stream. + try { + writer.write(""); + writer.flush(); + } + catch (Exception e) { + // Do nothing + } + finally { + try { + writer.close(); + } + catch (Exception e) { + // Do nothing + } + } + } + catch (IOException ioe){ + if (!done && !connection.isSocketClosed()) { + done = true; + // packetReader could be set to null by an concurrent disconnect() call. + // Therefore Prevent NPE exceptions by checking packetReader. + if (connection.packetReader != null) { + connection.packetReader.notifyConnectionError(ioe); + } + } + } + } + + /** + * Sends to the server a new stream element. This operation may be requested several times + * so we need to encapsulate the logic in one place. This message will be sent while doing + * TLS, SASL and resource binding. + * + * @throws IOException If an error occurs while sending the stanza to the server. + */ + void openStream() throws IOException { + StringBuilder stream = new StringBuilder(); + stream.append(""); + writer.write(stream.toString()); + writer.flush(); + } + + /** + * A TimerTask that keeps connections to the server alive by sending a space + * character on an interval. + */ + private class KeepAliveTask implements Runnable { + + private int delay; + private Thread thread; + + public KeepAliveTask(int delay) { + this.delay = delay; + } + + protected void setThread(Thread thread) { + this.thread = thread; + } + + public void run() { + try { + // Sleep a minimum of 15 seconds plus delay before sending first heartbeat. This will give time to + // properly finish TLS negotiation and then start sending heartbeats. + Thread.sleep(15000 + delay); + } + catch (InterruptedException ie) { + // Do nothing + } + while (!done && keepAliveThread == thread) { + synchronized (writer) { + // Send heartbeat if no packet has been sent to the server for a given time + if (System.currentTimeMillis() - lastActive >= delay) { + try { + writer.write(" "); + writer.flush(); + } + catch (Exception e) { + // Do nothing + } + } + } + try { + // Sleep until we should write the next keep-alive. + Thread.sleep(delay); + } + catch (InterruptedException ie) { + // Do nothing + } + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java new file mode 100644 index 0000000..aec6636 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyList.java @@ -0,0 +1,57 @@ +package org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.PrivacyItem; + +import java.util.List; + +/** + * A privacy list represents a list of contacts that is a read only class used to represent a set of allowed or blocked communications. + * Basically it can:

        + * + *
      • Handle many {@link org.jivesoftware.smack.packet.PrivacyItem}.
      • + *
      • Answer if it is the default list.
      • + *
      • Answer if it is the active list.
      • + *
      + * + * {@link PrivacyItem Privacy Items} can handle different kind of blocking communications based on JID, group, + * subscription type or globally. + * + * @author Francisco Vives + */ +public class PrivacyList { + + /** Holds if it is an active list or not **/ + private boolean isActiveList; + /** Holds if it is an default list or not **/ + private boolean isDefaultList; + /** Holds the list name used to print **/ + private String listName; + /** Holds the list of {@see PrivacyItem} **/ + private List items; + + protected PrivacyList(boolean isActiveList, boolean isDefaultList, + String listName, List privacyItems) { + super(); + this.isActiveList = isActiveList; + this.isDefaultList = isDefaultList; + this.listName = listName; + this.items = privacyItems; + } + + public boolean isActiveList() { + return isActiveList; + } + + public boolean isDefaultList() { + return isDefaultList; + } + + public List getItems() { + return items; + } + + public String toString() { + return listName; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java new file mode 100644 index 0000000..5644ed7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListListener.java @@ -0,0 +1,51 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2006-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.PrivacyItem; + +import java.util.List; + +/** + * Interface to implement classes to listen for server events about privacy communication. + * Listeners are registered with the {@link PrivacyListManager}. + * + * @see PrivacyListManager#addListener + * + * @author Francisco Vives + */ +public interface PrivacyListListener { + + /** + * Set or update a privacy list with PrivacyItem. + * + * @param listName the name of the new or updated privacy list. + * @param listItem the PrivacyItems that rules the list. + */ + public void setPrivacyList(String listName, List listItem); + + /** + * A privacy list has been modified by another. It gets notified. + * + * @param listName the name of the updated privacy list. + */ + public void updatedPrivacyList(String listName); + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java new file mode 100644 index 0000000..eb1d231 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/PrivacyListManager.java @@ -0,0 +1,466 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2006-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Privacy; +import org.jivesoftware.smack.packet.PrivacyItem; + +import java.util.*; + +/** + * A PrivacyListManager is used by XMPP clients to block or allow communications from other + * users. Use the manager to:
        + *
      • Retrieve privacy lists. + *
      • Add, remove, and edit privacy lists. + *
      • Set, change, or decline active lists. + *
      • Set, change, or decline the default list (i.e., the list that is active by default). + *
      + * Privacy Items can handle different kind of permission communications based on JID, group, + * subscription type or globally (@see PrivacyItem). + * + * @author Francisco Vives + */ +public class PrivacyListManager { + + // Keep the list of instances of this class. + private static Map instances = new Hashtable(); + + private Connection connection; + private final List listeners = new ArrayList(); + PacketFilter packetFilter = new AndFilter(new IQTypeFilter(IQ.Type.SET), + new PacketExtensionFilter("query", "jabber:iq:privacy")); + + static { + // Create a new PrivacyListManager on every established connection. In the init() + // method of PrivacyListManager, we'll add a listener that will delete the + // instance when the connection is closed. + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + new PrivacyListManager(connection); + } + }); + } + /** + * Creates a new privacy manager to maintain the communication privacy. Note: no + * information is sent to or received from the server until you attempt to + * get or set the privacy communication.

      + * + * @param connection the XMPP connection. + */ + private PrivacyListManager(Connection connection) { + this.connection = connection; + this.init(); + } + + /** Answer the connection userJID that owns the privacy. + * @return the userJID that owns the privacy + */ + private String getUser() { + return connection.getUser(); + } + + /** + * Initializes the packet listeners of the connection that will notify for any set privacy + * package. + */ + private void init() { + // Register the new instance and associate it with the connection + instances.put(connection, this); + // Add a listener to the connection that removes the registered instance when + // the connection is closed + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + + public void connectionClosedOnError(Exception e) { + // ignore + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + }); + + connection.addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + + if (packet == null || packet.getError() != null) { + return; + } + // The packet is correct. + Privacy privacy = (Privacy) packet; + + // Notifies the event to the listeners. + synchronized (listeners) { + for (PrivacyListListener listener : listeners) { + // Notifies the created or updated privacy lists + for (Map.Entry> entry : privacy.getItemLists().entrySet()) { + String listName = entry.getKey(); + List items = entry.getValue(); + if (items.isEmpty()) { + listener.updatedPrivacyList(listName); + } else { + listener.setPrivacyList(listName, items); + } + } + } + } + + // Send a result package acknowledging the reception of a privacy package. + + // Prepare the IQ packet to send + IQ iq = new IQ() { + public String getChildElementXML() { + return ""; + } + }; + iq.setType(IQ.Type.RESULT); + iq.setFrom(packet.getFrom()); + iq.setPacketID(packet.getPacketID()); + + // Send create & join packet. + connection.sendPacket(iq); + } + }, packetFilter); + } + + /** + * Returns the PrivacyListManager instance associated with a given Connection. + * + * @param connection the connection used to look for the proper PrivacyListManager. + * @return the PrivacyListManager associated with a given Connection. + */ + public static PrivacyListManager getInstanceFor(Connection connection) { + return instances.get(connection); + } + + /** + * Send the {@link Privacy} packet to the server in order to know some privacy content and then + * waits for the answer. + * + * @param requestPrivacy is the {@link Privacy} packet configured properly whose XML + * will be sent to the server. + * @return a new {@link Privacy} with the data received from the server. + * @exception XMPPException if the request or the answer failed, it raises an exception. + */ + private Privacy getRequest(Privacy requestPrivacy) throws XMPPException { + // The request is a get iq type + requestPrivacy.setType(Privacy.Type.GET); + requestPrivacy.setFrom(this.getUser()); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + + // Send create & join packet. + connection.sendPacket(requestPrivacy); + + // Wait up to a certain number of seconds for a reply. + Privacy privacyAnswer = + (Privacy) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Stop queuing results + response.cancel(); + + // Interprete the result and answer the privacy only if it is valid + if (privacyAnswer == null) { + throw new XMPPException("No response from server."); + } + else if (privacyAnswer.getError() != null) { + throw new XMPPException(privacyAnswer.getError()); + } + return privacyAnswer; + } + + /** + * Send the {@link Privacy} packet to the server in order to modify the server privacy and + * waits for the answer. + * + * @param requestPrivacy is the {@link Privacy} packet configured properly whose xml will be sent + * to the server. + * @return a new {@link Privacy} with the data received from the server. + * @exception XMPPException if the request or the answer failed, it raises an exception. + */ + private Packet setRequest(Privacy requestPrivacy) throws XMPPException { + + // The request is a get iq type + requestPrivacy.setType(Privacy.Type.SET); + requestPrivacy.setFrom(this.getUser()); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(requestPrivacy.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + + // Send create & join packet. + connection.sendPacket(requestPrivacy); + + // Wait up to a certain number of seconds for a reply. + Packet privacyAnswer = response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Stop queuing results + response.cancel(); + + // Interprete the result and answer the privacy only if it is valid + if (privacyAnswer == null) { + throw new XMPPException("No response from server."); + } else if (privacyAnswer.getError() != null) { + throw new XMPPException(privacyAnswer.getError()); + } + return privacyAnswer; + } + + /** + * Answer a privacy containing the list structre without {@link PrivacyItem}. + * + * @return a Privacy with the list names. + * @throws XMPPException if an error occurs. + */ + private Privacy getPrivacyWithListNames() throws XMPPException { + + // The request of the list is an empty privacy message + Privacy request = new Privacy(); + + // Send the package to the server and get the answer + return getRequest(request); + } + + /** + * Answer the active privacy list. + * + * @return the privacy list of the active list. + * @throws XMPPException if an error occurs. + */ + public PrivacyList getActiveList() throws XMPPException { + Privacy privacyAnswer = this.getPrivacyWithListNames(); + String listName = privacyAnswer.getActiveName(); + boolean isDefaultAndActive = privacyAnswer.getActiveName() != null + && privacyAnswer.getDefaultName() != null + && privacyAnswer.getActiveName().equals( + privacyAnswer.getDefaultName()); + return new PrivacyList(true, isDefaultAndActive, listName, getPrivacyListItems(listName)); + } + + /** + * Answer the default privacy list. + * + * @return the privacy list of the default list. + * @throws XMPPException if an error occurs. + */ + public PrivacyList getDefaultList() throws XMPPException { + Privacy privacyAnswer = this.getPrivacyWithListNames(); + String listName = privacyAnswer.getDefaultName(); + boolean isDefaultAndActive = privacyAnswer.getActiveName() != null + && privacyAnswer.getDefaultName() != null + && privacyAnswer.getActiveName().equals( + privacyAnswer.getDefaultName()); + return new PrivacyList(isDefaultAndActive, true, listName, getPrivacyListItems(listName)); + } + + /** + * Answer the privacy list items under listName with the allowed and blocked permissions. + * + * @param listName the name of the list to get the allowed and blocked permissions. + * @return a list of privacy items under the list listName. + * @throws XMPPException if an error occurs. + */ + private List getPrivacyListItems(String listName) throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setPrivacyList(listName, new ArrayList()); + + // Send the package to the server and get the answer + Privacy privacyAnswer = getRequest(request); + + return privacyAnswer.getPrivacyList(listName); + } + + /** + * Answer the privacy list items under listName with the allowed and blocked permissions. + * + * @param listName the name of the list to get the allowed and blocked permissions. + * @return a privacy list under the list listName. + * @throws XMPPException if an error occurs. + */ + public PrivacyList getPrivacyList(String listName) throws XMPPException { + + return new PrivacyList(false, false, listName, getPrivacyListItems(listName)); + } + + /** + * Answer every privacy list with the allowed and blocked permissions. + * + * @return an array of privacy lists. + * @throws XMPPException if an error occurs. + */ + public PrivacyList[] getPrivacyLists() throws XMPPException { + Privacy privacyAnswer = this.getPrivacyWithListNames(); + Set names = privacyAnswer.getPrivacyListNames(); + PrivacyList[] lists = new PrivacyList[names.size()]; + boolean isActiveList; + boolean isDefaultList; + int index=0; + for (String listName : names) { + isActiveList = listName.equals(privacyAnswer.getActiveName()); + isDefaultList = listName.equals(privacyAnswer.getDefaultName()); + lists[index] = new PrivacyList(isActiveList, isDefaultList, + listName, getPrivacyListItems(listName)); + index = index + 1; + } + return lists; + } + + + /** + * Set or change the active list to listName. + * + * @param listName the list name to set as the active one. + * @exception XMPPException if the request or the answer failed, it raises an exception. + */ + public void setActiveListName(String listName) throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setActiveName(listName); + + // Send the package to the server + setRequest(request); + } + + /** + * Client declines the use of active lists. + * + * @throws XMPPException if an error occurs. + */ + public void declineActiveList() throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setDeclineActiveList(true); + + // Send the package to the server + setRequest(request); + } + + /** + * Set or change the default list to listName. + * + * @param listName the list name to set as the default one. + * @exception XMPPException if the request or the answer failed, it raises an exception. + */ + public void setDefaultListName(String listName) throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setDefaultName(listName); + + // Send the package to the server + setRequest(request); + } + + /** + * Client declines the use of default lists. + * + * @throws XMPPException if an error occurs. + */ + public void declineDefaultList() throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setDeclineDefaultList(true); + + // Send the package to the server + setRequest(request); + } + + /** + * The client has created a new list. It send the new one to the server. + * + * @param listName the list that has changed its content. + * @param privacyItems a List with every privacy item in the list. + * @throws XMPPException if an error occurs. + */ + public void createPrivacyList(String listName, List privacyItems) throws XMPPException { + + this.updatePrivacyList(listName, privacyItems); + } + + /** + * The client has edited an existing list. It updates the server content with the resulting + * list of privacy items. The {@link PrivacyItem} list MUST contain all elements in the + * list (not the "delta"). + * + * @param listName the list that has changed its content. + * @param privacyItems a List with every privacy item in the list. + * @throws XMPPException if an error occurs. + */ + public void updatePrivacyList(String listName, List privacyItems) throws XMPPException { + + // Build the privacy package to add or update the new list + Privacy request = new Privacy(); + request.setPrivacyList(listName, privacyItems); + + // Send the package to the server + setRequest(request); + } + + /** + * Remove a privacy list. + * + * @param listName the list that has changed its content. + * @throws XMPPException if an error occurs. + */ + public void deletePrivacyList(String listName) throws XMPPException { + + // The request of the list is an privacy message with an empty list + Privacy request = new Privacy(); + request.setPrivacyList(listName, new ArrayList()); + + // Send the package to the server + setRequest(request); + } + + /** + * Adds a packet listener that will be notified of any new update in the user + * privacy communication. + * + * @param listener a packet listener. + */ + public void addListener(PrivacyListListener listener) { + // Keep track of the listener so that we can manually deliver extra + // messages to it later if needed. + synchronized (listeners) { + listeners.add(listener); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java new file mode 100644 index 0000000..514c823 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ReconnectionManager.java @@ -0,0 +1,210 @@ +package org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.StreamError; +import java.util.Random; +/** + * Handles the automatic reconnection process. Every time a connection is dropped without + * the application explictly closing it, the manager automatically tries to reconnect to + * the server.

      + * + * The reconnection mechanism will try to reconnect periodically: + *

        + *
      1. For the first minute it will attempt to connect once every ten seconds. + *
      2. For the next five minutes it will attempt to connect once a minute. + *
      3. If that fails it will indefinitely try to connect once every five minutes. + *
      + * + * @author Francisco Vives + */ +public class ReconnectionManager implements ConnectionListener { + + // Holds the connection to the server + private Connection connection; + private Thread reconnectionThread; + private int randomBase = new Random().nextInt(11) + 5; // between 5 and 15 seconds + + // Holds the state of the reconnection + boolean done = false; + + static { + // Create a new PrivacyListManager on every established connection. In the init() + // method of PrivacyListManager, we'll add a listener that will delete the + // instance when the connection is closed. + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + connection.addConnectionListener(new ReconnectionManager(connection)); + } + }); + } + + private ReconnectionManager(Connection connection) { + this.connection = connection; + } + + + /** + * Returns true if the reconnection mechanism is enabled. + * + * @return true if automatic reconnections are allowed. + */ + private boolean isReconnectionAllowed() { + return !done && !connection.isConnected() + && connection.isReconnectionAllowed(); + } + + /** + * Starts a reconnection mechanism if it was configured to do that. + * The algorithm is been executed when the first connection error is detected. + *

      + * The reconnection mechanism will try to reconnect periodically in this way: + *

        + *
      1. First it will try 6 times every 10 seconds. + *
      2. Then it will try 10 times every 1 minute. + *
      3. Finally it will try indefinitely every 5 minutes. + *
      + */ + synchronized protected void reconnect() { + if (this.isReconnectionAllowed()) { + // Since there is no thread running, creates a new one to attempt + // the reconnection. + // avoid to run duplicated reconnectionThread -- fd: 16/09/2010 + if (reconnectionThread!=null && reconnectionThread.isAlive()) return; + + reconnectionThread = new Thread() { + + /** + * Holds the current number of reconnection attempts + */ + private int attempts = 0; + + /** + * Returns the number of seconds until the next reconnection attempt. + * + * @return the number of seconds until the next reconnection attempt. + */ + private int timeDelay() { + attempts++; + if (attempts > 13) { + return randomBase*6*5; // between 2.5 and 7.5 minutes (~5 minutes) + } + if (attempts > 7) { + return randomBase*6; // between 30 and 90 seconds (~1 minutes) + } + return randomBase; // 10 seconds + } + + /** + * The process will try the reconnection until the connection succeed or the user + * cancell it + */ + public void run() { + // The process will try to reconnect until the connection is established or + // the user cancel the reconnection process {@link Connection#disconnect()} + while (ReconnectionManager.this.isReconnectionAllowed()) { + // Find how much time we should wait until the next reconnection + int remainingSeconds = timeDelay(); + // Sleep until we're ready for the next reconnection attempt. Notify + // listeners once per second about how much time remains before the next + // reconnection attempt. + while (ReconnectionManager.this.isReconnectionAllowed() && + remainingSeconds > 0) + { + try { + Thread.sleep(1000); + remainingSeconds--; + ReconnectionManager.this + .notifyAttemptToReconnectIn(remainingSeconds); + } + catch (InterruptedException e1) { + e1.printStackTrace(); + // Notify the reconnection has failed + ReconnectionManager.this.notifyReconnectionFailed(e1); + } + } + + // Makes a reconnection attempt + try { + if (ReconnectionManager.this.isReconnectionAllowed()) { + connection.connect(); + } + } + catch (XMPPException e) { + // Fires the failed reconnection notification + ReconnectionManager.this.notifyReconnectionFailed(e); + } + } + } + }; + reconnectionThread.setName("Smack Reconnection Manager"); + reconnectionThread.setDaemon(true); + reconnectionThread.start(); + } + } + + /** + * Fires listeners when a reconnection attempt has failed. + * + * @param exception the exception that occured. + */ + protected void notifyReconnectionFailed(Exception exception) { + if (isReconnectionAllowed()) { + for (ConnectionListener listener : connection.connectionListeners) { + listener.reconnectionFailed(exception); + } + } + } + + /** + * Fires listeners when The Connection will retry a reconnection. Expressed in seconds. + * + * @param seconds the number of seconds that a reconnection will be attempted in. + */ + protected void notifyAttemptToReconnectIn(int seconds) { + if (isReconnectionAllowed()) { + for (ConnectionListener listener : connection.connectionListeners) { + listener.reconnectingIn(seconds); + } + } + } + + public void connectionClosed() { + done = true; + } + + public void connectionClosedOnError(Exception e) { + done = false; + if (e instanceof XMPPException) { + XMPPException xmppEx = (XMPPException) e; + StreamError error = xmppEx.getStreamError(); + + // Make sure the error is not null + if (error != null) { + String reason = error.getCode(); + + if ("conflict".equals(reason)) { + return; + } + } + } + + if (this.isReconnectionAllowed()) { + this.reconnect(); + } + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + /** + * The connection has successfull gotten connected. + */ + public void reconnectionSuccessful() { + // ignore + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java new file mode 100644 index 0000000..8f651cc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/Roster.java @@ -0,0 +1,1045 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.RosterPacket; +import org.jivesoftware.smack.util.StringUtils; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Represents a user's roster, which is the collection of users a person receives + * presence updates for. Roster items are categorized into groups for easier management.

      + *

      + * Others users may attempt to subscribe to this user using a subscription request. Three + * modes are supported for handling these requests:

        + *
      • {@link SubscriptionMode#accept_all accept_all} -- accept all subscription requests.
      • + *
      • {@link SubscriptionMode#reject_all reject_all} -- reject all subscription requests.
      • + *
      • {@link SubscriptionMode#manual manual} -- manually process all subscription requests.
      • + *
      + * + * @author Matt Tucker + * @see Connection#getRoster() + */ +public class Roster { + + /** + * The default subscription processing mode to use when a Roster is created. By default + * all subscription requests are automatically accepted. + */ + private static SubscriptionMode defaultSubscriptionMode = SubscriptionMode.accept_all; + private RosterStorage persistentStorage; + + private Connection connection; + private final Map groups; + private final Map entries; + private final List unfiledEntries; + private final List rosterListeners; + private Map> presenceMap; + // The roster is marked as initialized when at least a single roster packet + // has been received and processed. + boolean rosterInitialized = false; + private PresencePacketListener presencePacketListener; + + private SubscriptionMode subscriptionMode = getDefaultSubscriptionMode(); + + private String requestPacketId; + + private boolean mOfflineOnError = true; + + /** + * Returns the default subscription processing mode to use when a new Roster is created. The + * subscription processing mode dictates what action Smack will take when subscription + * requests from other users are made. The default subscription mode + * is {@link SubscriptionMode#accept_all}. + * + * @return the default subscription mode to use for new Rosters + */ + public static SubscriptionMode getDefaultSubscriptionMode() { + return defaultSubscriptionMode; + } + + /** + * Sets the default subscription processing mode to use when a new Roster is created. The + * subscription processing mode dictates what action Smack will take when subscription + * requests from other users are made. The default subscription mode + * is {@link SubscriptionMode#accept_all}. + * + * @param subscriptionMode the default subscription mode to use for new Rosters. + */ + public static void setDefaultSubscriptionMode(SubscriptionMode subscriptionMode) { + defaultSubscriptionMode = subscriptionMode; + } + + Roster(final Connection connection, RosterStorage persistentStorage){ + this(connection); + this.persistentStorage = persistentStorage; + } + + /** + * Creates a new roster. + * + * @param connection an XMPP connection. + */ + Roster(final Connection connection) { + this.connection = connection; + //Disable roster versioning if server doesn't offer support for it + if(!connection.getConfiguration().isRosterVersioningAvailable()){ + persistentStorage=null; + } + groups = new ConcurrentHashMap(); + unfiledEntries = new CopyOnWriteArrayList(); + entries = new ConcurrentHashMap(); + rosterListeners = new CopyOnWriteArrayList(); + presenceMap = new ConcurrentHashMap>(); + // Listen for any roster packets. + PacketFilter rosterFilter = new PacketTypeFilter(RosterPacket.class); + connection.addPacketListener(new RosterPacketListener(), rosterFilter); + // Listen for any presence packets. + PacketFilter presenceFilter = new PacketTypeFilter(Presence.class); + presencePacketListener = new PresencePacketListener(); + connection.addPacketListener(presencePacketListener, presenceFilter); + + // Listen for connection events + final ConnectionListener connectionListener = new AbstractConnectionListener() { + + public void connectionClosed() { + // Changes the presence available contacts to unavailable + setOfflinePresences(); + } + + public void connectionClosedOnError(Exception e) { + // Changes the presence available contacts to unavailable + if (mOfflineOnError) + setOfflinePresences(); + } + + }; + + // if not connected add listener after successful login + if(!this.connection.isConnected()) { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + + public void connectionCreated(Connection connection) { + if(connection.equals(Roster.this.connection)) { + Roster.this.connection.addConnectionListener(connectionListener); + } + + } + }); + } else { + connection.addConnectionListener(connectionListener); + } + } + + /** + * Returns the subscription processing mode, which dictates what action + * Smack will take when subscription requests from other users are made. + * The default subscription mode is {@link SubscriptionMode#accept_all}.

      + *

      + * If using the manual mode, a PacketListener should be registered that + * listens for Presence packets that have a type of + * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}. + * + * @return the subscription mode. + */ + public SubscriptionMode getSubscriptionMode() { + return subscriptionMode; + } + + /** + * Sets the subscription processing mode, which dictates what action + * Smack will take when subscription requests from other users are made. + * The default subscription mode is {@link SubscriptionMode#accept_all}.

      + *

      + * If using the manual mode, a PacketListener should be registered that + * listens for Presence packets that have a type of + * {@link org.jivesoftware.smack.packet.Presence.Type#subscribe}. + * + * @param subscriptionMode the subscription mode. + */ + public void setSubscriptionMode(SubscriptionMode subscriptionMode) { + this.subscriptionMode = subscriptionMode; + } + + public void setOfflineOnError(boolean offlineOnError) { + this.mOfflineOnError = offlineOnError; + } + + /** + * Reloads the entire roster from the server. This is an asynchronous operation, + * which means the method will return immediately, and the roster will be + * reloaded at a later point when the server responds to the reload request. + * + * @throws IllegalStateException if connection is not logged in or logged in anonymously + */ + public void reload() { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Not logged in to server."); + } + if (connection.isAnonymous()) { + throw new IllegalStateException("Anonymous users can't have a roster."); + } + + RosterPacket packet = new RosterPacket(); + if(persistentStorage!=null){ + packet.setVersion(persistentStorage.getRosterVersion()); + } + requestPacketId = packet.getPacketID(); + PacketFilter idFilter = new PacketIDFilter(requestPacketId); + connection.addPacketListener(new RosterResultListener(), idFilter); + connection.sendPacket(packet); + } + + /** + * Adds a listener to this roster. The listener will be fired anytime one or more + * changes to the roster are pushed from the server. + * + * @param rosterListener a roster listener. + */ + public void addRosterListener(RosterListener rosterListener) { + if (!rosterListeners.contains(rosterListener)) { + rosterListeners.add(rosterListener); + } + } + + /** + * Removes a listener from this roster. The listener will be fired anytime one or more + * changes to the roster are pushed from the server. + * + * @param rosterListener a roster listener. + */ + public void removeRosterListener(RosterListener rosterListener) { + rosterListeners.remove(rosterListener); + } + + /** + * Creates a new group.

      + *

      + * Note: you must add at least one entry to the group for the group to be kept + * after a logout/login. This is due to the way that XMPP stores group information. + * + * @param name the name of the group. + * @return a new group. + * @throws IllegalStateException if connection is not logged in or logged in anonymously + */ + public RosterGroup createGroup(String name) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Not logged in to server."); + } + if (connection.isAnonymous()) { + throw new IllegalStateException("Anonymous users can't have a roster."); + } + if (groups.containsKey(name)) { + throw new IllegalArgumentException("Group with name " + name + " alread exists."); + } + + RosterGroup group = new RosterGroup(name, connection); + groups.put(name, group); + return group; + } + + /** + * Creates a new roster entry and presence subscription. The server will asynchronously + * update the roster with the subscription status. + * + * @param user the user. (e.g. johndoe@jabber.org) + * @param name the nickname of the user. + * @param groups the list of group names the entry will belong to, or null if the + * the roster entry won't belong to a group. + * @throws XMPPException if an XMPP exception occurs. + * @throws IllegalStateException if connection is not logged in or logged in anonymously + */ + public void createEntry(String user, String name, String[] groups) throws XMPPException { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Not logged in to server."); + } + if (connection.isAnonymous()) { + throw new IllegalStateException("Anonymous users can't have a roster."); + } + + // Create and send roster entry creation packet. + RosterPacket rosterPacket = new RosterPacket(); + rosterPacket.setType(IQ.Type.SET); + RosterPacket.Item item = new RosterPacket.Item(user, name); + if (groups != null) { + for (String group : groups) { + if (group != null && group.trim().length() > 0) { + item.addGroupName(group); + } + } + } + rosterPacket.addRosterItem(item); + // Wait up to a certain number of seconds for a reply from the server. + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(rosterPacket.getPacketID())); + connection.sendPacket(rosterPacket); + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + + // Create a presence subscription packet and send. + Presence presencePacket = new Presence(Presence.Type.subscribe); + presencePacket.setTo(user); + connection.sendPacket(presencePacket); + } + + private void insertRosterItems(List items){ + Collection addedEntries = new ArrayList(); + Collection updatedEntries = new ArrayList(); + Collection deletedEntries = new ArrayList(); + Iterator iter = items.iterator(); + while(iter.hasNext()){ + insertRosterItem(iter.next(), addedEntries,updatedEntries,deletedEntries); + } + fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); + } + + private void insertRosterItem(RosterPacket.Item item, Collection addedEntries, + Collection updatedEntries, Collection deletedEntries){ + RosterEntry entry = new RosterEntry(item.getUser(), item.getName(), + item.getItemType(), item.getItemStatus(), this, connection); + + // If the packet is of the type REMOVE then remove the entry + if (RosterPacket.ItemType.remove.equals(item.getItemType())) { + // Remove the entry from the entry list. + if (entries.containsKey(item.getUser())) { + entries.remove(item.getUser()); + } + // Remove the entry from the unfiled entry list. + if (unfiledEntries.contains(entry)) { + unfiledEntries.remove(entry); + } + // Removing the user from the roster, so remove any presence information + // about them. + String key = StringUtils.parseName(item.getUser()) + "@" + + StringUtils.parseServer(item.getUser()); + presenceMap.remove(key); + // Keep note that an entry has been removed + if(deletedEntries!=null){ + deletedEntries.add(item.getUser()); + } + } + else { + // Make sure the entry is in the entry list. + if (!entries.containsKey(item.getUser())) { + entries.put(item.getUser(), entry); + // Keep note that an entry has been added + if(addedEntries!=null){ + addedEntries.add(item.getUser()); + } + } + else { + // If the entry was in then list then update its state with the new values + entries.put(item.getUser(), entry); + + // Keep note that an entry has been updated + if(updatedEntries!=null){ + updatedEntries.add(item.getUser()); + } + } + // If the roster entry belongs to any groups, remove it from the + // list of unfiled entries. + if (!item.getGroupNames().isEmpty()) { + unfiledEntries.remove(entry); + } + // Otherwise add it to the list of unfiled entries. + else { + if (!unfiledEntries.contains(entry)) { + unfiledEntries.add(entry); + } + } + } + + // Find the list of groups that the user currently belongs to. + List currentGroupNames = new ArrayList(); + for (RosterGroup group: getGroups()) { + if (group.contains(entry)) { + currentGroupNames.add(group.getName()); + } + } + + // If the packet is not of the type REMOVE then add the entry to the groups + if (!RosterPacket.ItemType.remove.equals(item.getItemType())) { + // Create the new list of groups the user belongs to. + List newGroupNames = new ArrayList(); + for (String groupName : item.getGroupNames()) { + // Add the group name to the list. + newGroupNames.add(groupName); + + // Add the entry to the group. + RosterGroup group = getGroup(groupName); + if (group == null) { + group = createGroup(groupName); + groups.put(groupName, group); + } + // Add the entry. + group.addEntryLocal(entry); + } + + // We have the list of old and new group names. We now need to + // remove the entry from the all the groups it may no longer belong + // to. We do this by subracting the new group set from the old. + for (String newGroupName : newGroupNames) { + currentGroupNames.remove(newGroupName); + } + } + + // Loop through any groups that remain and remove the entries. + // This is neccessary for the case of remote entry removals. + for (String groupName : currentGroupNames) { + RosterGroup group = getGroup(groupName); + group.removeEntryLocal(entry); + if (group.getEntryCount() == 0) { + groups.remove(groupName); + } + } + // Remove all the groups with no entries. We have to do this because + // RosterGroup.removeEntry removes the entry immediately (locally) and the + // group could remain empty. + // TODO Check the performance/logic for rosters with large number of groups + for (RosterGroup group : getGroups()) { + if (group.getEntryCount() == 0) { + groups.remove(group.getName()); + } + } + } + + /** + * Removes a roster entry from the roster. The roster entry will also be removed from the + * unfiled entries or from any roster group where it could belong and will no longer be part + * of the roster. Note that this is an asynchronous call -- Smack must wait for the server + * to send an updated subscription status. + * + * @param entry a roster entry. + * @throws XMPPException if an XMPP error occurs. + * @throws IllegalStateException if connection is not logged in or logged in anonymously + */ + public void removeEntry(RosterEntry entry) throws XMPPException { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Not logged in to server."); + } + if (connection.isAnonymous()) { + throw new IllegalStateException("Anonymous users can't have a roster."); + } + + // Only remove the entry if it's in the entry list. + // The actual removal logic takes place in RosterPacketListenerprocess>>Packet(Packet) + if (!entries.containsKey(entry.getUser())) { + return; + } + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + // Set the item type as REMOVE so that the server will delete the entry + item.setItemType(RosterPacket.ItemType.remove); + packet.addRosterItem(item); + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + + /** + * Returns a count of the entries in the roster. + * + * @return the number of entries in the roster. + */ + public int getEntryCount() { + return getEntries().size(); + } + + /** + * Returns an unmodifiable collection of all entries in the roster, including entries + * that don't belong to any groups. + * + * @return all entries in the roster. + */ + public Collection getEntries() { + Set allEntries = new HashSet(); + // Loop through all roster groups and add their entries to the answer + for (RosterGroup rosterGroup : getGroups()) { + allEntries.addAll(rosterGroup.getEntries()); + } + // Add the roster unfiled entries to the answer + allEntries.addAll(unfiledEntries); + + return Collections.unmodifiableCollection(allEntries); + } + + /** + * Returns a count of the unfiled entries in the roster. An unfiled entry is + * an entry that doesn't belong to any groups. + * + * @return the number of unfiled entries in the roster. + */ + public int getUnfiledEntryCount() { + return unfiledEntries.size(); + } + + /** + * Returns an unmodifiable collection for the unfiled roster entries. An unfiled entry is + * an entry that doesn't belong to any groups. + * + * @return the unfiled roster entries. + */ + public Collection getUnfiledEntries() { + return Collections.unmodifiableList(unfiledEntries); + } + + /** + * Returns the roster entry associated with the given XMPP address or + * null if the user is not an entry in the roster. + * + * @param user the XMPP address of the user (eg "jsmith@example.com"). The address could be + * in any valid format (e.g. "domain/resource", "user@domain" or "user@domain/resource"). + * @return the roster entry or null if it does not exist. + */ + public RosterEntry getEntry(String user) { + if (user == null) { + return null; + } + return entries.get(user.toLowerCase()); + } + + /** + * Returns true if the specified XMPP address is an entry in the roster. + * + * @param user the XMPP address of the user (eg "jsmith@example.com"). The + * address could be in any valid format (e.g. "domain/resource", + * "user@domain" or "user@domain/resource"). + * @return true if the XMPP address is an entry in the roster. + */ + public boolean contains(String user) { + return getEntry(user) != null; + } + + /** + * Returns the roster group with the specified name, or null if the + * group doesn't exist. + * + * @param name the name of the group. + * @return the roster group with the specified name. + */ + public RosterGroup getGroup(String name) { + return groups.get(name); + } + + /** + * Returns the number of the groups in the roster. + * + * @return the number of groups in the roster. + */ + public int getGroupCount() { + return groups.size(); + } + + /** + * Returns an unmodifiable collections of all the roster groups. + * + * @return an iterator for all roster groups. + */ + public Collection getGroups() { + return Collections.unmodifiableCollection(groups.values()); + } + + /** + * Returns the presence info for a particular user. If the user is offline, or + * if no presence data is available (such as when you are not subscribed to the + * user's presence updates), unavailable presence will be returned.

      + *

      + * If the user has several presences (one for each resource), then the presence with + * highest priority will be returned. If multiple presences have the same priority, + * the one with the "most available" presence mode will be returned. In order, + * that's {@link org.jivesoftware.smack.packet.Presence.Mode#chat free to chat}, + * {@link org.jivesoftware.smack.packet.Presence.Mode#available available}, + * {@link org.jivesoftware.smack.packet.Presence.Mode#away away}, + * {@link org.jivesoftware.smack.packet.Presence.Mode#xa extended away}, and + * {@link org.jivesoftware.smack.packet.Presence.Mode#dnd do not disturb}.

      + *

      + * Note that presence information is received asynchronously. So, just after logging + * in to the server, presence values for users in the roster may be unavailable + * even if they are actually online. In other words, the value returned by this + * method should only be treated as a snapshot in time, and may not accurately reflect + * other user's presence instant by instant. If you need to track presence over time, + * such as when showing a visual representation of the roster, consider using a + * {@link RosterListener}. + * + * @param user an XMPP ID. The address could be in any valid format (e.g. + * "domain/resource", "user@domain" or "user@domain/resource"). Any resource + * information that's part of the ID will be discarded. + * @return the user's current presence, or unavailable presence if the user is offline + * or if no presence information is available.. + */ + public Presence getPresence(String user) { + String key = getPresenceMapKey(StringUtils.parseBareAddress(user)); + Map userPresences = presenceMap.get(key); + if (userPresences == null) { + Presence presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return presence; + } + else { + // Find the resource with the highest priority + // Might be changed to use the resource with the highest availability instead. + Presence presence = null; + + for (String resource : userPresences.keySet()) { + Presence p = userPresences.get(resource); + if (!p.isAvailable()) { + continue; + } + // Chose presence with highest priority first. + if (presence == null || p.getPriority() > presence.getPriority()) { + presence = p; + } + // If equal priority, choose "most available" by the mode value. + else if (p.getPriority() == presence.getPriority()) { + Presence.Mode pMode = p.getMode(); + // Default to presence mode of available. + if (pMode == null) { + pMode = Presence.Mode.available; + } + Presence.Mode presenceMode = presence.getMode(); + // Default to presence mode of available. + if (presenceMode == null) { + presenceMode = Presence.Mode.available; + } + if (pMode.compareTo(presenceMode) < 0) { + presence = p; + } + } + } + if (presence == null) { + presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return presence; + } + else { + return presence; + } + } + } + + /** + * Returns the presence info for a particular user's resource, or unavailable presence + * if the user is offline or if no presence information is available, such as + * when you are not subscribed to the user's presence updates. + * + * @param userWithResource a fully qualified XMPP ID including a resource (user@domain/resource). + * @return the user's current presence, or unavailable presence if the user is offline + * or if no presence information is available. + */ + public Presence getPresenceResource(String userWithResource) { + String key = getPresenceMapKey(userWithResource); + String resource = StringUtils.parseResource(userWithResource); + Map userPresences = presenceMap.get(key); + if (userPresences == null) { + Presence presence = new Presence(Presence.Type.unavailable); + presence.setFrom(userWithResource); + return presence; + } + else { + Presence presence = userPresences.get(resource); + if (presence == null) { + presence = new Presence(Presence.Type.unavailable); + presence.setFrom(userWithResource); + return presence; + } + else { + return presence; + } + } + } + + /** + * Returns an iterator (of Presence objects) for all of a user's current presences + * or an unavailable presence if the user is unavailable (offline) or if no presence + * information is available, such as when you are not subscribed to the user's presence + * updates. + * + * @param user a XMPP ID, e.g. jdoe@example.com. + * @return an iterator (of Presence objects) for all the user's current presences, + * or an unavailable presence if the user is offline or if no presence information + * is available. + */ + public Iterator getPresences(String user) { + String key = getPresenceMapKey(user); + Map userPresences = presenceMap.get(key); + if (userPresences == null) { + Presence presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return Arrays.asList(presence).iterator(); + } + else { + Collection answer = new ArrayList(); + for (Presence presence : userPresences.values()) { + if (presence.isAvailable()) { + answer.add(presence); + } + } + if (!answer.isEmpty()) { + return answer.iterator(); + } + else { + Presence presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return Arrays.asList(presence).iterator(); + } + } + } + + /** + * Cleans up all resources used by the roster. + */ + void cleanup() { + rosterListeners.clear(); + } + + /** + * Returns the key to use in the presenceMap for a fully qualified XMPP ID. + * The roster can contain any valid address format such us "domain/resource", + * "user@domain" or "user@domain/resource". If the roster contains an entry + * associated with the fully qualified XMPP ID then use the fully qualified XMPP + * ID as the key in presenceMap, otherwise use the bare address. Note: When the + * key in presenceMap is a fully qualified XMPP ID, the userPresences is useless + * since it will always contain one entry for the user. + * + * @param user the bare or fully qualified XMPP ID, e.g. jdoe@example.com or + * jdoe@example.com/Work. + * @return the key to use in the presenceMap for the fully qualified XMPP ID. + */ + private String getPresenceMapKey(String user) { + if (user == null) { + return null; + } + String key = user; + if (!contains(user)) { + key = StringUtils.parseBareAddress(user); + } + return key.toLowerCase(); + } + + /** + * Changes the presence of available contacts offline by simulating an unavailable + * presence sent from the server. After a disconnection, every Presence is set + * to offline. + */ + public void setOfflinePresences() { + Presence packetUnavailable; + for (String user : presenceMap.keySet()) { + Map resources = presenceMap.get(user); + if (resources != null) { + for (String resource : resources.keySet()) { + packetUnavailable = new Presence(Presence.Type.unavailable); + packetUnavailable.setFrom(user + "/" + resource); + presencePacketListener.processPacket(packetUnavailable); + } + } + } + } + + /** + * Fires roster changed event to roster listeners indicating that the + * specified collections of contacts have been added, updated or deleted + * from the roster. + * + * @param addedEntries the collection of address of the added contacts. + * @param updatedEntries the collection of address of the updated contacts. + * @param deletedEntries the collection of address of the deleted contacts. + */ + private void fireRosterChangedEvent(Collection addedEntries, Collection updatedEntries, + Collection deletedEntries) { + for (RosterListener listener : rosterListeners) { + if (!addedEntries.isEmpty()) { + listener.entriesAdded(addedEntries); + } + if (!updatedEntries.isEmpty()) { + listener.entriesUpdated(updatedEntries); + } + if (!deletedEntries.isEmpty()) { + listener.entriesDeleted(deletedEntries); + } + } + } + + /** + * Fires roster presence changed event to roster listeners. + * + * @param presence the presence change. + */ + private void fireRosterPresenceEvent(Presence presence) { + for (RosterListener listener : rosterListeners) { + listener.presenceChanged(presence); + } + } + + /** + * An enumeration for the subscription mode options. + */ + public enum SubscriptionMode { + + /** + * Automatically accept all subscription and unsubscription requests. This is + * the default mode and is suitable for simple client. More complex client will + * likely wish to handle subscription requests manually. + */ + accept_all, + + /** + * Automatically reject all subscription requests. + */ + reject_all, + + /** + * Subscription requests are ignored, which means they must be manually + * processed by registering a listener for presence packets and then looking + * for any presence requests that have the type Presence.Type.SUBSCRIBE or + * Presence.Type.UNSUBSCRIBE. + */ + manual + } + + /** + * Listens for all presence packets and processes them. + */ + private class PresencePacketListener implements PacketListener { + + public void processPacket(Packet packet) { + Presence presence = (Presence) packet; + String from = presence.getFrom(); + String key = getPresenceMapKey(from); + + // If an "available" presence, add it to the presence map. Each presence + // map will hold for a particular user a map with the presence + // packets saved for each resource. + if (presence.getType() == Presence.Type.available) { + Map userPresences; + // Get the user presence map + if (presenceMap.get(key) == null) { + userPresences = new ConcurrentHashMap(); + presenceMap.put(key, userPresences); + } + else { + userPresences = presenceMap.get(key); + } + // See if an offline presence was being stored in the map. If so, remove + // it since we now have an online presence. + userPresences.remove(""); + // Add the new presence, using the resources as a key. + userPresences.put(StringUtils.parseResource(from), presence); + // If the user is in the roster, fire an event. + RosterEntry entry = entries.get(key); + if (entry != null) { + fireRosterPresenceEvent(presence); + } + } + // If an "unavailable" packet. + else if (presence.getType() == Presence.Type.unavailable) { + // If no resource, this is likely an offline presence as part of + // a roster presence flood. In that case, we store it. + if ("".equals(StringUtils.parseResource(from))) { + Map userPresences; + // Get the user presence map + if (presenceMap.get(key) == null) { + userPresences = new ConcurrentHashMap(); + presenceMap.put(key, userPresences); + } + else { + userPresences = presenceMap.get(key); + } + userPresences.put("", presence); + } + // Otherwise, this is a normal offline presence. + else if (presenceMap.get(key) != null) { + Map userPresences = presenceMap.get(key); + // Store the offline presence, as it may include extra information + // such as the user being on vacation. + userPresences.put(StringUtils.parseResource(from), presence); + } + // If the user is in the roster, fire an event. + RosterEntry entry = entries.get(key); + if (entry != null) { + fireRosterPresenceEvent(presence); + } + } + else if (presence.getType() == Presence.Type.subscribe) { + if (subscriptionMode == SubscriptionMode.accept_all) { + // Accept all subscription requests. + Presence response = new Presence(Presence.Type.subscribed); + response.setTo(presence.getFrom()); + connection.sendPacket(response); + } + else if (subscriptionMode == SubscriptionMode.reject_all) { + // Reject all subscription requests. + Presence response = new Presence(Presence.Type.unsubscribed); + response.setTo(presence.getFrom()); + connection.sendPacket(response); + } + // Otherwise, in manual mode so ignore. + } + else if (presence.getType() == Presence.Type.unsubscribe) { + if (subscriptionMode != SubscriptionMode.manual) { + // Acknowledge and accept unsubscription notification so that the + // server will stop sending notifications saying that the contact + // has unsubscribed to our presence. + Presence response = new Presence(Presence.Type.unsubscribed); + response.setTo(presence.getFrom()); + connection.sendPacket(response); + } + // Otherwise, in manual mode so ignore. + } + // Error presence packets from a bare JID mean we invalidate all existing + // presence info for the user. + else if (presence.getType() == Presence.Type.error && + "".equals(StringUtils.parseResource(from))) + { + Map userPresences; + if (!presenceMap.containsKey(key)) { + userPresences = new ConcurrentHashMap(); + presenceMap.put(key, userPresences); + } + else { + userPresences = presenceMap.get(key); + // Any other presence data is invalidated by the error packet. + userPresences.clear(); + } + // Set the new presence using the empty resource as a key. + userPresences.put("", presence); + // If the user is in the roster, fire an event. + RosterEntry entry = entries.get(key); + if (entry != null) { + fireRosterPresenceEvent(presence); + } + } + } + } + + /** + * Listen for empty IQ results which indicate that the client has already a current + * roster version + * @author Till Klocke + * + */ + + private class RosterResultListener implements PacketListener{ + + public void processPacket(Packet packet) { + if(packet instanceof IQ){ + IQ result = (IQ)packet; + if(result.getType().equals(IQ.Type.RESULT) && result.getExtensions().isEmpty()){ + Collection addedEntries = new ArrayList(); + Collection updatedEntries = new ArrayList(); + Collection deletedEntries = new ArrayList(); + if(persistentStorage!=null){ + for(RosterPacket.Item item : persistentStorage.getEntries()){ + insertRosterItem(item,addedEntries,updatedEntries,deletedEntries); + } + } + synchronized (Roster.this) { + rosterInitialized = true; + Roster.this.notifyAll(); + } + fireRosterChangedEvent(addedEntries,updatedEntries,deletedEntries); + } + } + connection.removePacketListener(this); + } + } + + /** + * Listens for all roster packets and processes them. + */ + private class RosterPacketListener implements PacketListener { + + public void processPacket(Packet packet) { + // Keep a registry of the entries that were added, deleted or updated. An event + // will be fired for each affected entry + Collection addedEntries = new ArrayList(); + Collection updatedEntries = new ArrayList(); + Collection deletedEntries = new ArrayList(); + + String version=null; + RosterPacket rosterPacket = (RosterPacket) packet; + List rosterItems = new ArrayList(); + for(RosterPacket.Item item : rosterPacket.getRosterItems()){ + rosterItems.add(item); + } + //Here we check if the server send a versioned roster, if not we do not use + //the roster storage to store entries and work like in the old times + if(rosterPacket.getVersion()==null){ + persistentStorage=null; + } else{ + version = rosterPacket.getVersion(); + } + + if(persistentStorage!=null && !rosterInitialized){ + for(RosterPacket.Item item : persistentStorage.getEntries()){ + rosterItems.add(item); + } + } + + for (RosterPacket.Item item : rosterItems) { + insertRosterItem(item,addedEntries,updatedEntries,deletedEntries); + } + if(persistentStorage!=null){ + for (RosterPacket.Item i : rosterPacket.getRosterItems()){ + if(i.getItemType().equals(RosterPacket.ItemType.remove)){ + persistentStorage.removeEntry(i.getUser()); + } + else{ + persistentStorage.addEntry(i, version); + } + } + } + // Mark the roster as initialized. + synchronized (Roster.this) { + rosterInitialized = true; + Roster.this.notifyAll(); + } + + // Fire event for roster listeners. + fireRosterChangedEvent(addedEntries, updatedEntries, deletedEntries); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java new file mode 100644 index 0000000..31f7a1e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterEntry.java @@ -0,0 +1,239 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.RosterPacket; + +import java.util.*; + +/** + * Each user in your roster is represented by a roster entry, which contains the user's + * JID and a name or nickname you assign. + * + * @author Matt Tucker + */ +public class RosterEntry { + + private String user; + private String name; + private RosterPacket.ItemType type; + private RosterPacket.ItemStatus status; + final private Roster roster; + final private Connection connection; + + /** + * Creates a new roster entry. + * + * @param user the user. + * @param name the nickname for the entry. + * @param type the subscription type. + * @param status the subscription status (related to subscriptions pending to be approbed). + * @param connection a connection to the XMPP server. + */ + RosterEntry(String user, String name, RosterPacket.ItemType type, + RosterPacket.ItemStatus status, Roster roster, Connection connection) { + this.user = user; + this.name = name; + this.type = type; + this.status = status; + this.roster = roster; + this.connection = connection; + } + + /** + * Returns the JID of the user associated with this entry. + * + * @return the user associated with this entry. + */ + public String getUser() { + return user; + } + + /** + * Returns the name associated with this entry. + * + * @return the name. + */ + public String getName() { + return name; + } + + /** + * Sets the name associated with this entry. + * + * @param name the name. + */ + public void setName(String name) { + // Do nothing if the name hasn't changed. + if (name != null && name.equals(this.name)) { + return; + } + this.name = name; + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + packet.addRosterItem(toRosterItem(this)); + connection.sendPacket(packet); + } + + /** + * Updates the state of the entry with the new values. + * + * @param name the nickname for the entry. + * @param type the subscription type. + * @param status the subscription status (related to subscriptions pending to be approbed). + */ + void updateState(String name, RosterPacket.ItemType type, RosterPacket.ItemStatus status) { + this.name = name; + this.type = type; + this.status = status; + } + + /** + * Returns an unmodifiable collection of the roster groups that this entry belongs to. + * + * @return an iterator for the groups this entry belongs to. + */ + public Collection getGroups() { + List results = new ArrayList(); + // Loop through all roster groups and find the ones that contain this + // entry. This algorithm should be fine + for (RosterGroup group: roster.getGroups()) { + if (group.contains(this)) { + results.add(group); + } + } + return Collections.unmodifiableCollection(results); + } + + /** + * Returns the roster subscription type of the entry. When the type is + * RosterPacket.ItemType.none or RosterPacket.ItemType.from, + * refer to {@link RosterEntry getStatus()} to see if a subscription request + * is pending. + * + * @return the type. + */ + public RosterPacket.ItemType getType() { + return type; + } + + /** + * Returns the roster subscription status of the entry. When the status is + * RosterPacket.ItemStatus.SUBSCRIPTION_PENDING, the contact has to answer the + * subscription request. + * + * @return the status. + */ + public RosterPacket.ItemStatus getStatus() { + return status; + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + if (name != null) { + buf.append(name).append(": "); + } + buf.append(user); + Collection groups = getGroups(); + if (!groups.isEmpty()) { + buf.append(" ["); + Iterator iter = groups.iterator(); + RosterGroup group = iter.next(); + buf.append(group.getName()); + while (iter.hasNext()) { + buf.append(", "); + group = iter.next(); + buf.append(group.getName()); + } + buf.append("]"); + } + return buf.toString(); + } + + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object != null && object instanceof RosterEntry) { + return user.equals(((RosterEntry)object).getUser()); + } + else { + return false; + } + } + + /** + * Indicates whether some other object is "equal to" this by comparing all members. + *

      + * The {@link #equals(Object)} method returns true if the user JIDs are equal. + * + * @param obj the reference object with which to compare. + * @return true if this object is the same as the obj argument; false + * otherwise. + */ + public boolean equalsDeep(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RosterEntry other = (RosterEntry) obj; + if (name == null) { + if (other.name != null) + return false; + } + else if (!name.equals(other.name)) + return false; + if (status == null) { + if (other.status != null) + return false; + } + else if (!status.equals(other.status)) + return false; + if (type == null) { + if (other.type != null) + return false; + } + else if (!type.equals(other.type)) + return false; + if (user == null) { + if (other.user != null) + return false; + } + else if (!user.equals(other.user)) + return false; + return true; + } + + static RosterPacket.Item toRosterItem(RosterEntry entry) { + RosterPacket.Item item = new RosterPacket.Item(entry.getUser(), entry.getName()); + item.setItemType(entry.getType()); + item.setItemStatus(entry.getStatus()); + // Set the correct group names for the item. + for (RosterGroup group : entry.getGroups()) { + item.addGroupName(group.getName()); + } + return item; + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java new file mode 100644 index 0000000..e768f6d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterGroup.java @@ -0,0 +1,253 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.RosterPacket; +import org.jivesoftware.smack.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * A group of roster entries. + * + * @see Roster#getGroup(String) + * @author Matt Tucker + */ +public class RosterGroup { + + private String name; + private Connection connection; + private final List entries; + + /** + * Creates a new roster group instance. + * + * @param name the name of the group. + * @param connection the connection the group belongs to. + */ + RosterGroup(String name, Connection connection) { + this.name = name; + this.connection = connection; + entries = new ArrayList(); + } + + /** + * Returns the name of the group. + * + * @return the name of the group. + */ + public String getName() { + return name; + } + + /** + * Sets the name of the group. Changing the group's name is like moving all the group entries + * of the group to a new group specified by the new name. Since this group won't have entries + * it will be removed from the roster. This means that all the references to this object will + * be invalid and will need to be updated to the new group specified by the new name. + * + * @param name the name of the group. + */ + public void setName(String name) { + synchronized (entries) { + for (RosterEntry entry : entries) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + item.removeGroupName(this.name); + item.addGroupName(name); + packet.addRosterItem(item); + connection.sendPacket(packet); + } + } + } + + /** + * Returns the number of entries in the group. + * + * @return the number of entries in the group. + */ + public int getEntryCount() { + synchronized (entries) { + return entries.size(); + } + } + + /** + * Returns an unmodifiable collection of all entries in the group. + * + * @return all entries in the group. + */ + public Collection getEntries() { + synchronized (entries) { + return Collections.unmodifiableList(new ArrayList(entries)); + } + } + + /** + * Returns the roster entry associated with the given XMPP address or + * null if the user is not an entry in the group. + * + * @param user the XMPP address of the user (eg "jsmith@example.com"). + * @return the roster entry or null if it does not exist in the group. + */ + public RosterEntry getEntry(String user) { + if (user == null) { + return null; + } + // Roster entries never include a resource so remove the resource + // if it's a part of the XMPP address. + user = StringUtils.parseBareAddress(user); + String userLowerCase = user.toLowerCase(); + synchronized (entries) { + for (RosterEntry entry : entries) { + if (entry.getUser().equals(userLowerCase)) { + return entry; + } + } + } + return null; + } + + /** + * Returns true if the specified entry is part of this group. + * + * @param entry a roster entry. + * @return true if the entry is part of this group. + */ + public boolean contains(RosterEntry entry) { + synchronized (entries) { + return entries.contains(entry); + } + } + + /** + * Returns true if the specified XMPP address is an entry in this group. + * + * @param user the XMPP address of the user. + * @return true if the XMPP address is an entry in this group. + */ + public boolean contains(String user) { + return getEntry(user) != null; + } + + /** + * Adds a roster entry to this group. If the entry was unfiled then it will be removed from + * the unfiled list and will be added to this group. + * Note that this is an asynchronous call -- Smack must wait for the server + * to receive the updated roster. + * + * @param entry a roster entry. + * @throws XMPPException if an error occured while trying to add the entry to the group. + */ + public void addEntry(RosterEntry entry) throws XMPPException { + PacketCollector collector = null; + // Only add the entry if it isn't already in the list. + synchronized (entries) { + if (!entries.contains(entry)) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + item.addGroupName(getName()); + packet.addRosterItem(item); + // Wait up to a certain number of seconds for a reply from the server. + collector = connection + .createPacketCollector(new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + } + } + if (collector != null) { + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + } + + /** + * Removes a roster entry from this group. If the entry does not belong to any other group + * then it will be considered as unfiled, therefore it will be added to the list of unfiled + * entries. + * Note that this is an asynchronous call -- Smack must wait for the server + * to receive the updated roster. + * + * @param entry a roster entry. + * @throws XMPPException if an error occured while trying to remove the entry from the group. + */ + public void removeEntry(RosterEntry entry) throws XMPPException { + PacketCollector collector = null; + // Only remove the entry if it's in the entry list. + // Remove the entry locally, if we wait for RosterPacketListenerprocess>>Packet(Packet) + // to take place the entry will exist in the group until a packet is received from the + // server. + synchronized (entries) { + if (entries.contains(entry)) { + RosterPacket packet = new RosterPacket(); + packet.setType(IQ.Type.SET); + RosterPacket.Item item = RosterEntry.toRosterItem(entry); + item.removeGroupName(this.getName()); + packet.addRosterItem(item); + // Wait up to a certain number of seconds for a reply from the server. + collector = connection + .createPacketCollector(new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + } + } + if (collector != null) { + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + } + + public void addEntryLocal(RosterEntry entry) { + // Only add the entry if it isn't already in the list. + synchronized (entries) { + entries.remove(entry); + entries.add(entry); + } + } + + void removeEntryLocal(RosterEntry entry) { + // Only remove the entry if it's in the entry list. + synchronized (entries) { + if (entries.contains(entry)) { + entries.remove(entry); + } + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java new file mode 100644 index 0000000..8be9ddc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterListener.java @@ -0,0 +1,83 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.Presence; + +import java.util.Collection; + +/** + * A listener that is fired any time a roster is changed or the presence of + * a user in the roster is changed. + * + * @see Roster#addRosterListener(RosterListener) + * @author Matt Tucker + */ +public interface RosterListener { + + /** + * Called when roster entries are added. + * + * @param addresses the XMPP addresses of the contacts that have been added to the roster. + */ + public void entriesAdded(Collection addresses); + + /** + * Called when a roster entries are updated. + * + * @param addresses the XMPP addresses of the contacts whose entries have been updated. + */ + public void entriesUpdated(Collection addresses); + + /** + * Called when a roster entries are removed. + * + * @param addresses the XMPP addresses of the contacts that have been removed from the roster. + */ + public void entriesDeleted(Collection addresses); + + /** + * Called when the presence of a roster entry is changed. Care should be taken + * when using the presence data delivered as part of this event. Specifically, + * when a user account is online with multiple resources, the UI should account + * for that. For example, say a user is online with their desktop computer and + * mobile phone. If the user logs out of the IM client on their mobile phone, the + * user should not be shown in the roster (contact list) as offline since they're + * still available as another resource.

      + * + * To get the current "best presence" for a user after the presence update, query the roster: + *

      +     *    String user = presence.getFrom();
      +     *    Presence bestPresence = roster.getPresence(user);
      +     * 
      + * + * That will return the presence value for the user with the highest priority and + * availability. + * + * Note that this listener is triggered for presence (mode) changes only + * (e.g presence of types available and unavailable. Subscription-related + * presence packets will not cause this method to be called. + * + * @param presence the presence that changed. + * @see Roster#getPresence(String) + */ + public void presenceChanged(Presence presence); +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java new file mode 100644 index 0000000..8c5f386 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/RosterStorage.java @@ -0,0 +1,54 @@ +package org.jivesoftware.smack; + +import java.util.List; + +import org.jivesoftware.smack.packet.RosterPacket; + +/** + * This is an interface for persistent roster storage needed to implement XEP-0237 + * @author Till Klocke + * + */ + +public interface RosterStorage { + + /** + * This method returns a List object with all RosterEntries contained in this store. + * @return List object with all entries in local roster storage + */ + public List getEntries(); + /** + * This method returns the RosterEntry which belongs to a specific user. + * @param bareJid The bare JID of the RosterEntry + * @return The RosterEntry which belongs to that user + */ + public RosterPacket.Item getEntry(String bareJid); + /** + * Returns the number of entries in this roster store + * @return the number of entries + */ + public int getEntryCount(); + /** + * This methos returns the version number as specified by the "ver" attribute + * of the local store. Should return an emtpy string if store is empty. + * @return local roster version + */ + public String getRosterVersion(); + /** + * This method stores a new RosterEntry in this store or overrides an existing one. + * If ver is null an IllegalArgumentException should be thrown. + * @param entry the entry to save + * @param ver the version this roster push contained + */ + public void addEntry(RosterPacket.Item item, String ver); + /** + * Removes an entry from the persistent storage + * @param bareJid The bare JID of the entry to be removed + */ + public void removeEntry(String bareJid); + /** + * Update an entry which has been modified locally + * @param entry the entry to be updated + */ + public void updateLocalEntry(RosterPacket.Item item); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java new file mode 100644 index 0000000..f9f963a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java @@ -0,0 +1,590 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.Bind; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Session; +import org.jivesoftware.smack.sasl.*; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.*; + +/** + *

      This class is responsible authenticating the user using SASL, binding the resource + * to the connection and establishing a session with the server.

      + * + *

      Once TLS has been negotiated (i.e. the connection has been secured) it is possible to + * register with the server, authenticate using Non-SASL or authenticate using SASL. If the + * server supports SASL then Smack will first try to authenticate using SASL. But if that + * fails then Non-SASL will be tried.

      + * + *

      The server may support many SASL mechanisms to use for authenticating. Out of the box + * Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use + * {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered + * mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default, + * the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}.

      + * + *

      Once the user has been authenticated with SASL, it is necessary to bind a resource for + * the connection. If no resource is passed in {@link #authenticate(String, String, String)} + * then the server will assign a resource for the connection. In case a resource is passed + * then the server will receive the desired resource but may assign a modified resource for + * the connection.

      + * + *

      Once a resource has been binded and if the server supports sessions then Smack will establish + * a session so that instant messaging and presence functionalities may be used.

      + * + * @see org.jivesoftware.smack.sasl.SASLMechanism + * + * @author Gaston Dombiak + * @author Jay Kline + */ +public class SASLAuthentication implements UserAuthentication { + + private static Map implementedMechanisms = new HashMap(); + private static List mechanismsPreferences = new ArrayList(); + + private Connection connection; + private Collection serverMechanisms = new ArrayList(); + private SASLMechanism currentMechanism = null; + /** + * Boolean indicating if SASL negotiation has finished and was successful. + */ + private boolean saslNegotiated; + /** + * Boolean indication if SASL authentication has failed. When failed the server may end + * the connection. + */ + private boolean saslFailed; + private boolean resourceBinded; + private boolean sessionSupported; + /** + * The SASL related error condition if there was one provided by the server. + */ + private String errorCondition; + + static { + + // Register SASL mechanisms supported by Smack + registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class); + registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class); + registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class); + registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class); + registerSASLMechanism("PLAIN", SASLPlainMechanism.class); + registerSASLMechanism("ANONYMOUS", SASLAnonymous.class); + +// supportSASLMechanism("GSSAPI",0); + supportSASLMechanism("DIGEST-MD5",0); +// supportSASLMechanism("CRAM-MD5",2); + supportSASLMechanism("PLAIN",1); + supportSASLMechanism("ANONYMOUS",2); + + } + + /** + * Registers a new SASL mechanism + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + * @param mClass a SASLMechanism subclass. + */ + public static void registerSASLMechanism(String name, Class mClass) { + implementedMechanisms.put(name, mClass); + } + + /** + * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't + * be possible to authenticate users using the removed SASL mechanism. It also removes the + * mechanism from the supported list. + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void unregisterSASLMechanism(String name) { + implementedMechanisms.remove(name); + mechanismsPreferences.remove(name); + } + + + /** + * Registers a new SASL mechanism in the specified preference position. The client will try + * to authenticate using the most prefered SASL mechanism that is also supported by the server. + * The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)} + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void supportSASLMechanism(String name) { + mechanismsPreferences.add(0, name); + } + + /** + * Registers a new SASL mechanism in the specified preference position. The client will try + * to authenticate using the most prefered SASL mechanism that is also supported by the server. + * Use the index parameter to set the level of preference of the new SASL mechanism. + * A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be + * registered via {@link #registerSASLMechanism(String, Class)} + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + * @param index preference position amongst all the implemented SASL mechanism. Starts with 0. + */ + public static void supportSASLMechanism(String name, int index) { + mechanismsPreferences.add(index, name); + } + + /** + * Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't + * be possible to authenticate users using the removed SASL mechanism. Note that the mechanism + * is still registered, but will just not be used. + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void unsupportSASLMechanism(String name) { + mechanismsPreferences.remove(name); + } + + /** + * Returns the registerd SASLMechanism classes sorted by the level of preference. + * + * @return the registerd SASLMechanism classes sorted by the level of preference. + */ + public static List getRegisterSASLMechanisms() { + List answer = new ArrayList(); + for (String mechanismsPreference : mechanismsPreferences) { + answer.add(implementedMechanisms.get(mechanismsPreference)); + } + return answer; + } + + SASLAuthentication(Connection connection) { + super(); + this.connection = connection; + this.init(); + } + + /** + * Returns true if the server offered ANONYMOUS SASL as a way to authenticate users. + * + * @return true if the server offered ANONYMOUS SASL as a way to authenticate users. + */ + public boolean hasAnonymousAuthentication() { + return serverMechanisms.contains("ANONYMOUS"); + } + + /** + * Returns true if the server offered SASL authentication besides ANONYMOUS SASL. + * + * @return true if the server offered SASL authentication besides ANONYMOUS SASL. + */ + public boolean hasNonAnonymousAuthentication() { + return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication()); + } + + /** + * Performs SASL authentication of the specified user. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server may assign a full JID with a username or resource different than the requested + * by this method. + * + * @param username the username that is authenticating with the server. + * @param resource the desired resource. + * @param cbh the CallbackHandler used to get information from the user + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticate(String username, String resource, CallbackHandler cbh) + throws XMPPException { + // Locate the SASLMechanism to use + String selectedMechanism = null; + for (String mechanism : mechanismsPreferences) { + if (implementedMechanisms.containsKey(mechanism) && + serverMechanisms.contains(mechanism)) { + selectedMechanism = mechanism; + break; + } + } + if (selectedMechanism != null) { + // A SASL mechanism was found. Authenticate using the selected mechanism and then + // proceed to bind a resource + try { + Class mechanismClass = implementedMechanisms.get(selectedMechanism); + Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); + currentMechanism = (SASLMechanism) constructor.newInstance(this); + // Trigger SASL authentication with the selected mechanism. We use + // connection.getHost() since GSAPI requires the FQDN of the server, which + // may not match the XMPP domain. + currentMechanism.authenticate(username, connection.getHost(), cbh); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication " + + selectedMechanism + " failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed using mechanism " + + selectedMechanism); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(resource); + } else { + // SASL authentication failed + } + } + catch (XMPPException e) { + throw e; + } + catch (Exception e) { + e.printStackTrace(); + } + } + else { + throw new XMPPException("SASL Authentication failed. No known authentication mechanisims."); + } + throw new XMPPException("SASL authentication failed"); + } + + /** + * Performs SASL authentication of the specified user. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server may assign a full JID with a username or resource different than the requested + * by this method. + * + * @param username the username that is authenticating with the server. + * @param password the password to send to the server. + * @param resource the desired resource. + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticate(String username, String password, String resource) + throws XMPPException { + // Locate the SASLMechanism to use + String selectedMechanism = null; + for (String mechanism : mechanismsPreferences) { + if (implementedMechanisms.containsKey(mechanism) && + serverMechanisms.contains(mechanism)) { + selectedMechanism = mechanism; + break; + } + } + if (selectedMechanism != null) { + // A SASL mechanism was found. Authenticate using the selected mechanism and then + // proceed to bind a resource + try { + Class mechanismClass = implementedMechanisms.get(selectedMechanism); + Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); + currentMechanism = (SASLMechanism) constructor.newInstance(this); + // Trigger SASL authentication with the selected mechanism. We use + // connection.getHost() since GSAPI requires the FQDN of the server, which + // may not match the XMPP domain. + currentMechanism.authenticate(username, connection.getServiceName(), password); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication " + + selectedMechanism + " failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed using mechanism " + + selectedMechanism); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(resource); + } + else { + // SASL authentication failed so try a Non-SASL authentication + return new NonSASLAuthentication(connection) + .authenticate(username, password, resource); + } + } + catch (XMPPException e) { + throw e; + } + catch (Exception e) { + e.printStackTrace(); + // SASL authentication failed so try a Non-SASL authentication + return new NonSASLAuthentication(connection) + .authenticate(username, password, resource); + } + } + else { + // No SASL method was found so try a Non-SASL authentication + return new NonSASLAuthentication(connection).authenticate(username, password, resource); + } + } + + /** + * Performs ANONYMOUS SASL authentication. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server will assign a full JID with a randomly generated resource and possibly with + * no username. + * + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticateAnonymously() throws XMPPException { + try { + currentMechanism = new SASLAnonymous(this); + currentMechanism.authenticate(null,null,""); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(5000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed"); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(null); + } + else { + return new NonSASLAuthentication(connection).authenticateAnonymously(); + } + } catch (IOException e) { + return new NonSASLAuthentication(connection).authenticateAnonymously(); + } + } + + private String bindResourceAndEstablishSession(String resource) throws XMPPException { + // Wait until server sends response containing the element + synchronized (this) { + if (!resourceBinded) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (!resourceBinded) { + // Server never offered resource binding + throw new XMPPException("Resource binding not offered by server"); + } + + Bind bindResource = new Bind(); + bindResource.setResource(resource); + + PacketCollector collector = connection + .createPacketCollector(new PacketIDFilter(bindResource.getPacketID())); + // Send the packet + connection.sendPacket(bindResource); + // Wait up to a certain number of seconds for a response from the server. + Bind response = (Bind) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + String userJID = response.getJid(); + + if (sessionSupported) { + Session session = new Session(); + collector = connection.createPacketCollector(new PacketIDFilter(session.getPacketID())); + // Send the packet + connection.sendPacket(session); + // Wait up to a certain number of seconds for a response from the server. + IQ ack = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (ack == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (ack.getType() == IQ.Type.ERROR) { + throw new XMPPException(ack.getError()); + } + } + else { + // Server never offered session establishment + throw new XMPPException("Session establishment not offered by server"); + } + return userJID; + } + + /** + * Sets the available SASL mechanism reported by the server. The server will report the + * available SASL mechanism once the TLS negotiation was successful. This information is + * stored and will be used when doing the authentication for logging in the user. + * + * @param mechanisms collection of strings with the available SASL mechanism reported + * by the server. + */ + void setAvailableSASLMethods(Collection mechanisms) { + this.serverMechanisms = mechanisms; + } + + /** + * Returns true if the user was able to authenticate with the server usins SASL. + * + * @return true if the user was able to authenticate with the server usins SASL. + */ + public boolean isAuthenticated() { + return saslNegotiated; + } + + /** + * The server is challenging the SASL authentication we just sent. Forward the challenge + * to the current SASLMechanism we are using. The SASLMechanism will send a response to + * the server. The length of the challenge-response sequence varies according to the + * SASLMechanism in use. + * + * @param challenge a base64 encoded string representing the challenge. + * @throws IOException If a network error occures while authenticating. + */ + void challengeReceived(String challenge) throws IOException { + currentMechanism.challengeReceived(challenge); + } + + /** + * Notification message saying that SASL authentication was successful. The next step + * would be to bind the resource. + */ + void authenticated() { + synchronized (this) { + saslNegotiated = true; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + /** + * Notification message saying that SASL authentication has failed. The server may have + * closed the connection depending on the number of possible retries. + * + * @deprecated replaced by {@see #authenticationFailed(String)}. + */ + void authenticationFailed() { + authenticationFailed(null); + } + + /** + * Notification message saying that SASL authentication has failed. The server may have + * closed the connection depending on the number of possible retries. + * + * @param condition the error condition provided by the server. + */ + void authenticationFailed(String condition) { + synchronized (this) { + saslFailed = true; + errorCondition = condition; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + /** + * Notification message saying that the server requires the client to bind a + * resource to the stream. + */ + void bindingRequired() { + synchronized (this) { + resourceBinded = true; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + public void send(Packet stanza) { + connection.sendPacket(stanza); + } + + /** + * Notification message saying that the server supports sessions. When a server supports + * sessions the client needs to send a Session packet after successfully binding a resource + * for the session. + */ + void sessionsSupported() { + sessionSupported = true; + } + + /** + * Initializes the internal state in order to be able to be reused. The authentication + * is used by the connection at the first login and then reused after the connection + * is disconnected and then reconnected. + */ + protected void init() { + saslNegotiated = false; + saslFailed = false; + resourceBinded = false; + sessionSupported = false; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig new file mode 100644 index 0000000..73995d2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SASLAuthentication.java.orig @@ -0,0 +1,590 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.Bind; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Session; +import org.jivesoftware.smack.sasl.*; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.util.*; + +/** + *

      This class is responsible authenticating the user using SASL, binding the resource + * to the connection and establishing a session with the server.

      + * + *

      Once TLS has been negotiated (i.e. the connection has been secured) it is possible to + * register with the server, authenticate using Non-SASL or authenticate using SASL. If the + * server supports SASL then Smack will first try to authenticate using SASL. But if that + * fails then Non-SASL will be tried.

      + * + *

      The server may support many SASL mechanisms to use for authenticating. Out of the box + * Smack provides several SASL mechanisms, but it is possible to register new SASL Mechanisms. Use + * {@link #registerSASLMechanism(String, Class)} to register a new mechanisms. A registered + * mechanism wont be used until {@link #supportSASLMechanism(String, int)} is called. By default, + * the list of supported SASL mechanisms is determined from the {@link SmackConfiguration}.

      + * + *

      Once the user has been authenticated with SASL, it is necessary to bind a resource for + * the connection. If no resource is passed in {@link #authenticate(String, String, String)} + * then the server will assign a resource for the connection. In case a resource is passed + * then the server will receive the desired resource but may assign a modified resource for + * the connection.

      + * + *

      Once a resource has been binded and if the server supports sessions then Smack will establish + * a session so that instant messaging and presence functionalities may be used.

      + * + * @see org.jivesoftware.smack.sasl.SASLMechanism + * + * @author Gaston Dombiak + * @author Jay Kline + */ +public class SASLAuthentication implements UserAuthentication { + + private static Map implementedMechanisms = new HashMap(); + private static List mechanismsPreferences = new ArrayList(); + + private Connection connection; + private Collection serverMechanisms = new ArrayList(); + private SASLMechanism currentMechanism = null; + /** + * Boolean indicating if SASL negotiation has finished and was successful. + */ + private boolean saslNegotiated; + /** + * Boolean indication if SASL authentication has failed. When failed the server may end + * the connection. + */ + private boolean saslFailed; + private boolean resourceBinded; + private boolean sessionSupported; + /** + * The SASL related error condition if there was one provided by the server. + */ + private String errorCondition; + + static { + + // Register SASL mechanisms supported by Smack + registerSASLMechanism("EXTERNAL", SASLExternalMechanism.class); + registerSASLMechanism("GSSAPI", SASLGSSAPIMechanism.class); + registerSASLMechanism("DIGEST-MD5", SASLDigestMD5Mechanism.class); + registerSASLMechanism("CRAM-MD5", SASLCramMD5Mechanism.class); + registerSASLMechanism("PLAIN", SASLPlainMechanism.class); + registerSASLMechanism("ANONYMOUS", SASLAnonymous.class); + + supportSASLMechanism("GSSAPI",0); + supportSASLMechanism("DIGEST-MD5",1); + supportSASLMechanism("CRAM-MD5",2); + supportSASLMechanism("PLAIN",3); + supportSASLMechanism("ANONYMOUS",4); + + } + + /** + * Registers a new SASL mechanism + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + * @param mClass a SASLMechanism subclass. + */ + public static void registerSASLMechanism(String name, Class mClass) { + implementedMechanisms.put(name, mClass); + } + + /** + * Unregisters an existing SASL mechanism. Once the mechanism has been unregistered it won't + * be possible to authenticate users using the removed SASL mechanism. It also removes the + * mechanism from the supported list. + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void unregisterSASLMechanism(String name) { + implementedMechanisms.remove(name); + mechanismsPreferences.remove(name); + } + + + /** + * Registers a new SASL mechanism in the specified preference position. The client will try + * to authenticate using the most prefered SASL mechanism that is also supported by the server. + * The SASL mechanism must be registered via {@link #registerSASLMechanism(String, Class)} + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void supportSASLMechanism(String name) { + mechanismsPreferences.add(0, name); + } + + /** + * Registers a new SASL mechanism in the specified preference position. The client will try + * to authenticate using the most prefered SASL mechanism that is also supported by the server. + * Use the index parameter to set the level of preference of the new SASL mechanism. + * A value of 0 means that the mechanism is the most prefered one. The SASL mechanism must be + * registered via {@link #registerSASLMechanism(String, Class)} + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + * @param index preference position amongst all the implemented SASL mechanism. Starts with 0. + */ + public static void supportSASLMechanism(String name, int index) { + mechanismsPreferences.add(index, name); + } + + /** + * Un-supports an existing SASL mechanism. Once the mechanism has been unregistered it won't + * be possible to authenticate users using the removed SASL mechanism. Note that the mechanism + * is still registered, but will just not be used. + * + * @param name common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or KERBEROS_V4. + */ + public static void unsupportSASLMechanism(String name) { + mechanismsPreferences.remove(name); + } + + /** + * Returns the registerd SASLMechanism classes sorted by the level of preference. + * + * @return the registerd SASLMechanism classes sorted by the level of preference. + */ + public static List getRegisterSASLMechanisms() { + List answer = new ArrayList(); + for (String mechanismsPreference : mechanismsPreferences) { + answer.add(implementedMechanisms.get(mechanismsPreference)); + } + return answer; + } + + SASLAuthentication(Connection connection) { + super(); + this.connection = connection; + this.init(); + } + + /** + * Returns true if the server offered ANONYMOUS SASL as a way to authenticate users. + * + * @return true if the server offered ANONYMOUS SASL as a way to authenticate users. + */ + public boolean hasAnonymousAuthentication() { + return serverMechanisms.contains("ANONYMOUS"); + } + + /** + * Returns true if the server offered SASL authentication besides ANONYMOUS SASL. + * + * @return true if the server offered SASL authentication besides ANONYMOUS SASL. + */ + public boolean hasNonAnonymousAuthentication() { + return !serverMechanisms.isEmpty() && (serverMechanisms.size() != 1 || !hasAnonymousAuthentication()); + } + + /** + * Performs SASL authentication of the specified user. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server may assign a full JID with a username or resource different than the requested + * by this method. + * + * @param username the username that is authenticating with the server. + * @param resource the desired resource. + * @param cbh the CallbackHandler used to get information from the user + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticate(String username, String resource, CallbackHandler cbh) + throws XMPPException { + // Locate the SASLMechanism to use + String selectedMechanism = null; + for (String mechanism : mechanismsPreferences) { + if (implementedMechanisms.containsKey(mechanism) && + serverMechanisms.contains(mechanism)) { + selectedMechanism = mechanism; + break; + } + } + if (selectedMechanism != null) { + // A SASL mechanism was found. Authenticate using the selected mechanism and then + // proceed to bind a resource + try { + Class mechanismClass = implementedMechanisms.get(selectedMechanism); + Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); + currentMechanism = (SASLMechanism) constructor.newInstance(this); + // Trigger SASL authentication with the selected mechanism. We use + // connection.getHost() since GSAPI requires the FQDN of the server, which + // may not match the XMPP domain. + currentMechanism.authenticate(username, connection.getHost(), cbh); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication " + + selectedMechanism + " failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed using mechanism " + + selectedMechanism); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(resource); + } else { + // SASL authentication failed + } + } + catch (XMPPException e) { + throw e; + } + catch (Exception e) { + e.printStackTrace(); + } + } + else { + throw new XMPPException("SASL Authentication failed. No known authentication mechanisims."); + } + throw new XMPPException("SASL authentication failed"); + } + + /** + * Performs SASL authentication of the specified user. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server may assign a full JID with a username or resource different than the requested + * by this method. + * + * @param username the username that is authenticating with the server. + * @param password the password to send to the server. + * @param resource the desired resource. + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticate(String username, String password, String resource) + throws XMPPException { + // Locate the SASLMechanism to use + String selectedMechanism = null; + for (String mechanism : mechanismsPreferences) { + if (implementedMechanisms.containsKey(mechanism) && + serverMechanisms.contains(mechanism)) { + selectedMechanism = mechanism; + break; + } + } + if (selectedMechanism != null) { + // A SASL mechanism was found. Authenticate using the selected mechanism and then + // proceed to bind a resource + try { + Class mechanismClass = implementedMechanisms.get(selectedMechanism); + Constructor constructor = mechanismClass.getConstructor(SASLAuthentication.class); + currentMechanism = (SASLMechanism) constructor.newInstance(this); + // Trigger SASL authentication with the selected mechanism. We use + // connection.getHost() since GSAPI requires the FQDN of the server, which + // may not match the XMPP domain. + currentMechanism.authenticate(username, connection.getServiceName(), password); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication " + + selectedMechanism + " failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed using mechanism " + + selectedMechanism); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(resource); + } + else { + // SASL authentication failed so try a Non-SASL authentication + return new NonSASLAuthentication(connection) + .authenticate(username, password, resource); + } + } + catch (XMPPException e) { + throw e; + } + catch (Exception e) { + e.printStackTrace(); + // SASL authentication failed so try a Non-SASL authentication + return new NonSASLAuthentication(connection) + .authenticate(username, password, resource); + } + } + else { + // No SASL method was found so try a Non-SASL authentication + return new NonSASLAuthentication(connection).authenticate(username, password, resource); + } + } + + /** + * Performs ANONYMOUS SASL authentication. If SASL authentication was successful + * then resource binding and session establishment will be performed. This method will return + * the full JID provided by the server while binding a resource to the connection.

      + * + * The server will assign a full JID with a randomly generated resource and possibly with + * no username. + * + * @return the full JID provided by the server while binding a resource to the connection. + * @throws XMPPException if an error occures while authenticating. + */ + public String authenticateAnonymously() throws XMPPException { + try { + currentMechanism = new SASLAnonymous(this); + currentMechanism.authenticate(null,null,""); + + // Wait until SASL negotiation finishes + synchronized (this) { + if (!saslNegotiated && !saslFailed) { + try { + wait(5000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (saslFailed) { + // SASL authentication failed and the server may have closed the connection + // so throw an exception + if (errorCondition != null) { + throw new XMPPException("SASL authentication failed: " + errorCondition); + } + else { + throw new XMPPException("SASL authentication failed"); + } + } + + if (saslNegotiated) { + // Bind a resource for this connection and + return bindResourceAndEstablishSession(null); + } + else { + return new NonSASLAuthentication(connection).authenticateAnonymously(); + } + } catch (IOException e) { + return new NonSASLAuthentication(connection).authenticateAnonymously(); + } + } + + private String bindResourceAndEstablishSession(String resource) throws XMPPException { + // Wait until server sends response containing the element + synchronized (this) { + if (!resourceBinded) { + try { + wait(30000); + } + catch (InterruptedException e) { + // Ignore + } + } + } + + if (!resourceBinded) { + // Server never offered resource binding + throw new XMPPException("Resource binding not offered by server"); + } + + Bind bindResource = new Bind(); + bindResource.setResource(resource); + + PacketCollector collector = connection + .createPacketCollector(new PacketIDFilter(bindResource.getPacketID())); + // Send the packet + connection.sendPacket(bindResource); + // Wait up to a certain number of seconds for a response from the server. + Bind response = (Bind) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + String userJID = response.getJid(); + + if (sessionSupported) { + Session session = new Session(); + collector = connection.createPacketCollector(new PacketIDFilter(session.getPacketID())); + // Send the packet + connection.sendPacket(session); + // Wait up to a certain number of seconds for a response from the server. + IQ ack = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (ack == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (ack.getType() == IQ.Type.ERROR) { + throw new XMPPException(ack.getError()); + } + } + else { + // Server never offered session establishment + throw new XMPPException("Session establishment not offered by server"); + } + return userJID; + } + + /** + * Sets the available SASL mechanism reported by the server. The server will report the + * available SASL mechanism once the TLS negotiation was successful. This information is + * stored and will be used when doing the authentication for logging in the user. + * + * @param mechanisms collection of strings with the available SASL mechanism reported + * by the server. + */ + void setAvailableSASLMethods(Collection mechanisms) { + this.serverMechanisms = mechanisms; + } + + /** + * Returns true if the user was able to authenticate with the server usins SASL. + * + * @return true if the user was able to authenticate with the server usins SASL. + */ + public boolean isAuthenticated() { + return saslNegotiated; + } + + /** + * The server is challenging the SASL authentication we just sent. Forward the challenge + * to the current SASLMechanism we are using. The SASLMechanism will send a response to + * the server. The length of the challenge-response sequence varies according to the + * SASLMechanism in use. + * + * @param challenge a base64 encoded string representing the challenge. + * @throws IOException If a network error occures while authenticating. + */ + void challengeReceived(String challenge) throws IOException { + currentMechanism.challengeReceived(challenge); + } + + /** + * Notification message saying that SASL authentication was successful. The next step + * would be to bind the resource. + */ + void authenticated() { + synchronized (this) { + saslNegotiated = true; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + /** + * Notification message saying that SASL authentication has failed. The server may have + * closed the connection depending on the number of possible retries. + * + * @deprecated replaced by {@see #authenticationFailed(String)}. + */ + void authenticationFailed() { + authenticationFailed(null); + } + + /** + * Notification message saying that SASL authentication has failed. The server may have + * closed the connection depending on the number of possible retries. + * + * @param condition the error condition provided by the server. + */ + void authenticationFailed(String condition) { + synchronized (this) { + saslFailed = true; + errorCondition = condition; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + /** + * Notification message saying that the server requires the client to bind a + * resource to the stream. + */ + void bindingRequired() { + synchronized (this) { + resourceBinded = true; + // Wake up the thread that is waiting in the #authenticate method + notify(); + } + } + + public void send(Packet stanza) { + connection.sendPacket(stanza); + } + + /** + * Notification message saying that the server supports sessions. When a server supports + * sessions the client needs to send a Session packet after successfully binding a resource + * for the session. + */ + void sessionsSupported() { + sessionSupported = true; + } + + /** + * Initializes the internal state in order to be able to be reused. The authentication + * is used by the connection at the first login and then reused after the connection + * is disconnected and then reconnected. + */ + protected void init() { + saslNegotiated = false; + saslFailed = false; + resourceBinded = false; + sessionSupported = false; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java new file mode 100644 index 0000000..cabcd2c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/ServerTrustManager.java @@ -0,0 +1,336 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import javax.net.ssl.X509TrustManager; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Trust manager that checks all certificates presented by the server. This class + * is used during TLS negotiation. It is possible to disable/enable some or all checkings + * by configuring the {@link ConnectionConfiguration}. The truststore file that contains + * knows and trusted CA root certificates can also be configure in {@link ConnectionConfiguration}. + * + * @author Gaston Dombiak + */ +class ServerTrustManager implements X509TrustManager { + + private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)"); + + private ConnectionConfiguration configuration; + + /** + * Holds the domain of the remote server we are trying to connect + */ + private String server; + private KeyStore trustStore; + + private static Map stores = new HashMap(); + + public ServerTrustManager(String server, ConnectionConfiguration configuration) { + this.configuration = configuration; + this.server = server; + + InputStream in = null; + synchronized (stores) { + KeyStoreOptions options = new KeyStoreOptions(configuration.getTruststoreType(), + configuration.getTruststorePath(), configuration.getTruststorePassword()); + if (stores.containsKey(options)) { + trustStore = stores.get(options); + } else { + try { + trustStore = KeyStore.getInstance(options.getType()); + in = new FileInputStream(options.getPath()); + trustStore.load(in, options.getPassword().toCharArray()); + } + catch (Exception e) { + trustStore = null; + e.printStackTrace(); + } + finally { + if (in != null) { + try { + in.close(); + } + catch (IOException ioe) { + // Ignore. + } + } + } + stores.put(options, trustStore); + } + if (trustStore == null) + // Disable root CA checking + configuration.setVerifyRootCAEnabled(false); + } + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) + throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] x509Certificates, String arg1) + throws CertificateException { + + int nSize = x509Certificates.length; + + List peerIdentities = getPeerIdentity(x509Certificates[0]); + + if (configuration.isVerifyChainEnabled()) { + // Working down the chain, for every certificate in the chain, + // verify that the subject of the certificate is the issuer of the + // next certificate in the chain. + Principal principalLast = null; + for (int i = nSize -1; i >= 0 ; i--) { + X509Certificate x509certificate = x509Certificates[i]; + Principal principalIssuer = x509certificate.getIssuerDN(); + Principal principalSubject = x509certificate.getSubjectDN(); + if (principalLast != null) { + if (principalIssuer.equals(principalLast)) { + try { + PublicKey publickey = + x509Certificates[i + 1].getPublicKey(); + x509Certificates[i].verify(publickey); + } + catch (GeneralSecurityException generalsecurityexception) { + throw new CertificateException( + "signature verification failed of " + peerIdentities); + } + } + else { + throw new CertificateException( + "subject/issuer verification failed of " + peerIdentities); + } + } + principalLast = principalSubject; + } + } + + if (configuration.isVerifyRootCAEnabled()) { + // Verify that the the last certificate in the chain was issued + // by a third-party that the client trusts. + boolean trusted = false; + try { + trusted = trustStore.getCertificateAlias(x509Certificates[nSize - 1]) != null; + if (!trusted && nSize == 1 && configuration.isSelfSignedCertificateEnabled()) + { + System.out.println("Accepting self-signed certificate of remote server: " + + peerIdentities); + trusted = true; + } + } + catch (KeyStoreException e) { + e.printStackTrace(); + } + if (!trusted) { + throw new CertificateException("root certificate not trusted of " + peerIdentities); + } + } + + if (configuration.isNotMatchingDomainCheckEnabled()) { + // Verify that the first certificate in the chain corresponds to + // the server we desire to authenticate. + // Check if the certificate uses a wildcard indicating that subdomains are valid + if (peerIdentities.size() == 1 && peerIdentities.get(0).startsWith("*.")) { + // Remove the wildcard + String peerIdentity = peerIdentities.get(0).replace("*.", ""); + // Check if the requested subdomain matches the certified domain + if (!server.endsWith(peerIdentity)) { + throw new CertificateException("target verification failed of " + peerIdentities); + } + } + else if (!peerIdentities.contains(server)) { + throw new CertificateException("target verification failed of " + peerIdentities); + } + } + + if (configuration.isExpiredCertificatesCheckEnabled()) { + // For every certificate in the chain, verify that the certificate + // is valid at the current time. + Date date = new Date(); + for (int i = 0; i < nSize; i++) { + try { + x509Certificates[i].checkValidity(date); + } + catch (GeneralSecurityException generalsecurityexception) { + throw new CertificateException("invalid date of " + server); + } + } + } + + } + + /** + * Returns the identity of the remote server as defined in the specified certificate. The + * identity is defined in the subjectDN of the certificate and it can also be defined in + * the subjectAltName extension of type "xmpp". When the extension is being used then the + * identity defined in the extension in going to be returned. Otherwise, the value stored in + * the subjectDN is returned. + * + * @param x509Certificate the certificate the holds the identity of the remote server. + * @return the identity of the remote server as defined in the specified certificate. + */ + public static List getPeerIdentity(X509Certificate x509Certificate) { + // Look the identity in the subjectAltName extension if available + List names = getSubjectAlternativeNames(x509Certificate); + if (names.isEmpty()) { + String name = x509Certificate.getSubjectDN().getName(); + Matcher matcher = cnPattern.matcher(name); + if (matcher.find()) { + name = matcher.group(2); + } + // Create an array with the unique identity + names = new ArrayList(); + names.add(name); + } + return names; + } + + /** + * Returns the JID representation of an XMPP entity contained as a SubjectAltName extension + * in the certificate. If none was found then return null. + * + * @param certificate the certificate presented by the remote entity. + * @return the JID representation of an XMPP entity contained as a SubjectAltName extension + * in the certificate. If none was found then return null. + */ + private static List getSubjectAlternativeNames(X509Certificate certificate) { + List identities = new ArrayList(); + try { + Collection> altNames = certificate.getSubjectAlternativeNames(); + // Check that the certificate includes the SubjectAltName extension + if (altNames == null) { + return Collections.emptyList(); + } + // Use the type OtherName to search for the certified server name + /*for (List item : altNames) { + Integer type = (Integer) item.get(0); + if (type == 0) { + // Type OtherName found so return the associated value + try { + // Value is encoded using ASN.1 so decode it to get the server's identity + ASN1InputStream decoder = new ASN1InputStream((byte[]) item.toArray()[1]); + DEREncodable encoded = decoder.readObject(); + encoded = ((DERSequence) encoded).getObjectAt(1); + encoded = ((DERTaggedObject) encoded).getObject(); + encoded = ((DERTaggedObject) encoded).getObject(); + String identity = ((DERUTF8String) encoded).getString(); + // Add the decoded server name to the list of identities + identities.add(identity); + } + catch (UnsupportedEncodingException e) { + // Ignore + } + catch (IOException e) { + // Ignore + } + catch (Exception e) { + e.printStackTrace(); + } + } + // Other types are not good for XMPP so ignore them + System.out.println("SubjectAltName of invalid type found: " + certificate); + }*/ + } + catch (CertificateParsingException e) { + e.printStackTrace(); + } + return identities; + } + + private static class KeyStoreOptions { + private final String type; + private final String path; + private final String password; + + public KeyStoreOptions(String type, String path, String password) { + super(); + this.type = type; + this.path = path; + this.password = password; + } + + public String getType() { + return type; + } + + public String getPath() { + return path; + } + + public String getPassword() { + return password; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((password == null) ? 0 : password.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + KeyStoreOptions other = (KeyStoreOptions) obj; + if (password == null) { + if (other.password != null) + return false; + } else if (!password.equals(other.password)) + return false; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return true; + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java new file mode 100644 index 0000000..4c622df --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/SmackConfiguration.java @@ -0,0 +1,344 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; + +import java.io.InputStream; +import java.net.URL; +import java.util.*; + +/** + * Represents the configuration of Smack. The configuration is used for: + *

        + *
      • Initializing classes by loading them at start-up. + *
      • Getting the current Smack version. + *
      • Getting and setting global library behavior, such as the period of time + * to wait for replies to packets from the server. Note: setting these values + * via the API will override settings in the configuration file. + *
      + * + * Configuration settings are stored in META-INF/smack-config.xml (typically inside the + * smack.jar file). + * + * @author Gaston Dombiak + */ +public final class SmackConfiguration { + + private static final String SMACK_VERSION = "3.2.2"; + + private static int packetReplyTimeout = 5000; + private static int keepAliveInterval = 30000; + private static Vector defaultMechs = new Vector(); + + private static boolean localSocks5ProxyEnabled = true; + private static int localSocks5ProxyPort = 7777; + private static int packetCollectorSize = 5000; + + private SmackConfiguration() { + } + + /** + * Loads the configuration from the smack-config.xml file.

      + * + * So far this means that: + * 1) a set of classes will be loaded in order to execute their static init block + * 2) retrieve and set the current Smack release + */ + static { + try { + // Get an array of class loaders to try loading the providers files from. + ClassLoader[] classLoaders = getClassLoaders(); + for (ClassLoader classLoader : classLoaders) { + Enumeration configEnum = classLoader.getResources("META-INF/smack-config.xml"); + while (configEnum.hasMoreElements()) { + URL url = (URL) configEnum.nextElement(); + InputStream systemStream = null; + try { + systemStream = url.openStream(); + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(systemStream, "UTF-8"); + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("className")) { + // Attempt to load the class so that the class can get initialized + parseClassToLoad(parser); + } + else if (parser.getName().equals("packetReplyTimeout")) { + packetReplyTimeout = parseIntProperty(parser, packetReplyTimeout); + } + else if (parser.getName().equals("keepAliveInterval")) { + keepAliveInterval = parseIntProperty(parser, keepAliveInterval); + } + else if (parser.getName().equals("mechName")) { + defaultMechs.add(parser.nextText()); + } + else if (parser.getName().equals("localSocks5ProxyEnabled")) { + localSocks5ProxyEnabled = Boolean.parseBoolean(parser.nextText()); + } + else if (parser.getName().equals("localSocks5ProxyPort")) { + localSocks5ProxyPort = parseIntProperty(parser, localSocks5ProxyPort); + } + else if (parser.getName().equals("packetCollectorSize")) { + packetCollectorSize = parseIntProperty(parser, packetCollectorSize); + } + } + eventType = parser.next(); + } + while (eventType != XmlPullParser.END_DOCUMENT); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + try { + systemStream.close(); + } + catch (Exception e) { + // Ignore. + } + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Returns the Smack version information, eg "1.3.0". + * + * @return the Smack version information. + */ + public static String getVersion() { + return SMACK_VERSION; + } + + /** + * Returns the number of milliseconds to wait for a response from + * the server. The default value is 5000 ms. + * + * @return the milliseconds to wait for a response from the server + */ + public static int getPacketReplyTimeout() { + // The timeout value must be greater than 0 otherwise we will answer the default value + if (packetReplyTimeout <= 0) { + packetReplyTimeout = 5000; + } + return packetReplyTimeout; + } + + /** + * Sets the number of milliseconds to wait for a response from + * the server. + * + * @param timeout the milliseconds to wait for a response from the server + */ + public static void setPacketReplyTimeout(int timeout) { + if (timeout <= 0) { + throw new IllegalArgumentException(); + } + packetReplyTimeout = timeout; + } + + /** + * Returns the number of milleseconds delay between sending keep-alive + * requests to the server. The default value is 30000 ms. A value of -1 + * mean no keep-alive requests will be sent to the server. + * + * @return the milliseconds to wait between keep-alive requests, or -1 if + * no keep-alive should be sent. + */ + public static int getKeepAliveInterval() { + return keepAliveInterval; + } + + /** + * Sets the number of milleseconds delay between sending keep-alive + * requests to the server. The default value is 30000 ms. A value of -1 + * mean no keep-alive requests will be sent to the server. + * + * @param interval the milliseconds to wait between keep-alive requests, + * or -1 if no keep-alive should be sent. + */ + public static void setKeepAliveInterval(int interval) { + keepAliveInterval = interval; + } + + /** + * Gets the default max size of a packet collector before it will delete + * the older packets. + * + * @return The number of packets to queue before deleting older packets. + */ + public static int getPacketCollectorSize() { + return packetCollectorSize; + } + + /** + * Sets the default max size of a packet collector before it will delete + * the older packets. + * + * @param The number of packets to queue before deleting older packets. + */ + public static void setPacketCollectorSize(int collectorSize) { + packetCollectorSize = collectorSize; + } + + /** + * Add a SASL mechanism to the list to be used. + * + * @param mech the SASL mechanism to be added + */ + public static void addSaslMech(String mech) { + if(! defaultMechs.contains(mech) ) { + defaultMechs.add(mech); + } + } + + /** + * Add a Collection of SASL mechanisms to the list to be used. + * + * @param mechs the Collection of SASL mechanisms to be added + */ + public static void addSaslMechs(Collection mechs) { + for(String mech : mechs) { + addSaslMech(mech); + } + } + + /** + * Remove a SASL mechanism from the list to be used. + * + * @param mech the SASL mechanism to be removed + */ + public static void removeSaslMech(String mech) { + if( defaultMechs.contains(mech) ) { + defaultMechs.remove(mech); + } + } + + /** + * Remove a Collection of SASL mechanisms to the list to be used. + * + * @param mechs the Collection of SASL mechanisms to be removed + */ + public static void removeSaslMechs(Collection mechs) { + for(String mech : mechs) { + removeSaslMech(mech); + } + } + + /** + * Returns the list of SASL mechanisms to be used. If a SASL mechanism is + * listed here it does not guarantee it will be used. The server may not + * support it, or it may not be implemented. + * + * @return the list of SASL mechanisms to be used. + */ + public static List getSaslMechs() { + return defaultMechs; + } + + /** + * Returns true if the local Socks5 proxy should be started. Default is true. + * + * @return if the local Socks5 proxy should be started + */ + public static boolean isLocalSocks5ProxyEnabled() { + return localSocks5ProxyEnabled; + } + + /** + * Sets if the local Socks5 proxy should be started. Default is true. + * + * @param localSocks5ProxyEnabled if the local Socks5 proxy should be started + */ + public static void setLocalSocks5ProxyEnabled(boolean localSocks5ProxyEnabled) { + SmackConfiguration.localSocks5ProxyEnabled = localSocks5ProxyEnabled; + } + + /** + * Return the port of the local Socks5 proxy. Default is 7777. + * + * @return the port of the local Socks5 proxy + */ + public static int getLocalSocks5ProxyPort() { + return localSocks5ProxyPort; + } + + /** + * Sets the port of the local Socks5 proxy. Default is 7777. If you set the port to a negative + * value Smack tries the absolute value and all following until it finds an open port. + * + * @param localSocks5ProxyPort the port of the local Socks5 proxy to set + */ + public static void setLocalSocks5ProxyPort(int localSocks5ProxyPort) { + SmackConfiguration.localSocks5ProxyPort = localSocks5ProxyPort; + } + + private static void parseClassToLoad(XmlPullParser parser) throws Exception { + String className = parser.nextText(); + // Attempt to load the class so that the class can get initialized + try { + Class.forName(className); + } + catch (ClassNotFoundException cnfe) { + System.err.println("Error! A startup class specified in smack-config.xml could " + + "not be loaded: " + className); + } + } + + private static int parseIntProperty(XmlPullParser parser, int defaultValue) + throws Exception + { + try { + return Integer.parseInt(parser.nextText()); + } + catch (NumberFormatException nfe) { + nfe.printStackTrace(); + return defaultValue; + } + } + + /** + * Returns an array of class loaders to load resources from. + * + * @return an array of ClassLoader instances. + */ + private static ClassLoader[] getClassLoaders() { + ClassLoader[] classLoaders = new ClassLoader[2]; + classLoaders[0] = SmackConfiguration.class.getClassLoader(); + classLoaders[1] = Thread.currentThread().getContextClassLoader(); + // Clean up possible null values. Note that #getClassLoader may return a null value. + List loaders = new ArrayList(); + for (ClassLoader classLoader : classLoaders) { + if (classLoader != null) { + loaders.add(classLoader); + } + } + return loaders.toArray(new ClassLoader[loaders.size()]); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java new file mode 100644 index 0000000..1c3c50f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/UserAuthentication.java @@ -0,0 +1,79 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; + +/** + * There are two ways to authenticate a user with a server. Using SASL or Non-SASL + * authentication. This interface makes {@link SASLAuthentication} and + * {@link NonSASLAuthentication} polyphormic. + * + * @author Gaston Dombiak + * @author Jay Kline + */ +interface UserAuthentication { + + /** + * Authenticates the user with the server. This method will return the full JID provided by + * the server. The server may assign a full JID with a username and resource different than + * requested by this method. + * + * Note that using callbacks is the prefered method of authenticating users since it allows + * more flexability in the mechanisms used. + * + * @param username the requested username (authorization ID) for authenticating to the server + * @param resource the requested resource. + * @param cbh the CallbackHandler used to obtain authentication ID, password, or other + * information + * @return the full JID provided by the server while binding a resource for the connection. + * @throws XMPPException if an error occurs while authenticating. + */ + String authenticate(String username, String resource, CallbackHandler cbh) throws + XMPPException; + + /** + * Authenticates the user with the server. This method will return the full JID provided by + * the server. The server may assign a full JID with a username and resource different than + * the requested by this method. + * + * It is recommended that @{link #authenticate(String, String, CallbackHandler)} be used instead + * since it provides greater flexability in authenticaiton and authorization. + * + * @param username the username that is authenticating with the server. + * @param password the password to send to the server. + * @param resource the desired resource. + * @return the full JID provided by the server while binding a resource for the connection. + * @throws XMPPException if an error occures while authenticating. + */ + String authenticate(String username, String password, String resource) throws + XMPPException; + + /** + * Performs an anonymous authentication with the server. The server will created a new full JID + * for this connection. An exception will be thrown if the server does not support anonymous + * authentication. + * + * @return the full JID provided by the server while binding a resource for the connection. + * @throws XMPPException if an error occures while authenticating. + */ + String authenticateAnonymously() throws XMPPException; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java new file mode 100644 index 0000000..659414b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java @@ -0,0 +1,1099 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Collection; + +/** + * Creates a socket connection to a XMPP server. This is the default connection + * to a Jabber server and is specified in the XMPP Core (RFC 3920). + * + * @see Connection + * @author Matt Tucker + */ +public class XMPPConnection extends Connection { + + /** + * The socket which is used for this connection. + */ + protected Socket socket; + + private String user = null; + protected boolean connected = false; + private boolean socketClosed = false; + /** + * Flag that indicates if the user is currently authenticated with the server. + */ + private boolean authenticated = false; + /** + * Flag that indicates if the user was authenticated with the server when the connection + * to the server was closed (abruptly or not). + */ + private boolean wasAuthenticated = false; + private boolean anonymous = false; + private boolean usingTLS = false; + + PacketWriter packetWriter; + PacketReader packetReader; + + Roster roster = null; + + /** + * Collection of available stream compression methods offered by the server. + */ + private Collection compressionMethods; + /** + * Flag that indicates if stream compression is actually in use. + */ + private boolean usingCompression; + + + /** + * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be + * performed to determine the IP address and port corresponding to the + * service name; if that lookup fails, it's assumed that server resides at + * serviceName with the default port of 5222. Encrypted connections (TLS) + * will be used if available, stream compression is disabled, and standard SASL + * mechanisms will be used for authentication.

      + *

      + * This is the simplest constructor for connecting to an XMPP server. Alternatively, + * you can get fine-grained control over connection settings using the + * {@link #XMPPConnection(ConnectionConfiguration)} constructor.

      + *

      + * Note that XMPPConnection constructors do not establish a connection to the server + * and you must call {@link #connect()}.

      + *

      + * The CallbackHandler will only be used if the connection requires the client provide + * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback + * to prompt for a password to unlock the keystore containing the SSL certificate. + * + * @param serviceName the name of the XMPP server to connect to; e.g. example.com. + * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. + */ + public XMPPConnection(String serviceName, CallbackHandler callbackHandler) { + // Create the configuration for this new connection + super(new ConnectionConfiguration(serviceName)); + config.setCompressionEnabled(false); + config.setSASLAuthenticationEnabled(true); + config.setDebuggerEnabled(DEBUG_ENABLED); + config.setCallbackHandler(callbackHandler); + } + + /** + * Creates a new XMPP connection in the same way {@link #XMPPConnection(String,CallbackHandler)} does, but + * with no callback handler for password prompting of the keystore. This will work + * in most cases, provided the client is not required to provide a certificate to + * the server. + * + * @param serviceName the name of the XMPP server to connect to; e.g. example.com. + */ + public XMPPConnection(String serviceName) { + // Create the configuration for this new connection + super(new ConnectionConfiguration(serviceName)); + config.setCompressionEnabled(false); + config.setSASLAuthenticationEnabled(true); + config.setDebuggerEnabled(DEBUG_ENABLED); + } + + /** + * Creates a new XMPP connection in the same way {@link #XMPPConnection(ConnectionConfiguration,CallbackHandler)} does, but + * with no callback handler for password prompting of the keystore. This will work + * in most cases, provided the client is not required to provide a certificate to + * the server. + * + * + * @param config the connection configuration. + */ + public XMPPConnection(ConnectionConfiguration config) { + super(config); + } + + /** + * Creates a new XMPP connection using the specified connection configuration.

      + *

      + * Manually specifying connection configuration information is suitable for + * advanced users of the API. In many cases, using the + * {@link #XMPPConnection(String)} constructor is a better approach.

      + *

      + * Note that XMPPConnection constructors do not establish a connection to the server + * and you must call {@link #connect()}.

      + *

      + * + * The CallbackHandler will only be used if the connection requires the client provide + * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback + * to prompt for a password to unlock the keystore containing the SSL certificate. + * + * @param config the connection configuration. + * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. + */ + public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { + super(config); + config.setCallbackHandler(callbackHandler); + } + + public String getConnectionID() { + if (!isConnected()) { + return null; + } + return connectionID; + } + + public String getUser() { + if (!isAuthenticated()) { + return null; + } + return user; + } + + @Override + public synchronized void login(String username, String password, String resource) throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + // Do partial version of nameprep on the username. + username = username.toLowerCase().trim(); + + String response; + if (config.isSASLAuthenticationEnabled() && + saslAuthentication.hasNonAnonymousAuthentication()) { + // Authenticate using SASL + if (password != null) { + response = saslAuthentication.authenticate(username, password, resource); + } + else { + response = saslAuthentication + .authenticate(username, resource, config.getCallbackHandler()); + } + } + else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticate(username, password, resource); + } + + // If compression is enabled then request the server to use stream compression + if (config.isCompressionEnabled()) { + useCompression(); + } + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = false; + + // If we did not bind, don't attempt to do anything with roster or presence + if (resource == null) + return; + + // Set the user. + if (response != null) { + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + } + else { + this.user = username + "@" + getServiceName(); + if (resource != null) { + this.user += "/" + resource; + } + } + + // Create the roster if it is not a reconnection or roster already created by getRoster() + if (this.roster == null) { + if(rosterStorage==null){ + this.roster = new Roster(this); + } + else{ + this.roster = new Roster(this,rosterStorage); + } + } + if (config.isRosterLoadedAtLogin()) { + this.roster.reload(); + } + + // Set presence to online. + if (config.isSendPresence()) { + packetWriter.sendPacket(new Presence(Presence.Type.available)); + } + + // Stores the authentication for future reconnection + config.setLoginInfo(username, password, resource); + + // If debugging is enabled, change the the debug window title to include the + // name we are now logged-in as. + // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger + // will be null + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + @Override + public synchronized void loginAnonymously() throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + + String response; + if (config.isSASLAuthenticationEnabled() && + saslAuthentication.hasAnonymousAuthentication()) { + response = saslAuthentication.authenticateAnonymously(); + } + else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticateAnonymously(); + } + + // Set the user value. + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + + // If compression is enabled then request the server to use stream compression + if (config.isCompressionEnabled()) { + useCompression(); + } + + // Set presence to online. + packetWriter.sendPacket(new Presence(Presence.Type.available)); + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = true; + + // If debugging is enabled, change the the debug window title to include the + // name we are now logged-in as. + // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger + // will be null + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + public Roster getRoster() { + // synchronize against login() + synchronized(this) { + // if connection is authenticated the roster is already set by login() + // or a previous call to getRoster() + if (!isAuthenticated() || isAnonymous()) { + if (roster == null) { + roster = new Roster(this); + } + return roster; + } + } + + if (!config.isRosterLoadedAtLogin()) { + roster.reload(); + } + // If this is the first time the user has asked for the roster after calling + // login, we want to wait for the server to send back the user's roster. This + // behavior shields API users from having to worry about the fact that roster + // operations are asynchronous, although they'll still have to listen for + // changes to the roster. Note: because of this waiting logic, internal + // Smack code should be wary about calling the getRoster method, and may need to + // access the roster object directly. + if (!roster.rosterInitialized) { + try { + synchronized (roster) { + long waitTime = SmackConfiguration.getPacketReplyTimeout(); + long start = System.currentTimeMillis(); + while (!roster.rosterInitialized) { + if (waitTime <= 0) { + break; + } + roster.wait(waitTime); + long now = System.currentTimeMillis(); + waitTime -= now - start; + start = now; + } + } + } + catch (InterruptedException ie) { + // Ignore. + } + } + return roster; + } + + public boolean isConnected() { + return connected; + } + + public boolean isSecureConnection() { + return isUsingTLS(); + } + + public boolean isSocketClosed() { + return socketClosed; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public boolean isAnonymous() { + return anonymous; + } + + /** + * Forceful shutdown without full disconnect. + * + *

      Used when a reconnection is possible. + */ + public void quickShutdown() { + try { + this.setWasAuthenticated(authenticated); + authenticated = false; + + // Close socket before we close writer, so that stream end is not sent and reconnection is possible. + // This may cause a ConnectionClosedOnError notification to go out. + socketClosed = true; + try { + socket.shutdownInput(); + } + catch (Exception e) { + } + try { + socket.close(); + } catch (Exception e) { + } + + if (packetReader!=null){ + // Does not notify listeners + packetReader.quickShutdown(); + } + if (packetWriter!=null){ + packetWriter.shutdown(); + } + // Wait 150 ms for processes to clean-up, then shutdown. + try { + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + connected = false; + + // Close down the readers and writers. + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + saslAuthentication.init(); + } + catch (Exception e) { + System.err.println(e); + } + } + + /** + * Closes the connection by setting presence to unavailable then closing the stream to + * the XMPP server. The shutdown logic will be used during a planned disconnection or when + * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's + * packet reader, packet writer, and {@link Roster} will not be removed; thus + * connection's state is kept. + * + * @param unavailablePresence the presence packet to send during shutdown. + */ + protected void shutdown(Presence unavailablePresence) { + // Set presence to offline. + if (packetWriter != null){ + packetWriter.sendPacket(unavailablePresence); + } + + this.setWasAuthenticated(authenticated); + authenticated = false; + + if (packetReader!=null){ + packetReader.shutdown(); + } + if (packetWriter!=null){ + packetWriter.shutdown(); + } + // Wait 150 ms for processes to clean-up, then shutdown. + try { + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + socketClosed = true; + try { + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + connected = false; + + + // Close down the readers and writers. + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + // Make sure that the socket is really closed + try { + socket.close(); + } + catch (Exception e) { + // Ignore. + } + + saslAuthentication.init(); + } + + public synchronized void disconnect(Presence unavailablePresence) { + // If not connected, ignore this request. + PacketReader packetReader = this.packetReader; + PacketWriter packetWriter = this.packetWriter; + if (packetReader == null || packetWriter == null) { + return; + } + + shutdown(unavailablePresence); + + if (roster != null) { + roster.cleanup(); + roster = null; + } + chatManager = null; + + wasAuthenticated = false; + + packetWriter.cleanup(); + this.packetWriter = null; + packetReader.cleanup(); + this.packetReader = null; + } + + public void sendPacket(Packet packet) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (packet == null) { + throw new NullPointerException("Packet is null."); + } + packetWriter.sendPacket(packet); + } + + /** + * Registers a packet interceptor with this connection. The interceptor will be + * invoked every time a packet is about to be sent by this connection. Interceptors + * may modify the packet to be sent. A packet filter determines which packets + * will be delivered to the interceptor. + * + * @param packetInterceptor the packet interceptor to notify of packets about to be sent. + * @param packetFilter the packet filter to use. + * @deprecated replaced by {@link Connection#addPacketInterceptor(PacketInterceptor, PacketFilter)}. + */ + public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, + PacketFilter packetFilter) { + addPacketInterceptor(packetInterceptor, packetFilter); + } + + /** + * Removes a packet interceptor. + * + * @param packetInterceptor the packet interceptor to remove. + * @deprecated replaced by {@link Connection#removePacketInterceptor(PacketInterceptor)}. + */ + public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) { + removePacketInterceptor(packetInterceptor); + } + + /** + * Registers a packet listener with this connection. The listener will be + * notified of every packet that this connection sends. A packet filter determines + * which packets will be delivered to the listener. Note that the thread + * that writes packets will be used to invoke the listeners. Therefore, each + * packet listener should complete all operations quickly or use a different + * thread for processing. + * + * @param packetListener the packet listener to notify of sent packets. + * @param packetFilter the packet filter to use. + * @deprecated replaced by {@link #addPacketSendingListener(PacketListener, PacketFilter)}. + */ + public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) { + addPacketSendingListener(packetListener, packetFilter); + } + + /** + * Removes a packet listener for sending packets from this connection. + * + * @param packetListener the packet listener to remove. + * @deprecated replaced by {@link #removePacketSendingListener(PacketListener)}. + */ + public void removePacketWriterListener(PacketListener packetListener) { + removePacketSendingListener(packetListener); + } + + private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { + String host = config.getHost(); + int port = config.getPort(); + try { + if (config.getSocketFactory() == null) { + this.socket = new Socket(host, port); + } + else { + this.socket = config.getSocketFactory().createSocket(host, port); + } + } + catch (UnknownHostException uhe) { + String errorMessage = "Could not connect to " + host + ":" + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_timeout, errorMessage), + uhe); + } + catch (IOException ioe) { + String errorMessage = "XMPPError connecting to " + host + ":" + + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_error, errorMessage), ioe); + } + socketClosed = false; + initConnection(); + } + + /** + * Initializes the connection by creating a packet reader and writer and opening a + * XMPP stream to the server. + * + * @throws XMPPException if establishing a connection to the server fails. + */ + private void initConnection() throws XMPPException { + boolean isFirstInitialization = packetReader == null || packetWriter == null; + usingCompression = false; + + // Set the reader and writer instance variables + initReaderAndWriter(); + + try { + if (isFirstInitialization) { + packetWriter = new PacketWriter(this); + packetReader = new PacketReader(this); + + // If debugging is enabled, we should start the thread that will listen for + // all packets and then log them. + if (config.isDebuggerEnabled()) { + addPacketListener(debugger.getReaderListener(), null); + if (debugger.getWriterListener() != null) { + addPacketSendingListener(debugger.getWriterListener(), null); + } + } + } + else { + packetWriter.init(); + packetReader.init(); + } + + // Start the packet writer. This will open a XMPP stream to the server + packetWriter.startup(); + // Start the packet reader. The startup() method will block until we + // get an opening stream packet back from server. + packetReader.startup(); + + // Make note of the fact that we're now connected. + connected = true; + + // Start keep alive process (after TLS was negotiated - if available) + packetWriter.startKeepAliveProcess(); + + + if (isFirstInitialization) { + // Notify listeners that a new connection has been established + for (ConnectionCreationListener listener : getConnectionCreationListeners()) { + listener.connectionCreated(this); + } + } + else if (!wasAuthenticated) { + packetReader.notifyReconnection(); + } + + } + catch (XMPPException ex) { + // An exception occurred in setting up the connection. Make sure we shut down the + // readers and writers and close the socket. + + if (packetWriter != null) { + try { + packetWriter.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetWriter = null; + } + if (packetReader != null) { + try { + packetReader.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetReader = null; + } + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */} + writer = null; + } + if (socket != null) { + try { + socket.close(); + } + catch (Exception e) { /* ignore */ } + socket = null; + } + this.setWasAuthenticated(authenticated); + chatManager = null; + authenticated = false; + connected = false; + + throw ex; // Everything stoppped. Now throw the exception. + } + } + + private void initReaderAndWriter() throws XMPPException { + try { + if (!usingCompression) { + reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + writer = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); + } + else { + try { + Class zoClass = Class.forName("com.jcraft.jzlib.ZOutputStream"); + Constructor constructor = + zoClass.getConstructor(OutputStream.class, Integer.TYPE); + Object out = constructor.newInstance(socket.getOutputStream(), 9); + Method method = zoClass.getMethod("setFlushMode", Integer.TYPE); + method.invoke(out, 2); + writer = + new BufferedWriter(new OutputStreamWriter((OutputStream) out, "UTF-8")); + + Class ziClass = Class.forName("com.jcraft.jzlib.ZInputStream"); + constructor = ziClass.getConstructor(InputStream.class); + Object in = constructor.newInstance(socket.getInputStream()); + method = ziClass.getMethod("setFlushMode", Integer.TYPE); + method.invoke(in, 2); + reader = new BufferedReader(new InputStreamReader((InputStream) in, "UTF-8")); + } + catch (Exception e) { + e.printStackTrace(); + reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), "UTF-8")); + writer = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); + } + } + } + catch (IOException ioe) { + throw new XMPPException( + "XMPPError establishing connection with server.", + new XMPPError(XMPPError.Condition.remote_server_error, + "XMPPError establishing connection with server."), + ioe); + } + + // If debugging is enabled, we open a window and write out all network traffic. + initDebugger(); + } + + /*********************************************** + * TLS code below + **********************************************/ + + /** + * Returns true if the connection to the server has successfully negotiated TLS. Once TLS + * has been negotiatied the connection has been secured. + * + * @return true if the connection to the server has successfully negotiated TLS. + */ + public boolean isUsingTLS() { + return usingTLS; + } + + /** + * Notification message saying that the server supports TLS so confirm the server that we + * want to secure the connection. + * + * @param required true when the server indicates that TLS is required. + */ + void startTLSReceived(boolean required) { + if (required && config.getSecurityMode() == + ConnectionConfiguration.SecurityMode.disabled) { + packetReader.notifyConnectionError(new IllegalStateException( + "TLS required by server but not allowed by connection configuration")); + return; + } + + if (config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { + // Do not secure the connection using TLS since TLS was disabled + return; + } + try { + writer.write(""); + writer.flush(); + } + catch (IOException e) { + packetReader.notifyConnectionError(e); + } + } + + /** + * The server has indicated that TLS negotiation can start. We now need to secure the + * existing plain connection and perform a handshake. This method won't return until the + * connection has finished the handshake or an error occured while securing the connection. + * + * @throws Exception if an exception occurs. + */ + void proceedTLSReceived() throws Exception { + SSLContext context = this.config.getCustomSSLContext(); + KeyStore ks = null; + KeyManager[] kms = null; + PasswordCallback pcb = null; + + if(config.getCallbackHandler() == null) { + ks = null; + } else if (context == null) { + //System.out.println("Keystore type: "+configuration.getKeystoreType()); + if(config.getKeystoreType().equals("NONE")) { + ks = null; + pcb = null; + } + else if(config.getKeystoreType().equals("PKCS11")) { + try { + Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); + String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library(); + ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes()); + Provider p = (Provider)c.newInstance(config); + Security.addProvider(p); + ks = KeyStore.getInstance("PKCS11",p); + pcb = new PasswordCallback("PKCS11 Password: ",false); + this.config.getCallbackHandler().handle(new Callback[]{pcb}); + ks.load(null,pcb.getPassword()); + } + catch (Exception e) { + ks = null; + pcb = null; + } + } + else if(config.getKeystoreType().equals("Apple")) { + ks = KeyStore.getInstance("KeychainStore","Apple"); + ks.load(null,null); + //pcb = new PasswordCallback("Apple Keychain",false); + //pcb.setPassword(null); + } + else { + ks = KeyStore.getInstance(config.getKeystoreType()); + try { + pcb = new PasswordCallback("Keystore Password: ",false); + config.getCallbackHandler().handle(new Callback[]{pcb}); + ks.load(new FileInputStream(config.getKeystorePath()), pcb.getPassword()); + } + catch(Exception e) { + ks = null; + pcb = null; + } + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + try { + if(pcb == null) { + kmf.init(ks,null); + } else { + kmf.init(ks,pcb.getPassword()); + pcb.clearPassword(); + } + kms = kmf.getKeyManagers(); + } catch (NullPointerException npe) { + kms = null; + } + } + + // Verify certificate presented by the server + if (context == null) { + context = SSLContext.getInstance("TLS"); + context.init(kms, + new javax.net.ssl.TrustManager[]{new ServerTrustManager(getServiceName(), config)}, + new java.security.SecureRandom()); + } + Socket plain = socket; + // Secure the plain connection + socket = context.getSocketFactory().createSocket(plain, + plain.getInetAddress().getHostName(), plain.getPort(), true); + socket.setSoTimeout(0); + socket.setKeepAlive(true); + // Initialize the reader and writer with the new secured version + initReaderAndWriter(); + // Proceed to do the handshake + ((SSLSocket) socket).startHandshake(); + //if (((SSLSocket) socket).getWantClientAuth()) { + // System.err.println("Connection wants client auth"); + //} + //else if (((SSLSocket) socket).getNeedClientAuth()) { + // System.err.println("Connection needs client auth"); + //} + //else { + // System.err.println("Connection does not require client auth"); + // } + // Set that TLS was successful + usingTLS = true; + + // Set the new writer to use + packetWriter.setWriter(writer); + // Send a new opening stream to the server + packetWriter.openStream(); + } + + /** + * Sets the available stream compression methods offered by the server. + * + * @param methods compression methods offered by the server. + */ + void setAvailableCompressionMethods(Collection methods) { + compressionMethods = methods; + } + + /** + * Returns true if the specified compression method was offered by the server. + * + * @param method the method to check. + * @return true if the specified compression method was offered by the server. + */ + private boolean hasAvailableCompressionMethod(String method) { + return compressionMethods != null && compressionMethods.contains(method); + } + + public boolean isUsingCompression() { + return usingCompression; + } + + /** + * Starts using stream compression that will compress network traffic. Traffic can be + * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network + * connection. However, the server and the client will need to use more CPU time in order to + * un/compress network data so under high load the server performance might be affected.

      + *

      + * Stream compression has to have been previously offered by the server. Currently only the + * zlib method is supported by the client. Stream compression negotiation has to be done + * before authentication took place.

      + *

      + * Note: to use stream compression the smackx.jar file has to be present in the classpath. + * + * @return true if stream compression negotiation was successful. + */ + private boolean useCompression() { + // If stream compression was offered by the server and we want to use + // compression then send compression request to the server + if (authenticated) { + throw new IllegalStateException("Compression should be negotiated before authentication."); + } + try { + Class.forName("com.jcraft.jzlib.ZOutputStream"); + } + catch (ClassNotFoundException e) { + throw new IllegalStateException("Cannot use compression. Add smackx.jar to the classpath"); + } + if (hasAvailableCompressionMethod("zlib")) { + requestStreamCompression(); + // Wait until compression is being used or a timeout happened + synchronized (this) { + try { + this.wait(SmackConfiguration.getPacketReplyTimeout() * 5); + } + catch (InterruptedException e) { + // Ignore. + } + } + return usingCompression; + } + return false; + } + + /** + * Request the server that we want to start using stream compression. When using TLS + * then negotiation of stream compression can only happen after TLS was negotiated. If TLS + * compression is being used the stream compression should not be used. + */ + private void requestStreamCompression() { + try { + writer.write(""); + writer.write("zlib"); + writer.flush(); + } + catch (IOException e) { + packetReader.notifyConnectionError(e); + } + } + + /** + * Start using stream compression since the server has acknowledged stream compression. + * + * @throws Exception if there is an exception starting stream compression. + */ + void startStreamCompression() throws Exception { + // Secure the plain connection + usingCompression = true; + // Initialize the reader and writer with the new secured version + initReaderAndWriter(); + + // Set the new writer to use + packetWriter.setWriter(writer); + // Send a new opening stream to the server + packetWriter.openStream(); + // Notify that compression is being used + synchronized (this) { + this.notify(); + } + } + + /** + * Notifies the XMPP connection that stream compression was denied so that + * the connection process can proceed. + */ + void streamCompressionDenied() { + synchronized (this) { + this.notify(); + } + } + + /** + * Establishes a connection to the XMPP server and performs an automatic login + * only if the previous connection state was logged (authenticated). It basically + * creates and maintains a socket connection to the server.

      + *

      + * Listeners will be preserved from a previous connection if the reconnection + * occurs after an abrupt termination. + * + * @throws XMPPException if an error occurs while trying to establish the connection. + * Two possible errors can occur which will be wrapped by an XMPPException -- + * UnknownHostException (XMPP error code 504), and IOException (XMPP error code + * 502). The error codes and wrapped exceptions can be used to present more + * appropiate error messages to end-users. + */ + public void connect() throws XMPPException { + connect(true); + } + + public void connect(boolean bind) throws XMPPException { + // Establishes the connection, readers and writers + connectUsingConfiguration(config); + // Automatically makes the login if the user was previouslly connected successfully + // to the server and the connection was terminated abruptly + if (connected && wasAuthenticated) { + // Make the login + try { + if (isAnonymous()) { + // Make the anonymous login + loginAnonymously(); + } + else { + login(config.getUsername(), config.getPassword(), + bind ? config.getResource() : null); + } + packetReader.notifyReconnection(); + } + catch (XMPPException e) { + e.printStackTrace(); + } + } + } + + /** + * Sets whether the connection has already logged in the server. + * + * @param wasAuthenticated true if the connection has already been authenticated. + */ + private void setWasAuthenticated(boolean wasAuthenticated) { + if (!this.wasAuthenticated) { + this.wasAuthenticated = wasAuthenticated; + } + } + + @Override + public void setRosterStorage(RosterStorage storage) + throws IllegalStateException { + if(roster!=null){ + throw new IllegalStateException("Roster is already initialized"); + } + this.rosterStorage = storage; + } + + public boolean isEntityCapsEnabled() { + return config.isEntityCapsEnabled(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig new file mode 100644 index 0000000..cb49b3d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPConnection.java.orig @@ -0,0 +1,1099 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import java.io.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.net.Socket; +import java.net.UnknownHostException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.Security; +import java.util.Collection; + +/** + * Creates a socket connection to a XMPP server. This is the default connection + * to a Jabber server and is specified in the XMPP Core (RFC 3920). + * + * @see Connection + * @author Matt Tucker + */ +public class XMPPConnection extends Connection { + + /** + * The socket which is used for this connection. + */ + Socket socket; + + private String user = null; + protected boolean connected = false; + private boolean socketClosed = false; + /** + * Flag that indicates if the user is currently authenticated with the server. + */ + private boolean authenticated = false; + /** + * Flag that indicates if the user was authenticated with the server when the connection + * to the server was closed (abruptly or not). + */ + private boolean wasAuthenticated = false; + private boolean anonymous = false; + private boolean usingTLS = false; + + PacketWriter packetWriter; + PacketReader packetReader; + + Roster roster = null; + + /** + * Collection of available stream compression methods offered by the server. + */ + private Collection compressionMethods; + /** + * Flag that indicates if stream compression is actually in use. + */ + private boolean usingCompression; + + + /** + * Creates a new connection to the specified XMPP server. A DNS SRV lookup will be + * performed to determine the IP address and port corresponding to the + * service name; if that lookup fails, it's assumed that server resides at + * serviceName with the default port of 5222. Encrypted connections (TLS) + * will be used if available, stream compression is disabled, and standard SASL + * mechanisms will be used for authentication.

      + *

      + * This is the simplest constructor for connecting to an XMPP server. Alternatively, + * you can get fine-grained control over connection settings using the + * {@link #XMPPConnection(ConnectionConfiguration)} constructor.

      + *

      + * Note that XMPPConnection constructors do not establish a connection to the server + * and you must call {@link #connect()}.

      + *

      + * The CallbackHandler will only be used if the connection requires the client provide + * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback + * to prompt for a password to unlock the keystore containing the SSL certificate. + * + * @param serviceName the name of the XMPP server to connect to; e.g. example.com. + * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. + */ + public XMPPConnection(String serviceName, CallbackHandler callbackHandler) { + // Create the configuration for this new connection + super(new ConnectionConfiguration(serviceName)); + config.setCompressionEnabled(false); + config.setSASLAuthenticationEnabled(true); + config.setDebuggerEnabled(DEBUG_ENABLED); + config.setCallbackHandler(callbackHandler); + } + + /** + * Creates a new XMPP connection in the same way {@link #XMPPConnection(String,CallbackHandler)} does, but + * with no callback handler for password prompting of the keystore. This will work + * in most cases, provided the client is not required to provide a certificate to + * the server. + * + * @param serviceName the name of the XMPP server to connect to; e.g. example.com. + */ + public XMPPConnection(String serviceName) { + // Create the configuration for this new connection + super(new ConnectionConfiguration(serviceName)); + config.setCompressionEnabled(false); + config.setSASLAuthenticationEnabled(true); + config.setDebuggerEnabled(DEBUG_ENABLED); + } + + /** + * Creates a new XMPP connection in the same way {@link #XMPPConnection(ConnectionConfiguration,CallbackHandler)} does, but + * with no callback handler for password prompting of the keystore. This will work + * in most cases, provided the client is not required to provide a certificate to + * the server. + * + * + * @param config the connection configuration. + */ + public XMPPConnection(ConnectionConfiguration config) { + super(config); + } + + /** + * Creates a new XMPP connection using the specified connection configuration.

      + *

      + * Manually specifying connection configuration information is suitable for + * advanced users of the API. In many cases, using the + * {@link #XMPPConnection(String)} constructor is a better approach.

      + *

      + * Note that XMPPConnection constructors do not establish a connection to the server + * and you must call {@link #connect()}.

      + *

      + * + * The CallbackHandler will only be used if the connection requires the client provide + * an SSL certificate to the server. The CallbackHandler must handle the PasswordCallback + * to prompt for a password to unlock the keystore containing the SSL certificate. + * + * @param config the connection configuration. + * @param callbackHandler the CallbackHandler used to prompt for the password to the keystore. + */ + public XMPPConnection(ConnectionConfiguration config, CallbackHandler callbackHandler) { + super(config); + config.setCallbackHandler(callbackHandler); + } + + public String getConnectionID() { + if (!isConnected()) { + return null; + } + return connectionID; + } + + public String getUser() { + if (!isAuthenticated()) { + return null; + } + return user; + } + + @Override + public synchronized void login(String username, String password, String resource) throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + // Do partial version of nameprep on the username. + username = username.toLowerCase().trim(); + + String response; + if (config.isSASLAuthenticationEnabled() && + saslAuthentication.hasNonAnonymousAuthentication()) { + // Authenticate using SASL + if (password != null) { + response = saslAuthentication.authenticate(username, password, resource); + } + else { + response = saslAuthentication + .authenticate(username, resource, config.getCallbackHandler()); + } + } + else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticate(username, password, resource); + } + + // If compression is enabled then request the server to use stream compression + if (config.isCompressionEnabled()) { + useCompression(); + } + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = false; + + // If we did not bind, don't attempt to do anything with roster or presence + if (resource == null) + return; + + // Set the user. + if (response != null) { + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + } + else { + this.user = username + "@" + getServiceName(); + if (resource != null) { + this.user += "/" + resource; + } + } + + // Create the roster if it is not a reconnection or roster already created by getRoster() + if (this.roster == null) { + if(rosterStorage==null){ + this.roster = new Roster(this); + } + else{ + this.roster = new Roster(this,rosterStorage); + } + } + if (config.isRosterLoadedAtLogin()) { + this.roster.reload(); + } + + // Set presence to online. + if (config.isSendPresence()) { + packetWriter.sendPacket(new Presence(Presence.Type.available)); + } + + // Stores the authentication for future reconnection + config.setLoginInfo(username, password, resource); + + // If debugging is enabled, change the the debug window title to include the + // name we are now logged-in as. + // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger + // will be null + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + @Override + public synchronized void loginAnonymously() throws XMPPException { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (authenticated) { + throw new IllegalStateException("Already logged in to server."); + } + + String response; + if (config.isSASLAuthenticationEnabled() && + saslAuthentication.hasAnonymousAuthentication()) { + response = saslAuthentication.authenticateAnonymously(); + } + else { + // Authenticate using Non-SASL + response = new NonSASLAuthentication(this).authenticateAnonymously(); + } + + // Set the user value. + this.user = response; + // Update the serviceName with the one returned by the server + config.setServiceName(StringUtils.parseServer(response)); + + // If compression is enabled then request the server to use stream compression + if (config.isCompressionEnabled()) { + useCompression(); + } + + // Set presence to online. + packetWriter.sendPacket(new Presence(Presence.Type.available)); + + // Indicate that we're now authenticated. + authenticated = true; + anonymous = true; + + // If debugging is enabled, change the the debug window title to include the + // name we are now logged-in as. + // If DEBUG_ENABLED was set to true AFTER the connection was created the debugger + // will be null + if (config.isDebuggerEnabled() && debugger != null) { + debugger.userHasLogged(user); + } + } + + public Roster getRoster() { + // synchronize against login() + synchronized(this) { + // if connection is authenticated the roster is already set by login() + // or a previous call to getRoster() + if (!isAuthenticated() || isAnonymous()) { + if (roster == null) { + roster = new Roster(this); + } + return roster; + } + } + + if (!config.isRosterLoadedAtLogin()) { + roster.reload(); + } + // If this is the first time the user has asked for the roster after calling + // login, we want to wait for the server to send back the user's roster. This + // behavior shields API users from having to worry about the fact that roster + // operations are asynchronous, although they'll still have to listen for + // changes to the roster. Note: because of this waiting logic, internal + // Smack code should be wary about calling the getRoster method, and may need to + // access the roster object directly. + if (!roster.rosterInitialized) { + try { + synchronized (roster) { + long waitTime = SmackConfiguration.getPacketReplyTimeout(); + long start = System.currentTimeMillis(); + while (!roster.rosterInitialized) { + if (waitTime <= 0) { + break; + } + roster.wait(waitTime); + long now = System.currentTimeMillis(); + waitTime -= now - start; + start = now; + } + } + } + catch (InterruptedException ie) { + // Ignore. + } + } + return roster; + } + + public boolean isConnected() { + return connected; + } + + public boolean isSecureConnection() { + return isUsingTLS(); + } + + public boolean isSocketClosed() { + return socketClosed; + } + + public boolean isAuthenticated() { + return authenticated; + } + + public boolean isAnonymous() { + return anonymous; + } + + /** + * Forceful shutdown without full disconnect. + * + *

      Used when a reconnection is possible. + */ + public void quickShutdown() { + try { + this.setWasAuthenticated(authenticated); + authenticated = false; + + // Close socket before we close writer, so that stream end is not sent and reconnection is possible. + // This may cause a ConnectionClosedOnError notification to go out. + socketClosed = true; + try { + socket.shutdownInput(); + } + catch (Exception e) { + } + try { + socket.close(); + } catch (Exception e) { + } + + if (packetReader!=null){ + // Does not notify listeners + packetReader.quickShutdown(); + } + if (packetWriter!=null){ + packetWriter.shutdown(); + } + // Wait 150 ms for processes to clean-up, then shutdown. + try { + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + connected = false; + + // Close down the readers and writers. + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + saslAuthentication.init(); + } + catch (Exception e) { + System.err.println(e); + } + } + + /** + * Closes the connection by setting presence to unavailable then closing the stream to + * the XMPP server. The shutdown logic will be used during a planned disconnection or when + * dealing with an unexpected disconnection. Unlike {@link #disconnect()} the connection's + * packet reader, packet writer, and {@link Roster} will not be removed; thus + * connection's state is kept. + * + * @param unavailablePresence the presence packet to send during shutdown. + */ + protected void shutdown(Presence unavailablePresence) { + // Set presence to offline. + if (packetWriter != null){ + packetWriter.sendPacket(unavailablePresence); + } + + this.setWasAuthenticated(authenticated); + authenticated = false; + + if (packetReader!=null){ + packetReader.shutdown(); + } + if (packetWriter!=null){ + packetWriter.shutdown(); + } + // Wait 150 ms for processes to clean-up, then shutdown. + try { + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + socketClosed = true; + try { + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + connected = false; + + + // Close down the readers and writers. + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + // Make sure that the socket is really closed + try { + socket.close(); + } + catch (Exception e) { + // Ignore. + } + + saslAuthentication.init(); + } + + public synchronized void disconnect(Presence unavailablePresence) { + // If not connected, ignore this request. + PacketReader packetReader = this.packetReader; + PacketWriter packetWriter = this.packetWriter; + if (packetReader == null || packetWriter == null) { + return; + } + + shutdown(unavailablePresence); + + if (roster != null) { + roster.cleanup(); + roster = null; + } + chatManager = null; + + wasAuthenticated = false; + + packetWriter.cleanup(); + this.packetWriter = null; + packetReader.cleanup(); + this.packetReader = null; + } + + public void sendPacket(Packet packet) { + if (!isConnected()) { + throw new IllegalStateException("Not connected to server."); + } + if (packet == null) { + throw new NullPointerException("Packet is null."); + } + packetWriter.sendPacket(packet); + } + + /** + * Registers a packet interceptor with this connection. The interceptor will be + * invoked every time a packet is about to be sent by this connection. Interceptors + * may modify the packet to be sent. A packet filter determines which packets + * will be delivered to the interceptor. + * + * @param packetInterceptor the packet interceptor to notify of packets about to be sent. + * @param packetFilter the packet filter to use. + * @deprecated replaced by {@link Connection#addPacketInterceptor(PacketInterceptor, PacketFilter)}. + */ + public void addPacketWriterInterceptor(PacketInterceptor packetInterceptor, + PacketFilter packetFilter) { + addPacketInterceptor(packetInterceptor, packetFilter); + } + + /** + * Removes a packet interceptor. + * + * @param packetInterceptor the packet interceptor to remove. + * @deprecated replaced by {@link Connection#removePacketInterceptor(PacketInterceptor)}. + */ + public void removePacketWriterInterceptor(PacketInterceptor packetInterceptor) { + removePacketInterceptor(packetInterceptor); + } + + /** + * Registers a packet listener with this connection. The listener will be + * notified of every packet that this connection sends. A packet filter determines + * which packets will be delivered to the listener. Note that the thread + * that writes packets will be used to invoke the listeners. Therefore, each + * packet listener should complete all operations quickly or use a different + * thread for processing. + * + * @param packetListener the packet listener to notify of sent packets. + * @param packetFilter the packet filter to use. + * @deprecated replaced by {@link #addPacketSendingListener(PacketListener, PacketFilter)}. + */ + public void addPacketWriterListener(PacketListener packetListener, PacketFilter packetFilter) { + addPacketSendingListener(packetListener, packetFilter); + } + + /** + * Removes a packet listener for sending packets from this connection. + * + * @param packetListener the packet listener to remove. + * @deprecated replaced by {@link #removePacketSendingListener(PacketListener)}. + */ + public void removePacketWriterListener(PacketListener packetListener) { + removePacketSendingListener(packetListener); + } + + private void connectUsingConfiguration(ConnectionConfiguration config) throws XMPPException { + String host = config.getHost(); + int port = config.getPort(); + try { + if (config.getSocketFactory() == null) { + this.socket = new Socket(host, port); + } + else { + this.socket = config.getSocketFactory().createSocket(host, port); + } + } + catch (UnknownHostException uhe) { + String errorMessage = "Could not connect to " + host + ":" + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_timeout, errorMessage), + uhe); + } + catch (IOException ioe) { + String errorMessage = "XMPPError connecting to " + host + ":" + + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_error, errorMessage), ioe); + } + socketClosed = false; + initConnection(); + } + + /** + * Initializes the connection by creating a packet reader and writer and opening a + * XMPP stream to the server. + * + * @throws XMPPException if establishing a connection to the server fails. + */ + private void initConnection() throws XMPPException { + boolean isFirstInitialization = packetReader == null || packetWriter == null; + usingCompression = false; + + // Set the reader and writer instance variables + initReaderAndWriter(); + + try { + if (isFirstInitialization) { + packetWriter = new PacketWriter(this); + packetReader = new PacketReader(this); + + // If debugging is enabled, we should start the thread that will listen for + // all packets and then log them. + if (config.isDebuggerEnabled()) { + addPacketListener(debugger.getReaderListener(), null); + if (debugger.getWriterListener() != null) { + addPacketSendingListener(debugger.getWriterListener(), null); + } + } + } + else { + packetWriter.init(); + packetReader.init(); + } + + // Start the packet writer. This will open a XMPP stream to the server + packetWriter.startup(); + // Start the packet reader. The startup() method will block until we + // get an opening stream packet back from server. + packetReader.startup(); + + // Make note of the fact that we're now connected. + connected = true; + + // Start keep alive process (after TLS was negotiated - if available) + packetWriter.startKeepAliveProcess(); + + + if (isFirstInitialization) { + // Notify listeners that a new connection has been established + for (ConnectionCreationListener listener : getConnectionCreationListeners()) { + listener.connectionCreated(this); + } + } + else if (!wasAuthenticated) { + packetReader.notifyReconnection(); + } + + } + catch (XMPPException ex) { + // An exception occurred in setting up the connection. Make sure we shut down the + // readers and writers and close the socket. + + if (packetWriter != null) { + try { + packetWriter.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetWriter = null; + } + if (packetReader != null) { + try { + packetReader.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetReader = null; + } + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */} + writer = null; + } + if (socket != null) { + try { + socket.close(); + } + catch (Exception e) { /* ignore */ } + socket = null; + } + this.setWasAuthenticated(authenticated); + chatManager = null; + authenticated = false; + connected = false; + + throw ex; // Everything stoppped. Now throw the exception. + } + } + + private void initReaderAndWriter() throws XMPPException { + try { + if (!usingCompression) { + reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + writer = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); + } + else { + try { + Class zoClass = Class.forName("com.jcraft.jzlib.ZOutputStream"); + Constructor constructor = + zoClass.getConstructor(OutputStream.class, Integer.TYPE); + Object out = constructor.newInstance(socket.getOutputStream(), 9); + Method method = zoClass.getMethod("setFlushMode", Integer.TYPE); + method.invoke(out, 2); + writer = + new BufferedWriter(new OutputStreamWriter((OutputStream) out, "UTF-8")); + + Class ziClass = Class.forName("com.jcraft.jzlib.ZInputStream"); + constructor = ziClass.getConstructor(InputStream.class); + Object in = constructor.newInstance(socket.getInputStream()); + method = ziClass.getMethod("setFlushMode", Integer.TYPE); + method.invoke(in, 2); + reader = new BufferedReader(new InputStreamReader((InputStream) in, "UTF-8")); + } + catch (Exception e) { + e.printStackTrace(); + reader = new BufferedReader( + new InputStreamReader(socket.getInputStream(), "UTF-8")); + writer = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); + } + } + } + catch (IOException ioe) { + throw new XMPPException( + "XMPPError establishing connection with server.", + new XMPPError(XMPPError.Condition.remote_server_error, + "XMPPError establishing connection with server."), + ioe); + } + + // If debugging is enabled, we open a window and write out all network traffic. + initDebugger(); + } + + /*********************************************** + * TLS code below + **********************************************/ + + /** + * Returns true if the connection to the server has successfully negotiated TLS. Once TLS + * has been negotiatied the connection has been secured. + * + * @return true if the connection to the server has successfully negotiated TLS. + */ + public boolean isUsingTLS() { + return usingTLS; + } + + /** + * Notification message saying that the server supports TLS so confirm the server that we + * want to secure the connection. + * + * @param required true when the server indicates that TLS is required. + */ + void startTLSReceived(boolean required) { + if (required && config.getSecurityMode() == + ConnectionConfiguration.SecurityMode.disabled) { + packetReader.notifyConnectionError(new IllegalStateException( + "TLS required by server but not allowed by connection configuration")); + return; + } + + if (config.getSecurityMode() == ConnectionConfiguration.SecurityMode.disabled) { + // Do not secure the connection using TLS since TLS was disabled + return; + } + try { + writer.write(""); + writer.flush(); + } + catch (IOException e) { + packetReader.notifyConnectionError(e); + } + } + + /** + * The server has indicated that TLS negotiation can start. We now need to secure the + * existing plain connection and perform a handshake. This method won't return until the + * connection has finished the handshake or an error occured while securing the connection. + * + * @throws Exception if an exception occurs. + */ + void proceedTLSReceived() throws Exception { + SSLContext context = this.config.getCustomSSLContext(); + KeyStore ks = null; + KeyManager[] kms = null; + PasswordCallback pcb = null; + + if(config.getCallbackHandler() == null) { + ks = null; + } else if (context == null) { + //System.out.println("Keystore type: "+configuration.getKeystoreType()); + if(config.getKeystoreType().equals("NONE")) { + ks = null; + pcb = null; + } + else if(config.getKeystoreType().equals("PKCS11")) { + try { + Constructor c = Class.forName("sun.security.pkcs11.SunPKCS11").getConstructor(InputStream.class); + String pkcs11Config = "name = SmartCard\nlibrary = "+config.getPKCS11Library(); + ByteArrayInputStream config = new ByteArrayInputStream(pkcs11Config.getBytes()); + Provider p = (Provider)c.newInstance(config); + Security.addProvider(p); + ks = KeyStore.getInstance("PKCS11",p); + pcb = new PasswordCallback("PKCS11 Password: ",false); + this.config.getCallbackHandler().handle(new Callback[]{pcb}); + ks.load(null,pcb.getPassword()); + } + catch (Exception e) { + ks = null; + pcb = null; + } + } + else if(config.getKeystoreType().equals("Apple")) { + ks = KeyStore.getInstance("KeychainStore","Apple"); + ks.load(null,null); + //pcb = new PasswordCallback("Apple Keychain",false); + //pcb.setPassword(null); + } + else { + ks = KeyStore.getInstance(config.getKeystoreType()); + try { + pcb = new PasswordCallback("Keystore Password: ",false); + config.getCallbackHandler().handle(new Callback[]{pcb}); + ks.load(new FileInputStream(config.getKeystorePath()), pcb.getPassword()); + } + catch(Exception e) { + ks = null; + pcb = null; + } + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + try { + if(pcb == null) { + kmf.init(ks,null); + } else { + kmf.init(ks,pcb.getPassword()); + pcb.clearPassword(); + } + kms = kmf.getKeyManagers(); + } catch (NullPointerException npe) { + kms = null; + } + } + + // Verify certificate presented by the server + if (context == null) { + context = SSLContext.getInstance("TLS"); + context.init(kms, + new javax.net.ssl.TrustManager[]{new ServerTrustManager(getServiceName(), config)}, + new java.security.SecureRandom()); + } + Socket plain = socket; + // Secure the plain connection + socket = context.getSocketFactory().createSocket(plain, + plain.getInetAddress().getHostName(), plain.getPort(), true); + socket.setSoTimeout(0); + socket.setKeepAlive(true); + // Initialize the reader and writer with the new secured version + initReaderAndWriter(); + // Proceed to do the handshake + ((SSLSocket) socket).startHandshake(); + //if (((SSLSocket) socket).getWantClientAuth()) { + // System.err.println("Connection wants client auth"); + //} + //else if (((SSLSocket) socket).getNeedClientAuth()) { + // System.err.println("Connection needs client auth"); + //} + //else { + // System.err.println("Connection does not require client auth"); + // } + // Set that TLS was successful + usingTLS = true; + + // Set the new writer to use + packetWriter.setWriter(writer); + // Send a new opening stream to the server + packetWriter.openStream(); + } + + /** + * Sets the available stream compression methods offered by the server. + * + * @param methods compression methods offered by the server. + */ + void setAvailableCompressionMethods(Collection methods) { + compressionMethods = methods; + } + + /** + * Returns true if the specified compression method was offered by the server. + * + * @param method the method to check. + * @return true if the specified compression method was offered by the server. + */ + private boolean hasAvailableCompressionMethod(String method) { + return compressionMethods != null && compressionMethods.contains(method); + } + + public boolean isUsingCompression() { + return usingCompression; + } + + /** + * Starts using stream compression that will compress network traffic. Traffic can be + * reduced up to 90%. Therefore, stream compression is ideal when using a slow speed network + * connection. However, the server and the client will need to use more CPU time in order to + * un/compress network data so under high load the server performance might be affected.

      + *

      + * Stream compression has to have been previously offered by the server. Currently only the + * zlib method is supported by the client. Stream compression negotiation has to be done + * before authentication took place.

      + *

      + * Note: to use stream compression the smackx.jar file has to be present in the classpath. + * + * @return true if stream compression negotiation was successful. + */ + private boolean useCompression() { + // If stream compression was offered by the server and we want to use + // compression then send compression request to the server + if (authenticated) { + throw new IllegalStateException("Compression should be negotiated before authentication."); + } + try { + Class.forName("com.jcraft.jzlib.ZOutputStream"); + } + catch (ClassNotFoundException e) { + throw new IllegalStateException("Cannot use compression. Add smackx.jar to the classpath"); + } + if (hasAvailableCompressionMethod("zlib")) { + requestStreamCompression(); + // Wait until compression is being used or a timeout happened + synchronized (this) { + try { + this.wait(SmackConfiguration.getPacketReplyTimeout() * 5); + } + catch (InterruptedException e) { + // Ignore. + } + } + return usingCompression; + } + return false; + } + + /** + * Request the server that we want to start using stream compression. When using TLS + * then negotiation of stream compression can only happen after TLS was negotiated. If TLS + * compression is being used the stream compression should not be used. + */ + private void requestStreamCompression() { + try { + writer.write(""); + writer.write("zlib"); + writer.flush(); + } + catch (IOException e) { + packetReader.notifyConnectionError(e); + } + } + + /** + * Start using stream compression since the server has acknowledged stream compression. + * + * @throws Exception if there is an exception starting stream compression. + */ + void startStreamCompression() throws Exception { + // Secure the plain connection + usingCompression = true; + // Initialize the reader and writer with the new secured version + initReaderAndWriter(); + + // Set the new writer to use + packetWriter.setWriter(writer); + // Send a new opening stream to the server + packetWriter.openStream(); + // Notify that compression is being used + synchronized (this) { + this.notify(); + } + } + + /** + * Notifies the XMPP connection that stream compression was denied so that + * the connection process can proceed. + */ + void streamCompressionDenied() { + synchronized (this) { + this.notify(); + } + } + + /** + * Establishes a connection to the XMPP server and performs an automatic login + * only if the previous connection state was logged (authenticated). It basically + * creates and maintains a socket connection to the server.

      + *

      + * Listeners will be preserved from a previous connection if the reconnection + * occurs after an abrupt termination. + * + * @throws XMPPException if an error occurs while trying to establish the connection. + * Two possible errors can occur which will be wrapped by an XMPPException -- + * UnknownHostException (XMPP error code 504), and IOException (XMPP error code + * 502). The error codes and wrapped exceptions can be used to present more + * appropiate error messages to end-users. + */ + public void connect() throws XMPPException { + connect(true); + } + + public void connect(boolean bind) throws XMPPException { + // Establishes the connection, readers and writers + connectUsingConfiguration(config); + // Automatically makes the login if the user was previouslly connected successfully + // to the server and the connection was terminated abruptly + if (connected && wasAuthenticated) { + // Make the login + try { + if (isAnonymous()) { + // Make the anonymous login + loginAnonymously(); + } + else { + login(config.getUsername(), config.getPassword(), + bind ? config.getResource() : null); + } + packetReader.notifyReconnection(); + } + catch (XMPPException e) { + e.printStackTrace(); + } + } + } + + /** + * Sets whether the connection has already logged in the server. + * + * @param wasAuthenticated true if the connection has already been authenticated. + */ + private void setWasAuthenticated(boolean wasAuthenticated) { + if (!this.wasAuthenticated) { + this.wasAuthenticated = wasAuthenticated; + } + } + + @Override + public void setRosterStorage(RosterStorage storage) + throws IllegalStateException { + if(roster!=null){ + throw new IllegalStateException("Roster is already initialized"); + } + this.rosterStorage = storage; + } + + public boolean isEntityCapsEnabled() { + return config.isEntityCapsEnabled(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java new file mode 100644 index 0000000..6da24c2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPException.java @@ -0,0 +1,219 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.packet.StreamError; +import org.jivesoftware.smack.packet.XMPPError; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * A generic exception that is thrown when an error occurs performing an + * XMPP operation. XMPP servers can respond to error conditions with an error code + * and textual description of the problem, which are encapsulated in the XMPPError + * class. When appropriate, an XMPPError instance is attached instances of this exception.

      + * + * When a stream error occured, the server will send a stream error to the client before + * closing the connection. Stream errors are unrecoverable errors. When a stream error + * is sent to the client an XMPPException will be thrown containing the StreamError sent + * by the server. + * + * @see XMPPError + * @author Matt Tucker + */ +public class XMPPException extends Exception { + + private StreamError streamError = null; + private XMPPError error = null; + private Throwable wrappedThrowable = null; + + /** + * Creates a new XMPPException. + */ + public XMPPException() { + super(); + } + + /** + * Creates a new XMPPException with a description of the exception. + * + * @param message description of the exception. + */ + public XMPPException(String message) { + super(message); + } + + /** + * Creates a new XMPPException with the Throwable that was the root cause of the + * exception. + * + * @param wrappedThrowable the root cause of the exception. + */ + public XMPPException(Throwable wrappedThrowable) { + super(); + this.wrappedThrowable = wrappedThrowable; + } + + /** + * Cretaes a new XMPPException with the stream error that was the root case of the + * exception. When a stream error is received from the server then the underlying + * TCP connection will be closed by the server. + * + * @param streamError the root cause of the exception. + */ + public XMPPException(StreamError streamError) { + super(); + this.streamError = streamError; + } + + /** + * Cretaes a new XMPPException with the XMPPError that was the root case of the + * exception. + * + * @param error the root cause of the exception. + */ + public XMPPException(XMPPError error) { + super(); + this.error = error; + } + + /** + * Creates a new XMPPException with a description of the exception and the + * Throwable that was the root cause of the exception. + * + * @param message a description of the exception. + * @param wrappedThrowable the root cause of the exception. + */ + public XMPPException(String message, Throwable wrappedThrowable) { + super(message); + this.wrappedThrowable = wrappedThrowable; + } + + /** + * Creates a new XMPPException with a description of the exception, an XMPPError, + * and the Throwable that was the root cause of the exception. + * + * @param message a description of the exception. + * @param error the root cause of the exception. + * @param wrappedThrowable the root cause of the exception. + */ + public XMPPException(String message, XMPPError error, Throwable wrappedThrowable) { + super(message); + this.error = error; + this.wrappedThrowable = wrappedThrowable; + } + + /** + * Creates a new XMPPException with a description of the exception and the + * XMPPException that was the root cause of the exception. + * + * @param message a description of the exception. + * @param error the root cause of the exception. + */ + public XMPPException(String message, XMPPError error) { + super(message); + this.error = error; + } + + /** + * Returns the XMPPError asscociated with this exception, or null if there + * isn't one. + * + * @return the XMPPError asscociated with this exception. + */ + public XMPPError getXMPPError() { + return error; + } + + /** + * Returns the StreamError asscociated with this exception, or null if there + * isn't one. The underlying TCP connection is closed by the server after sending the + * stream error to the client. + * + * @return the StreamError asscociated with this exception. + */ + public StreamError getStreamError() { + return streamError; + } + + /** + * Returns the Throwable asscociated with this exception, or null if there + * isn't one. + * + * @return the Throwable asscociated with this exception. + */ + public Throwable getWrappedThrowable() { + return wrappedThrowable; + } + + public void printStackTrace() { + printStackTrace(System.err); + } + + public void printStackTrace(PrintStream out) { + super.printStackTrace(out); + if (wrappedThrowable != null) { + out.println("Nested Exception: "); + wrappedThrowable.printStackTrace(out); + } + } + + public void printStackTrace(PrintWriter out) { + super.printStackTrace(out); + if (wrappedThrowable != null) { + out.println("Nested Exception: "); + wrappedThrowable.printStackTrace(out); + } + } + + public String getMessage() { + String msg = super.getMessage(); + // If the message was not set, but there is an XMPPError, return the + // XMPPError as the message. + if (msg == null && error != null) { + return error.toString(); + } + else if (msg == null && streamError != null) { + return streamError.toString(); + } + return msg; + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + String message = super.getMessage(); + if (message != null) { + buf.append(message).append(": "); + } + if (error != null) { + buf.append(error); + } + if (streamError != null) { + buf.append(streamError); + } + if (wrappedThrowable != null) { + buf.append("\n -- caused by: ").append(wrappedThrowable); + } + + return buf.toString(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java new file mode 100644 index 0000000..04c7f19 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/XMPPLLConnection.java @@ -0,0 +1,464 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smack; + +import org.jivesoftware.smack.debugger.SmackDebugger; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; + +import java.io.*; +import java.net.Socket; +import java.net.UnknownHostException; +import java.lang.reflect.Constructor; +import java.util.Date; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + + +/** + * Link-local XMPP connection according to XEP-0174 connection. Automatically + * created by LLService and closed by inactivity. + * + */ +public class XMPPLLConnection extends XMPPConnection // public for debugging reasons +{ + private final static Set linkLocalListeners = + new CopyOnWriteArraySet(); + + + private LLService service; + private LLPresence localPresence, remotePresence; + private boolean initiator; + private long lastActivity = 0; + protected XMPPLLConnection connection; + private Thread timeoutThread; + + /** + * Instantiate a new link-local connection. Use the config parameter to + * specify if the connection is acting as server or client. + * + * @param config specification about how the new connection is to be set up. + */ + XMPPLLConnection(LLService service, LLConnectionConfiguration config) { + super(config); + connection = this; + this.service = service; + updateLastActivity(); + + // A timeout thread's purpose is to close down inactive connections + // after a certain amount of seconds (defaults to 15). + timeoutThread = new Thread() { + public void run() { + try { + while (connection != null) { + //synchronized (connection) { + Thread.sleep(14000); + long currentTime = new Date().getTime(); + if (currentTime - lastActivity > 15000) { + shutdown(); + break; + } + //} + } + } catch (InterruptedException ie) { + shutdown(); + } + } + }; + + timeoutThread.setName("Smack Link-local Connection Timeout (" + connection.connectionCounterValue + ")"); + timeoutThread.setDaemon(true); + + + if (config.isInitiator()) { + // we are connecting to remote host + localPresence = config.getLocalPresence(); + remotePresence = config.getRemotePresence(); + config.setServiceName(remotePresence.getServiceName()); + initiator = true; + } else { + // a remote host connected to us + localPresence = config.getLocalPresence(); + remotePresence = null; + config.setServiceName(null); + initiator = false; + socket = config.getSocket(); + } + } + + /** + * Tells if this connection instance is the initiator. + * + * @return true if this instance is the one connecting to a remote peer. + */ + public boolean isInitiator() { + return initiator; + } + + /** + * Return the user name of the remote peer (service name). + * + * @return the remote hosts service name / username + */ + public String getUser() { + // username is the service name of the local presence + return localPresence.getServiceName(); + } + + /** + * Sets the name of the service provided in the from the remote peer. + * + * @param serviceName the name of the service + */ + public void setServiceName(String serviceName) { + config.setServiceName(serviceName); + } + + + /** + * Set the remote presence. Used when being connected, + * will not know the remote service name until stream is initiated. + * + * @param remotePresence presence information about the connecting client. + */ + void setRemotePresence(LLPresence remotePresence) { + this.remotePresence = remotePresence; + } + + /** + * Start listen for data and a stream tag. + */ + void initListen() throws XMPPException { + initConnection(); + } + + /** + * Adds a listener that are notified when a new link-local connection + * has been established. + * + * @param listener A class implementing the LLConnectionListener interface. + */ + public static void addLLConnectionListener(LLConnectionListener listener) { + linkLocalListeners.add(listener); + } + + /** + * Removes a listener from the new connection listener list. + * + * @param listener The class implementing the LLConnectionListener interface that + * is to be removed. + */ + public static void removeLLConnectionListener(LLConnectionListener listener) { + linkLocalListeners.remove(listener); + } + + /** + * Create a socket, connect to the remote peer and initiate a XMPP stream session. + */ + public void connect() throws XMPPException { + String host = remotePresence.getHost(); + int port = remotePresence.getPort(); + + try { + socket = new Socket(host, port); + } + catch (UnknownHostException uhe) { + String errorMessage = "Could not connect to " + host + ":" + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_timeout, errorMessage), + uhe); + } + catch (IOException ioe) { + String errorMessage = "Error connecting to " + host + ":" + + port + "."; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.remote_server_error, errorMessage), ioe); + } + initConnection(); + + notifyLLListenersConnected(); + } + + + /** + * Handles the opening of a stream after a remote client has connected and opened a stream. + * @throws XMPPException if service name is missing or service is unknown to the mDNS daemon. + */ + public void streamInitiatingReceived() throws XMPPException { + if (config.getServiceName() == null) { + shutdown(); + } else { + packetWriter = new PacketWriter(this); + if (debugger != null) { + if (debugger.getWriterListener() != null) { + addPacketListener(debugger.getWriterListener(), null); + } + } + packetWriter.startup(); + notifyLLListenersConnected(); + } + } + + /** + * Notify new connection listeners that a new connection has been established. + */ + private void notifyLLListenersConnected() { + for (LLConnectionListener listener : linkLocalListeners) { + listener.connectionCreated(this); + } + } + + /** + * Update the timer telling when the last activity happend. Used by timeout + * thread to tell how long the connection has been inactive. + */ + void updateLastActivity() { + lastActivity = new Date().getTime(); + } + + /** + * Sends the specified packet to the remote peer. + * + * @param packet the packet to send + */ + @Override + public void sendPacket(Packet packet) { + updateLastActivity(); + // always add the from='' attribute + packet.setFrom(getUser()); + super.sendPacket(packet); + } + + /** + * Initializes the connection by creating a packet reader and writer and opening a + * XMPP stream to the server. + * + * @throws XMPPException if establishing a connection to the server fails. + */ + private void initConnection() throws XMPPException { + // Set the reader and writer instance variables + initReaderAndWriter(); + timeoutThread.start(); + + try { + // Don't initialize packet writer until we know it's a valid connection + // unless we are the initiator. If we are NOT the initializer, we instead + // wait for a stream initiation before doing anything. + if (isInitiator()) + packetWriter = new PacketWriter(this); + + // Initialize packet reader + packetReader = new LLPacketReader(service, this); + + // If debugging is enabled, we should start the thread that will listen for + // all packets and then log them. + // XXX FIXME debugging enabled not working + if (false) {//configuration.isDebuggerEnabled()) { + addPacketListener(debugger.getReaderListener(), null); + } + + // Make note of the fact that we're now connected. + connected = true; + + // If we are the initiator start the packet writer. This will open a XMPP + // stream to the server. If not, a packet writer will be started after + // receiving an initial stream start tag. + if (isInitiator()) + packetWriter.startup(); + // Start the packet reader. The startup() method will block until we + // get an opening stream packet back from server. + packetReader.startup(); + } + catch (XMPPException ex) { + // An exception occurred in setting up the connection. Make sure we shut down the + // readers and writers and close the socket. + + if (packetWriter != null) { + try { + packetWriter.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetWriter = null; + } + if (packetReader != null) { + try { + packetReader.shutdown(); + } + catch (Throwable ignore) { /* ignore */ } + packetReader = null; + } + if (socket != null) { + try { + socket.close(); + } + catch (Exception e) { /* ignore */ } + socket = null; + } + // closing reader after socket since reader.close() blocks otherwise + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + connected = false; + + throw ex; // Everything stoppped. Now throw the exception. + } + } + + private void initReaderAndWriter() throws XMPPException { + try { + reader = + new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + writer = new BufferedWriter( + new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); + } + catch (IOException ioe) { + throw new XMPPException( + "XMPPError establishing connection with server.", + new XMPPError(XMPPError.Condition.remote_server_error, + "XMPPError establishing connection with server."), + ioe); + } + + // If debugging is enabled, we open a window and write out all network traffic. + if (false) {//configuration.isDebuggerEnabled()) { + if (debugger == null) { + // Detect the debugger class to use. + String className = null; + // Use try block since we may not have permission to get a system + // property (for example, when an applet). + try { + className = System.getProperty("smack.debuggerClass"); + } + catch (Throwable t) { + // Ignore. + } + Class debuggerClass = null; + if (className != null) { + try { + debuggerClass = Class.forName(className); + } + catch (Exception e) { + e.printStackTrace(); + } + } + if (debuggerClass == null) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smackx.debugger.EnhancedDebugger"); + } + catch (Exception ex) { + try { + debuggerClass = + Class.forName("org.jivesoftware.smack.debugger.LiteDebugger"); + } + catch (Exception ex2) { + ex2.printStackTrace(); + } + } + } + // Create a new debugger instance. If an exception occurs then disable the debugging + // option + try { + Constructor constructor = debuggerClass + .getConstructor(XMPPLLConnection.class, Writer.class, Reader.class); + debugger = (SmackDebugger) constructor.newInstance(this, writer, reader); + reader = debugger.getReader(); + writer = debugger.getWriter(); + } + catch (Exception e) { + e.printStackTrace(); + DEBUG_ENABLED = false; + } + } + else { + // Obtain new reader and writer from the existing debugger + reader = debugger.newConnectionReader(reader); + writer = debugger.newConnectionWriter(writer); + } + } + } + + protected void shutdown() { + connection = null; + + if (packetReader != null) + packetReader.shutdown(); + if (packetWriter != null) + packetWriter.shutdown(); + + // Wait 150 ms for processes to clean-up, then shutdown. + try { + Thread.sleep(150); + } + catch (Exception e) { + // Ignore. + } + + // Close down the readers and writers. + if (reader != null) { + try { + reader.close(); + } + catch (Throwable ignore) { /* ignore */ } + reader = null; + } + if (writer != null) { + try { + writer.close(); + } + catch (Throwable ignore) { /* ignore */ } + writer = null; + } + + try { + socket.close(); + } + catch (Exception e) { + // Ignore. + } + } + + public void disconnect() { + // If not connected, ignore this request. + if (packetReader == null || packetWriter == null) { + return; + } + + shutdown(); + + packetWriter.cleanup(); + packetWriter = null; + packetReader.cleanup(); + packetReader = null; + } + + @Override + public boolean isAddFrom() { + return true; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java new file mode 100644 index 0000000..0d3e5b7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/ConsoleDebugger.java @@ -0,0 +1,181 @@ +package org.jivesoftware.smack.debugger; + +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 java.io.Reader; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Very simple debugger that prints to the console (stdout) 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 ConsoleDebugger 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 ConsoleDebugger(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) { + System.out.println( + 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) { + System.out.println( + 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) { + System.out.println( + dateFormatter.format(new Date()) + " RCV PKT (" + + connection.hashCode() + + "): " + + packet.toXML()); + } + } + }; + + connListener = new ConnectionListener() { + public void connectionClosed() { + System.out.println( + dateFormatter.format(new Date()) + " Connection closed (" + + connection.hashCode() + + ")"); + } + + public void connectionClosedOnError(Exception e) { + System.out.println( + dateFormatter.format(new Date()) + + " Connection closed due to an exception (" + + connection.hashCode() + + ")"); + e.printStackTrace(); + } + public void reconnectionFailed(Exception e) { + System.out.println( + dateFormatter.format(new Date()) + + " Reconnection failed due to an exception (" + + connection.hashCode() + + ")"); + e.printStackTrace(); + } + public void reconnectionSuccessful() { + System.out.println( + dateFormatter.format(new Date()) + " Connection reconnected (" + + connection.hashCode() + + ")"); + } + public void reconnectingIn(int seconds) { + System.out.println( + 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); + System.out.println(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/org/jivesoftware/smack/debugger/SmackDebugger.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/SmackDebugger.java new file mode 100644 index 0000000..562720b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/SmackDebugger.java @@ -0,0 +1,98 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.debugger; + +import java.io.*; + +import org.jivesoftware.smack.*; + +/** + * Interface that allows for implementing classes to debug XML traffic. That is a GUI window that + * displays XML traffic.

      + * + * Every implementation of this interface must have a public constructor with the following + * arguments: Connection, Writer, Reader. + * + * @author Gaston Dombiak + */ +public interface SmackDebugger { + + /** + * Called when a user has logged in to the server. The user could be an anonymous user, this + * means that the user would be of the form host/resource instead of the form + * user@host/resource. + * + * @param user the user@host/resource that has just logged in + */ + public abstract void userHasLogged(String user); + + /** + * Returns the special Reader that wraps the main Reader and logs data to the GUI. + * + * @return the special Reader that wraps the main Reader and logs data to the GUI. + */ + public abstract Reader getReader(); + + /** + * Returns the special Writer that wraps the main Writer and logs data to the GUI. + * + * @return the special Writer that wraps the main Writer and logs data to the GUI. + */ + public abstract Writer getWriter(); + + /** + * Returns a new special Reader that wraps the new connection Reader. The connection + * has been secured so the connection is using a new reader and writer. The debugger + * needs to wrap the new reader and writer to keep being notified of the connection + * traffic. + * + * @return a new special Reader that wraps the new connection Reader. + */ + public abstract Reader newConnectionReader(Reader reader); + + /** + * Returns a new special Writer that wraps the new connection Writer. The connection + * has been secured so the connection is using a new reader and writer. The debugger + * needs to wrap the new reader and writer to keep being notified of the connection + * traffic. + * + * @return a new special Writer that wraps the new connection Writer. + */ + public abstract Writer newConnectionWriter(Writer writer); + + /** + * Returns the 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. + * + * @return the PacketListener that will listen for all incoming packets and write them to + * the GUI + */ + public abstract PacketListener getReaderListener(); + + /** + * Returns the thread that will listen for all outgoing packets and write them to the GUI. + * + * @return the PacketListener that will listen for all sent packets and write them to + * the GUI + */ + public abstract PacketListener getWriterListener(); +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html new file mode 100644 index 0000000..afb861f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/debugger/package.html @@ -0,0 +1 @@ +Core debugger functionality. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java new file mode 100644 index 0000000..847b618 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/AndFilter.java @@ -0,0 +1,91 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +import java.util.List; +import java.util.ArrayList; + +/** + * Implements the logical AND operation over two or more packet filters. + * In other words, packets pass this filter if they pass all of the filters. + * + * @author Matt Tucker + */ +public class AndFilter implements PacketFilter { + + /** + * The list of filters. + */ + private List filters = new ArrayList(); + + /** + * Creates an empty AND filter. Filters should be added using the + * {@link #addFilter(PacketFilter)} method. + */ + public AndFilter() { + + } + + /** + * Creates an AND filter using the specified filters. + * + * @param filters the filters to add. + */ + public AndFilter(PacketFilter... filters) { + if (filters == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + for(PacketFilter filter : filters) { + if(filter == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + this.filters.add(filter); + } + } + + /** + * Adds a filter to the filter list for the AND operation. A packet + * will pass the filter if all of the filters in the list accept it. + * + * @param filter a filter to add to the filter list. + */ + public void addFilter(PacketFilter filter) { + if (filter == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + filters.add(filter); + } + + public boolean accept(Packet packet) { + for (PacketFilter filter : filters) { + if (!filter.accept(packet)) { + return false; + } + } + return true; + } + + public String toString() { + return filters.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java new file mode 100644 index 0000000..bc372f9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromContainsFilter.java @@ -0,0 +1,54 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Filters for packets where the "from" field contains a specified value. + * + * @author Matt Tucker + */ +public class FromContainsFilter implements PacketFilter { + + private String from; + + /** + * Creates a "from" contains filter using the "from" field part. + * + * @param from the from field value the packet must contain. + */ + public FromContainsFilter(String from) { + if (from == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + this.from = from.toLowerCase(); + } + + public boolean accept(Packet packet) { + if (packet.getFrom() == null) { + return false; + } + else { + return packet.getFrom().toLowerCase().indexOf(from) != -1; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java new file mode 100644 index 0000000..e1dfa6c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/FromMatchesFilter.java @@ -0,0 +1,75 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Filter for packets where the "from" field exactly matches a specified JID. If the specified + * address is a bare JID then the filter will match any address whose bare JID matches the + * specified JID. But if the specified address is a full JID then the filter will only match + * if the sender of the packet matches the specified resource. + * + * @author Gaston Dombiak + */ +public class FromMatchesFilter implements PacketFilter { + + private String address; + /** + * Flag that indicates if the checking will be done against bare JID addresses or full JIDs. + */ + private boolean matchBareJID = false; + + /** + * Creates a "from" filter using the "from" field part. If the specified address is a bare JID + * then the filter will match any address whose bare JID matches the specified JID. But if the + * specified address is a full JID then the filter will only match if the sender of the packet + * matches the specified resource. + * + * @param address the from field value the packet must match. Could be a full or bare JID. + */ + public FromMatchesFilter(String address) { + if (address == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + this.address = address.toLowerCase(); + matchBareJID = "".equals(StringUtils.parseResource(address)); + } + + public boolean accept(Packet packet) { + if (packet.getFrom() == null) { + return false; + } + else if (matchBareJID) { + // Check if the bare JID of the sender of the packet matches the specified JID + return packet.getFrom().toLowerCase().startsWith(address); + } + else { + // Check if the full JID of the sender of the packet matches the specified JID + return address.equals(packet.getFrom().toLowerCase()); + } + } + + public String toString() { + return "FromMatchesFilter: " + address; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java new file mode 100644 index 0000000..6f080c5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/IQTypeFilter.java @@ -0,0 +1,48 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; + +/** + * A filter for IQ packet types. Returns true only if the packet is an IQ packet + * and it matches the type provided in the constructor. + * + * @author Alexander Wenckus + * + */ +public class IQTypeFilter implements PacketFilter { + + private IQ.Type type; + + public IQTypeFilter(IQ.Type type) { + this.type = type; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.filter.PacketFilter#accept(org.jivesoftware.smack.packet.Packet) + */ + public boolean accept(Packet packet) { + return (packet instanceof IQ && ((IQ) packet).getType().equals(type)); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java new file mode 100644 index 0000000..a3430ec --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/MessageTypeFilter.java @@ -0,0 +1,54 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; + +/** + * Filters for packets of a specific type of Message (e.g. CHAT). + * + * @see org.jivesoftware.smack.packet.Message.Type + * @author Ward Harold + */ +public class MessageTypeFilter implements PacketFilter { + + private final Message.Type type; + + /** + * Creates a new message type filter using the specified message type. + * + * @param type the message type. + */ + public MessageTypeFilter(Message.Type type) { + this.type = type; + } + + public boolean accept(Packet packet) { + if (!(packet instanceof Message)) { + return false; + } + else { + return ((Message) packet).getType().equals(this.type); + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java new file mode 100644 index 0000000..59537d0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/NotFilter.java @@ -0,0 +1,50 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Implements the logical NOT operation on a packet filter. In other words, packets + * pass this filter if they do not pass the supplied filter. + * + * @author Matt Tucker + */ +public class NotFilter implements PacketFilter { + + private PacketFilter filter; + + /** + * Creates a NOT filter using the specified filter. + * + * @param filter the filter. + */ + public NotFilter(PacketFilter filter) { + if (filter == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + this.filter = filter; + } + + public boolean accept(Packet packet) { + return !filter.accept(packet); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java new file mode 100644 index 0000000..4c34fd0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/OrFilter.java @@ -0,0 +1,103 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Implements the logical OR operation over two or more packet filters. In + * other words, packets pass this filter if they pass any of the filters. + * + * @author Matt Tucker + */ +public class OrFilter implements PacketFilter { + + /** + * The current number of elements in the filter. + */ + private int size; + + /** + * The list of filters. + */ + private PacketFilter [] filters; + + /** + * Creates an empty OR filter. Filters should be added using the + * {@link #addFilter(PacketFilter)} method. + */ + public OrFilter() { + size = 0; + filters = new PacketFilter[3]; + } + + /** + * Creates an OR filter using the two specified filters. + * + * @param filter1 the first packet filter. + * @param filter2 the second packet filter. + */ + public OrFilter(PacketFilter filter1, PacketFilter filter2) { + if (filter1 == null || filter2 == null) { + throw new IllegalArgumentException("Parameters cannot be null."); + } + size = 2; + filters = new PacketFilter[2]; + filters[0] = filter1; + filters[1] = filter2; + } + + /** + * Adds a filter to the filter list for the OR operation. A packet + * will pass the filter if any filter in the list accepts it. + * + * @param filter a filter to add to the filter list. + */ + public void addFilter(PacketFilter filter) { + if (filter == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + // If there is no more room left in the filters array, expand it. + if (size == filters.length) { + PacketFilter [] newFilters = new PacketFilter[filters.length+2]; + for (int i=0; i + * + * Several pre-defined filters are defined. These filters can be logically combined + * for more complex packet filtering by using the + * {@link org.jivesoftware.smack.filter.AndFilter AndFilter} and + * {@link org.jivesoftware.smack.filter.OrFilter OrFilter} filters. It's also possible + * to define your own filters by implementing this interface. The code example below + * creates a trivial filter for packets with a specific ID. + * + *

      + * // Use an anonymous inner class to define a packet filter that returns
      + * // all packets that have a packet ID of "RS145".
      + * PacketFilter myFilter = new PacketFilter() {
      + *     public boolean accept(Packet packet) {
      + *         return "RS145".equals(packet.getPacketID());
      + *     }
      + * };
      + * // Create a new packet collector using the filter we created.
      + * PacketCollector myCollector = packetReader.createPacketCollector(myFilter);
      + * 
      + * + * @see org.jivesoftware.smack.PacketCollector + * @see org.jivesoftware.smack.PacketListener + * @author Matt Tucker + */ +public interface PacketFilter { + + /** + * Tests whether or not the specified packet should pass the filter. + * + * @param packet the packet to test. + * @return true if and only if packet passes the filter. + */ + public boolean accept(Packet packet); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java new file mode 100644 index 0000000..8d68201 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketIDFilter.java @@ -0,0 +1,53 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Filters for packets with a particular packet ID. + * + * @author Matt Tucker + */ +public class PacketIDFilter implements PacketFilter { + + private String packetID; + + /** + * Creates a new packet ID filter using the specified packet ID. + * + * @param packetID the packet ID to filter for. + */ + public PacketIDFilter(String packetID) { + if (packetID == null) { + throw new IllegalArgumentException("Packet ID cannot be null."); + } + this.packetID = packetID; + } + + public boolean accept(Packet packet) { + return packetID.equals(packet.getPacketID()); + } + + public String toString() { + return "PacketIDFilter by id: " + packetID; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java new file mode 100644 index 0000000..24a8539 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/PacketTypeFilter.java @@ -0,0 +1,61 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Filters for packets of a particular type. The type is given as a Class object, so + * example types would: + *
        + *
      • Message.class + *
      • IQ.class + *
      • Presence.class + *
      + * + * @author Matt Tucker + */ +public class PacketTypeFilter implements PacketFilter { + + Class packetType; + + /** + * Creates a new packet type filter that will filter for packets that are the + * same type as packetType. + * + * @param packetType the Class type. + */ + public PacketTypeFilter(Class packetType) { + // Ensure the packet type is a sub-class of Packet. + if (!Packet.class.isAssignableFrom(packetType)) { + throw new IllegalArgumentException("Packet type must be a sub-class of Packet."); + } + this.packetType = packetType; + } + + public boolean accept(Packet packet) { + return packetType.isInstance(packet); + } + + public String toString() { + return "PacketTypeFilter: " + packetType.getName(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java new file mode 100644 index 0000000..8ba8b2e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ThreadFilter.java @@ -0,0 +1,50 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Message; + +/** + * Filters for message packets with a particular thread value. + * + * @author Matt Tucker + */ +public class ThreadFilter implements PacketFilter { + + private String thread; + + /** + * Creates a new thread filter using the specified thread value. + * + * @param thread the thread value to filter for. + */ + public ThreadFilter(String thread) { + if (thread == null) { + throw new IllegalArgumentException("Thread cannot be null."); + } + this.thread = thread; + } + + public boolean accept(Packet packet) { + return packet instanceof Message && thread.equals(((Message) packet).getThread()); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java new file mode 100644 index 0000000..8069fcc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/ToContainsFilter.java @@ -0,0 +1,55 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.filter; + +import org.jivesoftware.smack.packet.Packet; + +/** + * Filters for packets where the "to" field contains a specified value. For example, + * the filter could be used to listen for all packets sent to a group chat nickname. + * + * @author Matt Tucker + */ +public class ToContainsFilter implements PacketFilter { + + private String to; + + /** + * Creates a "to" contains filter using the "to" field part. + * + * @param to the to field value the packet must contain. + */ + public ToContainsFilter(String to) { + if (to == null) { + throw new IllegalArgumentException("Parameter cannot be null."); + } + this.to = to.toLowerCase(); + } + + public boolean accept(Packet packet) { + if (packet.getTo() == null) { + return false; + } + else { + return packet.getTo().toLowerCase().indexOf(to) != -1; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html new file mode 100644 index 0000000..8b3fe80 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/filter/package.html @@ -0,0 +1 @@ +Allows {@link org.jivesoftware.smack.PacketCollector} and {@link org.jivesoftware.smack.PacketListener} instances to filter for packets with particular attributes. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html new file mode 100644 index 0000000..2758d78 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/package.html @@ -0,0 +1 @@ +Core classes of the Smack API. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java new file mode 100644 index 0000000..a47c079 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Authentication.java @@ -0,0 +1,186 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * Authentication packet, which can be used to login to a XMPP server as well + * as discover login information from the server. + */ +public class Authentication extends IQ { + + private String username = null; + private String password = null; + private String digest = null; + private String resource = null; + + /** + * Create a new authentication packet. By default, the packet will be in + * "set" mode in order to perform an actual authentication with the server. + * In order to send a "get" request to get the available authentication + * modes back from the server, change the type of the IQ packet to "get": + *

      + *

      setType(IQ.Type.GET); + */ + public Authentication() { + setType(IQ.Type.SET); + } + + /** + * Returns the username, or null if the username hasn't been sent. + * + * @return the username. + */ + public String getUsername() { + return username; + } + + /** + * Sets the username. + * + * @param username the username. + */ + public void setUsername(String username) { + this.username = username; + } + + /** + * Returns the plain text password or null if the password hasn't + * been set. + * + * @return the password. + */ + public String getPassword() { + return password; + } + + /** + * Sets the plain text password. + * + * @param password the password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Returns the password digest or null if the digest hasn't + * been set. Password digests offer a more secure alternative for + * authentication compared to plain text. The digest is the hex-encoded + * SHA-1 hash of the connection ID plus the user's password. If the + * digest and password are set, digest authentication will be used. If + * only one value is set, the respective authentication mode will be used. + * + * @return the digest of the user's password. + */ + public String getDigest() { + return digest; + } + + /** + * Sets the digest value using a connection ID and password. Password + * digests offer a more secure alternative for authentication compared to + * plain text. The digest is the hex-encoded SHA-1 hash of the connection ID + * plus the user's password. If the digest and password are set, digest + * authentication will be used. If only one value is set, the respective + * authentication mode will be used. + * + * @param connectionID the connection ID. + * @param password the password. + * @see org.jivesoftware.smack.Connection#getConnectionID() + */ + public void setDigest(String connectionID, String password) { + this.digest = StringUtils.hash(connectionID + password); + } + + /** + * Sets the digest value directly. Password digests offer a more secure + * alternative for authentication compared to plain text. The digest is + * the hex-encoded SHA-1 hash of the connection ID plus the user's password. + * If the digest and password are set, digest authentication will be used. + * If only one value is set, the respective authentication mode will be used. + * + * @param digest the digest, which is the SHA-1 hash of the connection ID + * the user's password, encoded as hex. + * @see org.jivesoftware.smack.Connection#getConnectionID() + */ + public void setDigest(String digest) { + this.digest = digest; + } + + /** + * Returns the resource or null if the resource hasn't been set. + * + * @return the resource. + */ + public String getResource() { + return resource; + } + + /** + * Sets the resource. + * + * @param resource the resource. + */ + public void setResource(String resource) { + this.resource = resource; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (username != null) { + if (username.equals("")) { + buf.append(""); + } + else { + buf.append("").append(username).append(""); + } + } + if (digest != null) { + if (digest.equals("")) { + buf.append(""); + } + else { + buf.append("").append(digest).append(""); + } + } + if (password != null && digest == null) { + if (password.equals("")) { + buf.append(""); + } + else { + buf.append("").append(StringUtils.escapeForXML(password)).append(""); + } + } + if (resource != null) { + if (resource.equals("")) { + buf.append(""); + } + else { + buf.append("").append(resource).append(""); + } + } + buf.append(""); + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java new file mode 100644 index 0000000..e0c4b5c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Bind.java @@ -0,0 +1,71 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +/** + * IQ packet used by Smack to bind a resource and to obtain the jid assigned by the server. + * There are two ways to bind a resource. One is simply sending an empty Bind packet where the + * server will assign a new resource for this connection. The other option is to set a desired + * resource but the server may return a modified version of the sent resource.

      + * + * For more information refer to the following + * link. + * + * @author Gaston Dombiak + */ +public class Bind extends IQ { + + private String resource = null; + private String jid = null; + + public Bind() { + setType(IQ.Type.SET); + } + + public String getResource() { + return resource; + } + + public void setResource(String resource) { + this.resource = resource; + } + + public String getJid() { + return jid; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (resource != null) { + buf.append("").append(resource).append(""); + } + if (jid != null) { + buf.append("").append(jid).append(""); + } + buf.append(""); + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java new file mode 100644 index 0000000..6cc7934 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/DefaultPacketExtension.java @@ -0,0 +1,133 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import java.util.*; + +/** + * Default implementation of the PacketExtension interface. Unless a PacketExtensionProvider + * is registered with {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager}, + * instances of this class will be returned when getting packet extensions.

      + * + * This class provides a very simple representation of an XML sub-document. Each element + * is a key in a Map with its CDATA being the value. For example, given the following + * XML sub-document: + * + *

      + * <foo xmlns="http://bar.com">
      + *     <color>blue</color>
      + *     <food>pizza</food>
      + * </foo>
      + * + * In this case, getValue("color") would return "blue", and getValue("food") would + * return "pizza". This parsing mechanism mechanism is very simplistic and will not work + * as desired in all cases (for example, if some of the elements have attributes. In those + * cases, a custom PacketExtensionProvider should be used. + * + * @author Matt Tucker + */ +public class DefaultPacketExtension implements PacketExtension { + + private String elementName; + private String namespace; + private Map map; + + /** + * Creates a new generic packet extension. + * + * @param elementName the name of the element of the XML sub-document. + * @param namespace the namespace of the element. + */ + public DefaultPacketExtension(String elementName, String namespace) { + this.elementName = elementName; + this.namespace = namespace; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return elementName; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return namespace; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">"); + for (String name : getNames()) { + String value = getValue(name); + buf.append("<").append(name).append(">"); + buf.append(value); + buf.append(""); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns an unmodifiable collection of the names that can be used to get + * values of the packet extension. + * + * @return the names. + */ + public synchronized Collection getNames() { + if (map == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashMap(map).keySet()); + } + + /** + * Returns a packet extension value given a name. + * + * @param name the name. + * @return the value. + */ + public synchronized String getValue(String name) { + if (map == null) { + return null; + } + return map.get(name); + } + + /** + * Sets a packet extension value using the given name. + * + * @param name the name. + * @param value the value. + */ + public synchronized void setValue(String name, String value) { + if (map == null) { + map = new HashMap(); + } + map.put(name, value); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java new file mode 100644 index 0000000..8b84467 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/IQ.java @@ -0,0 +1,236 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * The base IQ (Info/Query) packet. IQ packets are used to get and set information + * on the server, including authentication, roster operations, and creating + * accounts. Each IQ packet has a specific type that indicates what type of action + * is being taken: "get", "set", "result", or "error".

      + * + * IQ packets can contain a single child element that exists in a specific XML + * namespace. The combination of the element name and namespace determines what + * type of IQ packet it is. Some example IQ subpacket snippets:

        + * + *
      • <query xmlns="jabber:iq:auth"> -- an authentication IQ. + *
      • <query xmlns="jabber:iq:private"> -- a private storage IQ. + *
      • <pubsub xmlns="http://jabber.org/protocol/pubsub"> -- a pubsub IQ. + *
      + * + * @author Matt Tucker + */ +public abstract class IQ extends Packet { + + private Type type = Type.GET; + + /** + * Returns the type of the IQ packet. + * + * @return the type of the IQ packet. + */ + public Type getType() { + return type; + } + + /** + * Sets the type of the IQ packet. + * + * @param type the type of the IQ packet. + */ + public void setType(Type type) { + if (type == null) { + this.type = Type.GET; + } + else { + this.type = type; + } + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append("type=\"").append(getType()).append("\">"); + } + // Add the query section if there is one. + String queryXML = getChildElementXML(); + if (queryXML != null) { + buf.append(queryXML); + } + // Add the error sub-packet, if there is one. + XMPPError error = getError(); + if (error != null) { + buf.append(error.toXML()); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns the sub-element XML section of the IQ packet, or null if there + * isn't one. Packet extensions must be included, if any are defined.

      + * + * Extensions of this class must override this method. + * + * @return the child element section of the IQ XML. + */ + public abstract String getChildElementXML(); + + /** + * Convenience method to create a new empty {@link Type#RESULT IQ.Type.RESULT} + * IQ based on a {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET} + * IQ. The new packet will be initialized with:

        + *
      • The sender set to the recipient of the originating IQ. + *
      • The recipient set to the sender of the originating IQ. + *
      • The type set to {@link Type#RESULT IQ.Type.RESULT}. + *
      • The id set to the id of the originating IQ. + *
      • No child element of the IQ element. + *
      + * + * @param iq the {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET} IQ packet. + * @throws IllegalArgumentException if the IQ packet does not have a type of + * {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET}. + * @return a new {@link Type#RESULT IQ.Type.RESULT} IQ based on the originating IQ. + */ + public static IQ createResultIQ(final IQ request) { + if (!(request.getType() == Type.GET || request.getType() == Type.SET)) { + throw new IllegalArgumentException( + "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); + } + final IQ result = new IQ() { + public String getChildElementXML() { + return null; + } + }; + result.setType(Type.RESULT); + result.setPacketID(request.getPacketID()); + result.setFrom(request.getTo()); + result.setTo(request.getFrom()); + return result; + } + + /** + * Convenience method to create a new {@link Type#ERROR IQ.Type.ERROR} IQ + * based on a {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET} + * IQ. The new packet will be initialized with:
        + *
      • The sender set to the recipient of the originating IQ. + *
      • The recipient set to the sender of the originating IQ. + *
      • The type set to {@link Type#ERROR IQ.Type.ERROR}. + *
      • The id set to the id of the originating IQ. + *
      • The child element contained in the associated originating IQ. + *
      • The provided {@link XMPPError XMPPError}. + *
      + * + * @param iq the {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET} IQ packet. + * @param error the error to associate with the created IQ packet. + * @throws IllegalArgumentException if the IQ packet does not have a type of + * {@link Type#GET IQ.Type.GET} or {@link Type#SET IQ.Type.SET}. + * @return a new {@link Type#ERROR IQ.Type.ERROR} IQ based on the originating IQ. + */ + public static IQ createErrorResponse(final IQ request, final XMPPError error) { + if (!(request.getType() == Type.GET || request.getType() == Type.SET)) { + throw new IllegalArgumentException( + "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); + } + final IQ result = new IQ() { + public String getChildElementXML() { + return request.getChildElementXML(); + } + }; + result.setType(Type.ERROR); + result.setPacketID(request.getPacketID()); + result.setFrom(request.getTo()); + result.setTo(request.getFrom()); + result.setError(error); + return result; + } + + /** + * A class to represent the type of the IQ packet. The types are: + * + *
        + *
      • IQ.Type.GET + *
      • IQ.Type.SET + *
      • IQ.Type.RESULT + *
      • IQ.Type.ERROR + *
      + */ + public static class Type { + + public static final Type GET = new Type("get"); + public static final Type SET = new Type("set"); + public static final Type RESULT = new Type("result"); + public static final Type ERROR = new Type("error"); + + /** + * Converts a String into the corresponding types. Valid String values + * that can be converted to types are: "get", "set", "result", and "error". + * + * @param type the String value to covert. + * @return the corresponding Type. + */ + public static Type fromString(String type) { + if (type == null) { + return null; + } + type = type.toLowerCase(); + if (GET.toString().equals(type)) { + return GET; + } + else if (SET.toString().equals(type)) { + return SET; + } + else if (ERROR.toString().equals(type)) { + return ERROR; + } + else if (RESULT.toString().equals(type)) { + return RESULT; + } + else { + return null; + } + } + + private String value; + + private Type(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java new file mode 100644 index 0000000..d28a9f4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Message.java @@ -0,0 +1,672 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.util.StringUtils; + +import java.util.*; + +/** + * Represents XMPP message packets. A message can be one of several types: + * + *
        + *
      • Message.Type.NORMAL -- (Default) a normal text message used in email like interface. + *
      • Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. + *
      • Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. + *
      • Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. + *
      • Message.Type.ERROR -- indicates a messaging error. + *
      + * + * For each message type, different message fields are typically used as follows: + *

      + * + * + * + * + * + * + * + *
       Message type
      FieldNormalChatGroup ChatHeadlineXMPPError
      subject SHOULDSHOULD NOTSHOULD NOTSHOULD NOTSHOULD NOT
      thread OPTIONALSHOULDOPTIONALOPTIONALSHOULD NOT
      body SHOULDSHOULDSHOULDSHOULDSHOULD NOT
      error MUST NOTMUST NOTMUST NOTMUST NOTMUST
      + * + * @author Matt Tucker + */ +public class Message extends Packet { + + private Type type = Type.normal; + private String thread = null; + private String language; + + private final Set subjects = new HashSet(); + private final Set bodies = new HashSet(); + + /** + * Creates a new, "normal" message. + */ + public Message() { + } + + /** + * Creates a new "normal" message to the specified recipient. + * + * @param to the recipient of the message. + */ + public Message(String to) { + setTo(to); + } + + /** + * Creates a new message of the specified type to a recipient. + * + * @param to the user to send the message to. + * @param type the message type. + */ + public Message(String to, Type type) { + setTo(to); + this.type = type; + } + + /** + * Returns the type of the message. If no type has been set this method will return {@link + * org.jivesoftware.smack.packet.Message.Type#normal}. + * + * @return the type of the message. + */ + public Type getType() { + return type; + } + + /** + * Sets the type of the message. + * + * @param type the type of the message. + * @throws IllegalArgumentException if null is passed in as the type + */ + public void setType(Type type) { + if (type == null) { + throw new IllegalArgumentException("Type cannot be null."); + } + this.type = type; + } + + /** + * Returns the default subject of the message, or null if the subject has not been set. + * The subject is a short description of message contents. + *

      + * The default subject of a message is the subject that corresponds to the message's language. + * (see {@link #getLanguage()}) or if no language is set to the applications default + * language (see {@link Packet#getDefaultLanguage()}). + * + * @return the subject of the message. + */ + public String getSubject() { + return getSubject(null); + } + + /** + * Returns the subject corresponding to the language. If the language is null, the method result + * will be the same as {@link #getSubject()}. Null will be returned if the language does not have + * a corresponding subject. + * + * @param language the language of the subject to return. + * @return the subject related to the passed in language. + */ + public String getSubject(String language) { + Subject subject = getMessageSubject(language); + return subject == null ? null : subject.subject; + } + + private Subject getMessageSubject(String language) { + language = determineLanguage(language); + for (Subject subject : subjects) { + if (language.equals(subject.language)) { + return subject; + } + } + return null; + } + + /** + * Returns a set of all subjects in this Message, including the default message subject accessible + * from {@link #getSubject()}. + * + * @return a collection of all subjects in this message. + */ + public Collection getSubjects() { + return Collections.unmodifiableCollection(subjects); + } + + /** + * Sets the subject of the message. The subject is a short description of + * message contents. + * + * @param subject the subject of the message. + */ + public void setSubject(String subject) { + if (subject == null) { + removeSubject(""); // use empty string because #removeSubject(null) is ambiguous + return; + } + addSubject(null, subject); + } + + /** + * Adds a subject with a corresponding language. + * + * @param language the language of the subject being added. + * @param subject the subject being added to the message. + * @return the new {@link org.jivesoftware.smack.packet.Message.Subject} + * @throws NullPointerException if the subject is null, a null pointer exception is thrown + */ + public Subject addSubject(String language, String subject) { + language = determineLanguage(language); + Subject messageSubject = new Subject(language, subject); + subjects.add(messageSubject); + return messageSubject; + } + + /** + * Removes the subject with the given language from the message. + * + * @param language the language of the subject which is to be removed + * @return true if a subject was removed and false if it was not. + */ + public boolean removeSubject(String language) { + language = determineLanguage(language); + for (Subject subject : subjects) { + if (language.equals(subject.language)) { + return subjects.remove(subject); + } + } + return false; + } + + /** + * Removes the subject from the message and returns true if the subject was removed. + * + * @param subject the subject being removed from the message. + * @return true if the subject was successfully removed and false if it was not. + */ + public boolean removeSubject(Subject subject) { + return subjects.remove(subject); + } + + /** + * Returns all the languages being used for the subjects, not including the default subject. + * + * @return the languages being used for the subjects. + */ + public Collection getSubjectLanguages() { + Subject defaultSubject = getMessageSubject(null); + List languages = new ArrayList(); + for (Subject subject : subjects) { + if (!subject.equals(defaultSubject)) { + languages.add(subject.language); + } + } + return Collections.unmodifiableCollection(languages); + } + + /** + * Returns the default body of the message, or null if the body has not been set. The body + * is the main message contents. + *

      + * The default body of a message is the body that corresponds to the message's language. + * (see {@link #getLanguage()}) or if no language is set to the applications default + * language (see {@link Packet#getDefaultLanguage()}). + * + * @return the body of the message. + */ + public String getBody() { + return getBody(null); + } + + /** + * Returns the body corresponding to the language. If the language is null, the method result + * will be the same as {@link #getBody()}. Null will be returned if the language does not have + * a corresponding body. + * + * @param language the language of the body to return. + * @return the body related to the passed in language. + * @since 3.0.2 + */ + public String getBody(String language) { + Body body = getMessageBody(language); + return body == null ? null : body.message; + } + + private Body getMessageBody(String language) { + language = determineLanguage(language); + for (Body body : bodies) { + if (language.equals(body.language)) { + return body; + } + } + return null; + } + + /** + * Returns a set of all bodies in this Message, including the default message body accessible + * from {@link #getBody()}. + * + * @return a collection of all bodies in this Message. + * @since 3.0.2 + */ + public Collection getBodies() { + return Collections.unmodifiableCollection(bodies); + } + + /** + * Sets the body of the message. The body is the main message contents. + * + * @param body the body of the message. + */ + public void setBody(String body) { + if (body == null) { + removeBody(""); // use empty string because #removeBody(null) is ambiguous + return; + } + addBody(null, body); + } + + /** + * Adds a body with a corresponding language. + * + * @param language the language of the body being added. + * @param body the body being added to the message. + * @return the new {@link org.jivesoftware.smack.packet.Message.Body} + * @throws NullPointerException if the body is null, a null pointer exception is thrown + * @since 3.0.2 + */ + public Body addBody(String language, String body) { + language = determineLanguage(language); + Body messageBody = new Body(language, body); + bodies.add(messageBody); + return messageBody; + } + + /** + * Removes the body with the given language from the message. + * + * @param language the language of the body which is to be removed + * @return true if a body was removed and false if it was not. + */ + public boolean removeBody(String language) { + language = determineLanguage(language); + for (Body body : bodies) { + if (language.equals(body.language)) { + return bodies.remove(body); + } + } + return false; + } + + /** + * Removes the body from the message and returns true if the body was removed. + * + * @param body the body being removed from the message. + * @return true if the body was successfully removed and false if it was not. + * @since 3.0.2 + */ + public boolean removeBody(Body body) { + return bodies.remove(body); + } + + /** + * Returns all the languages being used for the bodies, not including the default body. + * + * @return the languages being used for the bodies. + * @since 3.0.2 + */ + public Collection getBodyLanguages() { + Body defaultBody = getMessageBody(null); + List languages = new ArrayList(); + for (Body body : bodies) { + if (!body.equals(defaultBody)) { + languages.add(body.language); + } + } + return Collections.unmodifiableCollection(languages); + } + + /** + * Returns the thread id of the message, which is a unique identifier for a sequence + * of "chat" messages. If no thread id is set, null will be returned. + * + * @return the thread id of the message, or null if it doesn't exist. + */ + public String getThread() { + return thread; + } + + /** + * Sets the thread id of the message, which is a unique identifier for a sequence + * of "chat" messages. + * + * @param thread the thread id of the message. + */ + public void setThread(String thread) { + this.thread = thread; + } + + /** + * Returns the xml:lang of this Message. + * + * @return the xml:lang of this Message. + * @since 3.0.2 + */ + public String getLanguage() { + return language; + } + + /** + * Sets the xml:lang of this Message. + * + * @param language the xml:lang of this Message. + * @since 3.0.2 + */ + public void setLanguage(String language) { + this.language = language; + } + + private String determineLanguage(String language) { + + // empty string is passed by #setSubject() and #setBody() and is the same as null + language = "".equals(language) ? null : language; + + // if given language is null check if message language is set + if (language == null && this.language != null) { + return this.language; + } + else if (language == null) { + return getDefaultLanguage(); + } + else { + return language; + } + + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + // Add the subject in the default language + Subject defaultSubject = getMessageSubject(null); + if (defaultSubject != null) { + buf.append("").append(StringUtils.escapeForXML(defaultSubject.subject)).append(""); + } + // Add the subject in other languages + for (Subject subject : getSubjects()) { + // Skip the default language + if(subject.equals(defaultSubject)) + continue; + buf.append(""); + buf.append(StringUtils.escapeForXML(subject.subject)); + buf.append(""); + } + // Add the body in the default language + Body defaultBody = getMessageBody(null); + if (defaultBody != null) { + buf.append("").append(StringUtils.escapeForXML(defaultBody.message)).append(""); + } + // Add the bodies in other languages + for (Body body : getBodies()) { + // Skip the default language + if(body.equals(defaultBody)) + continue; + buf.append(""); + buf.append(StringUtils.escapeForXML(body.getMessage())); + buf.append(""); + } + if (thread != null) { + buf.append("").append(thread).append(""); + } + // Append the error subpacket if the message type is an error. + if (type == Type.error) { + XMPPError error = getError(); + if (error != null) { + buf.append(error.toXML()); + } + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Message message = (Message) o; + + if(!super.equals(message)) { return false; } + if (bodies.size() != message.bodies.size() || !bodies.containsAll(message.bodies)) { + return false; + } + if (language != null ? !language.equals(message.language) : message.language != null) { + return false; + } + if (subjects.size() != message.subjects.size() || !subjects.containsAll(message.subjects)) { + return false; + } + if (thread != null ? !thread.equals(message.thread) : message.thread != null) { + return false; + } + return type == message.type; + + } + + public int hashCode() { + int result; + result = (type != null ? type.hashCode() : 0); + result = 31 * result + subjects.hashCode(); + result = 31 * result + (thread != null ? thread.hashCode() : 0); + result = 31 * result + (language != null ? language.hashCode() : 0); + result = 31 * result + bodies.hashCode(); + return result; + } + + /** + * Represents a message subject, its language and the content of the subject. + */ + public static class Subject { + + private String subject; + private String language; + + private Subject(String language, String subject) { + if (language == null) { + throw new NullPointerException("Language cannot be null."); + } + if (subject == null) { + throw new NullPointerException("Subject cannot be null."); + } + this.language = language; + this.subject = subject; + } + + /** + * Returns the language of this message subject. + * + * @return the language of this message subject. + */ + public String getLanguage() { + return language; + } + + /** + * Returns the subject content. + * + * @return the content of the subject. + */ + public String getSubject() { + return subject; + } + + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.language.hashCode(); + result = prime * result + this.subject.hashCode(); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Subject other = (Subject) obj; + // simplified comparison because language and subject are always set + return this.language.equals(other.language) && this.subject.equals(other.subject); + } + + } + + /** + * Represents a message body, its language and the content of the message. + */ + public static class Body { + + private String message; + private String language; + + private Body(String language, String message) { + if (language == null) { + throw new NullPointerException("Language cannot be null."); + } + if (message == null) { + throw new NullPointerException("Message cannot be null."); + } + this.language = language; + this.message = message; + } + + /** + * Returns the language of this message body. + * + * @return the language of this message body. + */ + public String getLanguage() { + return language; + } + + /** + * Returns the message content. + * + * @return the content of the message. + */ + public String getMessage() { + return message; + } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + this.language.hashCode(); + result = prime * result + this.message.hashCode(); + return result; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Body other = (Body) obj; + // simplified comparison because language and message are always set + return this.language.equals(other.language) && this.message.equals(other.message); + } + + } + + /** + * Represents the type of a message. + */ + public enum Type { + + /** + * (Default) a normal text message used in email like interface. + */ + normal, + + /** + * Typically short text message used in line-by-line chat interfaces. + */ + chat, + + /** + * Chat message sent to a groupchat server for group chats. + */ + groupchat, + + /** + * Text message to be displayed in scrolling marquee displays. + */ + headline, + + /** + * indicates a messaging error. + */ + error; + + public static Type fromString(String name) { + try { + return Type.valueOf(name); + } + catch (Exception e) { + return normal; + } + } + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java new file mode 100644 index 0000000..7309de8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Packet.java @@ -0,0 +1,482 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import java.io.ByteArrayOutputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * Base class for XMPP packets. Every packet has a unique ID (which is automatically + * generated, but can be overriden). Optionally, the "to" and "from" fields can be set, + * as well as an arbitrary number of properties. + * + * Properties provide an easy mechanism for clients to share data. Each property has a + * String name, and a value that is a Java primitive (int, long, float, double, boolean) + * or any Serializable object (a Java object is Serializable when it implements the + * Serializable interface). + * + * @author Matt Tucker + */ +public abstract class Packet { + + protected static final String DEFAULT_LANGUAGE = + java.util.Locale.getDefault().getLanguage().toLowerCase(); + + private static String DEFAULT_XML_NS = null; + + /** + * Constant used as packetID to indicate that a packet has no id. To indicate that a packet + * has no id set this constant as the packet's id. When the packet is asked for its id the + * answer will be null. + */ + public static final String ID_NOT_AVAILABLE = "ID_NOT_AVAILABLE"; + + /** + * Date format as defined in XEP-0082 - XMPP Date and Time Profiles. + * The time zone is set to UTC. + *

      + * Date formats are not synchronized. Since multiple threads access the format concurrently, + * it must be synchronized externally. + */ + public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + static { + XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + + /** + * A prefix helps to make sure that ID's are unique across mutliple instances. + */ + private static String prefix = StringUtils.randomString(5) + "-"; + + /** + * Keeps track of the current increment, which is appended to the prefix to + * forum a unique ID. + */ + private static long id = 0; + + private String xmlns = DEFAULT_XML_NS; + + /** + * Returns the next unique id. Each id made up of a short alphanumeric + * prefix along with a unique numeric value. + * + * @return the next id. + */ + public static synchronized String nextID() { + return prefix + Long.toString(id++); + } + + public static void setDefaultXmlns(String defaultXmlns) { + DEFAULT_XML_NS = defaultXmlns; + } + + private String packetID = null; + private String to = null; + private String from = null; + private final List packetExtensions + = new CopyOnWriteArrayList(); + + private final Map properties = new HashMap(); + private XMPPError error = null; + + /** + * Returns the unique ID of the packet. The returned value could be null when + * ID_NOT_AVAILABLE was set as the packet's id. + * + * @return the packet's unique ID or null if the packet's id is not available. + */ + public String getPacketID() { + if (ID_NOT_AVAILABLE.equals(packetID)) { + return null; + } + + if (packetID == null) { + packetID = nextID(); + } + return packetID; + } + + /** + * Sets the unique ID of the packet. To indicate that a packet has no id + * pass the constant ID_NOT_AVAILABLE as the packet's id value. + * + * @param packetID the unique ID for the packet. + */ + public void setPacketID(String packetID) { + this.packetID = packetID; + } + + /** + * Returns who the packet is being sent "to", or null if + * the value is not set. The XMPP protocol often makes the "to" + * attribute optional, so it does not always need to be set.

      + * + * The StringUtils class provides several useful methods for dealing with + * XMPP addresses such as parsing the + * {@link StringUtils#parseBareAddress(String) bare address}, + * {@link StringUtils#parseName(String) user name}, + * {@link StringUtils#parseServer(String) server}, and + * {@link StringUtils#parseResource(String) resource}. + * + * @return who the packet is being sent to, or null if the + * value has not been set. + */ + public String getTo() { + return to; + } + + /** + * Sets who the packet is being sent "to". The XMPP protocol often makes + * the "to" attribute optional, so it does not always need to be set. + * + * @param to who the packet is being sent to. + */ + public void setTo(String to) { + this.to = to; + } + + /** + * Returns who the packet is being sent "from" or null if + * the value is not set. The XMPP protocol often makes the "from" + * attribute optional, so it does not always need to be set.

      + * + * The StringUtils class provides several useful methods for dealing with + * XMPP addresses such as parsing the + * {@link StringUtils#parseBareAddress(String) bare address}, + * {@link StringUtils#parseName(String) user name}, + * {@link StringUtils#parseServer(String) server}, and + * {@link StringUtils#parseResource(String) resource}. + * + * @return who the packet is being sent from, or null if the + * value has not been set. + */ + public String getFrom() { + return from; + } + + /** + * Sets who the packet is being sent "from". The XMPP protocol often + * makes the "from" attribute optional, so it does not always need to + * be set. + * + * @param from who the packet is being sent to. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Returns the error associated with this packet, or null if there are + * no errors. + * + * @return the error sub-packet or null if there isn't an error. + */ + public XMPPError getError() { + return error; + } + + /** + * Sets the error for this packet. + * + * @param error the error to associate with this packet. + */ + public void setError(XMPPError error) { + this.error = error; + } + + /** + * Returns an unmodifiable collection of the packet extensions attached to the packet. + * + * @return the packet extensions. + */ + public synchronized Collection getExtensions() { + if (packetExtensions == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(new ArrayList(packetExtensions)); + } + + /** + * Returns the first extension of this packet that has the given namespace. + * + * @param namespace the namespace of the extension that is desired. + * @return the packet extension with the given namespace. + */ + public PacketExtension getExtension(String namespace) { + return getExtension(null, namespace); + } + + /** + * Returns the first packet extension that matches the specified element name and + * namespace, or null if it doesn't exist. If the provided elementName is null + * than only the provided namespace is attempted to be matched. Packet extensions are + * are arbitrary XML sub-documents in standard XMPP packets. By default, a + * DefaultPacketExtension instance will be returned for each extension. However, + * PacketExtensionProvider instances can be registered with the + * {@link org.jivesoftware.smack.provider.ProviderManager ProviderManager} + * class to handle custom parsing. In that case, the type of the Object + * will be determined by the provider. + * + * @param elementName the XML element name of the packet extension. (May be null) + * @param namespace the XML element namespace of the packet extension. + * @return the extension, or null if it doesn't exist. + */ + public PacketExtension getExtension(String elementName, String namespace) { + if (namespace == null) { + return null; + } + for (PacketExtension ext : packetExtensions) { + if ((elementName == null || elementName.equals(ext.getElementName())) + && namespace.equals(ext.getNamespace())) + { + return ext; + } + } + return null; + } + + /** + * Adds a packet extension to the packet. + * + * @param extension a packet extension. + */ + public void addExtension(PacketExtension extension) { + packetExtensions.add(extension); + } + + /** + * Removes a packet extension from the packet. + * + * @param extension the packet extension to remove. + */ + public void removeExtension(PacketExtension extension) { + packetExtensions.remove(extension); + } + + /** + * Returns the packet property with the specified name or null if the + * property doesn't exist. Property values that were orginally primitives will + * be returned as their object equivalent. For example, an int property will be + * returned as an Integer, a double as a Double, etc. + * + * @param name the name of the property. + * @return the property, or null if the property doesn't exist. + */ + public synchronized Object getProperty(String name) { + if (properties == null) { + return null; + } + return properties.get(name); + } + + /** + * Sets a property with an Object as the value. The value must be Serializable + * or an IllegalArgumentException will be thrown. + * + * @param name the name of the property. + * @param value the value of the property. + */ + public synchronized void setProperty(String name, Object value) { + if (!(value instanceof Serializable)) { + throw new IllegalArgumentException("Value must be serialiazble"); + } + properties.put(name, value); + } + + /** + * Deletes a property. + * + * @param name the name of the property to delete. + */ + public synchronized void deleteProperty(String name) { + if (properties == null) { + return; + } + properties.remove(name); + } + + /** + * Returns an unmodifiable collection of all the property names that are set. + * + * @return all property names. + */ + public synchronized Collection getPropertyNames() { + if (properties == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashSet(properties.keySet())); + } + + /** + * Returns the packet as XML. Every concrete extension of Packet must implement + * this method. In addition to writing out packet-specific data, every sub-class + * should also write out the error and the extensions data if they are defined. + * + * @return the XML format of the packet as a String. + */ + public abstract String toXML(); + + /** + * Returns the extension sub-packets (including properties data) as an XML + * String, or the Empty String if there are no packet extensions. + * + * @return the extension sub-packets as XML or the Empty String if there + * are no packet extensions. + */ + protected synchronized String getExtensionsXML() { + StringBuilder buf = new StringBuilder(); + // Add in all standard extension sub-packets. + for (PacketExtension extension : getExtensions()) { + buf.append(extension.toXML()); + } + // Add in packet properties. + if (properties != null && !properties.isEmpty()) { + buf.append(""); + // Loop through all properties and write them out. + for (String name : getPropertyNames()) { + Object value = getProperty(name); + buf.append(""); + buf.append("").append(StringUtils.escapeForXML(name)).append(""); + buf.append("").append(value).append(""); + } + else if (value instanceof Long) { + buf.append("long\">").append(value).append(""); + } + else if (value instanceof Float) { + buf.append("float\">").append(value).append(""); + } + else if (value instanceof Double) { + buf.append("double\">").append(value).append(""); + } + else if (value instanceof Boolean) { + buf.append("boolean\">").append(value).append(""); + } + else if (value instanceof String) { + buf.append("string\">"); + buf.append(StringUtils.escapeForXML((String)value)); + buf.append(""); + } + // Otherwise, it's a generic Serializable object. Serialized objects are in + // a binary format, which won't work well inside of XML. Therefore, we base-64 + // encode the binary data before adding it. + else { + ByteArrayOutputStream byteStream = null; + ObjectOutputStream out = null; + try { + byteStream = new ByteArrayOutputStream(); + out = new ObjectOutputStream(byteStream); + out.writeObject(value); + buf.append("java-object\">"); + String encodedVal = StringUtils.encodeBase64(byteStream.toByteArray()); + buf.append(encodedVal).append(""); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + if (out != null) { + try { + out.close(); + } + catch (Exception e) { + // Ignore. + } + } + if (byteStream != null) { + try { + byteStream.close(); + } + catch (Exception e) { + // Ignore. + } + } + } + } + buf.append(""); + } + buf.append(""); + } + return buf.toString(); + } + + public String getXmlns() { + return this.xmlns; + } + + /** + * Returns the default language used for all messages containing localized content. + * + * @return the default language + */ + public static String getDefaultLanguage() { + return DEFAULT_LANGUAGE; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Packet packet = (Packet) o; + + if (error != null ? !error.equals(packet.error) : packet.error != null) { return false; } + if (from != null ? !from.equals(packet.from) : packet.from != null) { return false; } + if (!packetExtensions.equals(packet.packetExtensions)) { return false; } + if (packetID != null ? !packetID.equals(packet.packetID) : packet.packetID != null) { + return false; + } + if (properties != null ? !properties.equals(packet.properties) + : packet.properties != null) { + return false; + } + if (to != null ? !to.equals(packet.to) : packet.to != null) { return false; } + return !(xmlns != null ? !xmlns.equals(packet.xmlns) : packet.xmlns != null); + } + + public int hashCode() { + int result; + result = (xmlns != null ? xmlns.hashCode() : 0); + result = 31 * result + (packetID != null ? packetID.hashCode() : 0); + result = 31 * result + (to != null ? to.hashCode() : 0); + result = 31 * result + (from != null ? from.hashCode() : 0); + result = 31 * result + packetExtensions.hashCode(); + result = 31 * result + properties.hashCode(); + result = 31 * result + (error != null ? error.hashCode() : 0); + return result; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java new file mode 100644 index 0000000..d2afbf8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PacketExtension.java @@ -0,0 +1,56 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +/** + * Interface to represent packet extensions. A packet extension is an XML subdocument + * with a root element name and namespace. Packet extensions are used to provide + * extended functionality beyond what is in the base XMPP specification. Examples of + * packet extensions include message events, message properties, and extra presence data. + * IQ packets cannot contain packet extensions. + * + * @see DefaultPacketExtension + * @see org.jivesoftware.smack.provider.PacketExtensionProvider + * @author Matt Tucker + */ +public interface PacketExtension { + + /** + * Returns the root element name. + * + * @return the element name. + */ + public String getElementName(); + + /** + * Returns the root element XML namespace. + * + * @return the namespace. + */ + public String getNamespace(); + + /** + * Returns the XML representation of the PacketExtension. + * + * @return the packet extension as XML. + */ + public String toXML(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java new file mode 100644 index 0000000..97402a0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Presence.java @@ -0,0 +1,358 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * Represents XMPP presence packets. Every presence packet has a type, which is one of + * the following values: + *

        + *
      • {@link Presence.Type#available available} -- (Default) indicates the user is available to + * receive messages. + *
      • {@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages. + *
      • {@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence. + *
      • {@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence. + *
      • {@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to + * sender's presence. + *
      • {@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to + * sender's presence. + *
      • {@link Presence.Type#error error} -- the presence packet contains an error message. + *

      + * + * A number of attributes are optional: + *

        + *
      • Status -- free-form text describing a user's presence (i.e., gone to lunch). + *
      • Priority -- non-negative numerical priority of a sender's resource. The + * highest resource priority is the default recipient of packets not addressed + * to a particular resource. + *
      • Mode -- one of five presence modes: {@link Mode#available available} (the default), + * {@link Mode#chat chat}, {@link Mode#away away}, {@link Mode#xa xa} (extended away), and + * {@link Mode#dnd dnd} (do not disturb). + *

      + * + * Presence packets are used for two purposes. First, to notify the server of our + * the clients current presence status. Second, they are used to subscribe and + * unsubscribe users from the roster. + * + * @see RosterPacket + * @author Matt Tucker + */ +public class Presence extends Packet { + + private Type type = Type.available; + private String status = null; + private int priority = Integer.MIN_VALUE; + private Mode mode = null; + private String language; + + /** + * Creates a new presence update. Status, priority, and mode are left un-set. + * + * @param type the type. + */ + public Presence(Type type) { + setType(type); + } + + /** + * Creates a new presence update with a specified status, priority, and mode. + * + * @param type the type. + * @param status a text message describing the presence update. + * @param priority the priority of this presence update. + * @param mode the mode type for this presence update. + */ + public Presence(Type type, String status, int priority, Mode mode) { + setType(type); + setStatus(status); + setPriority(priority); + setMode(mode); + } + + /** + * Returns true if the {@link Type presence type} is available (online) and + * false if the user is unavailable (offline), or if this is a presence packet + * involved in a subscription operation. This is a convenience method + * equivalent to getType() == Presence.Type.available. Note that even + * when the user is available, their presence mode may be {@link Mode#away away}, + * {@link Mode#xa extended away} or {@link Mode#dnd do not disturb}. Use + * {@link #isAway()} to determine if the user is away. + * + * @return true if the presence type is available. + */ + public boolean isAvailable() { + return type == Type.available; + } + + /** + * Returns true if the presence type is {@link Type#available available} and the presence + * mode is {@link Mode#away away}, {@link Mode#xa extended away}, or + * {@link Mode#dnd do not disturb}. False will be returned when the type or mode + * is any other value, including when the presence type is unavailable (offline). + * This is a convenience method equivalent to + * type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd). + * + * @return true if the presence type is available and the presence mode is away, xa, or dnd. + */ + public boolean isAway() { + return type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd); + } + + /** + * Returns the type of this presence packet. + * + * @return the type of the presence packet. + */ + public Type getType() { + return type; + } + + /** + * Sets the type of the presence packet. + * + * @param type the type of the presence packet. + */ + public void setType(Type type) { + if(type == null) { + throw new NullPointerException("Type cannot be null"); + } + this.type = type; + } + + /** + * Returns the status message of the presence update, or null if there + * is not a status. The status is free-form text describing a user's presence + * (i.e., "gone to lunch"). + * + * @return the status message. + */ + public String getStatus() { + return status; + } + + /** + * Sets the status message of the presence update. The status is free-form text + * describing a user's presence (i.e., "gone to lunch"). + * + * @param status the status message. + */ + public void setStatus(String status) { + this.status = status; + } + + /** + * Returns the priority of the presence, or Integer.MIN_VALUE if no priority has been set. + * + * @return the priority. + */ + public int getPriority() { + return priority; + } + + /** + * Sets the priority of the presence. The valid range is -128 through 128. + * + * @param priority the priority of the presence. + * @throws IllegalArgumentException if the priority is outside the valid range. + */ + public void setPriority(int priority) { + if (priority < -128 || priority > 128) { + throw new IllegalArgumentException("Priority value " + priority + + " is not valid. Valid range is -128 through 128."); + } + this.priority = priority; + } + + /** + * Returns the mode of the presence update, or null if the mode is not set. + * A null presence mode value is interpreted to be the same thing as + * {@link Presence.Mode#available}. + * + * @return the mode. + */ + public Mode getMode() { + return mode; + } + + /** + * Sets the mode of the presence update. A null presence mode value is interpreted + * to be the same thing as {@link Presence.Mode#available}. + * + * @param mode the mode. + */ + public void setMode(Mode mode) { + this.mode = mode; + } + + /** + * Returns the xml:lang of this Presence, or null if one has not been set. + * + * @return the xml:lang of this Presence, or null if one has not been set. + * @since 3.0.2 + */ + private String getLanguage() { + return language; + } + + /** + * Sets the xml:lang of this Presence. + * + * @param language the xml:lang of this Presence. + * @since 3.0.2 + */ + public void setLanguage(String language) { + this.language = language; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (status != null) { + buf.append("").append(StringUtils.escapeForXML(status)).append(""); + } + if (priority != Integer.MIN_VALUE) { + buf.append("").append(priority).append(""); + } + if (mode != null && mode != Mode.available) { + buf.append("").append(mode).append(""); + } + + buf.append(this.getExtensionsXML()); + + // Add the error sub-packet, if there is one. + XMPPError error = getError(); + if (error != null) { + buf.append(error.toXML()); + } + + buf.append(""); + + return buf.toString(); + } + + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(type); + if (mode != null) { + buf.append(": ").append(mode); + } + if (getStatus() != null) { + buf.append(" (").append(getStatus()).append(")"); + } + return buf.toString(); + } + + /** + * A enum to represent the presecence type. Not that presence type is often confused + * with presence mode. Generally, if a user is signed into a server, they have a presence + * type of {@link #available available}, even if the mode is {@link Mode#away away}, + * {@link Mode#dnd dnd}, etc. The presence type is only {@link #unavailable unavailable} when + * the user is signing out of the server. + */ + public enum Type { + + /** + * The user is available to receive messages (default). + */ + available, + + /** + * The user is unavailable to receive messages. + */ + unavailable, + + /** + * Request subscription to recipient's presence. + */ + subscribe, + + /** + * Grant subscription to sender's presence. + */ + subscribed, + + /** + * Request removal of subscription to sender's presence. + */ + unsubscribe, + + /** + * Grant removal of subscription to sender's presence. + */ + unsubscribed, + + /** + * The presence packet contains an error message. + */ + error + } + + /** + * An enum to represent the presence mode. + */ + public enum Mode { + + /** + * Free to chat. + */ + chat, + + /** + * Available (the default). + */ + available, + + /** + * Away. + */ + away, + + /** + * Away for an extended period of time. + */ + xa, + + /** + * Do not disturb. + */ + dnd + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java new file mode 100644 index 0000000..57162cb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Privacy.java @@ -0,0 +1,323 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2006-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import java.util.*; + +/** + * A Privacy IQ Packet, is used by the {@link org.jivesoftware.smack.PrivacyListManager} + * and {@link org.jivesoftware.smack.provider.PrivacyProvider} to allow and block + * communications from other users. It contains the appropriate structure to suit + * user-defined privacy lists. Different configured Privacy packages are used in the + * server & manager communication in order to: + *

        + *
      • Retrieving one's privacy lists. + *
      • Adding, removing, and editing one's privacy lists. + *
      • Setting, changing, or declining active lists. + *
      • Setting, changing, or declining the default list (i.e., the list that is active by default). + *
      + * Privacy Items can handle different kind of blocking communications based on JID, group, + * subscription type or globally {@link PrivacyItem} + * + * @author Francisco Vives + */ +public class Privacy extends IQ { + /** declineActiveList is true when the user declines the use of the active list **/ + private boolean declineActiveList=false; + /** activeName is the name associated with the active list set for the session **/ + private String activeName; + /** declineDefaultList is true when the user declines the use of the default list **/ + private boolean declineDefaultList=false; + /** defaultName is the name of the default list that applies to the user as a whole **/ + private String defaultName; + /** itemLists holds the set of privacy items classified in lists. It is a map where the + * key is the name of the list and the value a collection with privacy items. **/ + private Map> itemLists = new HashMap>(); + + /** + * Set or update a privacy list with privacy items. + * + * @param listName the name of the new privacy list. + * @param listItem the {@link PrivacyItem} that rules the list. + * @return the privacy List. + */ + public List setPrivacyList(String listName, List listItem) { + // Add new list to the itemLists + this.getItemLists().put(listName, listItem); + return listItem; + } + + /** + * Set the active list based on the default list. + * + * @return the active List. + */ + public List setActivePrivacyList() { + this.setActiveName(this.getDefaultName()); + return this.getItemLists().get(this.getActiveName()); + } + + /** + * Deletes an existing privacy list. If the privacy list being deleted was the default list + * then the user will end up with no default list. Therefore, the user will have to set a new + * default list. + * + * @param listName the name of the list being deleted. + */ + public void deletePrivacyList(String listName) { + // Remove the list from the cache + this.getItemLists().remove(listName); + + // Check if deleted list was the default list + if (this.getDefaultName() != null && listName.equals(this.getDefaultName())) { + this.setDefaultName(null); + } + } + + /** + * Returns the active privacy list or null if none was found. + * + * @return list with {@link PrivacyItem} or null if none was found. + */ + public List getActivePrivacyList() { + // Check if we have the default list + if (this.getActiveName() == null) { + return null; + } else { + return this.getItemLists().get(this.getActiveName()); + } + } + + /** + * Returns the default privacy list or null if none was found. + * + * @return list with {@link PrivacyItem} or null if none was found. + */ + public List getDefaultPrivacyList() { + // Check if we have the default list + if (this.getDefaultName() == null) { + return null; + } else { + return this.getItemLists().get(this.getDefaultName()); + } + } + + /** + * Returns a specific privacy list. + * + * @param listName the name of the list to get. + * @return a List with {@link PrivacyItem} + */ + public List getPrivacyList(String listName) { + return this.getItemLists().get(listName); + } + + /** + * Returns the privacy item in the specified order. + * + * @param listName the name of the privacy list. + * @param order the order of the element. + * @return a List with {@link PrivacyItem} + */ + public PrivacyItem getItem(String listName, int order) { + Iterator values = getPrivacyList(listName).iterator(); + PrivacyItem itemFound = null; + while (itemFound == null && values.hasNext()) { + PrivacyItem element = values.next(); + if (element.getOrder() == order) { + itemFound = element; + } + } + return itemFound; + } + + /** + * Sets a given privacy list as the new user default list. + * + * @param newDefault the new default privacy list. + * @return if the default list was changed. + */ + public boolean changeDefaultList(String newDefault) { + if (this.getItemLists().containsKey(newDefault)) { + this.setDefaultName(newDefault); + return true; + } else { + return false; + } + } + + /** + * Remove the list. + * + * @param listName name of the list to remove. + */ + public void deleteList(String listName) { + this.getItemLists().remove(listName); + } + + /** + * Returns the name associated with the active list set for the session. Communications + * will be verified against the active list. + * + * @return the name of the active list. + */ + public String getActiveName() { + return activeName; + } + + /** + * Sets the name associated with the active list set for the session. Communications + * will be verified against the active list. + * + * @param activeName is the name of the active list. + */ + public void setActiveName(String activeName) { + this.activeName = activeName; + } + + /** + * Returns the name of the default list that applies to the user as a whole. Default list is + * processed if there is no active list set for the target session/resource to which a stanza + * is addressed, or if there are no current sessions for the user. + * + * @return the name of the default list. + */ + public String getDefaultName() { + return defaultName; + } + + /** + * Sets the name of the default list that applies to the user as a whole. Default list is + * processed if there is no active list set for the target session/resource to which a stanza + * is addressed, or if there are no current sessions for the user. + * + * If there is no default list set, then all Privacy Items are processed. + * + * @param defaultName is the name of the default list. + */ + public void setDefaultName(String defaultName) { + this.defaultName = defaultName; + } + + /** + * Returns the collection of privacy list that the user holds. A Privacy List contains a set of + * rules that define if communication with the list owner is allowed or denied. + * Users may have zero, one or more privacy items. + * + * @return a map where the key is the name of the list and the value the + * collection of privacy items. + */ + public Map> getItemLists() { + return itemLists; + } + + /** + * Returns whether the receiver allows or declines the use of an active list. + * + * @return the decline status of the list. + */ + public boolean isDeclineActiveList() { + return declineActiveList; + } + + /** + * Sets whether the receiver allows or declines the use of an active list. + * + * @param declineActiveList indicates if the receiver declines the use of an active list. + */ + public void setDeclineActiveList(boolean declineActiveList) { + this.declineActiveList = declineActiveList; + } + + /** + * Returns whether the receiver allows or declines the use of a default list. + * + * @return the decline status of the list. + */ + public boolean isDeclineDefaultList() { + return declineDefaultList; + } + + /** + * Sets whether the receiver allows or declines the use of a default list. + * + * @param declineDefaultList indicates if the receiver declines the use of a default list. + */ + public void setDeclineDefaultList(boolean declineDefaultList) { + this.declineDefaultList = declineDefaultList; + } + + /** + * Returns all the list names the user has defined to group restrictions. + * + * @return a Set with Strings containing every list names. + */ + public Set getPrivacyListNames() { + return this.itemLists.keySet(); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + + // Add the active tag + if (this.isDeclineActiveList()) { + buf.append(""); + } else { + if (this.getActiveName() != null) { + buf.append(""); + } + } + // Add the default tag + if (this.isDeclineDefaultList()) { + buf.append(""); + } else { + if (this.getDefaultName() != null) { + buf.append(""); + } + } + + // Add the list with their privacy items + for (Map.Entry> entry : this.getItemLists().entrySet()) { + String listName = entry.getKey(); + List items = entry.getValue(); + // Begin the list tag + if (items.isEmpty()) { + buf.append(""); + } else { + buf.append(""); + } + for (PrivacyItem item : items) { + // Append the item xml representation + buf.append(item.toXML()); + } + // Close the list tag + if (!items.isEmpty()) { + buf.append(""); + } + } + + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java new file mode 100644 index 0000000..78cb8db --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/PrivacyItem.java @@ -0,0 +1,445 @@ +package org.jivesoftware.smack.packet; + +/** + * A privacy item acts a rule that when matched defines if a packet should be blocked or not. + * + * Privacy Items can handle different kind of blocking communications based on JID, group, + * subscription type or globally by:
        + *
      • Allowing or blocking messages. + *
      • Allowing or blocking inbound presence notifications. + *
      • Allowing or blocking outbound presence notifications. + *
      • Allowing or blocking IQ stanzas. + *
      • Allowing or blocking all communications. + *
      + * @author Francisco Vives + */ +public class PrivacyItem { + /** allow is the action associated with the item, it can allow or deny the communication. */ + private boolean allow; + /** order is a non-negative integer that is unique among all items in the list. */ + private int order; + /** rule hold the kind of communication ([jid|group|subscription]) it will allow or block and + * identifier to apply the action. + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". */ + private PrivacyRule rule; + + /** blocks incoming IQ stanzas. */ + private boolean filterIQ = false; + /** filterMessage blocks incoming message stanzas. */ + private boolean filterMessage = false; + /** blocks incoming presence notifications. */ + private boolean filterPresence_in = false; + /** blocks outgoing presence notifications. */ + private boolean filterPresence_out = false; + + /** + * Creates a new privacy item. + * + * @param type the type. + */ + public PrivacyItem(String type, boolean allow, int order) { + this.setRule(PrivacyRule.fromString(type)); + this.setAllow(allow); + this.setOrder(order); + } + + /** + * Returns the action associated with the item, it MUST be filled and will allow or deny + * the communication. + * + * @return the allow communication status. + */ + public boolean isAllow() { + return allow; + } + + /** + * Sets the action associated with the item, it can allow or deny the communication. + * + * @param allow indicates if the receiver allow or deny the communication. + */ + private void setAllow(boolean allow) { + this.allow = allow; + } + + + /** + * Returns whether the receiver allow or deny incoming IQ stanzas or not. + * + * @return the iq filtering status. + */ + public boolean isFilterIQ() { + return filterIQ; + } + + + /** + * Sets whether the receiver allows or denies incoming IQ stanzas or not. + * + * @param filterIQ indicates if the receiver allows or denies incoming IQ stanzas. + */ + public void setFilterIQ(boolean filterIQ) { + this.filterIQ = filterIQ; + } + + + /** + * Returns whether the receiver allows or denies incoming messages or not. + * + * @return the message filtering status. + */ + public boolean isFilterMessage() { + return filterMessage; + } + + + /** + * Sets wheather the receiver allows or denies incoming messages or not. + * + * @param filterMessage indicates if the receiver allows or denies incoming messages or not. + */ + public void setFilterMessage(boolean filterMessage) { + this.filterMessage = filterMessage; + } + + + /** + * Returns whether the receiver allows or denies incoming presence or not. + * + * @return the iq filtering incoming presence status. + */ + public boolean isFilterPresence_in() { + return filterPresence_in; + } + + + /** + * Sets whether the receiver allows or denies incoming presence or not. + * + * @param filterPresence_in indicates if the receiver allows or denies filtering incoming presence. + */ + public void setFilterPresence_in(boolean filterPresence_in) { + this.filterPresence_in = filterPresence_in; + } + + + /** + * Returns whether the receiver allows or denies incoming presence or not. + * + * @return the iq filtering incoming presence status. + */ + public boolean isFilterPresence_out() { + return filterPresence_out; + } + + + /** + * Sets whether the receiver allows or denies outgoing presence or not. + * + * @param filterPresence_out indicates if the receiver allows or denies filtering outgoing presence + */ + public void setFilterPresence_out(boolean filterPresence_out) { + this.filterPresence_out = filterPresence_out; + } + + + /** + * Returns the order where the receiver is processed. List items are processed in + * ascending order. + * + * The order MUST be filled and its value MUST be a non-negative integer + * that is unique among all items in the list. + * + * @return the order number. + */ + public int getOrder() { + return order; + } + + + /** + * Sets the order where the receiver is processed. + * + * The order MUST be filled and its value MUST be a non-negative integer + * that is unique among all items in the list. + * + * @param order indicates the order in the list. + */ + public void setOrder(int order) { + this.order = order; + } + + /** + * Sets the element identifier to apply the action. + * + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". + * + * @param value is the identifier to apply the action. + */ + public void setValue(String value) { + if (!(this.getRule() == null && value == null)) { + this.getRule().setValue(value); + } + } + + /** + * Returns the type hold the kind of communication it will allow or block. + * It MUST be filled with one of these values: jid, group or subscription. + * + * @return the type of communication it represent. + */ + public Type getType() { + if (this.getRule() == null) { + return null; + } else { + return this.getRule().getType(); + } + } + + /** + * Returns the element identifier to apply the action. + * + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". + * + * @return the identifier to apply the action. + */ + public String getValue() { + if (this.getRule() == null) { + return null; + } else { + return this.getRule().getValue(); + } + } + + + /** + * Returns whether the receiver allows or denies every kind of communication. + * + * When filterIQ, filterMessage, filterPresence_in and filterPresence_out are not set + * the receiver will block all communications. + * + * @return the all communications status. + */ + public boolean isFilterEverything() { + return !(this.isFilterIQ() || this.isFilterMessage() || this.isFilterPresence_in() + || this.isFilterPresence_out()); + } + + + private PrivacyRule getRule() { + return rule; + } + + private void setRule(PrivacyRule rule) { + this.rule = rule; + } + /** + * Answer an xml representation of the receiver according to the RFC 3921. + * + * @return the text xml representation. + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } else { + buf.append(">"); + if (this.isFilterIQ()) { + buf.append(""); + } + if (this.isFilterMessage()) { + buf.append(""); + } + if (this.isFilterPresence_in()) { + buf.append(""); + } + if (this.isFilterPresence_out()) { + buf.append(""); + } + buf.append(""); + } + return buf.toString(); + } + + + /** + * Privacy Rule represents the kind of action to apply. + * It holds the kind of communication ([jid|group|subscription]) it will allow or block and + * identifier to apply the action. + */ + + public static class PrivacyRule { + /** + * Type defines if the rule is based on JIDs, roster groups or presence subscription types. + * Available values are: [jid|group|subscription] + */ + private Type type; + /** + * The value hold the element identifier to apply the action. + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". + */ + private String value; + + /** + * If the type is "subscription", then the 'value' attribute MUST be one of "both", + * "to", "from", or "none" + */ + public static final String SUBSCRIPTION_BOTH = "both"; + public static final String SUBSCRIPTION_TO = "to"; + public static final String SUBSCRIPTION_FROM = "from"; + public static final String SUBSCRIPTION_NONE = "none"; + + /** + * Returns the type constant associated with the String value. + */ + protected static PrivacyRule fromString(String value) { + if (value == null) { + return null; + } + PrivacyRule rule = new PrivacyRule(); + rule.setType(Type.valueOf(value.toLowerCase())); + return rule; + } + + /** + * Returns the type hold the kind of communication it will allow or block. + * It MUST be filled with one of these values: jid, group or subscription. + * + * @return the type of communication it represent. + */ + public Type getType() { + return type; + } + + /** + * Sets the action associated with the item, it can allow or deny the communication. + * + * @param type indicates if the receiver allows or denies the communication. + */ + private void setType(Type type) { + this.type = type; + } + + /** + * Returns the element identifier to apply the action. + * + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". + * + * @return the identifier to apply the action. + */ + public String getValue() { + return value; + } + + /** + * Sets the element identifier to apply the action. + * + * If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID. + * If the type is "group", then the 'value' attribute SHOULD contain the name of a group + * in the user's roster. + * If the type is "subscription", then the 'value' attribute MUST be one of "both", "to", + * "from", or "none". + * + * @param value is the identifier to apply the action. + */ + protected void setValue(String value) { + if (this.isSuscription()) { + setSuscriptionValue(value); + } else { + this.value = value; + } + } + + /** + * Sets the element identifier to apply the action. + * + * The 'value' attribute MUST be one of "both", "to", "from", or "none". + * + * @param value is the identifier to apply the action. + */ + private void setSuscriptionValue(String value) { + String setValue; + if (value == null) { + // Do nothing + } + if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) { + setValue = SUBSCRIPTION_BOTH; + } + else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) { + setValue = SUBSCRIPTION_TO; + } + else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) { + setValue = SUBSCRIPTION_FROM; + } + else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) { + setValue = SUBSCRIPTION_NONE; + } + // Default to available. + else { + setValue = null; + } + this.value = setValue; + } + + /** + * Returns if the receiver represents a subscription rule. + * + * @return if the receiver represents a subscription rule. + */ + public boolean isSuscription () { + return this.getType() == Type.subscription; + } + } + + /** + * Type defines if the rule is based on JIDs, roster groups or presence subscription types. + */ + public static enum Type { + /** + * JID being analyzed should belong to a roster group of the list's owner. + */ + group, + /** + * JID being analyzed should have a resource match, domain match or bare JID match. + */ + jid, + /** + * JID being analyzed should belong to a contact present in the owner's roster with + * the specified subscription status. + */ + subscription + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java new file mode 100644 index 0000000..df22e27 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Registration.java @@ -0,0 +1,155 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents registration packets. An empty GET query will cause the server to return information + * about it's registration support. SET queries can be used to create accounts or update + * existing account information. XMPP servers may require a number of attributes to be set + * when creating a new account. The standard account attributes are as follows: + *
        + *
      • name -- the user's name. + *
      • first -- the user's first name. + *
      • last -- the user's last name. + *
      • email -- the user's email address. + *
      • city -- the user's city. + *
      • state -- the user's state. + *
      • zip -- the user's ZIP code. + *
      • phone -- the user's phone number. + *
      • url -- the user's website. + *
      • date -- the date the registration took place. + *
      • misc -- other miscellaneous information to associate with the account. + *
      • text -- textual information to associate with the account. + *
      • remove -- empty flag to remove account. + *
      + * + * @author Matt Tucker + */ +public class Registration extends IQ { + + private String instructions = null; + private Map attributes = new HashMap(); + private List requiredFields = new ArrayList(); + private boolean registered = false; + private boolean remove = false; + + /** + * Returns the registration instructions, or null if no instructions + * have been set. If present, instructions should be displayed to the end-user + * that will complete the registration process. + * + * @return the registration instructions, or null if there are none. + */ + public String getInstructions() { + return instructions; + } + + /** + * Sets the registration instructions. + * + * @param instructions the registration instructions. + */ + public void setInstructions(String instructions) { + this.instructions = instructions; + } + + /** + * Returns the map of String key/value pairs of account attributes. + * + * @return the account attributes. + */ + public Map getAttributes() { + return attributes; + } + + /** + * Sets the account attributes. The map must only contain String key/value pairs. + * + * @param attributes the account attributes. + */ + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public List getRequiredFields(){ + return requiredFields; + } + + public void addAttribute(String key, String value){ + attributes.put(key, value); + } + + public void setRegistered(boolean registered){ + this.registered = registered; + } + + public boolean isRegistered(){ + return this.registered; + } + + public String getField(String key){ + return attributes.get(key); + } + + public List getFieldNames(){ + return new ArrayList(attributes.keySet()); + } + + public void setUsername(String username){ + attributes.put("username", username); + } + + public void setPassword(String password){ + attributes.put("password", password); + } + + public void setRemove(boolean remove){ + this.remove = remove; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (instructions != null && !remove) { + buf.append("").append(instructions).append(""); + } + if (attributes != null && attributes.size() > 0 && !remove) { + for (String name : attributes.keySet()) { + String value = attributes.get(name); + buf.append("<").append(name).append(">"); + buf.append(value); + buf.append(""); + } + } + else if(remove){ + buf.append(""); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java new file mode 100644 index 0000000..98483c8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/RosterPacket.java @@ -0,0 +1,311 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import org.jivesoftware.smack.util.StringUtils; + +import java.util.*; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Represents XMPP roster packets. + * + * @author Matt Tucker + */ +public class RosterPacket extends IQ { + + private final List rosterItems = new ArrayList(); + /* + * The ver attribute following XEP-0237 + */ + private String version; + + /** + * Adds a roster item to the packet. + * + * @param item a roster item. + */ + public void addRosterItem(Item item) { + synchronized (rosterItems) { + rosterItems.add(item); + } + } + + public String getVersion(){ + return version; + } + + public void setVersion(String version){ + this.version = version; + } + + /** + * Returns the number of roster items in this roster packet. + * + * @return the number of roster items. + */ + public int getRosterItemCount() { + synchronized (rosterItems) { + return rosterItems.size(); + } + } + + /** + * Returns an unmodifiable collection for the roster items in the packet. + * + * @return an unmodifiable collection for the roster items in the packet. + */ + public Collection getRosterItems() { + synchronized (rosterItems) { + return Collections.unmodifiableList(new ArrayList(rosterItems)); + } + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (rosterItems) { + for (Item entry : rosterItems) { + buf.append(entry.toXML()); + } + } + buf.append(""); + return buf.toString(); + } + + /** + * A roster item, which consists of a JID, their name, the type of subscription, and + * the groups the roster item belongs to. + */ + public static class Item { + + private String user; + private String name; + private ItemType itemType; + private ItemStatus itemStatus; + private final Set groupNames; + + /** + * Creates a new roster item. + * + * @param user the user. + * @param name the user's name. + */ + public Item(String user, String name) { + this.user = user.toLowerCase(); + this.name = name; + itemType = null; + itemStatus = null; + groupNames = new CopyOnWriteArraySet(); + } + + /** + * Returns the user. + * + * @return the user. + */ + public String getUser() { + return user; + } + + /** + * Returns the user's name. + * + * @return the user's name. + */ + public String getName() { + return name; + } + + /** + * Sets the user's name. + * + * @param name the user's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the roster item type. + * + * @return the roster item type. + */ + public ItemType getItemType() { + return itemType; + } + + /** + * Sets the roster item type. + * + * @param itemType the roster item type. + */ + public void setItemType(ItemType itemType) { + this.itemType = itemType; + } + + /** + * Returns the roster item status. + * + * @return the roster item status. + */ + public ItemStatus getItemStatus() { + return itemStatus; + } + + /** + * Sets the roster item status. + * + * @param itemStatus the roster item status. + */ + public void setItemStatus(ItemStatus itemStatus) { + this.itemStatus = itemStatus; + } + + /** + * Returns an unmodifiable set of the group names that the roster item + * belongs to. + * + * @return an unmodifiable set of the group names. + */ + public Set getGroupNames() { + return Collections.unmodifiableSet(groupNames); + } + + /** + * Adds a group name. + * + * @param groupName the group name. + */ + public void addGroupName(String groupName) { + groupNames.add(groupName); + } + + /** + * Removes a group name. + * + * @param groupName the group name. + */ + public void removeGroupName(String groupName) { + groupNames.remove(groupName); + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + for (String groupName : groupNames) { + buf.append("").append(StringUtils.escapeForXML(groupName)).append(""); + } + buf.append(""); + return buf.toString(); + } + } + + /** + * The subscription status of a roster item. An optional element that indicates + * the subscription status if a change request is pending. + */ + public static class ItemStatus { + + /** + * Request to subcribe. + */ + public static final ItemStatus SUBSCRIPTION_PENDING = new ItemStatus("subscribe"); + + /** + * Request to unsubscribe. + */ + public static final ItemStatus UNSUBSCRIPTION_PENDING = new ItemStatus("unsubscribe"); + + public static ItemStatus fromString(String value) { + if (value == null) { + return null; + } + value = value.toLowerCase(); + if ("unsubscribe".equals(value)) { + return UNSUBSCRIPTION_PENDING; + } + else if ("subscribe".equals(value)) { + return SUBSCRIPTION_PENDING; + } + else { + return null; + } + } + + private String value; + + /** + * Returns the item status associated with the specified string. + * + * @param value the item status. + */ + private ItemStatus(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } + + public static enum ItemType { + + /** + * The user and subscriber have no interest in each other's presence. + */ + none, + + /** + * The user is interested in receiving presence updates from the subscriber. + */ + to, + + /** + * The subscriber is interested in receiving presence updates from the user. + */ + from, + + /** + * The user and subscriber have a mutual interest in each other's presence. + */ + both, + + /** + * The user wishes to stop receiving presence updates from the subscriber. + */ + remove + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java new file mode 100644 index 0000000..d034666 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/Session.java @@ -0,0 +1,45 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +/** + * IQ packet that will be sent to the server to establish a session.

      + * + * If a server supports sessions, it MUST include a session element in the + * stream features it advertises to a client after the completion of stream authentication. + * Upon being informed that session establishment is required by the server the client MUST + * establish a session if it desires to engage in instant messaging and presence functionality.

      + * + * For more information refer to the following + * link. + * + * @author Gaston Dombiak + */ +public class Session extends IQ { + + public Session() { + setType(IQ.Type.SET); + } + + public String getChildElementXML() { + return ""; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java new file mode 100644 index 0000000..8bb4c75 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/StreamError.java @@ -0,0 +1,106 @@ +/** + * $Revision: 2408 $ + * $Date: 2004-11-02 20:53:30 -0300 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +/** + * Represents a stream error packet. Stream errors are unrecoverable errors where the server + * will close the unrelying TCP connection after the stream error was sent to the client. + * These is the list of stream errors as defined in the XMPP spec:

      + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      CodeDescription
      bad-format the entity has sent XML that cannot be processed
      unsupported-encoding the entity has sent a namespace prefix that is + * unsupported
      bad-namespace-prefix Remote Server Timeout
      conflict the server is closing the active stream for this entity + * because a new stream has been initiated that conflicts with the existing + * stream.
      connection-timeout the entity has not generated any traffic over + * the stream for some period of time.
      host-gone the value of the 'to' attribute provided by the initiating + * entity in the stream header corresponds to a hostname that is no longer hosted by + * the server.
      host-unknown the value of the 'to' attribute provided by the + * initiating entity in the stream header does not correspond to a hostname that is + * hosted by the server.
      improper-addressing a stanza sent between two servers lacks a 'to' + * or 'from' attribute
      internal-server-error the server has experienced a + * misconfiguration.
      invalid-from the JID or hostname provided in a 'from' address does + * not match an authorized JID.
      invalid-id the stream ID or dialback ID is invalid or does not match + * an ID previously provided.
      invalid-namespace the streams namespace name is invalid.
      invalid-xml the entity has sent invalid XML over the stream.
      not-authorized the entity has attempted to send data before the + * stream has been authenticated
      policy-violation the entity has violated some local service + * policy.
      remote-connection-failed Rthe server is unable to properly connect + * to a remote entity.
      resource-constraint Rthe server lacks the system resources necessary + * to service the stream.
      restricted-xml the entity has attempted to send restricted XML + * features.
      see-other-host the server will not provide service to the initiating + * entity but is redirecting traffic to another host.
      system-shutdown the server is being shut down and all active streams + * are being closed.
      undefined-condition the error condition is not one of those defined + * by the other conditions in this list.
      unsupported-encoding the initiating entity has encoded the stream in + * an encoding that is not supported.
      unsupported-stanza-type the initiating entity has sent a first-level + * child of the stream that is not supported.
      unsupported-version the value of the 'version' attribute provided by + * the initiating entity in the stream header specifies a version of XMPP that is not + * supported.
      xml-not-well-formed the initiating entity has sent XML that is + * not well-formed.
      + * + * @author Gaston Dombiak + */ +public class StreamError { + + private String code; + + public StreamError(String code) { + super(); + this.code = code; + } + + /** + * Returns the error code. + * + * @return the error code. + */ + public String getCode() { + return code; + } + + public String toString() { + StringBuilder txt = new StringBuilder(); + txt.append("stream:error (").append(code).append(")"); + return txt.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java new file mode 100644 index 0000000..da65be8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/UnknownPacket.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012 Miron Cuperman. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + + + +/** + * Contains packets that are unknown to Smack, but may be processed by extensions. + */ +public abstract class UnknownPacket extends Packet implements PacketExtension { + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()); + if (getXmlns() != null) { + buf.append(" xmlns=\"").append(getNamespace()).append("\""); + } + buf.append("/>"); + return buf.toString(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java new file mode 100644 index 0000000..770a09c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/XMPPError.java @@ -0,0 +1,453 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.packet; + +import java.util.*; + +/** + * Represents a XMPP error sub-packet. Typically, a server responds to a request that has + * problems by sending the packet back and including an error packet. Each error has a code, type, + * error condition as well as as an optional text explanation. Typical errors are:

      + * + * + *
      + * + * + * > + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      CodeXMPP ErrorType
      500interna-server-errorWAIT
      403forbiddenAUTH
      400bad-requestMODIFY
      404item-not-foundCANCEL
      409conflictCANCEL
      501feature-not-implementedCANCEL
      302goneMODIFY
      400jid-malformedMODIFY
      406no-acceptable MODIFY
      405not-allowedCANCEL
      401not-authorizedAUTH
      402payment-requiredAUTH
      404recipient-unavailableWAIT
      302redirectMODIFY
      407registration-requiredAUTH
      404remote-server-not-foundCANCEL
      504remote-server-timeoutWAIT
      502remote-server-errorCANCEL
      500resource-constraintWAIT
      503service-unavailableCANCEL
      407subscription-requiredAUTH
      500undefined-conditionWAIT
      400unexpected-conditionWAIT
      408request-timeoutCANCEL
      + * + * @author Matt Tucker + */ +public class XMPPError { + + private int code; + private Type type; + private String condition; + private String message; + private List applicationExtensions = null; + + + /** + * Creates a new error with the specified condition infering the type and code. + * If the Condition is predefined, client code should be like: + * new XMPPError(XMPPError.Condition.remote_server_timeout); + * If the Condition is not predefined, invocations should be like + * new XMPPError(new XMPPError.Condition("my_own_error")); + * + * @param condition the error condition. + */ + public XMPPError(Condition condition) { + this.init(condition); + this.message = null; + } + + /** + * Creates a new error with the specified condition and message infering the type and code. + * If the Condition is predefined, client code should be like: + * new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation"); + * If the Condition is not predefined, invocations should be like + * new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation"); + * + * @param condition the error condition. + * @param messageText a message describing the error. + */ + public XMPPError(Condition condition, String messageText) { + this.init(condition); + this.message = messageText; + } + + /** + * Creates a new error with the specified code and no message. + * + * @param code the error code. + * @deprecated new errors should be created using the constructor XMPPError(condition) + */ + public XMPPError(int code) { + this.code = code; + this.message = null; + } + + /** + * Creates a new error with the specified code and message. + * deprecated + * + * @param code the error code. + * @param message a message describing the error. + * @deprecated new errors should be created using the constructor XMPPError(condition, message) + */ + public XMPPError(int code, String message) { + this.code = code; + this.message = message; + } + + /** + * Creates a new error with the specified code, type, condition and message. + * This constructor is used when the condition is not recognized automatically by XMPPError + * i.e. there is not a defined instance of ErrorCondition or it does not applies the default + * specification. + * + * @param code the error code. + * @param type the error type. + * @param condition the error condition. + * @param message a message describing the error. + * @param extension list of packet extensions + */ + public XMPPError(int code, Type type, String condition, String message, + List extension) { + this.code = code; + this.type = type; + this.condition = condition; + this.message = message; + this.applicationExtensions = extension; + } + + /** + * Initialize the error infering the type and code for the received condition. + * + * @param condition the error condition. + */ + private void init(Condition condition) { + // Look for the condition and its default code and type + ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition); + this.condition = condition.value; + if (defaultErrorSpecification != null) { + // If there is a default error specification for the received condition, + // it get configured with the infered type and code. + this.type = defaultErrorSpecification.getType(); + this.code = defaultErrorSpecification.getCode(); + } + } + /** + * Returns the error condition. + * + * @return the error condition. + */ + public String getCondition() { + return condition; + } + + /** + * Returns the error type. + * + * @return the error type. + */ + public Type getType() { + return type; + } + + /** + * Returns the error code. + * + * @return the error code. + */ + public int getCode() { + return code; + } + + /** + * Returns the message describing the error, or null if there is no message. + * + * @return the message describing the error, or null if there is no message. + */ + public String getMessage() { + return message; + } + + /** + * Returns the error as XML. + * + * @return the error as XML. + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (condition != null) { + buf.append("<").append(condition); + buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); + } + if (message != null) { + buf.append(""); + buf.append(message); + buf.append(""); + } + for (PacketExtension element : this.getExtensions()) { + buf.append(element.toXML()); + } + buf.append(""); + return buf.toString(); + } + + public String toString() { + StringBuilder txt = new StringBuilder(); + if (condition != null) { + txt.append(condition); + } + txt.append("(").append(code).append(")"); + if (message != null) { + txt.append(" ").append(message); + } + return txt.toString(); + } + + /** + * Returns an Iterator for the error extensions attached to the xmppError. + * An application MAY provide application-specific error information by including a + * properly-namespaced child in the error element. + * + * @return an Iterator for the error extensions. + */ + public synchronized List getExtensions() { + if (applicationExtensions == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(applicationExtensions); + } + + /** + * Returns the first patcket extension that matches the specified element name and + * namespace, or null if it doesn't exist. + * + * @param elementName the XML element name of the packet extension. + * @param namespace the XML element namespace of the packet extension. + * @return the extension, or null if it doesn't exist. + */ + public synchronized PacketExtension getExtension(String elementName, String namespace) { + if (applicationExtensions == null || elementName == null || namespace == null) { + return null; + } + for (PacketExtension ext : applicationExtensions) { + if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) { + return ext; + } + } + return null; + } + + /** + * Adds a packet extension to the error. + * + * @param extension a packet extension. + */ + public synchronized void addExtension(PacketExtension extension) { + if (applicationExtensions == null) { + applicationExtensions = new ArrayList(); + } + applicationExtensions.add(extension); + } + + /** + * Set the packet extension to the error. + * + * @param extension a packet extension. + */ + public synchronized void setExtension(List extension) { + applicationExtensions = extension; + } + + /** + * A class to represent the type of the Error. The types are: + * + *

        + *
      • XMPPError.Type.WAIT - retry after waiting (the error is temporary) + *
      • XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) + *
      • XMPPError.Type.MODIFY - retry after changing the data sent + *
      • XMPPError.Type.AUTH - retry after providing credentials + *
      • XMPPError.Type.CONTINUE - proceed (the condition was only a warning) + *
      + */ + public static enum Type { + WAIT, + CANCEL, + MODIFY, + AUTH, + CONTINUE + } + + /** + * A class to represent predefined error conditions. + */ + public static class Condition { + + public static final Condition interna_server_error = new Condition("internal-server-error"); + public static final Condition forbidden = new Condition("forbidden"); + public static final Condition bad_request = new Condition("bad-request"); + public static final Condition conflict = new Condition("conflict"); + public static final Condition feature_not_implemented = new Condition("feature-not-implemented"); + public static final Condition gone = new Condition("gone"); + public static final Condition item_not_found = new Condition("item-not-found"); + public static final Condition jid_malformed = new Condition("jid-malformed"); + public static final Condition no_acceptable = new Condition("not-acceptable"); + public static final Condition not_allowed = new Condition("not-allowed"); + public static final Condition not_authorized = new Condition("not-authorized"); + public static final Condition payment_required = new Condition("payment-required"); + public static final Condition recipient_unavailable = new Condition("recipient-unavailable"); + public static final Condition redirect = new Condition("redirect"); + public static final Condition registration_required = new Condition("registration-required"); + public static final Condition remote_server_error = new Condition("remote-server-error"); + public static final Condition remote_server_not_found = new Condition("remote-server-not-found"); + public static final Condition remote_server_timeout = new Condition("remote-server-timeout"); + public static final Condition resource_constraint = new Condition("resource-constraint"); + public static final Condition service_unavailable = new Condition("service-unavailable"); + public static final Condition subscription_required = new Condition("subscription-required"); + public static final Condition undefined_condition = new Condition("undefined-condition"); + public static final Condition unexpected_request = new Condition("unexpected-request"); + public static final Condition request_timeout = new Condition("request-timeout"); + + private String value; + + public Condition(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } + + + /** + * A class to represent the error specification used to infer common usage. + */ + private static class ErrorSpecification { + private int code; + private Type type; + private Condition condition; + private static Map instances = errorSpecifications(); + + private ErrorSpecification(Condition condition, Type type, int code) { + this.code = code; + this.type = type; + this.condition = condition; + } + + private static Map errorSpecifications() { + Map instances = new HashMap(22); + instances.put(Condition.interna_server_error, new ErrorSpecification( + Condition.interna_server_error, Type.WAIT, 500)); + instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden, + Type.AUTH, 403)); + instances.put(Condition.bad_request, new XMPPError.ErrorSpecification( + Condition.bad_request, Type.MODIFY, 400)); + instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification( + Condition.item_not_found, Type.CANCEL, 404)); + instances.put(Condition.conflict, new XMPPError.ErrorSpecification( + Condition.conflict, Type.CANCEL, 409)); + instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification( + Condition.feature_not_implemented, Type.CANCEL, 501)); + instances.put(Condition.gone, new XMPPError.ErrorSpecification( + Condition.gone, Type.MODIFY, 302)); + instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification( + Condition.jid_malformed, Type.MODIFY, 400)); + instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification( + Condition.no_acceptable, Type.MODIFY, 406)); + instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification( + Condition.not_allowed, Type.CANCEL, 405)); + instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification( + Condition.not_authorized, Type.AUTH, 401)); + instances.put(Condition.payment_required, new XMPPError.ErrorSpecification( + Condition.payment_required, Type.AUTH, 402)); + instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification( + Condition.recipient_unavailable, Type.WAIT, 404)); + instances.put(Condition.redirect, new XMPPError.ErrorSpecification( + Condition.redirect, Type.MODIFY, 302)); + instances.put(Condition.registration_required, new XMPPError.ErrorSpecification( + Condition.registration_required, Type.AUTH, 407)); + instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification( + Condition.remote_server_not_found, Type.CANCEL, 404)); + instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification( + Condition.remote_server_timeout, Type.WAIT, 504)); + instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification( + Condition.remote_server_error, Type.CANCEL, 502)); + instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification( + Condition.resource_constraint, Type.WAIT, 500)); + instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification( + Condition.service_unavailable, Type.CANCEL, 503)); + instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification( + Condition.subscription_required, Type.AUTH, 407)); + instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification( + Condition.undefined_condition, Type.WAIT, 500)); + instances.put(Condition.unexpected_request, new XMPPError.ErrorSpecification( + Condition.unexpected_request, Type.WAIT, 400)); + instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification( + Condition.request_timeout, Type.CANCEL, 408)); + + return instances; + } + + protected static ErrorSpecification specFor(Condition condition) { + return instances.get(condition); + } + + /** + * Returns the error condition. + * + * @return the error condition. + */ + protected Condition getCondition() { + return condition; + } + + /** + * Returns the error type. + * + * @return the error type. + */ + protected Type getType() { + return type; + } + + /** + * Returns the error code. + * + * @return the error code. + */ + protected int getCode() { + return code; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html new file mode 100644 index 0000000..18a6405 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/packet/package.html @@ -0,0 +1 @@ +XML packets that are part of the XMPP protocol. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java new file mode 100644 index 0000000..e7b4b93 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/EmbeddedExtensionProvider.java @@ -0,0 +1,109 @@ +/** + * All rights reserved. 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 org.jivesoftware.smack.provider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.pubsub.provider.ItemProvider; +import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * This class simplifies parsing of embedded elements by using the + * Template Method Pattern. + * After extracting the current element attributes and content of any child elements, the template method + * ({@link #createReturnExtension(String, String, Map, List)} is called. Subclasses + * then override this method to create the specific return type. + * + *

      To use this class, you simply register your subclasses as extension providers in the + * smack.properties file. Then they will be automatically picked up and used to parse + * any child elements. + * + *

      + * For example, given the following message
      + * 
      + * <message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo>
      + *    <event xmlns='http://jabber.org/protocol/pubsub#event>
      + *       <items node='princely_musings'>
      + *          <item id='asdjkwei3i34234n356'>
      + *             <entry xmlns='http://www.w3.org/2005/Atom'>
      + *                <title>Soliloquy</title>
      + *                <link rel='alternative' type='text/html'/>
      + *                <id>tag:denmark.lit,2003:entry-32397</id>
      + *             </entry>
      + *          </item>
      + *       </items>
      + *    </event>
      + * </message>
      + * 
      + * I would have a classes
      + * {@link ItemsProvider} extends {@link EmbeddedExtensionProvider}
      + * {@link ItemProvider} extends {@link EmbeddedExtensionProvider}
      + * and
      + * AtomProvider extends {@link PacketExtensionProvider}
      + * 
      + * These classes are then registered in the meta-inf/smack.providers file
      + * as follows.
      + * 
      + *   <extensionProvider>
      + *      <elementName>items</elementName>
      + *      <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      + *      <className>org.jivesoftware.smackx.provider.ItemsEventProvider</className>
      + *   </extensionProvider>
      + *   <extensionProvider>
      + *       <elementName>item</elementName>
      + *       <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      + *       <className>org.jivesoftware.smackx.provider.ItemProvider</className>
      + *   </extensionProvider>
      + * 
      + * 
      + * + * @author Robin Collier + */ +abstract public class EmbeddedExtensionProvider implements PacketExtensionProvider +{ + + final public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String namespace = parser.getNamespace(); + String name = parser.getName(); + Map attMap = new HashMap(); + + for(int i=0; i extensions = new ArrayList(); + + do + { + int tag = parser.next(); + + if (tag == XmlPullParser.START_TAG) + extensions.add(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); + } while (!name.equals(parser.getName())); + + return createReturnExtension(name, namespace, attMap, extensions); + } + + abstract protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java new file mode 100644 index 0000000..936c6be --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/IQProvider.java @@ -0,0 +1,47 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.xmlpull.v1.XmlPullParser; + +/** + * An interface for parsing custom IQ packets. Each IQProvider must be registered with + * the ProviderManager class for it to be used. Every implementation of this + * interface must have a public, no-argument constructor. + * + * @author Matt Tucker + */ +public interface IQProvider { + + /** + * Parse the IQ sub-document and create an IQ instance. Each IQ must have a + * single child element. At the beginning of the method call, the xml parser + * will be positioned at the opening tag of the IQ child element. At the end + * of the method call, the parser must be positioned on the closing tag + * of the child element. + * + * @param parser an XML parser. + * @return a new IQ instance. + * @throws Exception if an error occurs parsing the XML. + */ + public IQ parseIQ(XmlPullParser parser) throws Exception; +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java new file mode 100644 index 0000000..8fc0af3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PacketExtensionProvider.java @@ -0,0 +1,46 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * An interface for parsing custom packets extensions. Each PacketExtensionProvider must + * be registered with the ProviderManager class for it to be used. Every implementation + * of this interface must have a public, no-argument constructor. + * + * @author Matt Tucker + */ +public interface PacketExtensionProvider { + + /** + * Parse an extension sub-packet and create a PacketExtension instance. At + * the beginning of the method call, the xml parser will be positioned on the + * opening element of the packet extension. At the end of the method call, the + * parser must be positioned on the closing element of the packet extension. + * + * @param parser an XML parser. + * @return a new IQ instance. + * @throws java.lang.Exception if an error occurs parsing the XML. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java new file mode 100644 index 0000000..a995399 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/PrivacyProvider.java @@ -0,0 +1,134 @@ +package org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.DefaultPacketExtension; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Privacy; +import org.jivesoftware.smack.packet.PrivacyItem; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; + +/** + * The PrivacyProvider parses {@link Privacy} packets. {@link Privacy} + * Parses the query sub-document and creates an instance of {@link Privacy}. + * For each item in the list element, it creates an instance + * of {@link PrivacyItem} and {@link org.jivesoftware.smack.packet.PrivacyItem.PrivacyRule}. + * + * @author Francisco Vives + */ +public class PrivacyProvider implements IQProvider { + + public PrivacyProvider() { + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + Privacy privacy = new Privacy(); + /* privacy.addExtension(PacketParserUtils.parsePacketExtension(parser + .getName(), parser.getNamespace(), parser)); */ + privacy.addExtension(new DefaultPacketExtension(parser.getName(), parser.getNamespace())); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("active")) { + String activeName = parser.getAttributeValue("", "name"); + if (activeName == null) { + privacy.setDeclineActiveList(true); + } else { + privacy.setActiveName(activeName); + } + } + else if (parser.getName().equals("default")) { + String defaultName = parser.getAttributeValue("", "name"); + if (defaultName == null) { + privacy.setDeclineDefaultList(true); + } else { + privacy.setDefaultName(defaultName); + } + } + else if (parser.getName().equals("list")) { + parseList(parser, privacy); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return privacy; + } + + // Parse the list complex type + public void parseList(XmlPullParser parser, Privacy privacy) throws Exception { + boolean done = false; + String listName = parser.getAttributeValue("", "name"); + ArrayList items = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + items.add(parseItem(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("list")) { + done = true; + } + } + } + + privacy.setPrivacyList(listName, items); + } + + // Parse the list complex type + public PrivacyItem parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + // Retrieves the required attributes + String actionValue = parser.getAttributeValue("", "action"); + String orderValue = parser.getAttributeValue("", "order"); + String type = parser.getAttributeValue("", "type"); + + /* + * According the action value it sets the allow status. The fall-through action is assumed + * to be "allow" + */ + boolean allow = true; + if ("allow".equalsIgnoreCase(actionValue)) { + allow = true; + } else if ("deny".equalsIgnoreCase(actionValue)) { + allow = false; + } + // Set the order number + int order = Integer.parseInt(orderValue); + + // Create the privacy item + PrivacyItem item = new PrivacyItem(type, allow, order); + item.setValue(parser.getAttributeValue("", "value")); + + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("iq")) { + item.setFilterIQ(true); + } + if (parser.getName().equals("message")) { + item.setFilterMessage(true); + } + if (parser.getName().equals("presence-in")) { + item.setFilterPresence_in(true); + } + if (parser.getName().equals("presence-out")) { + item.setFilterPresence_out(true); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java new file mode 100644 index 0000000..a76850f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/ProviderManager.java @@ -0,0 +1,451 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; + +import java.io.InputStream; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages providers for parsing custom XML sub-documents of XMPP packets. Two types of + * providers exist:
        + *
      • IQProvider -- parses IQ requests into Java objects. + *
      • PacketExtension -- parses XML sub-documents attached to packets into + * PacketExtension instances.
      + * + * IQProvider

      + * + * By default, Smack only knows how to process IQ packets with sub-packets that + * are in a few namespaces such as:

        + *
      • jabber:iq:auth + *
      • jabber:iq:roster + *
      • jabber:iq:register
      + * + * Because many more IQ types are part of XMPP and its extensions, a pluggable IQ parsing + * mechanism is provided. IQ providers are registered programatically or by creating a + * smack.providers file in the META-INF directory of your JAR file. The file is an XML + * document that contains one or more iqProvider entries, as in the following example: + * + *
      + * <?xml version="1.0"?>
      + * <smackProviders>
      + *     <iqProvider>
      + *         <elementName>query</elementName>
      + *         <namespace>jabber:iq:time</namespace>
      + *         <className>org.jivesoftware.smack.packet.Time</className>
      + *     </iqProvider>
      + * </smackProviders>
      + * + * Each IQ provider is associated with an element name and a namespace. If multiple provider + * entries attempt to register to handle the same namespace, the first entry loaded from the + * classpath will take precedence. The IQ provider class can either implement the IQProvider + * interface, or extend the IQ class. In the former case, each IQProvider is responsible for + * parsing the raw XML stream to create an IQ instance. In the latter case, bean introspection + * is used to try to automatically set properties of the IQ instance using the values found + * in the IQ packet XML. For example, an XMPP time packet resembles the following: + *
      + * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
      + *     <query xmlns='jabber:iq:time'>
      + *         <utc>20020910T17:58:35</utc>
      + *         <tz>MDT</tz>
      + *         <display>Tue Sep 10 12:58:35 2002</display>
      + *     </query>
      + * </iq>
      + * + * In order for this packet to be automatically mapped to the Time object listed in the + * providers file above, it must have the methods setUtc(String), setTz(String), and + * setDisplay(String). The introspection service will automatically try to convert the String + * value from the XML into a boolean, int, long, float, double, or Class depending on the + * type the IQ instance expects.

      + * + * A pluggable system for packet extensions, child elements in a custom namespace for + * message and presence packets, also exists. Each extension provider + * is registered with a name space in the smack.providers file as in the following example: + * + *

      + * <?xml version="1.0"?>
      + * <smackProviders>
      + *     <extensionProvider>
      + *         <elementName>x</elementName>
      + *         <namespace>jabber:iq:event</namespace>
      + *         <className>org.jivesoftware.smack.packet.MessageEvent</className>
      + *     </extensionProvider>
      + * </smackProviders>
      + * + * If multiple provider entries attempt to register to handle the same element name and namespace, + * the first entry loaded from the classpath will take precedence. Whenever a packet extension + * is found in a packet, parsing will be passed to the correct provider. Each provider + * can either implement the PacketExtensionProvider interface or be a standard Java Bean. In + * the former case, each extension provider is responsible for parsing the raw XML stream to + * contruct an object. In the latter case, bean introspection is used to try to automatically + * set the properties of the class using the values in the packet extension sub-element. When an + * extension provider is not registered for an element name and namespace combination, Smack will + * store all top-level elements of the sub-packet in DefaultPacketExtension object and then + * attach it to the packet.

      + * + * It is possible to provide a custom provider manager instead of the default implementation + * provided by Smack. If you want to provide your own provider manager then you need to do it + * before creating any {@link org.jivesoftware.smack.Connection} by sending the static + * {@link #setInstance(ProviderManager)} message. Trying to change the provider manager after + * an Connection was created will result in an {@link IllegalStateException} error. + * + * @author Matt Tucker + */ +public class ProviderManager { + + private static ProviderManager instance; + + private Map extensionProviders = new ConcurrentHashMap(); + private Map iqProviders = new ConcurrentHashMap(); + + /** + * Returns the only ProviderManager valid instance. Use {@link #setInstance(ProviderManager)} + * to configure your own provider manager. If non was provided then an instance of this + * class will be used. + * + * @return the only ProviderManager valid instance. + */ + public static synchronized ProviderManager getInstance() { + if (instance == null) { + instance = new ProviderManager(); + } + return instance; + } + + /** + * Sets the only ProviderManager valid instance to be used by all Connections. If you + * want to provide your own provider manager then you need to do it before creating + * any Connection. Otherwise an IllegalStateException will be thrown. + * + * @param providerManager the only ProviderManager valid instance to be used. + * @throws IllegalStateException if a provider manager was already configued. + */ + public static synchronized void setInstance(ProviderManager providerManager) { + if (instance != null) { + throw new IllegalStateException("ProviderManager singleton already set"); + } + instance = providerManager; + } + + protected void initialize() { + // Load IQ processing providers. + try { + // Get an array of class loaders to try loading the providers files from. + ClassLoader[] classLoaders = getClassLoaders(); + for (ClassLoader classLoader : classLoaders) { + Enumeration providerEnum = classLoader.getResources( + "META-INF/smack.providers"); + while (providerEnum.hasMoreElements()) { + URL url = (URL) providerEnum.nextElement(); + InputStream providerStream = null; + providerStream = url.openStream(); + + // Parse the XML and add the providers + loadFromXML(providerStream); + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Parse the given input stream and load the providers specified in it + * and loads it into the provider manager. + * + * @param providerStream an input stream providing the xml content. + * @throws Exception if an error occurs. + */ + public void loadFromXML(InputStream providerStream) throws Exception { + try { + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(providerStream, "UTF-8"); + int eventType = parser.getEventType(); + do { + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("iqProvider")) { + parser.next(); + parser.next(); + String elementName = parser.nextText(); + parser.next(); + parser.next(); + String namespace = parser.nextText(); + parser.next(); + parser.next(); + String className = parser.nextText(); + // Only add the provider for the namespace if one isn't + // already registered. + String key = getProviderKey(elementName, namespace); + if (!iqProviders.containsKey(key)) { + // Attempt to load the provider class and then create + // a new instance if it's an IQProvider. Otherwise, if it's + // an IQ class, add the class object itself, then we'll use + // reflection later to create instances of the class. + try { + // Add the provider to the map. + Class provider = Class.forName(className); + if (IQProvider.class.isAssignableFrom(provider)) { + iqProviders.put(key, provider.newInstance()); + } + else if (IQ.class.isAssignableFrom(provider)) { + iqProviders.put(key, provider); + } + } + catch (ClassNotFoundException cnfe) { + cnfe.printStackTrace(); + } + } + } + else if (parser.getName().equals("extensionProvider")) { + parser.next(); + parser.next(); + String elementName = parser.nextText(); + parser.next(); + parser.next(); + String namespace = parser.nextText(); + parser.next(); + parser.next(); + String className = parser.nextText(); + // Only add the provider for the namespace if one isn't + // already registered. + String key = getProviderKey(elementName, namespace); + if (!extensionProviders.containsKey(key)) { + // Attempt to load the provider class and then create + // a new instance if it's a Provider. Otherwise, if it's + // a PacketExtension, add the class object itself and + // then we'll use reflection later to create instances + // of the class. + try { + // Add the provider to the map. + Class provider = Class.forName(className); + if (PacketExtensionProvider.class.isAssignableFrom( + provider)) { + extensionProviders.put(key, provider.newInstance()); + } + else if (PacketExtension.class.isAssignableFrom( + provider)) { + extensionProviders.put(key, provider); + } + } + catch (ClassNotFoundException cnfe) { + cnfe.printStackTrace(); + } + } + } + } + eventType = parser.next(); + } + while (eventType != XmlPullParser.END_DOCUMENT); + } + finally { + try { + providerStream.close(); + } + catch (Exception e) { + // Ignore. + } + } + } + + /** + * Returns the IQ provider registered to the specified XML element name and namespace. + * For example, if a provider was registered to the element name "query" and the + * namespace "jabber:iq:time", then the following packet would trigger the provider: + * + *

      +     * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'>
      +     *     <query xmlns='jabber:iq:time'>
      +     *         <utc>20020910T17:58:35</utc>
      +     *         <tz>MDT</tz>
      +     *         <display>Tue Sep 10 12:58:35 2002</display>
      +     *     </query>
      +     * </iq>
      + * + *

      Note: this method is generally only called by the internal Smack classes. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @return the IQ provider. + */ + public Object getIQProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return iqProviders.get(key); + } + + /** + * Returns an unmodifiable collection of all IQProvider instances. Each object + * in the collection will either be an IQProvider instance, or a Class object + * that implements the IQProvider interface. + * + * @return all IQProvider instances. + */ + public Collection getIQProviders() { + return Collections.unmodifiableCollection(iqProviders.values()); + } + + /** + * Adds an IQ provider (must be an instance of IQProvider or Class object that is an IQ) + * with the specified element name and name space. The provider will override any providers + * loaded through the classpath. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @param provider the IQ provider. + */ + public void addIQProvider(String elementName, String namespace, + Object provider) + { + if (!(provider instanceof IQProvider || (provider instanceof Class && + IQ.class.isAssignableFrom((Class)provider)))) + { + throw new IllegalArgumentException("Provider must be an IQProvider " + + "or a Class instance."); + } + String key = getProviderKey(elementName, namespace); + iqProviders.put(key, provider); + } + + /** + * Removes an IQ provider with the specified element name and namespace. This + * method is typically called to cleanup providers that are programatically added + * using the {@link #addIQProvider(String, String, Object) addIQProvider} method. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + */ + public void removeIQProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + iqProviders.remove(key); + } + + /** + * Returns the packet extension provider registered to the specified XML element name + * and namespace. For example, if a provider was registered to the element name "x" and the + * namespace "jabber:x:event", then the following packet would trigger the provider: + * + *
      +     * <message to='romeo@montague.net' id='message_1'>
      +     *     <body>Art thou not Romeo, and a Montague?</body>
      +     *     <x xmlns='jabber:x:event'>
      +     *         <composing/>
      +     *     </x>
      +     * </message>
      + * + *

      Note: this method is generally only called by the internal Smack classes. + * + * @param elementName element name associated with extension provider. + * @param namespace namespace associated with extension provider. + * @return the extenion provider. + */ + public Object getExtensionProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return extensionProviders.get(key); + } + + /** + * Adds an extension provider with the specified element name and name space. The provider + * will override any providers loaded through the classpath. The provider must be either + * a PacketExtensionProvider instance, or a Class object of a Javabean. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @param provider the extension provider. + */ + public void addExtensionProvider(String elementName, String namespace, + Object provider) + { + if (!(provider instanceof PacketExtensionProvider || provider instanceof Class)) { + throw new IllegalArgumentException("Provider must be a PacketExtensionProvider " + + "or a Class instance."); + } + String key = getProviderKey(elementName, namespace); + extensionProviders.put(key, provider); + } + + /** + * Removes an extension provider with the specified element name and namespace. This + * method is typically called to cleanup providers that are programatically added + * using the {@link #addExtensionProvider(String, String, Object) addExtensionProvider} method. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + */ + public void removeExtensionProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + extensionProviders.remove(key); + } + + /** + * Returns an unmodifiable collection of all PacketExtensionProvider instances. Each object + * in the collection will either be a PacketExtensionProvider instance, or a Class object + * that implements the PacketExtensionProvider interface. + * + * @return all PacketExtensionProvider instances. + */ + public Collection getExtensionProviders() { + return Collections.unmodifiableCollection(extensionProviders.values()); + } + + /** + * Returns a String key for a given element name and namespace. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return a unique key for the element name and namespace pair. + */ + private String getProviderKey(String elementName, String namespace) { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); + return buf.toString(); + } + + /** + * Returns an array of class loaders to load resources from. + * + * @return an array of ClassLoader instances. + */ + private ClassLoader[] getClassLoaders() { + ClassLoader[] classLoaders = new ClassLoader[2]; + classLoaders[0] = ProviderManager.class.getClassLoader(); + classLoaders[1] = Thread.currentThread().getContextClassLoader(); + // Clean up possible null values. Note that #getClassLoader may return a null value. + List loaders = new ArrayList(); + for (ClassLoader classLoader : classLoaders) { + if (classLoader != null) { + loaders.add(classLoader); + } + } + return loaders.toArray(new ClassLoader[loaders.size()]); + } + + private ProviderManager() { + super(); + initialize(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html new file mode 100644 index 0000000..fccc383 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/provider/package.html @@ -0,0 +1 @@ +Provides pluggable parsing of incoming IQ's and packet extensions. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java new file mode 100644 index 0000000..42433b4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/DirectSocketFactory.java @@ -0,0 +1,57 @@ +package org.jivesoftware.smack.proxy; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; + +/** + * SocketFactory for direct connection + * + * @author Atul Aggarwal + */ +class DirectSocketFactory + extends SocketFactory +{ + + public DirectSocketFactory() + { + } + + static private int roundrobin = 0; + + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException + { + Socket newSocket = new Socket(Proxy.NO_PROXY); + InetAddress resolved[] = InetAddress.getAllByName(host); + newSocket.connect(new InetSocketAddress(resolved[(roundrobin++) % resolved.length],port)); + return newSocket; + } + + public Socket createSocket(String host ,int port, InetAddress localHost, + int localPort) + throws IOException, UnknownHostException + { + return new Socket(host,port,localHost,localPort); + } + + public Socket createSocket(InetAddress host, int port) + throws IOException + { + Socket newSocket = new Socket(Proxy.NO_PROXY); + newSocket.connect(new InetSocketAddress(host,port)); + return newSocket; + } + + public Socket createSocket( InetAddress address, int port, + InetAddress localAddress, int localPort) + throws IOException + { + return new Socket(address,port,localAddress,localPort); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java new file mode 100644 index 0000000..9531f04 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/HTTPProxySocketFactory.java @@ -0,0 +1,156 @@ +package org.jivesoftware.smack.proxy; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; +import org.jivesoftware.smack.util.Base64; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Http Proxy Socket Factory which returns socket connected to Http Proxy + * + * @author Atul Aggarwal + */ +class HTTPProxySocketFactory + extends SocketFactory +{ + + private ProxyInfo proxy; + + public HTTPProxySocketFactory(ProxyInfo proxy) + { + this.proxy = proxy; + } + + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException + { + return httpProxifiedSocket(host, port); + } + + public Socket createSocket(String host ,int port, InetAddress localHost, + int localPort) + throws IOException, UnknownHostException + { + return httpProxifiedSocket(host, port); + } + + public Socket createSocket(InetAddress host, int port) + throws IOException + { + return httpProxifiedSocket(host.getHostAddress(), port); + + } + + public Socket createSocket( InetAddress address, int port, + InetAddress localAddress, int localPort) + throws IOException + { + return httpProxifiedSocket(address.getHostAddress(), port); + } + + private Socket httpProxifiedSocket(String host, int port) + throws IOException + { + String proxyhost = proxy.getProxyAddress(); + int proxyPort = proxy.getProxyPort(); + Socket socket = new Socket(proxyhost,proxyPort); + String hostport = "CONNECT " + host + ":" + port; + String proxyLine; + String username = proxy.getProxyUsername(); + if (username == null) + { + proxyLine = ""; + } + else + { + String password = proxy.getProxyPassword(); + proxyLine = "\r\nProxy-Authorization: Basic " + + new String (Base64.encodeBytes((username + ":" + + password).getBytes("UTF-8"))); + } + socket.getOutputStream().write((hostport + " HTTP/1.1\r\nHost: " + + hostport + proxyLine + "\r\n\r\n").getBytes("UTF-8")); + + InputStream in = socket.getInputStream(); + StringBuilder got = new StringBuilder(100); + int nlchars = 0; + + while (true) + { + char c = (char) in.read(); + got.append(c); + if (got.length() > 1024) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Recieved " + + "header of >1024 characters from " + + proxyhost + ", cancelling connection"); + } + if (c == -1) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP); + } + if ((nlchars == 0 || nlchars == 2) && c == '\r') + { + nlchars++; + } + else if ((nlchars == 1 || nlchars == 3) && c == '\n') + { + nlchars++; + } + else + { + nlchars = 0; + } + if (nlchars == 4) + { + break; + } + } + + if (nlchars != 4) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Never " + + "received blank line from " + + proxyhost + ", cancelling connection"); + } + + String gotstr = got.toString(); + + BufferedReader br = new BufferedReader(new StringReader(gotstr)); + String response = br.readLine(); + + if (response == null) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP, "Empty proxy " + + "response from " + proxyhost + ", cancelling"); + } + + Matcher m = RESPONSE_PATTERN.matcher(response); + if (!m.matches()) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP , "Unexpected " + + "proxy response from " + proxyhost + ": " + response); + } + + int code = Integer.parseInt(m.group(1)); + + if (code != HttpURLConnection.HTTP_OK) + { + throw new ProxyException(ProxyInfo.ProxyType.HTTP); + } + + return socket; + } + + private static final Pattern RESPONSE_PATTERN + = Pattern.compile("HTTP/\\S+\\s(\\d+)\\s(.*)\\s*"); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java new file mode 100644 index 0000000..3cb6135 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyException.java @@ -0,0 +1,27 @@ +package org.jivesoftware.smack.proxy; + +import java.io.IOException; + +/** + * An exception class to handle exceptions caused by proxy. + * + * @author Atul Aggarwal + */ +public class ProxyException + extends IOException +{ + public ProxyException(ProxyInfo.ProxyType type, String ex, Throwable cause) + { + super("Proxy Exception " + type.toString() + " : "+ex+", "+cause); + } + + public ProxyException(ProxyInfo.ProxyType type, String ex) + { + super("Proxy Exception " + type.toString() + " : "+ex); + } + + public ProxyException(ProxyInfo.ProxyType type) + { + super("Proxy Exception " + type.toString() + " : " + "Unknown Error"); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java new file mode 100644 index 0000000..a85de0d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/ProxyInfo.java @@ -0,0 +1,114 @@ +package org.jivesoftware.smack.proxy; + +import javax.net.SocketFactory; + +/** + * Class which stores proxy information such as proxy type, host, port, + * authentication etc. + * + * @author Atul Aggarwal + */ + +public class ProxyInfo +{ + public static enum ProxyType + { + NONE, + HTTP, + SOCKS4, + SOCKS5 + } + + private String proxyAddress; + private int proxyPort; + private String proxyUsername; + private String proxyPassword; + private ProxyType proxyType; + + public ProxyInfo( ProxyType pType, String pHost, int pPort, String pUser, + String pPass) + { + this.proxyType = pType; + this.proxyAddress = pHost; + this.proxyPort = pPort; + this.proxyUsername = pUser; + this.proxyPassword = pPass; + } + + public static ProxyInfo forHttpProxy(String pHost, int pPort, String pUser, + String pPass) + { + return new ProxyInfo(ProxyType.HTTP, pHost, pPort, pUser, pPass); + } + + public static ProxyInfo forSocks4Proxy(String pHost, int pPort, String pUser, + String pPass) + { + return new ProxyInfo(ProxyType.SOCKS4, pHost, pPort, pUser, pPass); + } + + public static ProxyInfo forSocks5Proxy(String pHost, int pPort, String pUser, + String pPass) + { + return new ProxyInfo(ProxyType.SOCKS5, pHost, pPort, pUser, pPass); + } + + public static ProxyInfo forNoProxy() + { + return new ProxyInfo(ProxyType.NONE, null, 0, null, null); + } + + public static ProxyInfo forDefaultProxy() + { + return new ProxyInfo(ProxyType.NONE, null, 0, null, null); + } + + public ProxyType getProxyType() + { + return proxyType; + } + + public String getProxyAddress() + { + return proxyAddress; + } + + public int getProxyPort() + { + return proxyPort; + } + + public String getProxyUsername() + { + return proxyUsername; + } + + public String getProxyPassword() + { + return proxyPassword; + } + + public SocketFactory getSocketFactory() + { + if(proxyType == ProxyType.NONE) + { + return new DirectSocketFactory(); + } + else if(proxyType == ProxyType.HTTP) + { + return new HTTPProxySocketFactory(this); + } + else if(proxyType == ProxyType.SOCKS4) + { + return new Socks4ProxySocketFactory(this); + } + else if(proxyType == ProxyType.SOCKS5) + { + return new Socks5ProxySocketFactory(this); + } + else + { + return null; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java new file mode 100644 index 0000000..010357e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/proxy/Socks4ProxySocketFactory.java @@ -0,0 +1,199 @@ +package org.jivesoftware.smack.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import javax.net.SocketFactory; + +/** + * Socket factory for socks4 proxy + * + * @author Atul Aggarwal + */ +public class Socks4ProxySocketFactory + extends SocketFactory +{ + private ProxyInfo proxy; + + public Socks4ProxySocketFactory(ProxyInfo proxy) + { + this.proxy = proxy; + } + + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException + { + return socks4ProxifiedSocket(host,port); + + } + + public Socket createSocket(String host ,int port, InetAddress localHost, + int localPort) + throws IOException, UnknownHostException + { + return socks4ProxifiedSocket(host,port); + } + + public Socket createSocket(InetAddress host, int port) + throws IOException + { + return socks4ProxifiedSocket(host.getHostAddress(),port); + } + + public Socket createSocket( InetAddress address, int port, + InetAddress localAddress, int localPort) + throws IOException + { + return socks4ProxifiedSocket(address.getHostAddress(),port); + + } + + private Socket socks4ProxifiedSocket(String host, int port) + throws IOException + { + Socket socket = null; + InputStream in = null; + OutputStream out = null; + String proxy_host = proxy.getProxyAddress(); + int proxy_port = proxy.getProxyPort(); + String user = proxy.getProxyUsername(); + String passwd = proxy.getProxyPassword(); + + try + { + socket=new Socket(proxy_host, proxy_port); + in=socket.getInputStream(); + out=socket.getOutputStream(); + socket.setTcpNoDelay(true); + + byte[] buf=new byte[1024]; + int index=0; + + /* + 1) CONNECT + + The client connects to the SOCKS server and sends a CONNECT request when + it wants to establish a connection to an application server. The client + includes in the request packet the IP address and the port number of the + destination host, and userid, in the following format. + + +----+----+----+----+----+----+----+----+----+----+....+----+ + | VN | CD | DSTPORT | DSTIP | USERID |NULL| + +----+----+----+----+----+----+----+----+----+----+....+----+ + # of bytes: 1 1 2 4 variable 1 + + VN is the SOCKS protocol version number and should be 4. CD is the + SOCKS command code and should be 1 for CONNECT request. NULL is a byte + of all zero bits. + */ + + index=0; + buf[index++]=4; + buf[index++]=1; + + buf[index++]=(byte)(port>>>8); + buf[index++]=(byte)(port&0xff); + + try + { + InetAddress addr=InetAddress.getByName(host); + byte[] byteAddress = addr.getAddress(); + for (int i = 0; i < byteAddress.length; i++) + { + buf[index++]=byteAddress[i]; + } + } + catch(UnknownHostException uhe) + { + throw new ProxyException(ProxyInfo.ProxyType.SOCKS4, + uhe.toString(), uhe); + } + + if(user!=null) + { + System.arraycopy(user.getBytes(), 0, buf, index, user.length()); + index+=user.length(); + } + buf[index++]=0; + out.write(buf, 0, index); + + /* + The SOCKS server checks to see whether such a request should be granted + based on any combination of source IP address, destination IP address, + destination port number, the userid, and information it may obtain by + consulting IDENT, cf. RFC 1413. If the request is granted, the SOCKS + server makes a connection to the specified port of the destination host. + A reply packet is sent to the client when this connection is established, + or when the request is rejected or the operation fails. + + +----+----+----+----+----+----+----+----+ + | VN | CD | DSTPORT | DSTIP | + +----+----+----+----+----+----+----+----+ + # of bytes: 1 1 2 4 + + VN is the version of the reply code and should be 0. CD is the result + code with one of the following values: + + 90: request granted + 91: request rejected or failed + 92: request rejected becasue SOCKS server cannot connect to + identd on the client + 93: request rejected because the client program and identd + report different user-ids + + The remaining fields are ignored. + */ + + int len=6; + int s=0; + while(s>>8); + buf[index++]=(byte)(port&0xff); + + out.write(buf, 0, index); + +/* + The SOCKS request information is sent by the client as soon as it has + established a connection to the SOCKS server, and completed the + authentication negotiations. The server evaluates the request, and + returns a reply formed as follows: + + +----+-----+-------+------+----------+----------+ + |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + + Where: + + o VER protocol version: X'05' + o REP Reply field: + o X'00' succeeded + o X'01' general SOCKS server failure + o X'02' connection not allowed by ruleset + o X'03' Network unreachable + o X'04' Host unreachable + o X'05' Connection refused + o X'06' TTL expired + o X'07' Command not supported + o X'08' Address type not supported + o X'09' to X'FF' unassigned + o RSV RESERVED + o ATYP address type of following address + o IP V4 address: X'01' + o DOMAINNAME: X'03' + o IP V6 address: X'04' + o BND.ADDR server bound address + o BND.PORT server bound port in network octet order +*/ + + //in.read(buf, 0, 4); + fill(in, buf, 4); + + if(buf[1]!=0) + { + try + { + socket.close(); + } + catch(Exception eee) + { + } + throw new ProxyException(ProxyInfo.ProxyType.SOCKS5, + "server returns "+buf[1]); + } + + switch(buf[3]&0xff) + { + case 1: + //in.read(buf, 0, 6); + fill(in, buf, 6); + break; + case 3: + //in.read(buf, 0, 1); + fill(in, buf, 1); + //in.read(buf, 0, buf[0]+2); + fill(in, buf, (buf[0]&0xff)+2); + break; + case 4: + //in.read(buf, 0, 18); + fill(in, buf, 18); + break; + default: + } + return socket; + + } + catch(RuntimeException e) + { + throw e; + } + catch(Exception e) + { + try + { + if(socket!=null) + { + socket.close(); + } + } + catch(Exception eee) + { + } + String message="ProxySOCKS5: "+e.toString(); + if(e instanceof Throwable) + { + throw new ProxyException(ProxyInfo.ProxyType.SOCKS5,message, + (Throwable)e); + } + throw new IOException(message); + } + } + + private void fill(InputStream in, byte[] buf, int len) + throws IOException + { + int s=0; + while(s + *
    • javax.net.ssl.keyStore to the location of the keyStore + *
    • javax.net.ssl.keyStorePassword to the password of the keyStore + *
    • javax.net.ssl.trustStore to the location of the trustStore + *
    • javax.net.ssl.trustStorePassword to the the password of the trustStore + * + * + * Then, when the server requests or requires the client certificate, java will + * simply provide the one in the keyStore. + * + * Also worth noting is the EXTERNAL mechanism in Smack is not enabled by default. + * To enable it, the implementer will need to call SASLAuthentication.supportSASLMechamism("EXTERNAL"); + * + * @author Jay Kline + */ +public class SASLExternalMechanism extends SASLMechanism { + + public SASLExternalMechanism(SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + protected String getName() { + return "EXTERNAL"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java new file mode 100644 index 0000000..3126d83 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLFacebookConnect.java @@ -0,0 +1,201 @@ +package org.jivesoftware.smack.sasl; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; + +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import de.measite.smack.Sasl; + +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.sasl.SASLMechanism; +import org.jivesoftware.smack.util.Base64; + +/** + * This class is originally from http://code.google.com/p/fbgc/source/browse/trunk/daemon/src/main/java/org/albino/mechanisms/FacebookConnectSASLMechanism.java + * I just adapted to match the SMACK package scheme and + */ +public class SASLFacebookConnect extends SASLMechanism { + + private String sessionKey = ""; + private String sessionSecret = ""; + private String apiKey = ""; + + static{ + SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM", + SASLFacebookConnect.class); + SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0); + } + + public SASLFacebookConnect(SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + // protected void authenticate() throws IOException, XMPPException { + // String[] mechanisms = { getName() }; + // Map props = new HashMap(); + // sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props, + // this); + // + // super.authenticate(); + // } + + protected void authenticate() throws IOException, XMPPException { + final StringBuilder stanza = new StringBuilder(); + stanza.append(""); + stanza.append(""); + + // Send the authentication to the server + getSASLAuthentication().send(new Packet(){ + + @Override + public String toXML() { + return stanza.toString(); + } + + }); + } + + public void authenticate(String apiKeyAndSessionKey, String host, String sessionSecret) + throws IOException, XMPPException { + + if(apiKeyAndSessionKey==null || sessionSecret==null) + throw new IllegalStateException("Invalid parameters!"); + + String[] keyArray = apiKeyAndSessionKey.split("\\|"); + + if(keyArray==null || keyArray.length != 2) + throw new IllegalStateException("Api key or session key is not present!"); + + this.apiKey = keyArray[0]; + this.sessionKey = keyArray[1]; + this.sessionSecret = sessionSecret; + + this.authenticationId = sessionKey; + this.password = sessionSecret; + this.hostname = host; + + String[] mechanisms = { "DIGEST-MD5" }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); + authenticate(); + } + + public void authenticate(String username, String host, CallbackHandler cbh) + throws IOException, XMPPException { + String[] mechanisms = { "DIGEST-MD5" }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh); + authenticate(); + } + + protected String getName() { + return "X-FACEBOOK-PLATFORM"; + } + + public void challengeReceived(String challenge) throws IOException { + // Build the challenge response stanza encoding the response text + final StringBuilder stanza = new StringBuilder(); + + byte response[] = null; + if (challenge != null) { + String decodedResponse = new String(Base64.decode(challenge)); + Map parameters = getQueryMap(decodedResponse); + + String version = "1.0"; + String nonce = parameters.get("nonce"); + String method = parameters.get("method"); + + Long callId = new GregorianCalendar().getTimeInMillis()/1000; + + String sig = "api_key="+apiKey + +"call_id="+callId + +"method="+method + +"nonce="+nonce + +"session_key="+sessionKey + +"v="+version + +sessionSecret; + + try { + sig = MD5(sig); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException(e); + } + + String composedResponse = "api_key="+apiKey+"&" + +"call_id="+callId+"&" + +"method="+method+"&" + +"nonce="+nonce+"&" + +"session_key="+sessionKey+"&" + +"v="+version+"&" + +"sig="+sig; + + response = composedResponse.getBytes(); + } + + String authenticationText=""; + + if (response != null) { + authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES); + } + + stanza.append(""); + stanza.append(authenticationText); + stanza.append(""); + + // Send the authentication to the server + getSASLAuthentication().send(new Packet(){ + + @Override + public String toXML() { + return stanza.toString(); + } + + }); + } + + private Map getQueryMap(String query) { + String[] params = query.split("&"); + Map map = new HashMap(); + for (String param : params) { + String name = param.split("=")[0]; + String value = param.split("=")[1]; + map.put(name, value); + } + return map; + } + + private String convertToHex(byte[] data) { + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < data.length; i++) { + int halfbyte = (data[i] >>> 4) & 0x0F; + int two_halfs = 0; + do { + if ((0 <= halfbyte) && (halfbyte <= 9)) + buf.append((char) ('0' + halfbyte)); + else + buf.append((char) ('a' + (halfbyte - 10))); + halfbyte = data[i] & 0x0F; + } while(two_halfs++ < 1); + } + return buf.toString(); + } + + public String MD5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { + MessageDigest md; + md = MessageDigest.getInstance("MD5"); + byte[] md5hash = new byte[32]; + md.update(text.getBytes("iso-8859-1"), 0, text.length()); + md5hash = md.digest(); + return convertToHex(md5hash); + } +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java new file mode 100644 index 0000000..593ca09 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLGSSAPIMechanism.java @@ -0,0 +1,90 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * + * All rights reserved. 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 org.jivesoftware.smack.sasl; + +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.XMPPException; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import de.measite.smack.Sasl; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; + +/** + * Implementation of the SASL GSSAPI mechanism + * + * @author Jay Kline + */ +public class SASLGSSAPIMechanism extends SASLMechanism { + + public SASLGSSAPIMechanism(SASLAuthentication saslAuthentication) { + super(saslAuthentication); + + System.setProperty("javax.security.auth.useSubjectCredsOnly","false"); + System.setProperty("java.security.auth.login.config","gss.conf"); + + } + + protected String getName() { + return "GSSAPI"; + } + + /** + * Builds and sends the auth stanza to the server. + * This overrides from the abstract class because the initial token + * needed for GSSAPI is binary, and not safe to put in a string, thus + * getAuthenticationText() cannot be used. + * + * @param username the username of the user being authenticated. + * @param host the hostname where the user account resides. + * @param cbh the CallbackHandler (not used with GSSAPI) + * @throws IOException If a network error occures while authenticating. + */ + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { + String[] mechanisms = { getName() }; + Map props = new HashMap(); + props.put(Sasl.SERVER_AUTH,"TRUE"); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); + authenticate(); + } + + /** + * Builds and sends the auth stanza to the server. + * This overrides from the abstract class because the initial token + * needed for GSSAPI is binary, and not safe to put in a string, thus + * getAuthenticationText() cannot be used. + * + * @param username the username of the user being authenticated. + * @param host the hostname where the user account resides. + * @param password the password of the user (ignored for GSSAPI) + * @throws IOException If a network error occures while authenticating. + */ + public void authenticate(String username, String host, String password) throws IOException, XMPPException { + String[] mechanisms = { getName() }; + Map props = new HashMap(); + props.put(Sasl.SERVER_AUTH,"TRUE"); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); + authenticate(); + } + + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java new file mode 100644 index 0000000..5a9ed09 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLMechanism.java @@ -0,0 +1,323 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.sasl; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.SASLAuthentication; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.Base64; + +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import org.apache.harmony.javax.security.auth.callback.CallbackHandler; +import org.apache.harmony.javax.security.auth.callback.UnsupportedCallbackException; +import org.apache.harmony.javax.security.auth.callback.Callback; +import org.apache.harmony.javax.security.auth.callback.NameCallback; +import org.apache.harmony.javax.security.auth.callback.PasswordCallback; +import org.apache.harmony.javax.security.sasl.RealmCallback; +import org.apache.harmony.javax.security.sasl.RealmChoiceCallback; +import de.measite.smack.Sasl; +import org.apache.harmony.javax.security.sasl.SaslClient; +import org.apache.harmony.javax.security.sasl.SaslException; + +/** + * Base class for SASL mechanisms. Subclasses must implement these methods: + *
        + *
      • {@link #getName()} -- returns the common name of the SASL mechanism.
      • + *
      + * Subclasses will likely want to implement their own versions of these mthods: + *
    • {@link #authenticate(String, String, String)} -- Initiate authentication stanza using the + * deprecated method.
    • + *
    • {@link #authenticate(String, String, CallbackHandler)} -- Initiate authentication stanza + * using the CallbackHandler method.
    • + *
    • {@link #challengeReceived(String)} -- Handle a challenge from the server.
    • + * + * + * @author Jay Kline + */ +public abstract class SASLMechanism implements CallbackHandler { + + private SASLAuthentication saslAuthentication; + protected SaslClient sc; + protected String authenticationId; + protected String password; + protected String hostname; + + + public SASLMechanism(SASLAuthentication saslAuthentication) { + this.saslAuthentication = saslAuthentication; + } + + /** + * Builds and sends the auth stanza to the server. Note that this method of + * authentication is not recommended, since it is very inflexable. Use + * {@link #authenticate(String, String, CallbackHandler)} whenever possible. + * + * @param username the username of the user being authenticated. + * @param host the hostname where the user account resides. + * @param password the password for this account. + * @throws IOException If a network error occurs while authenticating. + * @throws XMPPException If a protocol error occurs or the user is not authenticated. + */ + public void authenticate(String username, String host, String password) throws IOException, XMPPException { + //Since we were not provided with a CallbackHandler, we will use our own with the given + //information + + //Set the authenticationID as the username, since they must be the same in this case. + this.authenticationId = username; + this.password = password; + this.hostname = host; + + String[] mechanisms = { getName() }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this); + authenticate(); + } + + /** + * Builds and sends the auth stanza to the server. The callback handler will handle + * any additional information, such as the authentication ID or realm, if it is needed. + * + * @param username the username of the user being authenticated. + * @param host the hostname where the user account resides. + * @param cbh the CallbackHandler to obtain user information. + * @throws IOException If a network error occures while authenticating. + * @throws XMPPException If a protocol error occurs or the user is not authenticated. + */ + public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { + String[] mechanisms = { getName() }; + Map props = new HashMap(); + sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, cbh); + authenticate(); + } + + protected void authenticate() throws IOException, XMPPException { + String authenticationText = null; + try { + if(sc.hasInitialResponse()) { + byte[] response = sc.evaluateChallenge(new byte[0]); + authenticationText = Base64.encodeBytes(response,Base64.DONT_BREAK_LINES); + } + } catch (SaslException e) { + throw new XMPPException("SASL authentication failed", e); + } + + // Send the authentication to the server + getSASLAuthentication().send(new AuthMechanism(getName(), authenticationText)); + } + + + /** + * The server is challenging the SASL mechanism for the stanza he just sent. Send a + * response to the server's challenge. + * + * @param challenge a base64 encoded string representing the challenge. + * @throws IOException if an exception sending the response occurs. + */ + public void challengeReceived(String challenge) throws IOException { + byte response[]; + if(challenge != null) { + response = sc.evaluateChallenge(Base64.decode(challenge)); + } else { + response = sc.evaluateChallenge(new byte[0]); + } + + Packet responseStanza; + if (response == null) { + responseStanza = new Response(); + } + else { + responseStanza = new Response(Base64.encodeBytes(response,Base64.DONT_BREAK_LINES)); + } + + // Send the authentication to the server + getSASLAuthentication().send(responseStanza); + } + + /** + * Returns the common name of the SASL mechanism. E.g.: PLAIN, DIGEST-MD5 or GSSAPI. + * + * @return the common name of the SASL mechanism. + */ + protected abstract String getName(); + + + protected SASLAuthentication getSASLAuthentication() { + return saslAuthentication; + } + + /** + * + */ + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (int i = 0; i < callbacks.length; i++) { + if (callbacks[i] instanceof NameCallback) { + NameCallback ncb = (NameCallback)callbacks[i]; + ncb.setName(authenticationId); + } else if(callbacks[i] instanceof PasswordCallback) { + PasswordCallback pcb = (PasswordCallback)callbacks[i]; + pcb.setPassword(password.toCharArray()); + } else if(callbacks[i] instanceof RealmCallback) { + RealmCallback rcb = (RealmCallback)callbacks[i]; + rcb.setText(hostname); + } else if(callbacks[i] instanceof RealmChoiceCallback){ + //unused + //RealmChoiceCallback rccb = (RealmChoiceCallback)callbacks[i]; + } else { + throw new UnsupportedCallbackException(callbacks[i]); + } + } + } + + /** + * Initiating SASL authentication by select a mechanism. + */ + public class AuthMechanism extends Packet { + final private String name; + final private String authenticationText; + + public AuthMechanism(String name, String authenticationText) { + if (name == null) { + throw new NullPointerException("SASL mechanism name shouldn't be null."); + } + this.name = name; + this.authenticationText = authenticationText; + } + + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (authenticationText != null && + authenticationText.trim().length() > 0) { + stanza.append(authenticationText); + } + stanza.append(""); + return stanza.toString(); + } + } + + /** + * A SASL challenge stanza. + */ + public static class Challenge extends Packet { + final private String data; + + public Challenge(String data) { + this.data = data; + } + + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (data != null && + data.trim().length() > 0) { + stanza.append(data); + } + stanza.append(""); + return stanza.toString(); + } + } + + /** + * A SASL response stanza. + */ + public class Response extends Packet { + final private String authenticationText; + + public Response() { + authenticationText = null; + } + + public Response(String authenticationText) { + if (authenticationText == null || authenticationText.trim().length() == 0) { + this.authenticationText = null; + } + else { + this.authenticationText = authenticationText; + } + } + + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (authenticationText != null) { + stanza.append(authenticationText); + } + stanza.append(""); + return stanza.toString(); + } + } + + /** + * A SASL success stanza. + */ + public static class Success extends Packet { + final private String data; + + public Success(String data) { + this.data = data; + } + + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (data != null && + data.trim().length() > 0) { + stanza.append(data); + } + stanza.append(""); + return stanza.toString(); + } + } + + /** + * A SASL failure stanza. + */ + public static class Failure extends Packet { + final private String condition; + + public Failure(String condition) { + this.condition = condition; + } + + /** + * Get the SASL related error condition. + * + * @return the SASL related error condition. + */ + public String getCondition() { + return condition; + } + + public String toXML() { + StringBuilder stanza = new StringBuilder(); + stanza.append(""); + if (condition != null && + condition.trim().length() > 0) { + stanza.append("<").append(condition).append("/>"); + } + stanza.append(""); + return stanza.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java new file mode 100644 index 0000000..cd973eb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/SASLPlainMechanism.java @@ -0,0 +1,34 @@ +/** + * + * All rights reserved. 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 org.jivesoftware.smack.sasl; + +import org.jivesoftware.smack.SASLAuthentication; + +/** + * Implementation of the SASL PLAIN mechanism + * + * @author Jay Kline + */ +public class SASLPlainMechanism extends SASLMechanism { + + public SASLPlainMechanism(SASLAuthentication saslAuthentication) { + super(saslAuthentication); + } + + protected String getName() { + return "PLAIN"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html new file mode 100644 index 0000000..1e8cfb7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/sasl/package.html @@ -0,0 +1 @@ +SASL Mechanisms. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java new file mode 100644 index 0000000..b0ed3ed --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Base64.java @@ -0,0 +1,1767 @@ +package org.jivesoftware.smack.util; + +/** + *

      Encodes and decodes to and from Base64 notation.

      + *

      Homepage: http://iharder.net/base64.

      + * + *

      + * Change Log: + *

      + *
        + *
      • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug + * when using very small files (~< 40 bytes).
      • + *
      • v2.2 - Added some helper methods for encoding/decoding directly from + * one file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 dialects: + *
          + *
        1. The default is RFC3548 format.
        2. + *
        3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html
        4. + *
        5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as described + * in http://www.faqs.org/qa/rfcc-1940.html
        6. + *
        + * Special thanks to Jim Kellerman at http://www.powerset.com/ + * for contributing the new Base64 dialects. + *
      • + * + *
      • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
      • + *
      • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
      • + *
      • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
      • + *
      • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
      • + *
      • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
      • + *
      • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
      • + *
      • v1.4 - Added helper methods to read/write files.
      • + *
      • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
      • + *
      • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
      • + *
      • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
      • + *
      • v1.3.3 - Fixed I/O streams which were totally messed up.
      • + *
      + * + *

      + * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

      + * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.2.1 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding. */ + public final static int ENCODE = 1; + + + /** Specify decoding. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed. */ + public final static int GZIP = 2; + + + /** Don't break lines when encoding (violates strict Base64 specification) */ + public final static int DONT_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "UTF-8"; + + + // I think I end up not using the BAD_ENCODING indicator. + //private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + //private final static byte[] ALPHABET; + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = + { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_ALPHABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_ALPHABET; + else return _STANDARD_ALPHABET; + + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) + { + if( (options & URL_SAFE) == URL_SAFE ) return _URL_SAFE_DECODABET; + else if( (options & ORDERED) == ORDERED ) return _ORDERED_DECODABET; + else return _STANDARD_DECODABET; + + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + /** + * Encodes or decodes two files from the command line; + * feel free to delete this method (in fact you probably should) + * if you're embedding this code into a larger program. + */ + public final static void main( String[] args ) + { + if( args.length < 3 ){ + usage("Not enough arguments."); + } // end if: args.length < 3 + else { + String flag = args[0]; + String infile = args[1]; + String outfile = args[2]; + if( flag.equals( "-e" ) ){ + Base64.encodeFileToFile( infile, outfile ); + } // end if: encode + else if( flag.equals( "-d" ) ) { + Base64.decodeFileToFile( infile, outfile ); + } // end else if: decode + else { + usage( "Unknown flag: " + flag ); + } // end else + } // end else + } // end main + + /** + * Prints command line usage. + * + * @param msg A message to include with usage info. + */ + private final static void usage( String msg ) + { + System.err.println( msg ); + System.err.println( "Usage: java Base64 -e|-d inputfile outputfile" ); + } // end usage + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) + { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + *

      Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes.

      + *

      This is the lowest level of the encoding methods with + * all possible parameters.

      + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) + { + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + *

      + * Valid options:

      +     *   GZIP: gzip-compresses object before encoding it.
      +     *   DONT_BREAK_LINES: don't break lines at 76 characters
      +     *     Note: Technically, this makes your encoding non-compliant.
      +     * 
      + *

      + * Example: encodeObject( myObj, Base64.GZIP ) or + *

      + * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + { + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.io.ObjectOutputStream oos = null; + java.util.zip.GZIPOutputStream gzos = null; + + // Isolate options + int gzip = (options & GZIP); + int dontBreakLines = (options & DONT_BREAK_LINES); + + try + { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + + // GZip? + if( gzip == GZIP ) + { + gzos = new java.util.zip.GZIPOutputStream( b64os ); + oos = new java.io.ObjectOutputStream( gzos ); + } // end if: gzip + else + oos = new java.io.ObjectOutputStream( b64os ); + + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) + { + return encodeBytes( source, 0, source.length, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

      + * Valid options:

      +     *   GZIP: gzip-compresses object before encoding it.
      +     *   DONT_BREAK_LINES: don't break lines at 76 characters
      +     *     Note: Technically, this makes your encoding non-compliant.
      +     * 
      + *

      + * Example: encodeBytes( myData, Base64.GZIP ) or + *

      + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) + { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) + { + return encodeBytes( source, off, len, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

      + * Valid options:

      +     *   GZIP: gzip-compresses object before encoding it.
      +     *   DONT_BREAK_LINES: don't break lines at 76 characters
      +     *     Note: Technically, this makes your encoding non-compliant.
      +     * 
      + *

      + * Example: encodeBytes( myData, Base64.GZIP ) or + *

      + * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options; alphabet type is pulled from this (standard, url-safe, ordered) + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) + { + // Isolate options + int dontBreakLines = ( options & DONT_BREAK_LINES ); + int gzip = ( options & GZIP ); + + // Compress? + if( gzip == GZIP ) + { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + + try + { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else + { + // Convert option to boolean in way that code likes it. + boolean breakLines = dontBreakLines == 0; + + int len43 = len * 4 / 3; + byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) + { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength == MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) + { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Return value according to relevant encoding. + try + { + return new String( outBuff, 0, e, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( outBuff, 0, e ); + } // end catch + + } // end else: don't compress + + } // end encodeBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + *

      This is the lowest level of the decoding methods with + * all possible parameters.

      + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset, int options ) + { + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else + { + try{ + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + }catch( Exception e){ + System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); + System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); + System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); + System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); + return -1; + } // end catch + } + } // end decodeToBytes + + + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + { + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for( i = off; i < off+len; i++ ) + { + sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[ sbiCrop ]; + + if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better + { + if( sbiDecode >= EQUALS_SIGN_ENC ) + { + b4[ b4Posn++ ] = sbiCrop; + if( b4Posn > 3 ) + { + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( sbiCrop == EQUALS_SIGN ) + break; + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else + { + System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); + return null; + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode( String s ) + { + return decode( s, NO_OPTIONS ); + } + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode( String s, int options ) + { + byte[] bytes; + try + { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) + { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if( bytes != null && bytes.length >= 4 ) + { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) + { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try + { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) + { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) + { + // Just return originally-decoded bytes + } // end catch + finally + { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + { + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try + { + bais = new java.io.ByteArrayInputStream( objBytes ); + ois = new java.io.ObjectInputStream( bais ); + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + catch( java.lang.ClassNotFoundException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + finally + { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean encodeToFile( byte[] dataToEncode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + success = true; + } // end try + catch( java.io.IOException e ) + { + + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean decodeToFile( String dataToDecode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + success = true; + } // end try + catch( java.io.IOException e ) + { + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * @param filename Filename for reading encoded data + * @return decoded byte array or null if unsuccessful + * + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + { + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); + return null; + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error decoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * @param filename Filename for reading binary data + * @return base64-encoded string or null if unsuccessful + * + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + { + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4),40) ]; // Need max() for math on small files (v2.2.1) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error encoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads infile and encodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + { + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException ex ) { + ex.printStackTrace(); + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads infile and decodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + { + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException ex ) { + ex.printStackTrace(); + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream + { + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] alphabet; // Local copies to avoid extra method calls + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) + { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

      + * Valid options:

      +         *   ENCODE or DECODE: Encode or Decode as data is read.
      +         *   DONT_BREAK_LINES: don't break lines at 76 characters
      +         *     (only meaningful when encoding)
      +         *     Note: Technically, this makes your encoding non-compliant.
      +         * 
      + *

      + * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) + { + super( in ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.options = options; // Record for later, mostly to determine which alphabet to use + this.alphabet = getAlphabet(options); + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + public int read() throws java.io.IOException + { + // Do we need to get data? + if( position < 0 ) + { + if( encode ) + { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) + { + try + { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) + { + b3[i] = (byte)b; + numBinaryBytes++; + } // end if: not end of stream + + } // end try: read + catch( java.io.IOException e ) + { + // Only a problem if we got no data at all. + if( i == 0 ) + throw e; + + } // end catch + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) + { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else + { + return -1; + } // end else + } // end if: encoding + + // Else decoding + else + { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) + { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) + break; // Reads a -1 if end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) + { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else + { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) + { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ) + return -1; + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) + { + lineLength = 0; + return '\n'; + } // end if + else + { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) + position = -1; + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else + { + // When JDK1.4 is more accepted, use an assertion here. + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + public int read( byte[] dest, int off, int len ) throws java.io.IOException + { + int i; + int b; + for( i = 0; i < len; i++ ) + { + b = read(); + + //if( b < 0 && i == 0 ) + // return -1; + + if( b >= 0 ) + dest[off + i] = (byte)b; + else if( i == 0 ) + return -1; + else + break; // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream + { + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] alphabet; // Local copies to avoid extra method calls + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) + { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

      + * Valid options:

      +         *   ENCODE or DECODE: Encode or Decode as data is read.
      +         *   DONT_BREAK_LINES: don't break lines at 76 characters
      +         *     (only meaningful when encoding)
      +         *     Note: Technically, this makes your encoding non-compliant.
      +         * 
      + *

      + * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) + { + super( out ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.alphabet = getAlphabet(options); + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + public void write(int theByte) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to encode. + { + out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else + { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to output. + { + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + //out.write( Base64.decode4to3( buffer ) ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) + { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + public void write( byte[] theBytes, int off, int len ) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) + { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + public void flushBase64() throws java.io.IOException + { + if( position > 0 ) + { + if( encode ) + { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else + { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + public void close() throws java.io.IOException + { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException + { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() + { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java new file mode 100644 index 0000000..069b430 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/Cache.java @@ -0,0 +1,678 @@ +/** + * $Revision: 1456 $ + * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +import org.jivesoftware.smack.util.collections.AbstractMapEntry; + +import java.util.*; + +/** + * A specialized Map that is size-limited (using an LRU algorithm) and + * has an optional expiration time for cache items. The Map is thread-safe.

      + * + * The algorithm for cache is as follows: a HashMap is maintained for fast + * object lookup. Two linked lists are maintained: one keeps objects in the + * order they are accessed from cache, the other keeps objects in the order + * they were originally added to cache. When objects are added to cache, they + * are first wrapped by a CacheObject which maintains the following pieces + * of information:

        + *
      • A pointer to the node in the linked list that maintains accessed + * order for the object. Keeping a reference to the node lets us avoid + * linear scans of the linked list. + *
      • A pointer to the node in the linked list that maintains the age + * of the object in cache. Keeping a reference to the node lets us avoid + * linear scans of the linked list.
      + *

      + * To get an object from cache, a hash lookup is performed to get a reference + * to the CacheObject that wraps the real object we are looking for. + * The object is subsequently moved to the front of the accessed linked list + * and any necessary cache cleanups are performed. Cache deletion and expiration + * is performed as needed. + * + * @author Matt Tucker + */ +public class Cache implements Map { + + /** + * The map the keys and values are stored in. + */ + protected Map> map; + + /** + * Linked list to maintain order that cache objects are accessed + * in, most used to least used. + */ + protected LinkedList lastAccessedList; + + /** + * Linked list to maintain time that cache objects were initially added + * to the cache, most recently added to oldest added. + */ + protected LinkedList ageList; + + /** + * Maximum number of items the cache will hold. + */ + protected int maxCacheSize; + + /** + * Maximum length of time objects can exist in cache before expiring. + */ + protected long maxLifetime; + + /** + * Maintain the number of cache hits and misses. A cache hit occurs every + * time the get method is called and the cache contains the requested + * object. A cache miss represents the opposite occurence.

      + * + * Keeping track of cache hits and misses lets one measure how efficient + * the cache is; the higher the percentage of hits, the more efficient. + */ + protected long cacheHits, cacheMisses = 0L; + + /** + * Create a new cache and specify the maximum size of for the cache in + * bytes, and the maximum lifetime of objects. + * + * @param maxSize the maximum number of objects the cache will hold. -1 + * means the cache has no max size. + * @param maxLifetime the maximum amount of time (in ms) objects can exist in + * cache before being deleted. -1 means objects never expire. + */ + public Cache(int maxSize, long maxLifetime) { + if (maxSize == 0) { + throw new IllegalArgumentException("Max cache size cannot be 0."); + } + this.maxCacheSize = maxSize; + this.maxLifetime = maxLifetime; + + // Our primary data structure is a hash map. The default capacity of 11 + // is too small in almost all cases, so we set it bigger. + map = new HashMap>(103); + + lastAccessedList = new LinkedList(); + ageList = new LinkedList(); + } + + public synchronized V put(K key, V value) { + V oldValue = null; + // Delete an old entry if it exists. + if (map.containsKey(key)) { + oldValue = remove(key, true); + } + + CacheObject cacheObject = new CacheObject(value); + map.put(key, cacheObject); + // Make an entry into the cache order list. + // Store the cache order list entry so that we can get back to it + // during later lookups. + cacheObject.lastAccessedListNode = lastAccessedList.addFirst(key); + // Add the object to the age list + LinkedListNode ageNode = ageList.addFirst(key); + ageNode.timestamp = System.currentTimeMillis(); + cacheObject.ageListNode = ageNode; + + // If cache is too full, remove least used cache entries until it is not too full. + cullCache(); + + return oldValue; + } + + public synchronized V get(Object key) { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + CacheObject cacheObject = map.get(key); + if (cacheObject == null) { + // The object didn't exist in cache, so increment cache misses. + cacheMisses++; + return null; + } + // Remove the object from it's current place in the cache order list, + // and re-insert it at the front of the list. + cacheObject.lastAccessedListNode.remove(); + lastAccessedList.addFirst(cacheObject.lastAccessedListNode); + + // The object exists in cache, so increment cache hits. Also, increment + // the object's read count. + cacheHits++; + cacheObject.readCount++; + + return cacheObject.object; + } + + public synchronized V remove(Object key) { + return remove(key, false); + } + + /* + * Remove operation with a flag so we can tell coherence if the remove was + * caused by cache internal processing such as eviction or loading + */ + public synchronized V remove(Object key, boolean internal) { + //noinspection SuspiciousMethodCalls + CacheObject cacheObject = map.remove(key); + // If the object is not in cache, stop trying to remove it. + if (cacheObject == null) { + return null; + } + // Remove from the cache order list + cacheObject.lastAccessedListNode.remove(); + cacheObject.ageListNode.remove(); + // Remove references to linked list nodes + cacheObject.ageListNode = null; + cacheObject.lastAccessedListNode = null; + + return cacheObject.object; + } + + public synchronized void clear() { + Object[] keys = map.keySet().toArray(); + for (Object key : keys) { + remove(key); + } + + // Now, reset all containers. + map.clear(); + lastAccessedList.clear(); + ageList.clear(); + + cacheHits = 0; + cacheMisses = 0; + } + + public synchronized int size() { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return map.size(); + } + + public synchronized boolean isEmpty() { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return map.isEmpty(); + } + + public synchronized Collection values() { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return Collections.unmodifiableCollection(new AbstractCollection() { + Collection> values = map.values(); + public Iterator iterator() { + return new Iterator() { + Iterator> it = values.iterator(); + + public boolean hasNext() { + return it.hasNext(); + } + + public V next() { + return it.next().object; + } + + public void remove() { + it.remove(); + } + }; + } + + public int size() { + return values.size(); + } + }); + } + + public synchronized boolean containsKey(Object key) { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return map.containsKey(key); + } + + public void putAll(Map map) { + for (Entry entry : map.entrySet()) { + V value = entry.getValue(); + // If the map is another DefaultCache instance than the + // entry values will be CacheObject instances that need + // to be converted to the normal object form. + if (value instanceof CacheObject) { + //noinspection unchecked + value = ((CacheObject) value).object; + } + put(entry.getKey(), value); + } + } + + public synchronized boolean containsValue(Object value) { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + //noinspection unchecked + CacheObject cacheObject = new CacheObject((V) value); + + return map.containsValue(cacheObject); + } + + public synchronized Set> entrySet() { + // Warning -- this method returns CacheObject instances and not Objects + // in the same form they were put into cache. + + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return new AbstractSet>() { + private final Set>> set = map.entrySet(); + + public Iterator> iterator() { + return new Iterator>() { + private final Iterator>> it = set.iterator(); + public boolean hasNext() { + return it.hasNext(); + } + + public Entry next() { + Map.Entry> entry = it.next(); + return new AbstractMapEntry(entry.getKey(), entry.getValue().object) { + @Override + public V setValue(V value) { + throw new UnsupportedOperationException("Cannot set"); + } + }; + } + + public void remove() { + it.remove(); + } + }; + + } + + public int size() { + return set.size(); + } + }; + } + + public synchronized Set keySet() { + // First, clear all entries that have been in cache longer than the + // maximum defined age. + deleteExpiredEntries(); + + return Collections.unmodifiableSet(map.keySet()); + } + + public long getCacheHits() { + return cacheHits; + } + + public long getCacheMisses() { + return cacheMisses; + } + + public int getMaxCacheSize() { + return maxCacheSize; + } + + public synchronized void setMaxCacheSize(int maxCacheSize) { + this.maxCacheSize = maxCacheSize; + // It's possible that the new max size is smaller than our current cache + // size. If so, we need to delete infrequently used items. + cullCache(); + } + + public long getMaxLifetime() { + return maxLifetime; + } + + public void setMaxLifetime(long maxLifetime) { + this.maxLifetime = maxLifetime; + } + + /** + * Clears all entries out of cache where the entries are older than the + * maximum defined age. + */ + protected synchronized void deleteExpiredEntries() { + // Check if expiration is turned on. + if (maxLifetime <= 0) { + return; + } + + // Remove all old entries. To do this, we remove objects from the end + // of the linked list until they are no longer too old. We get to avoid + // any hash lookups or looking at any more objects than is strictly + // neccessary. + LinkedListNode node = ageList.getLast(); + // If there are no entries in the age list, return. + if (node == null) { + return; + } + + // Determine the expireTime, which is the moment in time that elements + // should expire from cache. Then, we can do an easy check to see + // if the expire time is greater than the expire time. + long expireTime = System.currentTimeMillis() - maxLifetime; + + while (expireTime > node.timestamp) { + if (remove(node.object, true) == null) { + System.err.println("Error attempting to remove(" + node.object.toString() + + ") - cacheObject not found in cache!"); + // remove from the ageList + node.remove(); + } + + // Get the next node. + node = ageList.getLast(); + // If there are no more entries in the age list, return. + if (node == null) { + return; + } + } + } + + /** + * Removes the least recently used elements if the cache size is greater than + * or equal to the maximum allowed size until the cache is at least 10% empty. + */ + protected synchronized void cullCache() { + // Check if a max cache size is defined. + if (maxCacheSize < 0) { + return; + } + + // See if the cache is too big. If so, clean out cache until it's 10% free. + if (map.size() > maxCacheSize) { + // First, delete any old entries to see how much memory that frees. + deleteExpiredEntries(); + // Next, delete the least recently used elements until 10% of the cache + // has been freed. + int desiredSize = (int) (maxCacheSize * .90); + for (int i=map.size(); i>desiredSize; i--) { + // Get the key and invoke the remove method on it. + if (remove(lastAccessedList.getLast().object, true) == null) { + System.err.println("Error attempting to cullCache with remove(" + + lastAccessedList.getLast().object.toString() + ") - " + + "cacheObject not found in cache!"); + lastAccessedList.getLast().remove(); + } + } + } + } + + /** + * Wrapper for all objects put into cache. It's primary purpose is to maintain + * references to the linked lists that maintain the creation time of the object + * and the ordering of the most used objects. + * + * This class is optimized for speed rather than strictly correct encapsulation. + */ + private static class CacheObject { + + /** + * Underlying object wrapped by the CacheObject. + */ + public V object; + + /** + * A reference to the node in the cache order list. We keep the reference + * here to avoid linear scans of the list. Every time the object is + * accessed, the node is removed from its current spot in the list and + * moved to the front. + */ + public LinkedListNode lastAccessedListNode; + + /** + * A reference to the node in the age order list. We keep the reference + * here to avoid linear scans of the list. The reference is used if the + * object has to be deleted from the list. + */ + public LinkedListNode ageListNode; + + /** + * A count of the number of times the object has been read from cache. + */ + public int readCount = 0; + + /** + * Creates a new cache object wrapper. + * + * @param object the underlying Object to wrap. + */ + public CacheObject(V object) { + this.object = object; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof CacheObject)) { + return false; + } + + final CacheObject cacheObject = (CacheObject) o; + + return object.equals(cacheObject.object); + + } + + public int hashCode() { + return object.hashCode(); + } + } + + /** + * Simple LinkedList implementation. The main feature is that list nodes + * are public, which allows very fast delete operations when one has a + * reference to the node that is to be deleted.

      + */ + private static class LinkedList { + + /** + * The root of the list keeps a reference to both the first and last + * elements of the list. + */ + private LinkedListNode head = new LinkedListNode("head", null, null); + + /** + * Creates a new linked list. + */ + public LinkedList() { + head.next = head.previous = head; + } + + /** + * Returns the first linked list node in the list. + * + * @return the first element of the list. + */ + public LinkedListNode getFirst() { + LinkedListNode node = head.next; + if (node == head) { + return null; + } + return node; + } + + /** + * Returns the last linked list node in the list. + * + * @return the last element of the list. + */ + public LinkedListNode getLast() { + LinkedListNode node = head.previous; + if (node == head) { + return null; + } + return node; + } + + /** + * Adds a node to the beginning of the list. + * + * @param node the node to add to the beginning of the list. + * @return the node + */ + public LinkedListNode addFirst(LinkedListNode node) { + node.next = head.next; + node.previous = head; + node.previous.next = node; + node.next.previous = node; + return node; + } + + /** + * Adds an object to the beginning of the list by automatically creating a + * a new node and adding it to the beginning of the list. + * + * @param object the object to add to the beginning of the list. + * @return the node created to wrap the object. + */ + public LinkedListNode addFirst(Object object) { + LinkedListNode node = new LinkedListNode(object, head.next, head); + node.previous.next = node; + node.next.previous = node; + return node; + } + + /** + * Adds an object to the end of the list by automatically creating a + * a new node and adding it to the end of the list. + * + * @param object the object to add to the end of the list. + * @return the node created to wrap the object. + */ + public LinkedListNode addLast(Object object) { + LinkedListNode node = new LinkedListNode(object, head, head.previous); + node.previous.next = node; + node.next.previous = node; + return node; + } + + /** + * Erases all elements in the list and re-initializes it. + */ + public void clear() { + //Remove all references in the list. + LinkedListNode node = getLast(); + while (node != null) { + node.remove(); + node = getLast(); + } + + //Re-initialize. + head.next = head.previous = head; + } + + /** + * Returns a String representation of the linked list with a comma + * delimited list of all the elements in the list. + * + * @return a String representation of the LinkedList. + */ + public String toString() { + LinkedListNode node = head.next; + StringBuilder buf = new StringBuilder(); + while (node != head) { + buf.append(node.toString()).append(", "); + node = node.next; + } + return buf.toString(); + } + } + + /** + * Doubly linked node in a LinkedList. Most LinkedList implementations keep the + * equivalent of this class private. We make it public so that references + * to each node in the list can be maintained externally. + * + * Exposing this class lets us make remove operations very fast. Remove is + * built into this class and only requires two reference reassignments. If + * remove existed in the main LinkedList class, a linear scan would have to + * be performed to find the correct node to delete. + * + * The linked list implementation was specifically written for the Jive + * cache system. While it can be used as a general purpose linked list, for + * most applications, it is more suitable to use the linked list that is part + * of the Java Collections package. + */ + private static class LinkedListNode { + + public LinkedListNode previous; + public LinkedListNode next; + public Object object; + + /** + * This class is further customized for the Jive cache system. It + * maintains a timestamp of when a Cacheable object was first added to + * cache. Timestamps are stored as long values and represent the number + * of milliseconds passed since January 1, 1970 00:00:00.000 GMT.

      + * + * The creation timestamp is used in the case that the cache has a + * maximum lifetime set. In that case, when + * [current time] - [creation time] > [max lifetime], the object will be + * deleted from cache. + */ + public long timestamp; + + /** + * Constructs a new linked list node. + * + * @param object the Object that the node represents. + * @param next a reference to the next LinkedListNode in the list. + * @param previous a reference to the previous LinkedListNode in the list. + */ + public LinkedListNode(Object object, LinkedListNode next, + LinkedListNode previous) + { + this.object = object; + this.next = next; + this.previous = previous; + } + + /** + * Removes this node from the linked list that it is a part of. + */ + public void remove() { + previous.next = next; + next.previous = previous; + } + + /** + * Returns a String representation of the linked list node by calling the + * toString method of the node's object. + * + * @return a String representation of the LinkedListNode. + */ + public String toString() { + return object.toString(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java new file mode 100644 index 0000000..2765a19 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/DNSUtil.java @@ -0,0 +1,234 @@ +/** + * $Revision: 1456 $ + * $Date: 2005-06-01 22:04:54 -0700 (Wed, 01 Jun 2005) $ + * + * Copyright 2003-2005 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +import java.util.Map; + +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; + +/** + * Utilty class to perform DNS lookups for XMPP services. + * + * @author Matt Tucker + */ +public class DNSUtil { + + /** + * Create a cache to hold the 100 most recently accessed DNS lookups for a + * period of 10 minutes. + */ + @SuppressWarnings("unchecked") + private static Map ccache = new Cache(100, 1000 * 60 * 10); + @SuppressWarnings("unchecked") + private static Map scache = new Cache(100, 1000 * 60 * 10); + + private static HostAddress resolveSRV(String domain) { + String bestHost = null; + int bestPort = -1; + int bestPriority = Integer.MAX_VALUE; + int bestWeight = 0; + Lookup lookup; + try { + lookup = new Lookup(domain, Type.SRV); + Record recs[] = lookup.run(); + if (recs == null) { return null; } + for (Record rec : recs) { + SRVRecord record = (SRVRecord) rec; + if (record != null && record.getTarget() != null) { + int weight = (int) (record.getWeight() * record.getWeight() * Math + .random()); + if (record.getPriority() < bestPriority) { + bestPriority = record.getPriority(); + bestWeight = weight; + bestHost = record.getTarget().toString(); + bestPort = record.getPort(); + } else if (record.getPriority() == bestPriority) { + if (weight > bestWeight) { + bestPriority = record.getPriority(); + bestWeight = weight; + bestHost = record.getTarget().toString(); + bestPort = record.getPort(); + } + } + } + } + } catch (TextParseException e) { + } catch (NullPointerException e) { + } + if (bestHost == null) { + return null; + } + // Host entries in DNS should end with a ".". + if (bestHost.endsWith(".")) { + bestHost = bestHost.substring(0, bestHost.length() - 1); + } + return new HostAddress(bestHost, bestPort); + } + + /** + * Returns the host name and port that the specified XMPP server can be + * reached at for client-to-server communication. A DNS lookup for a SRV + * record in the form "_xmpp-client._tcp.example.com" is attempted, + * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in + * the older form of "_jabber._tcp.example.com" is attempted since servers + * that implement an older version of the protocol may be listed using that + * notation. If that lookup fails as well, it's assumed that the XMPP server + * lives at the host resolved by a DNS lookup at the specified domain on the + * default port of 5222. + *

      + * + * As an example, a lookup for "example.com" may return + * "im.example.com:5269". + * + * Note on SRV record selection. We now check priority and weight, but we + * still don't do this correctly. The missing behavior is this: if we fail + * to reach a host based on its SRV record then we need to select another + * host from the other SRV records. In Smack 3.1.1 we're not going to be + * able to do the major system redesign to correct this. + * + * @param domain + * the domain. + * @return a HostAddress, which encompasses the hostname and port that the + * XMPP server can be reached at for the specified domain. + */ + public static HostAddress resolveXMPPDomain(String domain) { + // Return item from cache if it exists. + synchronized (ccache) { + if (ccache.containsKey(domain)) { + HostAddress address = (HostAddress) ccache.get(domain); + if (address != null) { + return address; + } + } + } + HostAddress result = resolveSRV("_xmpp-client._tcp." + domain); + if (result == null) { + result = resolveSRV("_jabber._tcp." + domain); + } + if (result == null) { + result = new HostAddress(domain, 5222); + } + // Add item to cache. + synchronized (ccache) { + ccache.put(domain, result); + } + return result; + } + + /** + * Returns the host name and port that the specified XMPP server can be + * reached at for server-to-server communication. A DNS lookup for a SRV + * record in the form "_xmpp-server._tcp.example.com" is attempted, + * according to section 14.4 of RFC 3920. If that lookup fails, a lookup in + * the older form of "_jabber._tcp.example.com" is attempted since servers + * that implement an older version of the protocol may be listed using that + * notation. If that lookup fails as well, it's assumed that the XMPP server + * lives at the host resolved by a DNS lookup at the specified domain on the + * default port of 5269. + *

      + * + * As an example, a lookup for "example.com" may return + * "im.example.com:5269". + * + * @param domain + * the domain. + * @return a HostAddress, which encompasses the hostname and port that the + * XMPP server can be reached at for the specified domain. + */ + public static HostAddress resolveXMPPServerDomain(String domain) { + // Return item from cache if it exists. + synchronized (scache) { + if (scache.containsKey(domain)) { + HostAddress address = (HostAddress) scache.get(domain); + if (address != null) { + return address; + } + } + } + HostAddress result = resolveSRV("_xmpp-server._tcp." + domain); + if (result == null) { + result = resolveSRV("_jabber._tcp." + domain); + } + if (result == null) { + result = new HostAddress(domain, 5269); + } + // Add item to cache. + synchronized (scache) { + scache.put(domain, result); + } + return result; + } + + /** + * Encapsulates a hostname and port. + */ + public static class HostAddress { + + private String host; + private int port; + + private HostAddress(String host, int port) { + this.host = host; + this.port = port; + } + + /** + * Returns the hostname. + * + * @return the hostname. + */ + public String getHost() { + return host; + } + + /** + * Returns the port. + * + * @return the port. + */ + public int getPort() { + return port; + } + + public String toString() { + return host + ":" + port; + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HostAddress)) { + return false; + } + + final HostAddress address = (HostAddress) o; + + if (!host.equals(address.host)) { + return false; + } + return port == address.port; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java new file mode 100644 index 0000000..8100927 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableReader.java @@ -0,0 +1,118 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +import java.io.*; +import java.util.*; + +/** + * An ObservableReader is a wrapper on a Reader that notifies to its listeners when + * reading character streams. + * + * @author Gaston Dombiak + */ +public class ObservableReader extends Reader { + + Reader wrappedReader = null; + List listeners = new ArrayList(); + + public ObservableReader(Reader wrappedReader) { + this.wrappedReader = wrappedReader; + } + + public int read(char[] cbuf, int off, int len) throws IOException { + int count = wrappedReader.read(cbuf, off, len); + if (count > 0) { + String str = new String(cbuf, off, count); + // Notify that a new string has been read + ReaderListener[] readerListeners = null; + synchronized (listeners) { + readerListeners = new ReaderListener[listeners.size()]; + listeners.toArray(readerListeners); + } + for (int i = 0; i < readerListeners.length; i++) { + readerListeners[i].read(str); + } + } + return count; + } + + public void close() throws IOException { + wrappedReader.close(); + } + + public int read() throws IOException { + return wrappedReader.read(); + } + + public int read(char cbuf[]) throws IOException { + return wrappedReader.read(cbuf); + } + + public long skip(long n) throws IOException { + return wrappedReader.skip(n); + } + + public boolean ready() throws IOException { + return wrappedReader.ready(); + } + + public boolean markSupported() { + return wrappedReader.markSupported(); + } + + public void mark(int readAheadLimit) throws IOException { + wrappedReader.mark(readAheadLimit); + } + + public void reset() throws IOException { + wrappedReader.reset(); + } + + /** + * Adds a reader listener to this reader that will be notified when + * new strings are read. + * + * @param readerListener a reader listener. + */ + public void addReaderListener(ReaderListener readerListener) { + if (readerListener == null) { + return; + } + synchronized (listeners) { + if (!listeners.contains(readerListener)) { + listeners.add(readerListener); + } + } + } + + /** + * Removes a reader listener from this reader. + * + * @param readerListener a reader listener. + */ + public void removeReaderListener(ReaderListener readerListener) { + synchronized (listeners) { + listeners.remove(readerListener); + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java new file mode 100644 index 0000000..5da46aa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/ObservableWriter.java @@ -0,0 +1,120 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +import java.io.*; +import java.util.*; + +/** + * An ObservableWriter is a wrapper on a Writer that notifies to its listeners when + * writing to character streams. + * + * @author Gaston Dombiak + */ +public class ObservableWriter extends Writer { + + Writer wrappedWriter = null; + List listeners = new ArrayList(); + + public ObservableWriter(Writer wrappedWriter) { + this.wrappedWriter = wrappedWriter; + } + + public void write(char cbuf[], int off, int len) throws IOException { + wrappedWriter.write(cbuf, off, len); + String str = new String(cbuf, off, len); + notifyListeners(str); + } + + public void flush() throws IOException { + wrappedWriter.flush(); + } + + public void close() throws IOException { + wrappedWriter.close(); + } + + public void write(int c) throws IOException { + wrappedWriter.write(c); + } + + public void write(char cbuf[]) throws IOException { + wrappedWriter.write(cbuf); + String str = new String(cbuf); + notifyListeners(str); + } + + public void write(String str) throws IOException { + wrappedWriter.write(str); + notifyListeners(str); + } + + public void write(String str, int off, int len) throws IOException { + wrappedWriter.write(str, off, len); + str = str.substring(off, off + len); + notifyListeners(str); + } + + /** + * Notify that a new string has been written. + * + * @param str the written String to notify + */ + private void notifyListeners(String str) { + WriterListener[] writerListeners = null; + synchronized (listeners) { + writerListeners = new WriterListener[listeners.size()]; + listeners.toArray(writerListeners); + } + for (int i = 0; i < writerListeners.length; i++) { + writerListeners[i].write(str); + } + } + + /** + * Adds a writer listener to this writer that will be notified when + * new strings are sent. + * + * @param writerListener a writer listener. + */ + public void addWriterListener(WriterListener writerListener) { + if (writerListener == null) { + return; + } + synchronized (listeners) { + if (!listeners.contains(writerListener)) { + listeners.add(writerListener); + } + } + } + + /** + * Removes a writer listener from this writer. + * + * @param writerListener a writer listener. + */ + public void removeWriterListener(WriterListener writerListener) { + synchronized (listeners) { + listeners.remove(writerListener); + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java new file mode 100644 index 0000000..a2eccb5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/PacketParserUtils.java @@ -0,0 +1,885 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.sasl.SASLMechanism.Failure; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class that helps to parse packets. Any parsing packets method that must be shared + * between many clients must be placed in this utility class. + * + * @author Gaston Dombiak + */ +public class PacketParserUtils { + + /** + * Namespace used to store packet properties. + */ + private static final String PROPERTIES_NAMESPACE = + "http://www.jivesoftware.com/xmlns/xmpp/properties"; + + /** + * Parses a message packet. + * + * @param parser the XML parser, positioned at the start of a message packet. + * @return a Message packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static Packet parseMessage(XmlPullParser parser) throws Exception { + Message message = new Message(); + String id = parser.getAttributeValue("", "id"); + message.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); + message.setTo(parser.getAttributeValue("", "to")); + message.setFrom(parser.getAttributeValue("", "from")); + message.setType(Message.Type.fromString(parser.getAttributeValue("", "type"))); + String language = getLanguageAttribute(parser); + + // determine message's default language + String defaultLanguage = null; + if (language != null && !"".equals(language.trim())) { + message.setLanguage(language); + defaultLanguage = language; + } + else { + defaultLanguage = Packet.getDefaultLanguage(); + } + + // Parse sub-elements. We include extra logic to make sure the values + // are only read once. This is because it's possible for the names to appear + // in arbitrary sub-elements. + boolean done = false; + String thread = null; + Map properties = null; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("subject")) { + String xmlLang = getLanguageAttribute(parser); + if (xmlLang == null) { + xmlLang = defaultLanguage; + } + + String subject = parseContent(parser); + + if (message.getSubject(xmlLang) == null) { + message.addSubject(xmlLang, subject); + } + } + else if (elementName.equals("body")) { + String xmlLang = getLanguageAttribute(parser); + if (xmlLang == null) { + xmlLang = defaultLanguage; + } + + String body = parseContent(parser); + + if (message.getBody(xmlLang) == null) { + message.addBody(xmlLang, body); + } + } + else if (elementName.equals("thread")) { + if (thread == null) { + thread = parser.nextText(); + } + } + else if (elementName.equals("error")) { + message.setError(parseError(parser)); + } + else if (elementName.equals("properties") && + namespace.equals(PROPERTIES_NAMESPACE)) + { + properties = parseProperties(parser); + } + // Otherwise, it must be a packet extension. + else { + message.addExtension( + PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("message")) { + done = true; + } + } + } + + message.setThread(thread); + // Set packet properties. + if (properties != null) { + for (String name : properties.keySet()) { + message.setProperty(name, properties.get(name)); + } + } + return message; + } + + /** + * Returns the content of a tag as string regardless of any tags included. + * + * @param parser the XML pull parser + * @return the content of a tag as string + * @throws XmlPullParserException if parser encounters invalid XML + * @throws IOException if an IO error occurs + */ + private static String parseContent(XmlPullParser parser) + throws XmlPullParserException, IOException { + String content = ""; + int parserDepth = parser.getDepth(); + while (!(parser.next() == XmlPullParser.END_TAG && parser + .getDepth() == parserDepth)) { + content += parser.getText(); + } + return content; + } + + /** + * Parses a presence packet. + * + * @param parser the XML parser, positioned at the start of a presence packet. + * @return a Presence packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static Presence parsePresence(XmlPullParser parser) throws Exception { + Presence.Type type = Presence.Type.available; + String typeString = parser.getAttributeValue("", "type"); + if (typeString != null && !typeString.equals("")) { + try { + type = Presence.Type.valueOf(typeString); + } + catch (IllegalArgumentException iae) { + System.err.println("Found invalid presence type " + typeString); + } + } + Presence presence = new Presence(type); + presence.setTo(parser.getAttributeValue("", "to")); + presence.setFrom(parser.getAttributeValue("", "from")); + String id = parser.getAttributeValue("", "id"); + presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); + + String language = getLanguageAttribute(parser); + if (language != null && !"".equals(language.trim())) { + presence.setLanguage(language); + } + presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); + + // Parse sub-elements + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("status")) { + presence.setStatus(parser.nextText()); + } + else if (elementName.equals("priority")) { + try { + int priority = Integer.parseInt(parser.nextText()); + presence.setPriority(priority); + } + catch (NumberFormatException nfe) { + // Ignore. + } + catch (IllegalArgumentException iae) { + // Presence priority is out of range so assume priority to be zero + presence.setPriority(0); + } + } + else if (elementName.equals("show")) { + String modeText = parser.nextText(); + try { + presence.setMode(Presence.Mode.valueOf(modeText)); + } + catch (IllegalArgumentException iae) { + System.err.println("Found invalid presence mode " + modeText); + } + } + else if (elementName.equals("error")) { + presence.setError(parseError(parser)); + } + else if (elementName.equals("properties") && + namespace.equals(PROPERTIES_NAMESPACE)) + { + Map properties = parseProperties(parser); + // Set packet properties. + for (String name : properties.keySet()) { + presence.setProperty(name, properties.get(name)); + } + } + // Otherwise, it must be a packet extension. + else { + presence.addExtension( + PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("presence")) { + done = true; + } + } + } + return presence; + } + + /** + * Parses an IQ packet. + * + * @param parser the XML parser, positioned at the start of an IQ packet. + * @return an IQ object. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception { + IQ iqPacket = null; + + String id = parser.getAttributeValue("", "id"); + String to = parser.getAttributeValue("", "to"); + String from = parser.getAttributeValue("", "from"); + IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); + XMPPError error = null; + + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("error")) { + error = PacketParserUtils.parseError(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) { + iqPacket = parseAuthentication(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) { + iqPacket = parseRoster(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) { + iqPacket = parseRegistration(parser); + } + else if (elementName.equals("bind") && + namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) { + iqPacket = parseResourceBinding(parser); + } + // Otherwise, see if there is a registered provider for + // this element name and namespace. + else { + Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace); + if (provider != null) { + if (provider instanceof IQProvider) { + iqPacket = ((IQProvider)provider).parseIQ(parser); + } + else if (provider instanceof Class) { + iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName, + (Class)provider, parser); + } + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("iq")) { + done = true; + } + } + } + // Decide what to do when an IQ packet was not understood + if (iqPacket == null) { + if (IQ.Type.GET == type || IQ.Type.SET == type ) { + // If the IQ stanza is of type "get" or "set" containing a child element + // qualified by a namespace it does not understand, then answer an IQ of + // type "error" with code 501 ("feature-not-implemented") + iqPacket = new IQ() { + public String getChildElementXML() { + return null; + } + }; + iqPacket.setPacketID(id); + iqPacket.setTo(from); + iqPacket.setFrom(to); + iqPacket.setType(IQ.Type.ERROR); + iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented)); + connection.sendPacket(iqPacket); + return null; + } + else { + // If an IQ packet wasn't created above, create an empty IQ packet. + iqPacket = new IQ() { + public String getChildElementXML() { + return null; + } + }; + } + } + + // Set basic values on the iq packet. + iqPacket.setPacketID(id); + iqPacket.setTo(to); + iqPacket.setFrom(from); + iqPacket.setType(type); + iqPacket.setError(error); + + return iqPacket; + } + + private static Authentication parseAuthentication(XmlPullParser parser) throws Exception { + Authentication authentication = new Authentication(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("username")) { + authentication.setUsername(parser.nextText()); + } + else if (parser.getName().equals("password")) { + authentication.setPassword(parser.nextText()); + } + else if (parser.getName().equals("digest")) { + authentication.setDigest(parser.nextText()); + } + else if (parser.getName().equals("resource")) { + authentication.setResource(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + return authentication; + } + + private static RosterPacket parseRoster(XmlPullParser parser) throws Exception { + RosterPacket roster = new RosterPacket(); + boolean done = false; + RosterPacket.Item item = null; + while (!done) { + if(parser.getEventType()==XmlPullParser.START_TAG && + parser.getName().equals("query")){ + String version = parser.getAttributeValue(null, "ver"); + roster.setVersion(version); + } + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + String jid = parser.getAttributeValue("", "jid"); + String name = parser.getAttributeValue("", "name"); + // Create packet. + item = new RosterPacket.Item(jid, name); + // Set status. + String ask = parser.getAttributeValue("", "ask"); + RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask); + item.setItemStatus(status); + // Set type. + String subscription = parser.getAttributeValue("", "subscription"); + RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none"); + item.setItemType(type); + } + if (parser.getName().equals("group") && item!= null) { + final String groupName = parser.nextText(); + if (groupName != null && groupName.trim().length() > 0) { + item.addGroupName(groupName); + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + roster.addRosterItem(item); + } + if (parser.getName().equals("query")) { + done = true; + } + } + } + return roster; + } + + private static Registration parseRegistration(XmlPullParser parser) throws Exception { + Registration registration = new Registration(); + Map fields = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + // Any element that's in the jabber:iq:register namespace, + // attempt to parse it if it's in the form value. + if (parser.getNamespace().equals("jabber:iq:register")) { + String name = parser.getName(); + String value = ""; + if (fields == null) { + fields = new HashMap(); + } + + if (parser.next() == XmlPullParser.TEXT) { + value = parser.getText(); + } + // Ignore instructions, but anything else should be added to the map. + if (!name.equals("instructions")) { + fields.put(name, value); + } + else { + registration.setInstructions(value); + } + } + // Otherwise, it must be a packet extension. + else { + registration.addExtension( + PacketParserUtils.parsePacketExtension( + parser.getName(), + parser.getNamespace(), + parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + registration.setAttributes(fields); + return registration; + } + + private static Bind parseResourceBinding(XmlPullParser parser) throws IOException, + XmlPullParserException { + Bind bind = new Bind(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("resource")) { + bind.setResource(parser.nextText()); + } + else if (parser.getName().equals("jid")) { + bind.setJid(parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("bind")) { + done = true; + } + } + } + + return bind; + } + + /** + * Parse the available SASL mechanisms reported from the server. + * + * @param parser the XML parser, positioned at the start of the mechanisms stanza. + * @return a collection of Stings with the mechanisms included in the mechanisms stanza. + * @throws Exception if an exception occurs while parsing the stanza. + */ + public static Collection parseMechanisms(XmlPullParser parser) throws Exception { + List mechanisms = new ArrayList(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("mechanism")) { + mechanisms.add(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("mechanisms")) { + done = true; + } + } + } + return mechanisms; + } + + /** + * Parse the available compression methods reported from the server. + * + * @param parser the XML parser, positioned at the start of the compression stanza. + * @return a collection of Stings with the methods included in the compression stanza. + * @throws Exception if an exception occurs while parsing the stanza. + */ + public static Collection parseCompressionMethods(XmlPullParser parser) + throws IOException, XmlPullParserException { + List methods = new ArrayList(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("method")) { + methods.add(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("compression")) { + done = true; + } + } + } + return methods; + } + + /** + * Parse a properties sub-packet. If any errors occur while de-serializing Java object + * properties, an exception will be printed and not thrown since a thrown + * exception will shut down the entire connection. ClassCastExceptions will occur + * when both the sender and receiver of the packet don't have identical versions + * of the same class. + * + * @param parser the XML parser, positioned at the start of a properties sub-packet. + * @return a map of the properties. + * @throws Exception if an error occurs while parsing the properties. + */ + public static Map parseProperties(XmlPullParser parser) throws Exception { + Map properties = new HashMap(); + while (true) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) { + // Parse a property + boolean done = false; + String name = null; + String type = null; + String valueText = null; + Object value = null; + while (!done) { + eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("name")) { + name = parser.nextText(); + } + else if (elementName.equals("value")) { + type = parser.getAttributeValue("", "type"); + valueText = parser.nextText(); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("property")) { + if ("integer".equals(type)) { + value = Integer.valueOf(valueText); + } + else if ("long".equals(type)) { + value = Long.valueOf(valueText); + } + else if ("float".equals(type)) { + value = Float.valueOf(valueText); + } + else if ("double".equals(type)) { + value = Double.valueOf(valueText); + } + else if ("boolean".equals(type)) { + value = Boolean.valueOf(valueText); + } + else if ("string".equals(type)) { + value = valueText; + } + else if ("java-object".equals(type)) { + try { + byte [] bytes = StringUtils.decodeBase64(valueText); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); + value = in.readObject(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + if (name != null && value != null) { + properties.put(name, value); + } + done = true; + } + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("properties")) { + break; + } + } + } + return properties; + } + + /** + * Parses SASL authentication error packets. + * + * @param parser the XML parser. + * @return a SASL Failure packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static Failure parseSASLFailure(XmlPullParser parser) throws Exception { + String condition = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + if (!parser.getName().equals("failure")) { + condition = parser.getName(); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("failure")) { + done = true; + } + } + } + return new Failure(condition); + } + + /** + * Parses stream error packets. + * + *

      If there are multiple stream errors, ignores "text" ones. + * + * @param parser the XML parser. + * @return a stream error packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static StreamError parseStreamError(XmlPullParser parser) throws IOException, + XmlPullParserException { + StreamError streamError = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG && + (streamError == null || "text".equals(streamError.getCode())) + ) { + streamError = new StreamError(parser.getName()); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("error")) { + done = true; + } + } + } + return streamError; +} + + /** + * Parses error sub-packets. + * + * @param parser the XML parser. + * @return an error sub-packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static XMPPError parseError(XmlPullParser parser) throws Exception { + final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; + String errorCode = "-1"; + String type = null; + String message = null; + String condition = null; + List extensions = new ArrayList(); + + // Parse the error header + for (int i=0; i properties = null; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("subject")) { + String xmlLang = getLanguageAttribute(parser); + if (xmlLang == null) { + xmlLang = defaultLanguage; + } + + String subject = parseContent(parser); + + if (message.getSubject(xmlLang) == null) { + message.addSubject(xmlLang, subject); + } + } + else if (elementName.equals("body")) { + String xmlLang = getLanguageAttribute(parser); + if (xmlLang == null) { + xmlLang = defaultLanguage; + } + + String body = parseContent(parser); + + if (message.getBody(xmlLang) == null) { + message.addBody(xmlLang, body); + } + } + else if (elementName.equals("thread")) { + if (thread == null) { + thread = parser.nextText(); + } + } + else if (elementName.equals("error")) { + message.setError(parseError(parser)); + } + else if (elementName.equals("properties") && + namespace.equals(PROPERTIES_NAMESPACE)) + { + properties = parseProperties(parser); + } + // Otherwise, it must be a packet extension. + else { + message.addExtension( + PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("message")) { + done = true; + } + } + } + + message.setThread(thread); + // Set packet properties. + if (properties != null) { + for (String name : properties.keySet()) { + message.setProperty(name, properties.get(name)); + } + } + return message; + } + + /** + * Returns the content of a tag as string regardless of any tags included. + * + * @param parser the XML pull parser + * @return the content of a tag as string + * @throws XmlPullParserException if parser encounters invalid XML + * @throws IOException if an IO error occurs + */ + private static String parseContent(XmlPullParser parser) + throws XmlPullParserException, IOException { + String content = ""; + int parserDepth = parser.getDepth(); + while (!(parser.next() == XmlPullParser.END_TAG && parser + .getDepth() == parserDepth)) { + content += parser.getText(); + } + return content; + } + + /** + * Parses a presence packet. + * + * @param parser the XML parser, positioned at the start of a presence packet. + * @return a Presence packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static Presence parsePresence(XmlPullParser parser) throws Exception { + Presence.Type type = Presence.Type.available; + String typeString = parser.getAttributeValue("", "type"); + if (typeString != null && !typeString.equals("")) { + try { + type = Presence.Type.valueOf(typeString); + } + catch (IllegalArgumentException iae) { + System.err.println("Found invalid presence type " + typeString); + } + } + Presence presence = new Presence(type); + presence.setTo(parser.getAttributeValue("", "to")); + presence.setFrom(parser.getAttributeValue("", "from")); + String id = parser.getAttributeValue("", "id"); + presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); + + String language = getLanguageAttribute(parser); + if (language != null && !"".equals(language.trim())) { + presence.setLanguage(language); + } + presence.setPacketID(id == null ? Packet.ID_NOT_AVAILABLE : id); + + // Parse sub-elements + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("status")) { + presence.setStatus(parser.nextText()); + } + else if (elementName.equals("priority")) { + try { + int priority = Integer.parseInt(parser.nextText()); + presence.setPriority(priority); + } + catch (NumberFormatException nfe) { + // Ignore. + } + catch (IllegalArgumentException iae) { + // Presence priority is out of range so assume priority to be zero + presence.setPriority(0); + } + } + else if (elementName.equals("show")) { + String modeText = parser.nextText(); + try { + presence.setMode(Presence.Mode.valueOf(modeText)); + } + catch (IllegalArgumentException iae) { + System.err.println("Found invalid presence mode " + modeText); + } + } + else if (elementName.equals("error")) { + presence.setError(parseError(parser)); + } + else if (elementName.equals("properties") && + namespace.equals(PROPERTIES_NAMESPACE)) + { + Map properties = parseProperties(parser); + // Set packet properties. + for (String name : properties.keySet()) { + presence.setProperty(name, properties.get(name)); + } + } + // Otherwise, it must be a packet extension. + else { + presence.addExtension( + PacketParserUtils.parsePacketExtension(elementName, namespace, parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("presence")) { + done = true; + } + } + } + return presence; + } + + /** + * Parses an IQ packet. + * + * @param parser the XML parser, positioned at the start of an IQ packet. + * @return an IQ object. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static IQ parseIQ(XmlPullParser parser, Connection connection) throws Exception { + IQ iqPacket = null; + + String id = parser.getAttributeValue("", "id"); + String to = parser.getAttributeValue("", "to"); + String from = parser.getAttributeValue("", "from"); + IQ.Type type = IQ.Type.fromString(parser.getAttributeValue("", "type")); + XMPPError error = null; + + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + if (elementName.equals("error")) { + error = PacketParserUtils.parseError(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:auth")) { + iqPacket = parseAuthentication(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:roster")) { + iqPacket = parseRoster(parser); + } + else if (elementName.equals("query") && namespace.equals("jabber:iq:register")) { + iqPacket = parseRegistration(parser); + } + else if (elementName.equals("bind") && + namespace.equals("urn:ietf:params:xml:ns:xmpp-bind")) { + iqPacket = parseResourceBinding(parser); + } + // Otherwise, see if there is a registered provider for + // this element name and namespace. + else { + Object provider = ProviderManager.getInstance().getIQProvider(elementName, namespace); + if (provider != null) { + if (provider instanceof IQProvider) { + iqPacket = ((IQProvider)provider).parseIQ(parser); + } + else if (provider instanceof Class) { + iqPacket = (IQ)PacketParserUtils.parseWithIntrospection(elementName, + (Class)provider, parser); + } + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("iq")) { + done = true; + } + } + } + // Decide what to do when an IQ packet was not understood + if (iqPacket == null) { + if (IQ.Type.GET == type || IQ.Type.SET == type ) { + // If the IQ stanza is of type "get" or "set" containing a child element + // qualified by a namespace it does not understand, then answer an IQ of + // type "error" with code 501 ("feature-not-implemented") + iqPacket = new IQ() { + public String getChildElementXML() { + return null; + } + }; + iqPacket.setPacketID(id); + iqPacket.setTo(from); + iqPacket.setFrom(to); + iqPacket.setType(IQ.Type.ERROR); + iqPacket.setError(new XMPPError(XMPPError.Condition.feature_not_implemented)); + connection.sendPacket(iqPacket); + return null; + } + else { + // If an IQ packet wasn't created above, create an empty IQ packet. + iqPacket = new IQ() { + public String getChildElementXML() { + return null; + } + }; + } + } + + // Set basic values on the iq packet. + iqPacket.setPacketID(id); + iqPacket.setTo(to); + iqPacket.setFrom(from); + iqPacket.setType(type); + iqPacket.setError(error); + + return iqPacket; + } + + private static Authentication parseAuthentication(XmlPullParser parser) throws Exception { + Authentication authentication = new Authentication(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("username")) { + authentication.setUsername(parser.nextText()); + } + else if (parser.getName().equals("password")) { + authentication.setPassword(parser.nextText()); + } + else if (parser.getName().equals("digest")) { + authentication.setDigest(parser.nextText()); + } + else if (parser.getName().equals("resource")) { + authentication.setResource(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + return authentication; + } + + private static RosterPacket parseRoster(XmlPullParser parser) throws Exception { + RosterPacket roster = new RosterPacket(); + boolean done = false; + RosterPacket.Item item = null; + while (!done) { + if(parser.getEventType()==XmlPullParser.START_TAG && + parser.getName().equals("query")){ + String version = parser.getAttributeValue(null, "ver"); + roster.setVersion(version); + } + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + String jid = parser.getAttributeValue("", "jid"); + String name = parser.getAttributeValue("", "name"); + // Create packet. + item = new RosterPacket.Item(jid, name); + // Set status. + String ask = parser.getAttributeValue("", "ask"); + RosterPacket.ItemStatus status = RosterPacket.ItemStatus.fromString(ask); + item.setItemStatus(status); + // Set type. + String subscription = parser.getAttributeValue("", "subscription"); + RosterPacket.ItemType type = RosterPacket.ItemType.valueOf(subscription != null ? subscription : "none"); + item.setItemType(type); + } + if (parser.getName().equals("group") && item!= null) { + final String groupName = parser.nextText(); + if (groupName != null && groupName.trim().length() > 0) { + item.addGroupName(groupName); + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + roster.addRosterItem(item); + } + if (parser.getName().equals("query")) { + done = true; + } + } + } + return roster; + } + + private static Registration parseRegistration(XmlPullParser parser) throws Exception { + Registration registration = new Registration(); + Map fields = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + // Any element that's in the jabber:iq:register namespace, + // attempt to parse it if it's in the form value. + if (parser.getNamespace().equals("jabber:iq:register")) { + String name = parser.getName(); + String value = ""; + if (fields == null) { + fields = new HashMap(); + } + + if (parser.next() == XmlPullParser.TEXT) { + value = parser.getText(); + } + // Ignore instructions, but anything else should be added to the map. + if (!name.equals("instructions")) { + fields.put(name, value); + } + else { + registration.setInstructions(value); + } + } + // Otherwise, it must be a packet extension. + else { + registration.addExtension( + PacketParserUtils.parsePacketExtension( + parser.getName(), + parser.getNamespace(), + parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + registration.setAttributes(fields); + return registration; + } + + private static Bind parseResourceBinding(XmlPullParser parser) throws IOException, + XmlPullParserException { + Bind bind = new Bind(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("resource")) { + bind.setResource(parser.nextText()); + } + else if (parser.getName().equals("jid")) { + bind.setJid(parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("bind")) { + done = true; + } + } + } + + return bind; + } + + /** + * Parse the available SASL mechanisms reported from the server. + * + * @param parser the XML parser, positioned at the start of the mechanisms stanza. + * @return a collection of Stings with the mechanisms included in the mechanisms stanza. + * @throws Exception if an exception occurs while parsing the stanza. + */ + public static Collection parseMechanisms(XmlPullParser parser) throws Exception { + List mechanisms = new ArrayList(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("mechanism")) { + mechanisms.add(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("mechanisms")) { + done = true; + } + } + } + return mechanisms; + } + + /** + * Parse the available compression methods reported from the server. + * + * @param parser the XML parser, positioned at the start of the compression stanza. + * @return a collection of Stings with the methods included in the compression stanza. + * @throws Exception if an exception occurs while parsing the stanza. + */ + public static Collection parseCompressionMethods(XmlPullParser parser) + throws IOException, XmlPullParserException { + List methods = new ArrayList(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("method")) { + methods.add(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("compression")) { + done = true; + } + } + } + return methods; + } + + /** + * Parse a properties sub-packet. If any errors occur while de-serializing Java object + * properties, an exception will be printed and not thrown since a thrown + * exception will shut down the entire connection. ClassCastExceptions will occur + * when both the sender and receiver of the packet don't have identical versions + * of the same class. + * + * @param parser the XML parser, positioned at the start of a properties sub-packet. + * @return a map of the properties. + * @throws Exception if an error occurs while parsing the properties. + */ + public static Map parseProperties(XmlPullParser parser) throws Exception { + Map properties = new HashMap(); + while (true) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("property")) { + // Parse a property + boolean done = false; + String name = null; + String type = null; + String valueText = null; + Object value = null; + while (!done) { + eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + if (elementName.equals("name")) { + name = parser.nextText(); + } + else if (elementName.equals("value")) { + type = parser.getAttributeValue("", "type"); + valueText = parser.nextText(); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("property")) { + if ("integer".equals(type)) { + value = Integer.valueOf(valueText); + } + else if ("long".equals(type)) { + value = Long.valueOf(valueText); + } + else if ("float".equals(type)) { + value = Float.valueOf(valueText); + } + else if ("double".equals(type)) { + value = Double.valueOf(valueText); + } + else if ("boolean".equals(type)) { + value = Boolean.valueOf(valueText); + } + else if ("string".equals(type)) { + value = valueText; + } + else if ("java-object".equals(type)) { + try { + byte [] bytes = StringUtils.decodeBase64(valueText); + ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes)); + value = in.readObject(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + if (name != null && value != null) { + properties.put(name, value); + } + done = true; + } + } + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("properties")) { + break; + } + } + } + return properties; + } + + /** + * Parses SASL authentication error packets. + * + * @param parser the XML parser. + * @return a SASL Failure packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static Failure parseSASLFailure(XmlPullParser parser) throws Exception { + String condition = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + if (!parser.getName().equals("failure")) { + condition = parser.getName(); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("failure")) { + done = true; + } + } + } + return new Failure(condition); + } + + /** + * Parses stream error packets. + * + *

      If there are multiple stream errors, ignores "text" ones. + * + * @param parser the XML parser. + * @return a stream error packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static StreamError parseStreamError(XmlPullParser parser) throws IOException, + XmlPullParserException { + StreamError streamError = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG && + (streamError == null || "text".equals(streamError.getCode())) + ) { + streamError = new StreamError(parser.getName()); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("error")) { + done = true; + } + } + } + return streamError; +} + + /** + * Parses error sub-packets. + * + * @param parser the XML parser. + * @return an error sub-packet. + * @throws Exception if an exception occurs while parsing the packet. + */ + public static XMPPError parseError(XmlPullParser parser) throws Exception { + final String errorNamespace = "urn:ietf:params:xml:ns:xmpp-stanzas"; + String errorCode = "-1"; + String type = null; + String message = null; + String condition = null; + List extensions = new ArrayList(); + + // Parse the error header + for (int i=0; i + * Date formats are not synchronized. Since multiple threads access the format concurrently, it + * must be synchronized externally or you can use the convenience methods + * {@link #parseXEP0082Date(String)} and {@link #formatXEP0082Date(Date)}. + */ + public static final DateFormat XEP_0082_UTC_FORMAT = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + static { + XEP_0082_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private static final char[] QUOTE_ENCODE = """.toCharArray(); + private static final char[] APOS_ENCODE = "'".toCharArray(); + private static final char[] AMP_ENCODE = "&".toCharArray(); + private static final char[] LT_ENCODE = "<".toCharArray(); + private static final char[] GT_ENCODE = ">".toCharArray(); + + /** + * Parses the given date string in the XEP-0082 - XMPP Date and Time Profiles format. + * + * @param dateString the date string to parse + * @return the parsed Date + * @throws ParseException if the specified string cannot be parsed + */ + public static Date parseXEP0082Date(String dateString) throws ParseException { + synchronized (XEP_0082_UTC_FORMAT) { + return XEP_0082_UTC_FORMAT.parse(dateString); + } + } + + /** + * Formats a Date into a XEP-0082 - XMPP Date and Time Profiles string. + * + * @param date the time value to be formatted into a time string + * @return the formatted time string in XEP-0082 format + */ + public static String formatXEP0082Date(Date date) { + synchronized (XEP_0082_UTC_FORMAT) { + return XEP_0082_UTC_FORMAT.format(date); + } + } + + /** + * Returns the name portion of a XMPP address. For example, for the + * address "matt@jivesoftware.com/Smack", "matt" would be returned. If no + * username is present in the address, the empty string will be returned. + * + * @param XMPPAddress the XMPP address. + * @return the name portion of the XMPP address. + */ + public static String parseName(String XMPPAddress) { + if (XMPPAddress == null) { + return null; + } + int atIndex = XMPPAddress.lastIndexOf("@"); + if (atIndex <= 0) { + return ""; + } + else { + return XMPPAddress.substring(0, atIndex); + } + } + + /** + * Returns the server portion of a XMPP address. For example, for the + * address "matt@jivesoftware.com/Smack", "jivesoftware.com" would be returned. + * If no server is present in the address, the empty string will be returned. + * + * @param XMPPAddress the XMPP address. + * @return the server portion of the XMPP address. + */ + public static String parseServer(String XMPPAddress) { + if (XMPPAddress == null) { + return null; + } + int atIndex = XMPPAddress.lastIndexOf("@"); + // If the String ends with '@', return the empty string. + if (atIndex + 1 > XMPPAddress.length()) { + return ""; + } + int slashIndex = XMPPAddress.indexOf("/"); + if (slashIndex > 0 && slashIndex > atIndex) { + return XMPPAddress.substring(atIndex + 1, slashIndex); + } + else { + return XMPPAddress.substring(atIndex + 1); + } + } + + /** + * Returns the resource portion of a XMPP address. For example, for the + * address "matt@jivesoftware.com/Smack", "Smack" would be returned. If no + * resource is present in the address, the empty string will be returned. + * + * @param XMPPAddress the XMPP address. + * @return the resource portion of the XMPP address. + */ + public static String parseResource(String XMPPAddress) { + if (XMPPAddress == null) { + return null; + } + int slashIndex = XMPPAddress.indexOf("/"); + if (slashIndex + 1 > XMPPAddress.length() || slashIndex < 0) { + return ""; + } + else { + return XMPPAddress.substring(slashIndex + 1); + } + } + + /** + * Returns the XMPP address with any resource information removed. For example, + * for the address "matt@jivesoftware.com/Smack", "matt@jivesoftware.com" would + * be returned. + * + * @param XMPPAddress the XMPP address. + * @return the bare XMPP address without resource information. + */ + public static String parseBareAddress(String XMPPAddress) { + if (XMPPAddress == null) { + return null; + } + int slashIndex = XMPPAddress.indexOf("/"); + if (slashIndex < 0) { + return XMPPAddress; + } + else if (slashIndex == 0) { + return ""; + } + else { + return XMPPAddress.substring(0, slashIndex); + } + } + + /** + * Escapes the node portion of a JID according to "JID Escaping" (JEP-0106). + * Escaping replaces characters prohibited by node-prep with escape sequences, + * as follows:

      + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      Unescaped CharacterEncoded Sequence
      <space>\20
      "\22
      &\26
      '\27
      /\2f
      :\3a
      <\3c
      >\3e
      @\40
      \\5c

      + * + * This process is useful when the node comes from an external source that doesn't + * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because + * the <space> character isn't a valid part of a node, the username should + * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" + * after case-folding, etc. has been applied).

      + * + * All node escaping and un-escaping must be performed manually at the appropriate + * time; the JID class will not escape or un-escape automatically. + * + * @param node the node. + * @return the escaped version of the node. + */ + public static String escapeNode(String node) { + if (node == null) { + return null; + } + StringBuilder buf = new StringBuilder(node.length() + 8); + for (int i=0, n=node.length(); i': buf.append("\\3e"); break; + case '@': buf.append("\\40"); break; + case '\\': buf.append("\\5c"); break; + default: { + if (Character.isWhitespace(c)) { + buf.append("\\20"); + } + else { + buf.append(c); + } + } + } + } + return buf.toString(); + } + + /** + * Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).

      + * Escaping replaces characters prohibited by node-prep with escape sequences, + * as follows:

      + * + * + * + * + * + * + * + * + * + * + * + * + * + *
      Unescaped CharacterEncoded Sequence
      <space>\20
      "\22
      &\26
      '\27
      /\2f
      :\3a
      <\3c
      >\3e
      @\40
      \\5c

      + * + * This process is useful when the node comes from an external source that doesn't + * conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because + * the <space> character isn't a valid part of a node, the username should + * be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com" + * after case-folding, etc. has been applied).

      + * + * All node escaping and un-escaping must be performed manually at the appropriate + * time; the JID class will not escape or un-escape automatically. + * + * @param node the escaped version of the node. + * @return the un-escaped version of the node. + */ + public static String unescapeNode(String node) { + if (node == null) { + return null; + } + char [] nodeChars = node.toCharArray(); + StringBuilder buf = new StringBuilder(nodeChars.length); + for (int i=0, n=nodeChars.length; i'); i+=2; break compare; + } + } + else if (c2 == '4') { + if (c3 == '0') { + buf.append("@"); + i+=2; + break compare; + } + } + else if (c2 == '5') { + if (c3 == 'c') { + buf.append("\\"); + i+=2; + break compare; + } + } + } + buf.append(c); + } + } + return buf.toString(); + } + + /** + * Escapes all necessary characters in the String so that it can be used + * in an XML doc. + * + * @param string the string to escape. + * @return the string with appropriate characters escaped. + */ + public static String escapeForXML(String string) { + if (string == null) { + return null; + } + char ch; + int i=0; + int last=0; + char[] input = string.toCharArray(); + int len = input.length; + StringBuilder out = new StringBuilder((int)(len*1.3)); + for (; i < len; i++) { + ch = input[i]; + if (ch > '>') { + } + else if (ch == '<') { + if (i > last) { + out.append(input, last, i - last); + } + last = i + 1; + out.append(LT_ENCODE); + } + else if (ch == '>') { + if (i > last) { + out.append(input, last, i - last); + } + last = i + 1; + out.append(GT_ENCODE); + } + + else if (ch == '&') { + if (i > last) { + out.append(input, last, i - last); + } + // Do nothing if the string is of the form ë (unicode value) + if (!(len > i + 5 + && input[i + 1] == '#' + && Character.isDigit(input[i + 2]) + && Character.isDigit(input[i + 3]) + && Character.isDigit(input[i + 4]) + && input[i + 5] == ';')) { + last = i + 1; + out.append(AMP_ENCODE); + } + } + else if (ch == '"') { + if (i > last) { + out.append(input, last, i - last); + } + last = i + 1; + out.append(QUOTE_ENCODE); + } + else if (ch == '\'') { + if (i > last) { + out.append(input, last, i - last); + } + last = i + 1; + out.append(APOS_ENCODE); + } + } + if (last == 0) { + return string; + } + if (i > last) { + out.append(input, last, i - last); + } + return out.toString(); + } + + /** + * Used by the hash method. + */ + private static MessageDigest digest = null; + + /** + * Hashes a String using the SHA-1 algorithm and returns the result as a + * String of hexadecimal numbers. This method is synchronized to avoid + * excessive MessageDigest object creation. If calling this method becomes + * a bottleneck in your code, you may wish to maintain a pool of + * MessageDigest objects instead of using this method. + *

      + * A hash is a one-way function -- that is, given an + * input, an output is easily computed. However, given the output, the + * input is almost impossible to compute. This is useful for passwords + * since we can store the hash and a hacker will then have a very hard time + * determining the original password. + * + * @param data the String to compute the hash of. + * @return a hashed version of the passed-in String + */ + public synchronized static String hash(String data) { + if (digest == null) { + try { + digest = MessageDigest.getInstance("SHA-1"); + } + catch (NoSuchAlgorithmException nsae) { + System.err.println("Failed to load the SHA-1 MessageDigest. " + + "Jive will be unable to function normally."); + } + } + // Now, compute hash. + try { + digest.update(data.getBytes("UTF-8")); + } + catch (UnsupportedEncodingException e) { + System.err.println(e); + } + return encodeHex(digest.digest()); + } + + /** + * Encodes an array of bytes as String representation of hexadecimal. + * + * @param bytes an array of bytes to convert to a hex string. + * @return generated hex string. + */ + public static String encodeHex(byte[] bytes) { + StringBuilder hex = new StringBuilder(bytes.length * 2); + + for (byte aByte : bytes) { + if (((int) aByte & 0xff) < 0x10) { + hex.append("0"); + } + hex.append(Integer.toString((int) aByte & 0xff, 16)); + } + + return hex.toString(); + } + + /** + * Encodes a String as a base64 String. + * + * @param data a String to encode. + * @return a base64 encoded String. + */ + public static String encodeBase64(String data) { + byte [] bytes = null; + try { + bytes = data.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException uee) { + uee.printStackTrace(); + } + return encodeBase64(bytes); + } + + /** + * Encodes a byte array into a base64 String. + * + * @param data a byte array to encode. + * @return a base64 encode String. + */ + public static String encodeBase64(byte[] data) { + return encodeBase64(data, false); + } + + /** + * Encodes a byte array into a bse64 String. + * + * @param data The byte arry to encode. + * @param lineBreaks True if the encoding should contain line breaks and false if it should not. + * @return A base64 encoded String. + */ + public static String encodeBase64(byte[] data, boolean lineBreaks) { + return encodeBase64(data, 0, data.length, lineBreaks); + } + + /** + * Encodes a byte array into a bse64 String. + * + * @param data The byte arry to encode. + * @param offset the offset of the bytearray to begin encoding at. + * @param len the length of bytes to encode. + * @param lineBreaks True if the encoding should contain line breaks and false if it should not. + * @return A base64 encoded String. + */ + public static String encodeBase64(byte[] data, int offset, int len, boolean lineBreaks) { + return Base64.encodeBytes(data, offset, len, (lineBreaks ? Base64.NO_OPTIONS : Base64.DONT_BREAK_LINES)); + } + + /** + * Decodes a base64 String. + * + * @param data a base64 encoded String to decode. + * @return the decoded String. + */ + public static byte[] decodeBase64(String data) { + return Base64.decode(data); + } + + /** + * Pseudo-random number generator object for use with randomString(). + * The Random class is not considered to be cryptographically secure, so + * only use these random Strings for low to medium security applications. + */ + private static Random randGen = new Random(); + + /** + * Array of numbers and letters of mixed case. Numbers appear in the list + * twice so that there is a more equal chance that a number will be picked. + * We can use the array to get a random number or letter by picking a random + * array index. + */ + private static char[] numbersAndLetters = ("0123456789abcdefghijklmnopqrstuvwxyz" + + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); + + /** + * Returns a random String of numbers and letters (lower and upper case) + * of the specified length. The method uses the Random class that is + * built-in to Java which is suitable for low to medium grade security uses. + * This means that the output is only pseudo random, i.e., each number is + * mathematically generated so is not truly random.

      + * + * The specified length must be at least one. If not, the method will return + * null. + * + * @param length the desired length of the random String to return. + * @return a random String of numbers and letters of the specified length. + */ + public static String randomString(int length) { + if (length < 1) { + return null; + } + // Create a char buffer to put random letters and numbers in. + char [] randBuffer = new char[length]; + for (int i=0; i { + public A a; + public B b; + + public Tuple(A a, B b) { + this.a = a; + this.b = b; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java new file mode 100644 index 0000000..dcf56d9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/WriterListener.java @@ -0,0 +1,41 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smack.util; + +/** + * Interface that allows for implementing classes to listen for string writing + * events. Listeners are registered with ObservableWriter objects. + * + * @see ObservableWriter#addWriterListener + * @see ObservableWriter#removeWriterListener + * + * @author Gaston Dombiak + */ +public interface WriterListener { + + /** + * Notification that the Writer has written a new string. + * + * @param str the written string + */ + public abstract void write(String str); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java new file mode 100644 index 0000000..c2ec156 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractEmptyIterator.java @@ -0,0 +1,89 @@ +// GenericsNote: Converted. +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.NoSuchElementException; + +/** + * Provides an implementation of an empty iterator. + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ + * @since Commons Collections 3.1 + */ +abstract class AbstractEmptyIterator { + + /** + * Constructor. + */ + protected AbstractEmptyIterator() { + super(); + } + + public boolean hasNext() { + return false; + } + + public E next() { + throw new NoSuchElementException("Iterator contains no elements"); + } + + public boolean hasPrevious() { + return false; + } + + public E previous() { + throw new NoSuchElementException("Iterator contains no elements"); + } + + public int nextIndex() { + return 0; + } + + public int previousIndex() { + return -1; + } + + public void add(E obj) { + throw new UnsupportedOperationException("add() not supported for empty Iterator"); + } + + public void set(E obj) { + throw new IllegalStateException("Iterator contains no elements"); + } + + public void remove() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public E getKey() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public E getValue() { + throw new IllegalStateException("Iterator contains no elements"); + } + + public E setValue(E value) { + throw new IllegalStateException("Iterator contains no elements"); + } + + public void reset() { + // do nothing + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java new file mode 100644 index 0000000..f6fb34a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractHashedMap.java @@ -0,0 +1,1338 @@ +// GenericsNote: Converted -- However, null keys will now be represented in the internal structures, a big change. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.*; + +/** + * An abstract implementation of a hash-based map which provides numerous points for + * subclasses to override. + *

      + * This class implements all the features necessary for a subclass hash-based map. + * Key-value entries are stored in instances of the HashEntry class, + * which can be overridden and replaced. The iterators can similarly be replaced, + * without the need to replace the KeySet, EntrySet and Values view classes. + *

      + * Overridable methods are provided to change the default hashing behaviour, and + * to change how entries are added to and removed from the map. Hopefully, all you + * need for unusual subclasses is here. + *

      + * NOTE: From Commons Collections 3.1 this class extends AbstractMap. + * This is to provide backwards compatibility for ReferenceMap between v3.0 and v3.1. + * This extends clause will be removed in v4.0. + * + * @author java util HashMap + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @since Commons Collections 3.0 + */ +public class AbstractHashedMap extends AbstractMap implements IterableMap { + + protected static final String NO_NEXT_ENTRY = "No next() entry in the iteration"; + protected static final String NO_PREVIOUS_ENTRY = "No previous() entry in the iteration"; + protected static final String REMOVE_INVALID = "remove() can only be called once after next()"; + protected static final String GETKEY_INVALID = "getKey() can only be called after next() and before remove()"; + protected static final String GETVALUE_INVALID = "getValue() can only be called after next() and before remove()"; + protected static final String SETVALUE_INVALID = "setValue() can only be called after next() and before remove()"; + + /** + * The default capacity to use + */ + protected static final int DEFAULT_CAPACITY = 16; + /** + * The default threshold to use + */ + protected static final int DEFAULT_THRESHOLD = 12; + /** + * The default load factor to use + */ + protected static final float DEFAULT_LOAD_FACTOR = 0.75f; + /** + * The maximum capacity allowed + */ + protected static final int MAXIMUM_CAPACITY = 1 << 30; + /** + * An object for masking null + */ + protected static final Object NULL = new Object(); + + /** + * Load factor, normally 0.75 + */ + protected transient float loadFactor; + /** + * The size of the map + */ + protected transient int size; + /** + * Map entries + */ + protected transient HashEntry[] data; + /** + * Size at which to rehash + */ + protected transient int threshold; + /** + * Modification count for iterators + */ + protected transient int modCount; + /** + * Entry set + */ + protected transient EntrySet entrySet; + /** + * Key set + */ + protected transient KeySet keySet; + /** + * Values + */ + protected transient Values values; + + /** + * Constructor only used in deserialization, do not use otherwise. + */ + protected AbstractHashedMap() { + super(); + } + + /** + * Constructor which performs no validation on the passed in parameters. + * + * @param initialCapacity the initial capacity, must be a power of two + * @param loadFactor the load factor, must be > 0.0f and generally < 1.0f + * @param threshold the threshold, must be sensible + */ + protected AbstractHashedMap(int initialCapacity, float loadFactor, int threshold) { + super(); + this.loadFactor = loadFactor; + this.data = new HashEntry[initialCapacity]; + this.threshold = threshold; + init(); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * default load factor. + * + * @param initialCapacity the initial capacity + * @throws IllegalArgumentException if the initial capacity is less than one + */ + protected AbstractHashedMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * Constructs a new, empty map with the specified initial capacity and + * load factor. + * + * @param initialCapacity the initial capacity + * @param loadFactor the load factor + * @throws IllegalArgumentException if the initial capacity is less than one + * @throws IllegalArgumentException if the load factor is less than or equal to zero + */ + protected AbstractHashedMap(int initialCapacity, float loadFactor) { + super(); + if (initialCapacity < 1) { + throw new IllegalArgumentException("Initial capacity must be greater than 0"); + } + if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) { + throw new IllegalArgumentException("Load factor must be greater than 0"); + } + this.loadFactor = loadFactor; + this.threshold = calculateThreshold(initialCapacity, loadFactor); + initialCapacity = calculateNewCapacity(initialCapacity); + this.data = new HashEntry[initialCapacity]; + init(); + } + + /** + * Constructor copying elements from another map. + * + * @param map the map to copy + * @throws NullPointerException if the map is null + */ + protected AbstractHashedMap(Map map) { + this(Math.max(2 * map.size(), DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR); + putAll(map); + } + + /** + * Initialise subclasses during construction, cloning or deserialization. + */ + protected void init() { + } + + //----------------------------------------------------------------------- + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + public V get(Object key) { + int hashCode = hash((key == null) ? NULL : key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) { + return entry.getValue(); + } + entry = entry.next; + } + return null; + } + + /** + * Gets the size of the map. + * + * @return the size + */ + public int size() { + return size; + } + + /** + * Checks whether the map is currently empty. + * + * @return true if the map is currently size zero + */ + public boolean isEmpty() { + return (size == 0); + } + + //----------------------------------------------------------------------- + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + public boolean containsKey(Object key) { + int hashCode = hash((key == null) ? NULL : key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { + return true; + } + entry = entry.next; + } + return false; + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + */ + public boolean containsValue(Object value) { + if (value == null) { + for (int i = 0, isize = data.length; i < isize; i++) { + HashEntry entry = data[i]; + while (entry != null) { + if (entry.getValue() == null) { + return true; + } + entry = entry.next; + } + } + } else { + for (int i = 0, isize = data.length; i < isize; i++) { + HashEntry entry = data[i]; + while (entry != null) { + if (isEqualValue(value, entry.getValue())) { + return true; + } + entry = entry.next; + } + } + } + return false; + } + + //----------------------------------------------------------------------- + /** + * Puts a key-value mapping into this map. + * + * @param key the key to add + * @param value the value to add + * @return the value previously mapped to this key, null if none + */ + public V put(K key, V value) { + int hashCode = hash((key == null) ? NULL : key); + int index = hashIndex(hashCode, data.length); + HashEntry entry = data[index]; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { + V oldValue = entry.getValue(); + updateEntry(entry, value); + return oldValue; + } + entry = entry.next; + } + addMapping(index, hashCode, key, value); + return null; + } + + /** + * Puts all the values from the specified map into this map. + *

      + * This implementation iterates around the specified map and + * uses {@link #put(Object, Object)}. + * + * @param map the map to add + * @throws NullPointerException if the map is null + */ + public void putAll(Map map) { + int mapSize = map.size(); + if (mapSize == 0) { + return; + } + int newSize = (int) ((size + mapSize) / loadFactor + 1); + ensureCapacity(calculateNewCapacity(newSize)); + // Have to cast here because of compiler inference problems. + for (Iterator it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + put(entry.getKey(), entry.getValue()); + } + } + + /** + * Removes the specified mapping from this map. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + */ + public V remove(Object key) { + int hashCode = hash((key == null) ? NULL : key); + int index = hashIndex(hashCode, data.length); + HashEntry entry = data[index]; + HashEntry previous = null; + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { + V oldValue = entry.getValue(); + removeMapping(entry, index, previous); + return oldValue; + } + previous = entry; + entry = entry.next; + } + return null; + } + + /** + * Clears the map, resetting the size to zero and nullifying references + * to avoid garbage collection issues. + */ + public void clear() { + modCount++; + HashEntry[] data = this.data; + for (int i = data.length - 1; i >= 0; i--) { + data[i] = null; + } + size = 0; + } + + /** + * Gets the hash code for the key specified. + * This implementation uses the additional hashing routine from JDK1.4. + * Subclasses can override this to return alternate hash codes. + * + * @param key the key to get a hash code for + * @return the hash code + */ + protected int hash(Object key) { + // same as JDK 1.4 + int h = key.hashCode(); + h += ~(h << 9); + h ^= (h >>> 14); + h += (h << 4); + h ^= (h >>> 10); + return h; + } + + /** + * Compares two keys, in internal converted form, to see if they are equal. + * This implementation uses the equals method. + * Subclasses can override this to match differently. + * + * @param key1 the first key to compare passed in from outside + * @param key2 the second key extracted from the entry via entry.key + * @return true if equal + */ + protected boolean isEqualKey(Object key1, Object key2) { + return (key1 == key2 || ((key1 != null) && key1.equals(key2))); + } + + /** + * Compares two values, in external form, to see if they are equal. + * This implementation uses the equals method and assumes neither value is null. + * Subclasses can override this to match differently. + * + * @param value1 the first value to compare passed in from outside + * @param value2 the second value extracted from the entry via getValue() + * @return true if equal + */ + protected boolean isEqualValue(Object value1, Object value2) { + return (value1 == value2 || value1.equals(value2)); + } + + /** + * Gets the index into the data storage for the hashCode specified. + * This implementation uses the least significant bits of the hashCode. + * Subclasses can override this to return alternate bucketing. + * + * @param hashCode the hash code to use + * @param dataSize the size of the data to pick a bucket from + * @return the bucket index + */ + protected int hashIndex(int hashCode, int dataSize) { + return hashCode & (dataSize - 1); + } + + //----------------------------------------------------------------------- + /** + * Gets the entry mapped to the key specified. + *

      + * This method exists for subclasses that may need to perform a multi-step + * process accessing the entry. The public methods in this class don't use this + * method to gain a small performance boost. + * + * @param key the key + * @return the entry, null if no match + */ + protected HashEntry getEntry(Object key) { + int hashCode = hash((key == null) ? NULL : key); + HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index + while (entry != null) { + if (entry.hashCode == hashCode && isEqualKey(key, entry.getKey())) { + return entry; + } + entry = entry.next; + } + return null; + } + + //----------------------------------------------------------------------- + /** + * Updates an existing key-value mapping to change the value. + *

      + * This implementation calls setValue() on the entry. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to update + * @param newValue the new value to store + */ + protected void updateEntry(HashEntry entry, V newValue) { + entry.setValue(newValue); + } + + /** + * Reuses an existing key-value mapping, storing completely new data. + *

      + * This implementation sets all the data fields on the entry. + * Subclasses could populate additional entry fields. + * + * @param entry the entry to update, not null + * @param hashIndex the index in the data array + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void reuseEntry(HashEntry entry, int hashIndex, int hashCode, K key, V value) { + entry.next = data[hashIndex]; + entry.hashCode = hashCode; + entry.key = key; + entry.value = value; + } + + //----------------------------------------------------------------------- + /** + * Adds a new key-value mapping into this map. + *

      + * This implementation calls createEntry(), addEntry() + * and checkCapacity(). + * It also handles changes to modCount and size. + * Subclasses could override to fully control adds to the map. + * + * @param hashIndex the index into the data array to store at + * @param hashCode the hash code of the key to add + * @param key the key to add + * @param value the value to add + */ + protected void addMapping(int hashIndex, int hashCode, K key, V value) { + modCount++; + HashEntry entry = createEntry(data[hashIndex], hashCode, key, value); + addEntry(entry, hashIndex); + size++; + checkCapacity(); + } + + /** + * Creates an entry to store the key-value data. + *

      + * This implementation creates a new HashEntry instance. + * Subclasses can override this to return a different storage class, + * or implement caching. + * + * @param next the next entry in sequence + * @param hashCode the hash code to use + * @param key the key to store + * @param value the value to store + * @return the newly created entry + */ + protected HashEntry createEntry(HashEntry next, int hashCode, K key, V value) { + return new HashEntry(next, hashCode, key, value); + } + + /** + * Adds an entry into this map. + *

      + * This implementation adds the entry to the data storage table. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to add + * @param hashIndex the index into the data array to store at + */ + protected void addEntry(HashEntry entry, int hashIndex) { + data[hashIndex] = entry; + } + + //----------------------------------------------------------------------- + /** + * Removes a mapping from the map. + *

      + * This implementation calls removeEntry() and destroyEntry(). + * It also handles changes to modCount and size. + * Subclasses could override to fully control removals from the map. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + protected void removeMapping(HashEntry entry, int hashIndex, HashEntry previous) { + modCount++; + removeEntry(entry, hashIndex, previous); + size--; + destroyEntry(entry); + } + + /** + * Removes an entry from the chain stored in a particular index. + *

      + * This implementation removes the entry from the data storage table. + * The size is not updated. + * Subclasses could override to handle changes to the map. + * + * @param entry the entry to remove + * @param hashIndex the index into the data structure + * @param previous the previous entry in the chain + */ + protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) { + if (previous == null) { + data[hashIndex] = entry.next; + } else { + previous.next = entry.next; + } + } + + /** + * Kills an entry ready for the garbage collector. + *

      + * This implementation prepares the HashEntry for garbage collection. + * Subclasses can override this to implement caching (override clear as well). + * + * @param entry the entry to destroy + */ + protected void destroyEntry(HashEntry entry) { + entry.next = null; + entry.key = null; + entry.value = null; + } + + //----------------------------------------------------------------------- + /** + * Checks the capacity of the map and enlarges it if necessary. + *

      + * This implementation uses the threshold to check if the map needs enlarging + */ + protected void checkCapacity() { + if (size >= threshold) { + int newCapacity = data.length * 2; + if (newCapacity <= MAXIMUM_CAPACITY) { + ensureCapacity(newCapacity); + } + } + } + + /** + * Changes the size of the data structure to the capacity proposed. + * + * @param newCapacity the new capacity of the array (a power of two, less or equal to max) + */ + protected void ensureCapacity(int newCapacity) { + int oldCapacity = data.length; + if (newCapacity <= oldCapacity) { + return; + } + if (size == 0) { + threshold = calculateThreshold(newCapacity, loadFactor); + data = new HashEntry[newCapacity]; + } else { + HashEntry oldEntries[] = data; + HashEntry newEntries[] = new HashEntry[newCapacity]; + + modCount++; + for (int i = oldCapacity - 1; i >= 0; i--) { + HashEntry entry = oldEntries[i]; + if (entry != null) { + oldEntries[i] = null; // gc + do { + HashEntry next = entry.next; + int index = hashIndex(entry.hashCode, newCapacity); + entry.next = newEntries[index]; + newEntries[index] = entry; + entry = next; + } while (entry != null); + } + } + threshold = calculateThreshold(newCapacity, loadFactor); + data = newEntries; + } + } + + /** + * Calculates the new capacity of the map. + * This implementation normalizes the capacity to a power of two. + * + * @param proposedCapacity the proposed capacity + * @return the normalized new capacity + */ + protected int calculateNewCapacity(int proposedCapacity) { + int newCapacity = 1; + if (proposedCapacity > MAXIMUM_CAPACITY) { + newCapacity = MAXIMUM_CAPACITY; + } else { + while (newCapacity < proposedCapacity) { + newCapacity <<= 1; // multiply by two + } + if (newCapacity > MAXIMUM_CAPACITY) { + newCapacity = MAXIMUM_CAPACITY; + } + } + return newCapacity; + } + + /** + * Calculates the new threshold of the map, where it will be resized. + * This implementation uses the load factor. + * + * @param newCapacity the new capacity + * @param factor the load factor + * @return the new resize threshold + */ + protected int calculateThreshold(int newCapacity, float factor) { + return (int) (newCapacity * factor); + } + + //----------------------------------------------------------------------- + /** + * Gets the next field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the next field of the entry + * @throws NullPointerException if the entry is null + * @since Commons Collections 3.1 + */ + protected HashEntry entryNext(HashEntry entry) { + return entry.next; + } + + /** + * Gets the hashCode field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the hashCode field of the entry + * @throws NullPointerException if the entry is null + * @since Commons Collections 3.1 + */ + protected int entryHashCode(HashEntry entry) { + return entry.hashCode; + } + + /** + * Gets the key field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the key field of the entry + * @throws NullPointerException if the entry is null + * @since Commons Collections 3.1 + */ + protected K entryKey(HashEntry entry) { + return entry.key; + } + + /** + * Gets the value field from a HashEntry. + * Used in subclasses that have no visibility of the field. + * + * @param entry the entry to query, must not be null + * @return the value field of the entry + * @throws NullPointerException if the entry is null + * @since Commons Collections 3.1 + */ + protected V entryValue(HashEntry entry) { + return entry.value; + } + + //----------------------------------------------------------------------- + /** + * Gets an iterator over the map. + * Changes made to the iterator affect this map. + *

      + * A MapIterator returns the keys in the map. It also provides convenient + * methods to get the key and value, and set the value. + * It avoids the need to create an entrySet/keySet/values object. + * It also avoids creating the Map.Entry object. + * + * @return the map iterator + */ + public MapIterator mapIterator() { + if (size == 0) { + return EmptyMapIterator.INSTANCE; + } + return new HashMapIterator(this); + } + + /** + * MapIterator implementation. + */ + protected static class HashMapIterator extends HashIterator implements MapIterator { + + protected HashMapIterator(AbstractHashedMap parent) { + super(parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + + public K getKey() { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return current.getKey(); + } + + public V getValue() { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return current.getValue(); + } + + public V setValue(V value) { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return current.setValue(value); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the entrySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the entries, use {@link #mapIterator()}. + * + * @return the entrySet view + */ + public Set> entrySet() { + if (entrySet == null) { + entrySet = new EntrySet(this); + } + return entrySet; + } + + /** + * Creates an entry set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the entrySet iterator + */ + protected Iterator> createEntrySetIterator() { + if (size() == 0) { + return EmptyIterator.INSTANCE; + } + return new EntrySetIterator(this); + } + + /** + * EntrySet implementation. + */ + protected static class EntrySet extends AbstractSet> { + /** + * The parent map + */ + protected final AbstractHashedMap parent; + + protected EntrySet(AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + public int size() { + return parent.size(); + } + + public void clear() { + parent.clear(); + } + + public boolean contains(Map.Entry entry) { + Map.Entry e = entry; + Entry match = parent.getEntry(e.getKey()); + return (match != null && match.equals(e)); + } + + public boolean remove(Object obj) { + if (obj instanceof Map.Entry == false) { + return false; + } + if (contains(obj) == false) { + return false; + } + Map.Entry entry = (Map.Entry) obj; + K key = entry.getKey(); + parent.remove(key); + return true; + } + + public Iterator> iterator() { + return parent.createEntrySetIterator(); + } + } + + /** + * EntrySet iterator. + */ + protected static class EntrySetIterator extends HashIterator implements Iterator> { + + protected EntrySetIterator(AbstractHashedMap parent) { + super(parent); + } + + public HashEntry next() { + return super.nextEntry(); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the keySet view of the map. + * Changes made to the view affect this map. + * To simply iterate through the keys, use {@link #mapIterator()}. + * + * @return the keySet view + */ + public Set keySet() { + if (keySet == null) { + keySet = new KeySet(this); + } + return keySet; + } + + /** + * Creates a key set iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the keySet iterator + */ + protected Iterator createKeySetIterator() { + if (size() == 0) { + return EmptyIterator.INSTANCE; + } + return new KeySetIterator(this); + } + + /** + * KeySet implementation. + */ + protected static class KeySet extends AbstractSet { + /** + * The parent map + */ + protected final AbstractHashedMap parent; + + protected KeySet(AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + public int size() { + return parent.size(); + } + + public void clear() { + parent.clear(); + } + + public boolean contains(Object key) { + return parent.containsKey(key); + } + + public boolean remove(Object key) { + boolean result = parent.containsKey(key); + parent.remove(key); + return result; + } + + public Iterator iterator() { + return parent.createKeySetIterator(); + } + } + + /** + * KeySet iterator. + */ + protected static class KeySetIterator extends HashIterator implements Iterator { + + protected KeySetIterator(AbstractHashedMap parent) { + super(parent); + } + + public K next() { + return super.nextEntry().getKey(); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the values view of the map. + * Changes made to the view affect this map. + * To simply iterate through the values, use {@link #mapIterator()}. + * + * @return the values view + */ + public Collection values() { + if (values == null) { + values = new Values(this); + } + return values; + } + + /** + * Creates a values iterator. + * Subclasses can override this to return iterators with different properties. + * + * @return the values iterator + */ + protected Iterator createValuesIterator() { + if (size() == 0) { + return EmptyIterator.INSTANCE; + } + return new ValuesIterator(this); + } + + /** + * Values implementation. + */ + protected static class Values extends AbstractCollection { + /** + * The parent map + */ + protected final AbstractHashedMap parent; + + protected Values(AbstractHashedMap parent) { + super(); + this.parent = parent; + } + + public int size() { + return parent.size(); + } + + public void clear() { + parent.clear(); + } + + public boolean contains(Object value) { + return parent.containsValue(value); + } + + public Iterator iterator() { + return parent.createValuesIterator(); + } + } + + /** + * Values iterator. + */ + protected static class ValuesIterator extends HashIterator implements Iterator { + + protected ValuesIterator(AbstractHashedMap parent) { + super(parent); + } + + public V next() { + return super.nextEntry().getValue(); + } + } + + //----------------------------------------------------------------------- + /** + * HashEntry used to store the data. + *

      + * If you subclass AbstractHashedMap but not HashEntry + * then you will not be able to access the protected fields. + * The entryXxx() methods on AbstractHashedMap exist + * to provide the necessary access. + */ + protected static class HashEntry implements Map.Entry, KeyValue { + /** + * The next entry in the hash chain + */ + protected HashEntry next; + /** + * The hash code of the key + */ + protected int hashCode; + /** + * The key + */ + private K key; + /** + * The value + */ + private V value; + + protected HashEntry(HashEntry next, int hashCode, K key, V value) { + super(); + this.next = next; + this.hashCode = hashCode; + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public void setKey(K key) { + this.key = key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V old = this.value; + this.value = value; + return old; + } + + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + Map.Entry other = (Map.Entry) obj; + return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); + } + + public String toString() { + return new StringBuilder().append(getKey()).append('=').append(getValue()).toString(); + } + } + + /** + * Base Iterator + */ + protected static abstract class HashIterator { + + /** + * The parent map + */ + protected final AbstractHashedMap parent; + /** + * The current index into the array of buckets + */ + protected int hashIndex; + /** + * The last returned entry + */ + protected HashEntry last; + /** + * The next entry + */ + protected HashEntry next; + /** + * The modification count expected + */ + protected int expectedModCount; + + protected HashIterator(AbstractHashedMap parent) { + super(); + this.parent = parent; + HashEntry[] data = parent.data; + int i = data.length; + HashEntry next = null; + while (i > 0 && next == null) { + next = data[--i]; + } + this.next = next; + this.hashIndex = i; + this.expectedModCount = parent.modCount; + } + + public boolean hasNext() { + return (next != null); + } + + protected HashEntry nextEntry() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + HashEntry newCurrent = next; + if (newCurrent == null) { + throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); + } + HashEntry[] data = parent.data; + int i = hashIndex; + HashEntry n = newCurrent.next; + while (n == null && i > 0) { + n = data[--i]; + } + next = n; + hashIndex = i; + last = newCurrent; + return newCurrent; + } + + protected HashEntry currentEntry() { + return last; + } + + public void remove() { + if (last == null) { + throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); + } + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + parent.remove(last.getKey()); + last = null; + expectedModCount = parent.modCount; + } + + public String toString() { + if (last != null) { + return "Iterator[" + last.getKey() + "=" + last.getValue() + "]"; + } else { + return "Iterator[]"; + } + } + } + + //----------------------------------------------------------------------- + /** + * Writes the map data to the stream. This method must be overridden if a + * subclass must be setup before put() is used. + *

      + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

      + * The solution adopted here is to serialize the state data of this class in + * this protected method. This method must be called by the + * writeObject() of the first serializable subclass. + *

      + * Subclasses may override if they have a specific field that must be present + * on read before this implementation will work. Generally, the read determines + * what must be serialized here, if anything. + * + * @param out the output stream + */ + protected void doWriteObject(ObjectOutputStream out) throws IOException { + out.writeFloat(loadFactor); + out.writeInt(data.length); + out.writeInt(size); + for (MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); + out.writeObject(it.getValue()); + } + } + + /** + * Reads the map data from the stream. This method must be overridden if a + * subclass must be setup before put() is used. + *

      + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

      + * The solution adopted here is to deserialize the state data of this class in + * this protected method. This method must be called by the + * readObject() of the first serializable subclass. + *

      + * Subclasses may override if the subclass has a specific field that must be present + * before put() or calculateThreshold() will work correctly. + * + * @param in the input stream + */ + protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + loadFactor = in.readFloat(); + int capacity = in.readInt(); + int size = in.readInt(); + init(); + data = new HashEntry[capacity]; + for (int i = 0; i < size; i++) { + K key = (K) in.readObject(); + V value = (V) in.readObject(); + put(key, value); + } + threshold = calculateThreshold(data.length, loadFactor); + } + + //----------------------------------------------------------------------- + /** + * Clones the map without cloning the keys or values. + *

      + * To implement clone(), a subclass must implement the + * Cloneable interface and make this method public. + * + * @return a shallow clone + */ + protected Object clone() { + try { + AbstractHashedMap cloned = (AbstractHashedMap) super.clone(); + cloned.data = new HashEntry[data.length]; + cloned.entrySet = null; + cloned.keySet = null; + cloned.values = null; + cloned.modCount = 0; + cloned.size = 0; + cloned.init(); + cloned.putAll(this); + return cloned; + + } catch (CloneNotSupportedException ex) { + return null; // should never happen + } + } + + /** + * Compares this map with another. + * + * @param obj the object to compare to + * @return true if equal + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map == false) { + return false; + } + Map map = (Map) obj; + if (map.size() != size()) { + return false; + } + MapIterator it = mapIterator(); + try { + while (it.hasNext()) { + Object key = it.next(); + Object value = it.getValue(); + if (value == null) { + if (map.get(key) != null || map.containsKey(key) == false) { + return false; + } + } else { + if (value.equals(map.get(key)) == false) { + return false; + } + } + } + } catch (ClassCastException ignored) { + return false; + } catch (NullPointerException ignored) { + return false; + } + return true; + } + + /** + * Gets the standard Map hashCode. + * + * @return the hash code defined in the Map interface + */ + public int hashCode() { + int total = 0; + Iterator it = createEntrySetIterator(); + while (it.hasNext()) { + total += it.next().hashCode(); + } + return total; + } + + /** + * Gets the map as a String. + * + * @return a string version of the map + */ + public String toString() { + if (size() == 0) { + return "{}"; + } + StringBuilder buf = new StringBuilder(32 * size()); + buf.append('{'); + + MapIterator it = mapIterator(); + boolean hasNext = it.hasNext(); + while (hasNext) { + Object key = it.next(); + Object value = it.getValue(); + buf.append(key == this ? "(this Map)" : key).append('=').append(value == this ? "(this Map)" : value); + + hasNext = it.hasNext(); + if (hasNext) { + buf.append(',').append(' '); + } + } + + buf.append('}'); + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java new file mode 100644 index 0000000..decc342 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractKeyValue.java @@ -0,0 +1,80 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + + +/** + * Abstract pair class to assist with creating KeyValue and MapEntry implementations. + * + * @author James Strachan + * @author Michael A. Smith + * @author Neil O'Toole + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @since Commons Collections 3.0 + */ +public abstract class AbstractKeyValue implements KeyValue { + + /** + * The key + */ + protected K key; + /** + * The value + */ + protected V value; + + /** + * Constructs a new pair with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + protected AbstractKeyValue(K key, V value) { + super(); + this.key = key; + this.value = value; + } + + /** + * Gets the key from the pair. + * + * @return the key + */ + public K getKey() { + return key; + } + + /** + * Gets the value from the pair. + * + * @return the value + */ + public V getValue() { + return value; + } + + /** + * Gets a debugging String view of the pair. + * + * @return a String view of the entry + */ + public String toString() { + return new StringBuilder().append(getKey()).append('=').append(getValue()).toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java new file mode 100644 index 0000000..2feb308 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractMapEntry.java @@ -0,0 +1,89 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.Map; + +/** + * Abstract Pair class to assist with creating correct Map Entry implementations. + * + * @author James Strachan + * @author Michael A. Smith + * @author Neil O'Toole + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @since Commons Collections 3.0 + */ +public abstract class AbstractMapEntry extends AbstractKeyValue implements Map.Entry { + + /** + * Constructs a new entry with the given key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + protected AbstractMapEntry(K key, V value) { + super(key, value); + } + + // Map.Entry interface + //------------------------------------------------------------------------- + /** + * Sets the value stored in this Map Entry. + *

      + * This Map Entry is not connected to a Map, so only the local data is changed. + * + * @param value the new value + * @return the previous value + */ + public V setValue(V value) { + V answer = this.value; + this.value = value; + return answer; + } + + /** + * Compares this Map Entry with another Map Entry. + *

      + * Implemented per API documentation of {@link java.util.Map.Entry#equals(Object)} + * + * @param obj the object to compare to + * @return true if equal key and value + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + Map.Entry other = (Map.Entry) obj; + return (getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) && (getValue() == null ? other.getValue() == null : getValue().equals(other.getValue())); + } + + /** + * Gets a hashCode compatible with the equals method. + *

      + * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()} + * + * @return a suitable hash code + */ + public int hashCode() { + return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java new file mode 100644 index 0000000..b57f17d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/AbstractReferenceMap.java @@ -0,0 +1,1025 @@ +// Converted, with some major refactors required. Not as memory-efficient as before, could use additional refactoring. +// Perhaps use four different types of HashEntry classes for max efficiency: +// normal HashEntry for HARD,HARD +// HardRefEntry for HARD,(SOFT|WEAK) +// RefHardEntry for (SOFT|WEAK),HARD +// RefRefEntry for (SOFT|WEAK),(SOFT|WEAK) +/* + * Copyright 2002-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.*; + +/** + * An abstract implementation of a hash-based map that allows the entries to + * be removed by the garbage collector. + *

      + * This class implements all the features necessary for a subclass reference + * hash-based map. Key-value entries are stored in instances of the + * ReferenceEntry class which can be overridden and replaced. + * The iterators can similarly be replaced, without the need to replace the KeySet, + * EntrySet and Values view classes. + *

      + * Overridable methods are provided to change the default hashing behaviour, and + * to change how entries are added to and removed from the map. Hopefully, all you + * need for unusual subclasses is here. + *

      + * When you construct an AbstractReferenceMap, you can specify what + * kind of references are used to store the map's keys and values. + * If non-hard references are used, then the garbage collector can remove + * mappings if a key or value becomes unreachable, or if the JVM's memory is + * running low. For information on how the different reference types behave, + * see {@link Reference}. + *

      + * Different types of references can be specified for keys and values. + * The keys can be configured to be weak but the values hard, + * in which case this class will behave like a + * + * WeakHashMap. However, you can also specify hard keys and + * weak values, or any other combination. The default constructor uses + * hard keys and soft values, providing a memory-sensitive cache. + *

      + * This {@link Map} implementation does not allow null elements. + * Attempting to add a null key or value to the map will raise a + * NullPointerException. + *

      + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

      + * This implementation is not synchronized. + * You can use {@link java.util.Collections#synchronizedMap} to + * provide synchronized access to a ReferenceMap. + * + * @author Paul Jack + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @see java.lang.ref.Reference + * @since Commons Collections 3.1 (extracted from ReferenceMap in 3.0) + */ +public abstract class AbstractReferenceMap extends AbstractHashedMap { + + /** + * Constant indicating that hard references should be used + */ + public static final int HARD = 0; + + /** + * Constant indicating that soft references should be used + */ + public static final int SOFT = 1; + + /** + * Constant indicating that weak references should be used + */ + public static final int WEAK = 2; + + /** + * The reference type for keys. Must be HARD, SOFT, WEAK. + * + * @serial + */ + protected int keyType; + + /** + * The reference type for values. Must be HARD, SOFT, WEAK. + * + * @serial + */ + protected int valueType; + + /** + * Should the value be automatically purged when the associated key has been collected? + */ + protected boolean purgeValues; + + /** + * ReferenceQueue used to eliminate stale mappings. + * See purge. + */ + private transient ReferenceQueue queue; + + //----------------------------------------------------------------------- + /** + * Constructor used during deserialization. + */ + protected AbstractReferenceMap() { + super(); + } + + /** + * Constructs a new empty map with the specified reference types, + * load factor and initial capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link #SOFT} or {@link #WEAK} + * @param valueType the type of reference to use for values; + * must be {@link #SOFT} or {@link #WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + protected AbstractReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean purgeValues) { + super(capacity, loadFactor); + verify("keyType", keyType); + verify("valueType", valueType); + this.keyType = keyType; + this.valueType = valueType; + this.purgeValues = purgeValues; + } + + /** + * Initialise this subclass during construction, cloning or deserialization. + */ + protected void init() { + queue = new ReferenceQueue(); + } + + //----------------------------------------------------------------------- + /** + * Checks the type int is a valid value. + * + * @param name the name for error messages + * @param type the type value to check + * @throws IllegalArgumentException if the value if invalid + */ + private static void verify(String name, int type) { + if ((type < HARD) || (type > WEAK)) { + throw new IllegalArgumentException(name + " must be HARD, SOFT, WEAK."); + } + } + + //----------------------------------------------------------------------- + /** + * Gets the size of the map. + * + * @return the size + */ + public int size() { + purgeBeforeRead(); + return super.size(); + } + + /** + * Checks whether the map is currently empty. + * + * @return true if the map is currently size zero + */ + public boolean isEmpty() { + purgeBeforeRead(); + return super.isEmpty(); + } + + /** + * Checks whether the map contains the specified key. + * + * @param key the key to search for + * @return true if the map contains the key + */ + public boolean containsKey(Object key) { + purgeBeforeRead(); + Entry entry = getEntry(key); + if (entry == null) { + return false; + } + return (entry.getValue() != null); + } + + /** + * Checks whether the map contains the specified value. + * + * @param value the value to search for + * @return true if the map contains the value + */ + public boolean containsValue(Object value) { + purgeBeforeRead(); + if (value == null) { + return false; + } + return super.containsValue(value); + } + + /** + * Gets the value mapped to the key specified. + * + * @param key the key + * @return the mapped value, null if no match + */ + public V get(Object key) { + purgeBeforeRead(); + Entry entry = getEntry(key); + if (entry == null) { + return null; + } + return entry.getValue(); + } + + + /** + * Puts a key-value mapping into this map. + * Neither the key nor the value may be null. + * + * @param key the key to add, must not be null + * @param value the value to add, must not be null + * @return the value previously mapped to this key, null if none + * @throws NullPointerException if either the key or value is null + */ + public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("null keys not allowed"); + } + if (value == null) { + throw new NullPointerException("null values not allowed"); + } + + purgeBeforeWrite(); + return super.put(key, value); + } + + /** + * Removes the specified mapping from this map. + * + * @param key the mapping to remove + * @return the value mapped to the removed key, null if key not in map + */ + public V remove(Object key) { + if (key == null) { + return null; + } + purgeBeforeWrite(); + return super.remove(key); + } + + /** + * Clears this map. + */ + public void clear() { + super.clear(); + while (queue.poll() != null) { + } // drain the queue + } + + //----------------------------------------------------------------------- + /** + * Gets a MapIterator over the reference map. + * The iterator only returns valid key/value pairs. + * + * @return a map iterator + */ + public MapIterator mapIterator() { + return new ReferenceMapIterator(this); + } + + /** + * Returns a set view of this map's entries. + * An iterator returned entry is valid until next() is called again. + * The setValue() method on the toArray entries has no effect. + * + * @return a set view of this map's entries + */ + public Set> entrySet() { + if (entrySet == null) { + entrySet = new ReferenceEntrySet(this); + } + return entrySet; + } + + /** + * Returns a set view of this map's keys. + * + * @return a set view of this map's keys + */ + public Set keySet() { + if (keySet == null) { + keySet = new ReferenceKeySet(this); + } + return keySet; + } + + /** + * Returns a collection view of this map's values. + * + * @return a set view of this map's values + */ + public Collection values() { + if (values == null) { + values = new ReferenceValues(this); + } + return values; + } + + //----------------------------------------------------------------------- + /** + * Purges stale mappings from this map before read operations. + *

      + * This implementation calls {@link #purge()} to maintain a consistent state. + */ + protected void purgeBeforeRead() { + purge(); + } + + /** + * Purges stale mappings from this map before write operations. + *

      + * This implementation calls {@link #purge()} to maintain a consistent state. + */ + protected void purgeBeforeWrite() { + purge(); + } + + /** + * Purges stale mappings from this map. + *

      + * Note that this method is not synchronized! Special + * care must be taken if, for instance, you want stale + * mappings to be removed on a periodic basis by some + * background thread. + */ + protected void purge() { + Reference ref = queue.poll(); + while (ref != null) { + purge(ref); + ref = queue.poll(); + } + } + + /** + * Purges the specified reference. + * + * @param ref the reference to purge + */ + protected void purge(Reference ref) { + // The hashCode of the reference is the hashCode of the + // mapping key, even if the reference refers to the + // mapping value... + int hash = ref.hashCode(); + int index = hashIndex(hash, data.length); + HashEntry previous = null; + HashEntry entry = data[index]; + while (entry != null) { + if (((ReferenceEntry) entry).purge(ref)) { + if (previous == null) { + data[index] = entry.next; + } else { + previous.next = entry.next; + } + this.size--; + return; + } + previous = entry; + entry = entry.next; + } + + } + + //----------------------------------------------------------------------- + /** + * Gets the entry mapped to the key specified. + * + * @param key the key + * @return the entry, null if no match + */ + protected HashEntry getEntry(Object key) { + if (key == null) { + return null; + } else { + return super.getEntry(key); + } + } + + /** + * Gets the hash code for a MapEntry. + * Subclasses can override this, for example to use the identityHashCode. + * + * @param key the key to get a hash code for, may be null + * @param value the value to get a hash code for, may be null + * @return the hash code, as per the MapEntry specification + */ + protected int hashEntry(Object key, Object value) { + return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); + } + + /** + * Compares two keys, in internal converted form, to see if they are equal. + *

      + * This implementation converts the key from the entry to a real reference + * before comparison. + * + * @param key1 the first key to compare passed in from outside + * @param key2 the second key extracted from the entry via entry.key + * @return true if equal + */ + protected boolean isEqualKey(Object key1, Object key2) { + //if ((key1 == null) && (key2 != null) || (key1 != null) || (key2 == null)) { + // return false; + //} + // GenericsNote: Conversion from reference handled by getKey() which replaced all .key references + //key2 = (keyType > HARD ? ((Reference) key2).get() : key2); + return (key1 == key2 || key1.equals(key2)); + } + + /** + * Creates a ReferenceEntry instead of a HashEntry. + * + * @param next the next entry in sequence + * @param hashCode the hash code to use + * @param key the key to store + * @param value the value to store + * @return the newly created entry + */ + public HashEntry createEntry(HashEntry next, int hashCode, K key, V value) { + return new ReferenceEntry(this, (ReferenceEntry) next, hashCode, key, value); + } + + /** + * Creates an entry set iterator. + * + * @return the entrySet iterator + */ + protected Iterator> createEntrySetIterator() { + return new ReferenceEntrySetIterator(this); + } + + /** + * Creates an key set iterator. + * + * @return the keySet iterator + */ + protected Iterator createKeySetIterator() { + return new ReferenceKeySetIterator(this); + } + + /** + * Creates an values iterator. + * + * @return the values iterator + */ + protected Iterator createValuesIterator() { + return new ReferenceValuesIterator(this); + } + + //----------------------------------------------------------------------- + /** + * EntrySet implementation. + */ + static class ReferenceEntrySet extends EntrySet { + + protected ReferenceEntrySet(AbstractHashedMap parent) { + super(parent); + } + + public Object[] toArray() { + return toArray(new Object[0]); + } + + public T[] toArray(T[] arr) { + // special implementation to handle disappearing entries + ArrayList> list = new ArrayList>(); + Iterator> iterator = iterator(); + while (iterator.hasNext()) { + Map.Entry e = iterator.next(); + list.add(new DefaultMapEntry(e.getKey(), e.getValue())); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * KeySet implementation. + */ + static class ReferenceKeySet extends KeySet { + + protected ReferenceKeySet(AbstractHashedMap parent) { + super(parent); + } + + public Object[] toArray() { + return toArray(new Object[0]); + } + + public T[] toArray(T[] arr) { + // special implementation to handle disappearing keys + List list = new ArrayList(parent.size()); + for (Iterator it = iterator(); it.hasNext();) { + list.add(it.next()); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * Values implementation. + */ + static class ReferenceValues extends Values { + + protected ReferenceValues(AbstractHashedMap parent) { + super(parent); + } + + public Object[] toArray() { + return toArray(new Object[0]); + } + + public T[] toArray(T[] arr) { + // special implementation to handle disappearing values + List list = new ArrayList(parent.size()); + for (Iterator it = iterator(); it.hasNext();) { + list.add(it.next()); + } + return list.toArray(arr); + } + } + + //----------------------------------------------------------------------- + /** + * A MapEntry implementation for the map. + *

      + * If getKey() or getValue() returns null, it means + * the mapping is stale and should be removed. + * + * @since Commons Collections 3.1 + */ + protected static class ReferenceEntry extends HashEntry { + /** + * The parent map + */ + protected final AbstractReferenceMap parent; + + protected Reference refKey; + protected Reference refValue; + + /** + * Creates a new entry object for the ReferenceMap. + * + * @param parent the parent map + * @param next the next entry in the hash bucket + * @param hashCode the hash code of the key + * @param key the key + * @param value the value + */ + public ReferenceEntry(AbstractReferenceMap parent, ReferenceEntry next, int hashCode, K key, V value) { + super(next, hashCode, null, null); + this.parent = parent; + if (parent.keyType != HARD) { + refKey = toReference(parent.keyType, key, hashCode); + } else { + this.setKey(key); + } + if (parent.valueType != HARD) { + refValue = toReference(parent.valueType, value, hashCode); // the key hashCode is passed in deliberately + } else { + this.setValue(value); + } + } + + /** + * Gets the key from the entry. + * This method dereferences weak and soft keys and thus may return null. + * + * @return the key, which may be null if it was garbage collected + */ + public K getKey() { + return (parent.keyType > HARD) ? refKey.get() : super.getKey(); + } + + /** + * Gets the value from the entry. + * This method dereferences weak and soft value and thus may return null. + * + * @return the value, which may be null if it was garbage collected + */ + public V getValue() { + return (parent.valueType > HARD) ? refValue.get() : super.getValue(); + } + + /** + * Sets the value of the entry. + * + * @param obj the object to store + * @return the previous value + */ + public V setValue(V obj) { + V old = getValue(); + if (parent.valueType > HARD) { + refValue.clear(); + refValue = toReference(parent.valueType, obj, hashCode); + } else { + super.setValue(obj); + } + return old; + } + + /** + * Compares this map entry to another. + *

      + * This implementation uses isEqualKey and + * isEqualValue on the main map for comparison. + * + * @param obj the other map entry to compare to + * @return true if equal, false if not + */ + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Map.Entry == false) { + return false; + } + + Map.Entry entry = (Map.Entry) obj; + Object entryKey = entry.getKey(); // convert to hard reference + Object entryValue = entry.getValue(); // convert to hard reference + if ((entryKey == null) || (entryValue == null)) { + return false; + } + // compare using map methods, aiding identity subclass + // note that key is direct access and value is via method + return parent.isEqualKey(entryKey, getKey()) && parent.isEqualValue(entryValue, getValue()); + } + + /** + * Gets the hashcode of the entry using temporary hard references. + *

      + * This implementation uses hashEntry on the main map. + * + * @return the hashcode of the entry + */ + public int hashCode() { + return parent.hashEntry(getKey(), getValue()); + } + + /** + * Constructs a reference of the given type to the given referent. + * The reference is registered with the queue for later purging. + * + * @param type HARD, SOFT or WEAK + * @param referent the object to refer to + * @param hash the hash code of the key of the mapping; + * this number might be different from referent.hashCode() if + * the referent represents a value and not a key + */ + protected Reference toReference(int type, T referent, int hash) { + switch (type) { + case SOFT: + return new SoftRef(hash, referent, parent.queue); + case WEAK: + return new WeakRef(hash, referent, parent.queue); + default: + throw new Error("Attempt to create hard reference in ReferenceMap!"); + } + } + + /** + * Purges the specified reference + * + * @param ref the reference to purge + * @return true or false + */ + boolean purge(Reference ref) { + boolean r = (parent.keyType > HARD) && (refKey == ref); + r = r || ((parent.valueType > HARD) && (refValue == ref)); + if (r) { + if (parent.keyType > HARD) { + refKey.clear(); + } + if (parent.valueType > HARD) { + refValue.clear(); + } else if (parent.purgeValues) { + setValue(null); + } + } + return r; + } + + /** + * Gets the next entry in the bucket. + * + * @return the next entry in the bucket + */ + protected ReferenceEntry next() { + return (ReferenceEntry) next; + } + } + + //----------------------------------------------------------------------- + /** + * The EntrySet iterator. + */ + static class ReferenceIteratorBase { + /** + * The parent map + */ + final AbstractReferenceMap parent; + + // These fields keep track of where we are in the table. + int index; + ReferenceEntry entry; + ReferenceEntry previous; + + // These Object fields provide hard references to the + // current and next entry; this assures that if hasNext() + // returns true, next() will actually return a valid element. + K nextKey; + V nextValue; + K currentKey; + V currentValue; + + int expectedModCount; + + public ReferenceIteratorBase(AbstractReferenceMap parent) { + super(); + this.parent = parent; + index = (parent.size() != 0 ? parent.data.length : 0); + // have to do this here! size() invocation above + // may have altered the modCount. + expectedModCount = parent.modCount; + } + + public boolean hasNext() { + checkMod(); + while (nextNull()) { + ReferenceEntry e = entry; + int i = index; + while ((e == null) && (i > 0)) { + i--; + e = (ReferenceEntry) parent.data[i]; + } + entry = e; + index = i; + if (e == null) { + currentKey = null; + currentValue = null; + return false; + } + nextKey = e.getKey(); + nextValue = e.getValue(); + if (nextNull()) { + entry = entry.next(); + } + } + return true; + } + + private void checkMod() { + if (parent.modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + } + + private boolean nextNull() { + return (nextKey == null) || (nextValue == null); + } + + protected ReferenceEntry nextEntry() { + checkMod(); + if (nextNull() && !hasNext()) { + throw new NoSuchElementException(); + } + previous = entry; + entry = entry.next(); + currentKey = nextKey; + currentValue = nextValue; + nextKey = null; + nextValue = null; + return previous; + } + + protected ReferenceEntry currentEntry() { + checkMod(); + return previous; + } + + public ReferenceEntry superNext() { + return nextEntry(); + } + + public void remove() { + checkMod(); + if (previous == null) { + throw new IllegalStateException(); + } + parent.remove(currentKey); + previous = null; + currentKey = null; + currentValue = null; + expectedModCount = parent.modCount; + } + } + + /** + * The EntrySet iterator. + */ + static class ReferenceEntrySetIterator extends ReferenceIteratorBase implements Iterator> { + + public ReferenceEntrySetIterator(AbstractReferenceMap abstractReferenceMap) { + super(abstractReferenceMap); + } + + public ReferenceEntry next() { + return superNext(); + } + + } + + /** + * The keySet iterator. + */ + static class ReferenceKeySetIterator extends ReferenceIteratorBase implements Iterator { + + ReferenceKeySetIterator(AbstractReferenceMap parent) { + super(parent); + } + + public K next() { + return nextEntry().getKey(); + } + } + + /** + * The values iterator. + */ + static class ReferenceValuesIterator extends ReferenceIteratorBase implements Iterator { + + ReferenceValuesIterator(AbstractReferenceMap parent) { + super(parent); + } + + public V next() { + return nextEntry().getValue(); + } + } + + /** + * The MapIterator implementation. + */ + static class ReferenceMapIterator extends ReferenceIteratorBase implements MapIterator { + + protected ReferenceMapIterator(AbstractReferenceMap parent) { + super(parent); + } + + public K next() { + return nextEntry().getKey(); + } + + public K getKey() { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); + } + return current.getKey(); + } + + public V getValue() { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); + } + return current.getValue(); + } + + public V setValue(V value) { + HashEntry current = currentEntry(); + if (current == null) { + throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); + } + return current.setValue(value); + } + } + + //----------------------------------------------------------------------- + // These two classes store the hashCode of the key of + // of the mapping, so that after they're dequeued a quick + // lookup of the bucket in the table can occur. + + /** + * A soft reference holder. + */ + static class SoftRef extends SoftReference { + /** + * the hashCode of the key (even if the reference points to a value) + */ + private int hash; + + public SoftRef(int hash, T r, ReferenceQueue q) { + super(r, q); + this.hash = hash; + } + + public int hashCode() { + return hash; + } + } + + /** + * A weak reference holder. + */ + static class WeakRef extends WeakReference { + /** + * the hashCode of the key (even if the reference points to a value) + */ + private int hash; + + public WeakRef(int hash, T r, ReferenceQueue q) { + super(r, q); + this.hash = hash; + } + + public int hashCode() { + return hash; + } + } + + //----------------------------------------------------------------------- + /** + * Replaces the superclass method to store the state of this class. + *

      + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

      + * The solution adopted here is to serialize the state data of this class in + * this protected method. This method must be called by the + * writeObject() of the first serializable subclass. + *

      + * Subclasses may override if they have a specific field that must be present + * on read before this implementation will work. Generally, the read determines + * what must be serialized here, if anything. + * + * @param out the output stream + */ + protected void doWriteObject(ObjectOutputStream out) throws IOException { + out.writeInt(keyType); + out.writeInt(valueType); + out.writeBoolean(purgeValues); + out.writeFloat(loadFactor); + out.writeInt(data.length); + for (MapIterator it = mapIterator(); it.hasNext();) { + out.writeObject(it.next()); + out.writeObject(it.getValue()); + } + out.writeObject(null); // null terminate map + // do not call super.doWriteObject() as code there doesn't work for reference map + } + + /** + * Replaces the superclassm method to read the state of this class. + *

      + * Serialization is not one of the JDK's nicest topics. Normal serialization will + * initialise the superclass before the subclass. Sometimes however, this isn't + * what you want, as in this case the put() method on read can be + * affected by subclass state. + *

      + * The solution adopted here is to deserialize the state data of this class in + * this protected method. This method must be called by the + * readObject() of the first serializable subclass. + *

      + * Subclasses may override if the subclass has a specific field that must be present + * before put() or calculateThreshold() will work correctly. + * + * @param in the input stream + */ + protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + this.keyType = in.readInt(); + this.valueType = in.readInt(); + this.purgeValues = in.readBoolean(); + this.loadFactor = in.readFloat(); + int capacity = in.readInt(); + init(); + data = new HashEntry[capacity]; + while (true) { + K key = (K) in.readObject(); + if (key == null) { + break; + } + V value = (V) in.readObject(); + put(key, value); + } + threshold = calculateThreshold(data.length, loadFactor); + // do not call super.doReadObject() as code there doesn't work for reference map + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java new file mode 100644 index 0000000..ef752d0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/DefaultMapEntry.java @@ -0,0 +1,65 @@ +// GenericsNote: Converted. +/* + * Copyright 2001-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + + +import java.util.Map; + +/** + * A restricted implementation of {@link java.util.Map.Entry} that prevents + * the MapEntry contract from being broken. + * + * @author James Strachan + * @author Michael A. Smith + * @author Neil O'Toole + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @since Commons Collections 3.0 + */ +public final class DefaultMapEntry extends AbstractMapEntry { + + /** + * Constructs a new entry with the specified key and given value. + * + * @param key the key for the entry, may be null + * @param value the value for the entry, may be null + */ + public DefaultMapEntry(final K key, final V value) { + super(key, value); + } + + /** + * Constructs a new entry from the specified KeyValue. + * + * @param pair the pair to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultMapEntry(final KeyValue pair) { + super(pair.getKey(), pair.getValue()); + } + + /** + * Constructs a new entry from the specified MapEntry. + * + * @param entry the entry to copy, must not be null + * @throws NullPointerException if the entry is null + */ + public DefaultMapEntry(final Map.Entry entry) { + super(entry.getKey(), entry.getValue()); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java new file mode 100644 index 0000000..6a8707f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyIterator.java @@ -0,0 +1,58 @@ +// GenericsNote: Converted. +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.Iterator; + +/** + * Provides an implementation of an empty iterator. + *

      + * This class provides an implementation of an empty iterator. + * This class provides for binary compatability between Commons Collections + * 2.1.1 and 3.1 due to issues with IteratorUtils. + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ + * @since Commons Collections 2.1.1 and 3.1 + */ +public class EmptyIterator extends AbstractEmptyIterator implements ResettableIterator { + + /** + * Singleton instance of the iterator. + * + * @since Commons Collections 3.1 + */ + public static final ResettableIterator RESETTABLE_INSTANCE = new EmptyIterator(); + /** + * Singleton instance of the iterator. + * + * @since Commons Collections 2.1.1 and 3.1 + */ + public static final Iterator INSTANCE = RESETTABLE_INSTANCE; + + public static Iterator getInstance() { + return INSTANCE; + } + + /** + * Constructor. + */ + protected EmptyIterator() { + super(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java new file mode 100644 index 0000000..013f5ed --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/EmptyMapIterator.java @@ -0,0 +1,42 @@ +// GenericsNote: Converted. +/* + * Copyright 2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +/** + * Provides an implementation of an empty map iterator. + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:24 $ + * @since Commons Collections 3.1 + */ +public class EmptyMapIterator extends AbstractEmptyIterator implements MapIterator, ResettableIterator { + + /** + * Singleton instance of the iterator. + * + * @since Commons Collections 3.1 + */ + public static final MapIterator INSTANCE = new EmptyMapIterator(); + + /** + * Constructor. + */ + protected EmptyMapIterator() { + super(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java new file mode 100644 index 0000000..251b587 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/IterableMap.java @@ -0,0 +1,61 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.Map; + +/** + * Defines a map that can be iterated directly without needing to create an entry set. + *

      + * A map iterator is an efficient way of iterating over maps. + * There is no need to access the entry set or cast to Map Entry objects. + *

      + * IterableMap map = new HashedMap();
      + * MapIterator it = map.mapIterator();
      + * while (it.hasNext()) {
      + *   Object key = it.next();
      + *   Object value = it.getValue();
      + *   it.setValue("newValue");
      + * }
      + * 
      + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ + * @since Commons Collections 3.0 + */ +public interface IterableMap extends Map { + + /** + * Obtains a MapIterator over the map. + *

      + * A map iterator is an efficient way of iterating over maps. + * There is no need to access the entry set or cast to Map Entry objects. + *

      +     * IterableMap map = new HashedMap();
      +     * MapIterator it = map.mapIterator();
      +     * while (it.hasNext()) {
      +     *   Object key = it.next();
      +     *   Object value = it.getValue();
      +     *   it.setValue("newValue");
      +     * }
      +     * 
      + * + * @return a map iterator + */ + MapIterator mapIterator(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java new file mode 100644 index 0000000..c73621d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/KeyValue.java @@ -0,0 +1,46 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +/** + * Defines a simple key value pair. + *

      + * A Map Entry has considerable additional semantics over and above a simple + * key-value pair. This interface defines the minimum key value, with just the + * two get methods. + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ + * @since Commons Collections 3.0 + */ +public interface KeyValue { + + /** + * Gets the key from the pair. + * + * @return the key + */ + K getKey(); + + /** + * Gets the value from the pair. + * + * @return the value + */ + V getValue(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java new file mode 100644 index 0000000..fe2398c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/MapIterator.java @@ -0,0 +1,109 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.Iterator; + +/** + * Defines an iterator that operates over a Map. + *

      + * This iterator is a special version designed for maps. It can be more + * efficient to use this rather than an entry set iterator where the option + * is available, and it is certainly more convenient. + *

      + * A map that provides this interface may not hold the data internally using + * Map Entry objects, thus this interface can avoid lots of object creation. + *

      + * In use, this iterator iterates through the keys in the map. After each call + * to next(), the getValue() method provides direct + * access to the value. The value can also be set using setValue(). + *

      + * MapIterator it = map.mapIterator();
      + * while (it.hasNext()) {
      + *   Object key = it.next();
      + *   Object value = it.getValue();
      + *   it.setValue(newValue);
      + * }
      + * 
      + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ + * @since Commons Collections 3.0 + */ +public interface MapIterator extends Iterator { + + /** + * Checks to see if there are more entries still to be iterated. + * + * @return true if the iterator has more elements + */ + boolean hasNext(); + + /** + * Gets the next key from the Map. + * + * @return the next key in the iteration + * @throws java.util.NoSuchElementException + * if the iteration is finished + */ + K next(); + + //----------------------------------------------------------------------- + /** + * Gets the current key, which is the key returned by the last call + * to next(). + * + * @return the current key + * @throws IllegalStateException if next() has not yet been called + */ + K getKey(); + + /** + * Gets the current value, which is the value associated with the last key + * returned by next(). + * + * @return the current value + * @throws IllegalStateException if next() has not yet been called + */ + V getValue(); + + //----------------------------------------------------------------------- + /** + * Removes the last returned key from the underlying Map (optional operation). + *

      + * This method can be called once per call to next(). + * + * @throws UnsupportedOperationException if remove is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has already been called + * since the last call to next() + */ + void remove(); + + /** + * Sets the value associated with the current key (optional operation). + * + * @param value the new value + * @return the previous value + * @throws UnsupportedOperationException if setValue is not supported by the map + * @throws IllegalStateException if next() has not yet been called + * @throws IllegalStateException if remove() has been called since the + * last call to next() + */ + V setValue(V value); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java new file mode 100644 index 0000000..f30954d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ReferenceMap.java @@ -0,0 +1,161 @@ +// GenericsNote: Converted. +/* + * Copyright 2002-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + +/** + * A Map implementation that allows mappings to be + * removed by the garbage collector. + *

      + * When you construct a ReferenceMap, you can specify what kind + * of references are used to store the map's keys and values. + * If non-hard references are used, then the garbage collector can remove + * mappings if a key or value becomes unreachable, or if the JVM's memory is + * running low. For information on how the different reference types behave, + * see {@link java.lang.ref.Reference}. + *

      + * Different types of references can be specified for keys and values. + * The keys can be configured to be weak but the values hard, + * in which case this class will behave like a + * + * WeakHashMap. However, you can also specify hard keys and + * weak values, or any other combination. The default constructor uses + * hard keys and soft values, providing a memory-sensitive cache. + *

      + * This map is similar to ReferenceIdentityMap. + * It differs in that keys and values in this class are compared using equals(). + *

      + * This {@link java.util.Map} implementation does not allow null elements. + * Attempting to add a null key or value to the map will raise a NullPointerException. + *

      + * This implementation is not synchronized. + * You can use {@link java.util.Collections#synchronizedMap} to + * provide synchronized access to a ReferenceMap. + * Remember that synchronization will not stop the garbage collecter removing entries. + *

      + * All the available iterators can be reset back to the start by casting to + * ResettableIterator and calling reset(). + *

      + * NOTE: As from Commons Collections 3.1 this map extends AbstractReferenceMap + * (previously it extended AbstractMap). As a result, the implementation is now + * extensible and provides a MapIterator. + * + * @author Paul Jack + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:32 $ + * @see java.lang.ref.Reference + * @since Commons Collections 3.0 (previously in main package v2.1) + */ +public class ReferenceMap extends AbstractReferenceMap implements Serializable { + + /** + * Serialization version + */ + private static final long serialVersionUID = 1555089888138299607L; + + /** + * Constructs a new ReferenceMap that will + * use hard references to keys and soft references to values. + */ + public ReferenceMap() { + super(HARD, SOFT, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param valueType the type of reference to use for values; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + */ + public ReferenceMap(int keyType, int valueType) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, false); + } + + /** + * Constructs a new ReferenceMap that will + * use the specified types of references. + * + * @param keyType the type of reference to use for keys; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param valueType the type of reference to use for values; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceMap(int keyType, int valueType, boolean purgeValues) { + super(keyType, valueType, DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR, purgeValues); + } + + /** + * Constructs a new ReferenceMap with the + * specified reference types, load factor and initial + * capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param valueType the type of reference to use for values; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + */ + public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor) { + super(keyType, valueType, capacity, loadFactor, false); + } + + /** + * Constructs a new ReferenceMap with the + * specified reference types, load factor and initial + * capacity. + * + * @param keyType the type of reference to use for keys; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param valueType the type of reference to use for values; + * must be {@link #HARD}, {@link #SOFT}, {@link #WEAK} + * @param capacity the initial capacity for the map + * @param loadFactor the load factor for the map + * @param purgeValues should the value be automatically purged when the + * key is garbage collected + */ + public ReferenceMap(int keyType, int valueType, int capacity, float loadFactor, boolean purgeValues) { + super(keyType, valueType, capacity, loadFactor, purgeValues); + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + doWriteObject(out); + } + + /** + * Read the map in using a custom routine. + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + doReadObject(in); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java new file mode 100644 index 0000000..cf814f7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/collections/ResettableIterator.java @@ -0,0 +1,38 @@ +// GenericsNote: Converted. +/* + * Copyright 2003-2004 The Apache Software Foundation + * + * 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 org.jivesoftware.smack.util.collections; + +import java.util.Iterator; + +/** + * Defines an iterator that can be reset back to an initial state. + *

      + * This interface allows an iterator to be repeatedly reused. + * + * @author Matt Hall, John Watkinson, Stephen Colebourne + * @version $Revision: 1.1 $ $Date: 2005/10/11 17:05:19 $ + * @since Commons Collections 3.0 + */ +public interface ResettableIterator extends Iterator { + + /** + * Resets the iterator back to the position at which the iterator + * was created. + */ + public void reset(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html new file mode 100644 index 0000000..e34bfe3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smack/util/package.html @@ -0,0 +1 @@ +Utility classes. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java new file mode 100644 index 0000000..4acaa49 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatState.java @@ -0,0 +1,50 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +/** + * Represents the current state of a users interaction with another user. Implemented according to + * XEP-0085. + * + * @author Alexander Wenckus + */ +public enum ChatState { + /** + * User is actively participating in the chat session. + */ + active, + /** + * User is composing a message. + */ + composing, + /** + * User had been composing but now has stopped. + */ + paused, + /** + * User has not been actively participating in the chat session. + */ + inactive, + /** + * User has effectively ended their participation in the chat session. + */ + gone +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java new file mode 100644 index 0000000..9a1bf79 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateListener.java @@ -0,0 +1,40 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.MessageListener; + +/** + * Events for when the state of a user in a chat changes. + * + * @author Alexander Wenckus + */ +public interface ChatStateListener extends MessageListener { + + /** + * Fired when the state of a chat with another user changes. + * + * @param chat the chat in which the state has changed. + * @param state the new state of the participant. + */ + void stateChanged(Chat chat, ChatState state); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java new file mode 100644 index 0000000..d452a9f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ChatStateManager.java @@ -0,0 +1,200 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.util.collections.ReferenceMap; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.ChatStateExtension; + +import java.util.Map; +import java.util.WeakHashMap; + +/** + * Handles chat state for all chats on a particular Connection. This class manages both the + * packet extensions and the disco response neccesary for compliance with + * XEP-0085. + * + * NOTE: {@link org.jivesoftware.smackx.ChatStateManager#getInstance(org.jivesoftware.smack.Connection)} + * needs to be called in order for the listeners to be registered appropriately with the connection. + * If this does not occur you will not receive the update notifications. + * + * @author Alexander Wenckus + * @see org.jivesoftware.smackx.ChatState + * @see org.jivesoftware.smackx.packet.ChatStateExtension + */ +public class ChatStateManager { + + private static final Map managers = + new WeakHashMap(); + + private static final PacketFilter filter = new NotFilter( + new PacketExtensionFilter("http://jabber.org/protocol/chatstates")); + + /** + * Returns the ChatStateManager related to the Connection and it will create one if it does + * not yet exist. + * + * @param connection the connection to return the ChatStateManager + * @return the ChatStateManager related the the connection. + */ + public static ChatStateManager getInstance(final Connection connection) { + if(connection == null) { + return null; + } + synchronized (managers) { + ChatStateManager manager = managers.get(connection); + if (manager == null) { + manager = new ChatStateManager(connection); + manager.init(); + managers.put(connection, manager); + } + + return manager; + } + } + + private final Connection connection; + + private final OutgoingMessageInterceptor outgoingInterceptor = new OutgoingMessageInterceptor(); + + private final IncomingMessageInterceptor incomingInterceptor = new IncomingMessageInterceptor(); + + /** + * Maps chat to last chat state. + */ + private final Map chatStates = + new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.HARD); + + private ChatStateManager(Connection connection) { + this.connection = connection; + } + + private void init() { + connection.getChatManager().addOutgoingMessageInterceptor(outgoingInterceptor, + filter); + connection.getChatManager().addChatListener(incomingInterceptor); + + ServiceDiscoveryManager.getInstanceFor(connection) + .addFeature("http://jabber.org/protocol/chatstates"); + } + + /** + * Sets the current state of the provided chat. This method will send an empty bodied Message + * packet with the state attached as a {@link org.jivesoftware.smack.packet.PacketExtension}, if + * and only if the new chat state is different than the last state. + * + * @param newState the new state of the chat + * @param chat the chat. + * @throws org.jivesoftware.smack.XMPPException + * when there is an error sending the message + * packet. + */ + public void setCurrentState(ChatState newState, Chat chat) throws XMPPException { + if(chat == null || newState == null) { + throw new IllegalArgumentException("Arguments cannot be null."); + } + if(!updateChatState(chat, newState)) { + return; + } + Message message = new Message(); + ChatStateExtension extension = new ChatStateExtension(newState); + message.addExtension(extension); + + chat.sendMessage(message); + } + + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ChatStateManager that = (ChatStateManager) o; + + return connection.equals(that.connection); + + } + + public int hashCode() { + return connection.hashCode(); + } + + private boolean updateChatState(Chat chat, ChatState newState) { + ChatState lastChatState = chatStates.get(chat); + if (lastChatState != newState) { + chatStates.put(chat, newState); + return true; + } + return false; + } + + private void fireNewChatState(Chat chat, ChatState state) { + for (MessageListener listener : chat.getListeners()) { + if (listener instanceof ChatStateListener) { + ((ChatStateListener) listener).stateChanged(chat, state); + } + } + } + + private class OutgoingMessageInterceptor implements PacketInterceptor { + + public void interceptPacket(Packet packet) { + Message message = (Message) packet; + Chat chat = connection.getChatManager().getThreadChat(message.getThread()); + if (chat == null) { + return; + } + if (updateChatState(chat, ChatState.active)) { + message.addExtension(new ChatStateExtension(ChatState.active)); + } + } + } + + private class IncomingMessageInterceptor implements ChatManagerListener, MessageListener { + + public void chatCreated(final Chat chat, boolean createdLocally) { + chat.addMessageListener(this); + } + + public void processMessage(Chat chat, Message message) { + PacketExtension extension + = message.getExtension("http://jabber.org/protocol/chatstates"); + if (extension == null) { + return; + } + + ChatState state; + try { + state = ChatState.valueOf(extension.getElementName()); + } + catch (Exception ex) { + return; + } + + fireNewChatState(chat, state); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java new file mode 100644 index 0000000..7ae0c51 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/DefaultMessageEventRequestListener.java @@ -0,0 +1,55 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +/** + * + * Default implementation of the MessageEventRequestListener interface.

      + * + * This class automatically sends a delivered notification to the sender of the message + * if the sender has requested to be notified when the message is delivered. + * + * @author Gaston Dombiak + */ +public class DefaultMessageEventRequestListener implements MessageEventRequestListener { + + public void deliveredNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + // Send to the message's sender that the message has been delivered + messageEventManager.sendDeliveredNotification(from, packetID); + } + + public void displayedNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } + + public void composingNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } + + public void offlineNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager) + { + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java new file mode 100644 index 0000000..ec6dde4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Form.java @@ -0,0 +1,545 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.DataForm; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.StringTokenizer; + +/** + * Represents a Form for gathering data. The form could be of the following types: + *

        + *
      • form -> Indicates a form to fill out.
      • + *
      • submit -> The form is filled out, and this is the data that is being returned from + * the form.
      • + *
      • cancel -> The form was cancelled. Tell the asker that piece of information.
      • + *
      • result -> Data results being returned from a search, or some other query.
      • + *
      + * + * Depending of the form's type different operations are available. For example, it's only possible + * to set answers if the form is of type "submit". + * + * @author Gaston Dombiak + */ +public class Form { + + public static final String TYPE_FORM = "form"; + public static final String TYPE_SUBMIT = "submit"; + public static final String TYPE_CANCEL = "cancel"; + public static final String TYPE_RESULT = "result"; + + private DataForm dataForm; + + /** + * Returns a new ReportedData if the packet is used for gathering data and includes an + * extension that matches the elementName and namespace "x","jabber:x:data". + * + * @param packet the packet used for gathering data. + * @return the data form parsed from the packet or null if there was not + * a form in the packet. + */ + public static Form getFormFrom(Packet packet) { + // Check if the packet includes the DataForm extension + PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); + if (packetExtension != null) { + // Check if the existing DataForm is not a result of a search + DataForm dataForm = (DataForm) packetExtension; + if (dataForm.getReportedData() == null) + return new Form(dataForm); + } + // Otherwise return null + return null; + } + + /** + * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be + * used for gathering data. + * + * @param dataForm the data form used for gathering data. + */ + public Form(DataForm dataForm) { + this.dataForm = dataForm; + } + + /** + * Creates a new Form of a given type from scratch.

      + * + * Possible form types are: + *

        + *
      • form -> Indicates a form to fill out.
      • + *
      • submit -> The form is filled out, and this is the data that is being returned from + * the form.
      • + *
      • cancel -> The form was cancelled. Tell the asker that piece of information.
      • + *
      • result -> Data results being returned from a search, or some other query.
      • + *
      + * + * @param type the form's type (e.g. form, submit,cancel,result). + */ + public Form(String type) { + this.dataForm = new DataForm(type); + } + + /** + * Adds a new field to complete as part of the form. + * + * @param field the field to complete. + */ + public void addField(FormField field) { + dataForm.addField(field); + } + + /** + * Sets a new String value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised.

      + * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * can use this message where the String value is the String representation of the object. + * + * @param variable the variable name that was completed. + * @param value the String value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type.. + */ + public void setAnswer(String variable, String value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType()) + && !FormField.TYPE_JID_SINGLE.equals(field.getType()) + && !FormField.TYPE_HIDDEN.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type String."); + } + setAnswer(field, value); + } + + /** + * Sets a new int value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the int value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, int value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type int."); + } + setAnswer(field, value); + } + + /** + * Sets a new long value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the long value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, long value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type long."); + } + setAnswer(field, value); + } + + /** + * Sets a new float value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the float value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, float value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type float."); + } + setAnswer(field, value); + } + + /** + * Sets a new double value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the double value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, double value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) + && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) + && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type double."); + } + setAnswer(field, value); + } + + /** + * Sets a new boolean value to a given form's field. The field whose variable matches the + * requested variable will be completed with the specified value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable name that was completed. + * @param value the boolean value that was answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable or + * if the answer type does not correspond with the field type. + */ + public void setAnswer(String variable, boolean value) { + FormField field = getField(variable); + if (field == null) { + throw new IllegalArgumentException("Field not found for the specified variable name."); + } + if (!FormField.TYPE_BOOLEAN.equals(field.getType())) { + throw new IllegalArgumentException("This field is not of type boolean."); + } + setAnswer(field, (value ? "1" : "0")); + } + + /** + * Sets a new Object value to a given form's field. In fact, the object representation + * (i.e. #toString) will be the actual value of the field.

      + * + * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you + * will need to use {@link #setAnswer(String, String))} where the String value is the + * String representation of the object.

      + * + * Before setting the new value to the field we will check if the form is of type submit. If + * the form isn't of type submit means that it's not possible to complete the form and an + * exception will be thrown. + * + * @param field the form field that was completed. + * @param value the Object value that was answered. The object representation will be the + * actual value. + * @throws IllegalStateException if the form is not of type "submit". + */ + private void setAnswer(FormField field, Object value) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + field.resetValues(); + field.addValue(value.toString()); + } + + /** + * Sets a new values to a given form's field. The field whose variable matches the requested + * variable will be completed with the specified values. If no field could be found for + * the specified variable then an exception will be raised.

      + * + * The Objects contained in the List could be of any type. The String representation of them + * (i.e. #toString) will be actually used when sending the answer to the server. + * + * @param variable the variable that was completed. + * @param values the values that were answered. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setAnswer(String variable, List values) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + // Check that the field can accept a collection of values + if (!FormField.TYPE_JID_MULTI.equals(field.getType()) + && !FormField.TYPE_LIST_MULTI.equals(field.getType()) + && !FormField.TYPE_LIST_SINGLE.equals(field.getType()) + && !FormField.TYPE_HIDDEN.equals(field.getType())) { + throw new IllegalArgumentException("This field only accept list of values."); + } + // Clear the old values + field.resetValues(); + // Set the new values. The string representation of each value will be actually used. + field.addValues(values); + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Sets the default value as the value of a given form's field. The field whose variable matches + * the requested variable will be completed with its default value. If no field could be found + * for the specified variable then an exception will be raised. + * + * @param variable the variable to complete with its default value. + * @throws IllegalStateException if the form is not of type "submit". + * @throws IllegalArgumentException if the form does not include the specified variable. + */ + public void setDefaultAnswer(String variable) { + if (!isSubmitType()) { + throw new IllegalStateException("Cannot set an answer if the form is not of type " + + "\"submit\""); + } + FormField field = getField(variable); + if (field != null) { + // Clear the old values + field.resetValues(); + // Set the default value + for (Iterator it = field.getValues(); it.hasNext();) { + field.addValue(it.next()); + } + } + else { + throw new IllegalArgumentException("Couldn't find a field for the specified variable."); + } + } + + /** + * Returns an Iterator for the fields that are part of the form. + * + * @return an Iterator for the fields that are part of the form. + */ + public Iterator getFields() { + return dataForm.getFields(); + } + + /** + * Returns the field of the form whose variable matches the specified variable. + * The fields of type FIXED will never be returned since they do not specify a + * variable. + * + * @param variable the variable to look for in the form fields. + * @return the field of the form whose variable matches the specified variable. + */ + public FormField getField(String variable) { + if (variable == null || variable.equals("")) { + throw new IllegalArgumentException("Variable must not be null or blank."); + } + // Look for the field whose variable matches the requested variable + FormField field; + for (Iterator it=getFields();it.hasNext();) { + field = it.next(); + if (variable.equals(field.getVariable())) { + return field; + } + } + return null; + } + + /** + * Returns the instructions that explain how to fill out the form and what the form is about. + * + * @return instructions that explain how to fill out the form. + */ + public String getInstructions() { + StringBuilder sb = new StringBuilder(); + // Join the list of instructions together separated by newlines + for (Iterator it = dataForm.getInstructions(); it.hasNext();) { + sb.append((String) it.next()); + // If this is not the last instruction then append a newline + if (it.hasNext()) { + sb.append("\n"); + } + } + return sb.toString(); + } + + + /** + * Returns the description of the data. It is similar to the title on a web page or an X + * window. You can put a on either a form to fill out, or a set of data results. + * + * @return description of the data. + */ + public String getTitle() { + return dataForm.getTitle(); + } + + + /** + * Returns the meaning of the data within the context. The data could be part of a form + * to fill out, a form submission or data results.<p> + * + * Possible form types are: + * <ul> + * <li>form -> Indicates a form to fill out.</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * @return the form's type. + */ + public String getType() { + return dataForm.getType(); + } + + + /** + * Sets instructions that explain how to fill out the form and what the form is about. + * + * @param instructions instructions that explain how to fill out the form. + */ + public void setInstructions(String instructions) { + // Split the instructions into multiple instructions for each existent newline + ArrayList<String> instructionsList = new ArrayList<String>(); + StringTokenizer st = new StringTokenizer(instructions, "\n"); + while (st.hasMoreTokens()) { + instructionsList.add(st.nextToken()); + } + // Set the new list of instructions + dataForm.setInstructions(instructionsList); + + } + + + /** + * Sets the description of the data. It is similar to the title on a web page or an X window. + * You can put a <title/> on either a form to fill out, or a set of data results. + * + * @param title description of the data. + */ + public void setTitle(String title) { + dataForm.setTitle(title); + } + + /** + * Returns a DataForm that serves to send this Form to the server. If the form is of type + * submit, it may contain fields with no value. These fields will be removed since they only + * exist to assist the user while editing/completing the form in a UI. + * + * @return the wrapped DataForm. + */ + public DataForm getDataFormToSend() { + if (isSubmitType()) { + // Create a new DataForm that contains only the answered fields + DataForm dataFormToSend = new DataForm(getType()); + for(Iterator<FormField> it=getFields();it.hasNext();) { + FormField field = it.next(); + if (field.getValues().hasNext()) { + dataFormToSend.addField(field); + } + } + return dataFormToSend; + } + return dataForm; + } + + /** + * Returns true if the form is a form to fill out. + * + * @return if the form is a form to fill out. + */ + private boolean isFormType() { + return TYPE_FORM.equals(dataForm.getType()); + } + + /** + * Returns true if the form is a form to submit. + * + * @return if the form is a form to submit. + */ + private boolean isSubmitType() { + return TYPE_SUBMIT.equals(dataForm.getType()); + } + + /** + * Returns a new Form to submit the completed values. The new Form will include all the fields + * of the original form except for the fields of type FIXED. Only the HIDDEN fields will + * include the same value of the original form. The other fields of the new form MUST be + * completed. If a field remains with no answer when sending the completed form, then it won't + * be included as part of the completed form.<p> + * + * The reason why the fields with variables are included in the new form is to provide a model + * for binding with any UI. This means that the UIs will use the original form (of type + * "form") to learn how to render the form, but the UIs will bind the fields to the form of + * type submit. + * + * @return a Form to submit the completed values. + */ + public Form createAnswerForm() { + if (!isFormType()) { + throw new IllegalStateException("Only forms of type \"form\" could be answered"); + } + // Create a new Form + Form form = new Form(TYPE_SUBMIT); + for (Iterator<FormField> fields=getFields(); fields.hasNext();) { + FormField field = fields.next(); + // Add to the new form any type of field that includes a variable. + // Note: The fields of type FIXED are the only ones that don't specify a variable + if (field.getVariable() != null) { + FormField newField = new FormField(field.getVariable()); + newField.setType(field.getType()); + form.addField(newField); + // Set the answer ONLY to the hidden fields + if (FormField.TYPE_HIDDEN.equals(field.getType())) { + // Since a hidden field could have many values we need to collect them + // in a list + List<String> values = new ArrayList<String>(); + for (Iterator<String> it=field.getValues();it.hasNext();) { + values.add(it.next()); + } + form.setAnswer(field.getVariable(), values); + } + } + } + return form; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java new file mode 100644 index 0000000..2f2c00e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/FormField.java @@ -0,0 +1,358 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a field of a form. The field could be used to represent a question to complete, + * a completed question or a data returned from a search. The exact interpretation of the field + * depends on the context where the field is used. + * + * @author Gaston Dombiak + */ +public class FormField { + + public static final String TYPE_BOOLEAN = "boolean"; + public static final String TYPE_FIXED = "fixed"; + public static final String TYPE_HIDDEN = "hidden"; + public static final String TYPE_JID_MULTI = "jid-multi"; + public static final String TYPE_JID_SINGLE = "jid-single"; + public static final String TYPE_LIST_MULTI = "list-multi"; + public static final String TYPE_LIST_SINGLE = "list-single"; + public static final String TYPE_TEXT_MULTI = "text-multi"; + public static final String TYPE_TEXT_PRIVATE = "text-private"; + public static final String TYPE_TEXT_SINGLE = "text-single"; + + private String description; + private boolean required = false; + private String label; + private String variable; + private String type; + private final List<Option> options = new ArrayList<Option>(); + private final List<String> values = new ArrayList<String>(); + + /** + * Creates a new FormField with the variable name that uniquely identifies the field + * in the context of the form. + * + * @param variable the variable name of the question. + */ + public FormField(String variable) { + this.variable = variable; + } + + /** + * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable + * name. + */ + public FormField() { + this.type = FormField.TYPE_FIXED; + } + + /** + * Returns a description that provides extra clarification about the question. This information + * could be presented to the user either in tool-tip, help button, or as a section of text + * before the question.<p> + * <p/> + * If the question is of type FIXED then the description should remain empty. + * + * @return description that provides extra clarification about the question. + */ + public String getDescription() { + return description; + } + + /** + * Returns the label of the question which should give enough information to the user to + * fill out the form. + * + * @return label of the question. + */ + public String getLabel() { + return label; + } + + /** + * Returns an Iterator for the available options that the user has in order to answer + * the question. + * + * @return Iterator for the available options. + */ + public Iterator<Option> getOptions() { + synchronized (options) { + return Collections.unmodifiableList(new ArrayList<Option>(options)).iterator(); + } + } + + /** + * Returns true if the question must be answered in order to complete the questionnaire. + * + * @return true if the question must be answered in order to complete the questionnaire. + */ + public boolean isRequired() { + return required; + } + + /** + * Returns an indicative of the format for the data to answer. Valid formats are: + * <p/> + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @return format for the data to answer. + */ + public String getType() { + return type; + } + + /** + * Returns an Iterator for the default values of the question if the question is part + * of a form to fill out. Otherwise, returns an Iterator for the answered values of + * the question. + * + * @return an Iterator for the default values or answered values of the question. + */ + public Iterator<String> getValues() { + synchronized (values) { + return Collections.unmodifiableList(new ArrayList<String>(values)).iterator(); + } + } + + /** + * Returns the variable name that the question is filling out. + * + * @return the variable name of the question. + */ + public String getVariable() { + return variable; + } + + /** + * Sets a description that provides extra clarification about the question. This information + * could be presented to the user either in tool-tip, help button, or as a section of text + * before the question.<p> + * <p/> + * If the question is of type FIXED then the description should remain empty. + * + * @param description provides extra clarification about the question. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Sets the label of the question which should give enough information to the user to + * fill out the form. + * + * @param label the label of the question. + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * Sets if the question must be answered in order to complete the questionnaire. + * + * @param required if the question must be answered in order to complete the questionnaire. + */ + public void setRequired(boolean required) { + this.required = required; + } + + /** + * Sets an indicative of the format for the data to answer. Valid formats are: + * <p/> + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @param type an indicative of the format for the data to answer. + */ + public void setType(String type) { + this.type = type; + } + + /** + * Adds a default value to the question if the question is part of a form to fill out. + * Otherwise, adds an answered value to the question. + * + * @param value a default value or an answered value of the question. + */ + public void addValue(String value) { + synchronized (values) { + values.add(value); + } + } + + /** + * Adds a default values to the question if the question is part of a form to fill out. + * Otherwise, adds an answered values to the question. + * + * @param newValues default values or an answered values of the question. + */ + public void addValues(List<String> newValues) { + synchronized (values) { + values.addAll(newValues); + } + } + + /** + * Removes all the values of the field. + */ + protected void resetValues() { + synchronized (values) { + values.removeAll(new ArrayList<String>(values)); + } + } + + /** + * Adss an available options to the question that the user has in order to answer + * the question. + * + * @param option a new available option for the question. + */ + public void addOption(Option option) { + synchronized (options) { + options.add(option); + } + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<field"); + // Add attributes + if (getLabel() != null) { + buf.append(" label=\"").append(getLabel()).append("\""); + } + if (getVariable() != null) { + buf.append(" var=\"").append(getVariable()).append("\""); + } + if (getType() != null) { + buf.append(" type=\"").append(getType()).append("\""); + } + buf.append(">"); + // Add elements + if (getDescription() != null) { + buf.append("<desc>").append(getDescription()).append("</desc>"); + } + if (isRequired()) { + buf.append("<required/>"); + } + // Loop through all the values and append them to the string buffer + for (Iterator<String> i = getValues(); i.hasNext();) { + buf.append("<value>").append(i.next()).append("</value>"); + } + // Loop through all the values and append them to the string buffer + for (Iterator i = getOptions(); i.hasNext();) { + buf.append(((Option) i.next()).toXML()); + } + buf.append("</field>"); + return buf.toString(); + } + + /** + * Represents the available option of a given FormField. + * + * @author Gaston Dombiak + */ + public static class Option { + + private String label; + private String value; + + public Option(String value) { + this.value = value; + } + + public Option(String label, String value) { + this.label = label; + this.value = value; + } + + /** + * Returns the label that represents the option. + * + * @return the label that represents the option. + */ + public String getLabel() { + return label; + } + + /** + * Returns the value of the option. + * + * @return the value of the option. + */ + public String getValue() { + return value; + } + + public String toString() { + return getLabel(); + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<option"); + // Add attribute + if (getLabel() != null) { + buf.append(" label=\"").append(getLabel()).append("\""); + } + buf.append(">"); + // Add element + buf.append("<value>").append(StringUtils.escapeForXML(getValue())).append("</value>"); + + buf.append("</option>"); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java new file mode 100644 index 0000000..5b5836f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/Gateway.java @@ -0,0 +1,333 @@ +package org.jivesoftware.smackx; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Registration; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; + +/** + * This class provides an abstract view to gateways/transports. This class handles all + * actions regarding gateways and transports. + * @author Till Klocke + * + */ +public class Gateway { + + private Connection connection; + private ServiceDiscoveryManager sdManager; + private Roster roster; + private String entityJID; + private Registration registerInfo; + private Identity identity; + private DiscoverInfo info; + + Gateway(Connection connection, String entityJID){ + this.connection = connection; + this.roster = connection.getRoster(); + this.sdManager = ServiceDiscoveryManager.getInstanceFor(connection); + this.entityJID = entityJID; + } + + Gateway(Connection connection, String entityJID, DiscoverInfo info, Identity identity){ + this(connection, entityJID); + this.info = info; + this.identity = identity; + } + + private void discoverInfo() throws XMPPException{ + info = sdManager.discoverInfo(entityJID); + Iterator<Identity> iterator = info.getIdentities(); + while(iterator.hasNext()){ + Identity temp = iterator.next(); + if(temp.getCategory().equalsIgnoreCase("gateway")){ + this.identity = temp; + break; + } + } + } + + private Identity getIdentity() throws XMPPException{ + if(identity==null){ + discoverInfo(); + } + return identity; + } + + private Registration getRegisterInfo(){ + if(registerInfo==null){ + refreshRegisterInfo(); + } + return registerInfo; + } + + private void refreshRegisterInfo(){ + Registration packet = new Registration(); + packet.setFrom(connection.getUser()); + packet.setType(IQ.Type.GET); + packet.setTo(entityJID); + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(packet.getPacketID())); + connection.sendPacket(packet); + Packet result = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if(result instanceof Registration && result.getError()==null){ + Registration register = (Registration)result; + this.registerInfo = register; + } + } + + /** + * Checks if this gateway supports In-Band registration + * @return true if In-Band registration is supported + * @throws XMPPException + */ + public boolean canRegister() throws XMPPException{ + if(info==null){ + discoverInfo(); + } + return info.containsFeature("jabber:iq:register"); + } + + /** + * Returns all fields that are required to register to this gateway + * @return a list of required fields + */ + public List<String> getRequiredFields(){ + return getRegisterInfo().getRequiredFields(); + } + + /** + * Returns the name as proposed in this gateways identity discovered via service + * discovery + * @return a String of its name + * @throws XMPPException + */ + public String getName() throws XMPPException{ + if(identity==null){ + discoverInfo(); + } + return identity.getName(); + } + + /** + * Returns the type as proposed in this gateways identity discovered via service + * discovery. See {@link http://xmpp.org/registrar/disco-categories.html} for + * possible types + * @return a String describing the type + * @throws XMPPException + */ + public String getType() throws XMPPException{ + if(identity==null){ + discoverInfo(); + } + return identity.getType(); + } + + /** + * Returns true if the registration informations indicates that you are already + * registered with this gateway + * @return true if already registered + * @throws XMPPException + */ + public boolean isRegistered() throws XMPPException{ + return getRegisterInfo().isRegistered(); + } + + /** + * Returns the value of specific field of the registration information. Can be used + * to retrieve for example to retrieve username/password used on an already registered + * gateway. + * @param fieldName name of the field + * @return a String containing the value of the field or null + */ + public String getField(String fieldName){ + return getRegisterInfo().getField(fieldName); + } + + /** + * Returns a List of Strings of all field names which contain values. + * @return a List of field names + */ + public List<String> getFieldNames(){ + return getRegisterInfo().getFieldNames(); + } + + /** + * A convenience method for retrieving the username of an existing account + * @return String describing the username + */ + public String getUsername(){ + return getField("username"); + } + + /** + * A convenience method for retrieving the password of an existing accoung + * @return String describing the password + */ + public String getPassword(){ + return getField("password"); + } + + /** + * Returns instructions for registering with this gateway + * @return String containing instructions + */ + public String getInstructions(){ + return getRegisterInfo().getInstructions(); + } + + /** + * With this method you can register with this gateway or modify an existing registration + * @param username String describing the username + * @param password String describing the password + * @param fields additional fields like email. + * @throws XMPPException + */ + public void register(String username, String password, Map<String,String> fields)throws XMPPException{ + if(getRegisterInfo().isRegistered()) { + throw new IllegalStateException("You are already registered with this gateway"); + } + Registration register = new Registration(); + register.setFrom(connection.getUser()); + register.setTo(entityJID); + register.setType(IQ.Type.SET); + register.setUsername(username); + register.setPassword(password); + for(String s : fields.keySet()){ + register.addAttribute(s, fields.get(s)); + } + PacketCollector resultCollector = + connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); + connection.sendPacket(register); + Packet result = + resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + resultCollector.cancel(); + if(result!=null && result instanceof IQ){ + IQ resultIQ = (IQ)result; + if(resultIQ.getError()!=null){ + throw new XMPPException(resultIQ.getError()); + } + if(resultIQ.getType()==IQ.Type.ERROR){ + throw new XMPPException(resultIQ.getError()); + } + connection.addPacketListener(new GatewayPresenceListener(), + new PacketTypeFilter(Presence.class)); + roster.createEntry(entityJID, getIdentity().getName(), new String[]{}); + } + else{ + throw new XMPPException("Packet reply timeout"); + } + } + + /** + * A convenience method for registering or modifying an account on this gateway without + * additional fields + * @param username String describing the username + * @param password String describing the password + * @throws XMPPException + */ + public void register(String username, String password) throws XMPPException{ + register(username, password,new HashMap<String,String>()); + } + + /** + * This method removes an existing registration from this gateway + * @throws XMPPException + */ + public void unregister() throws XMPPException{ + Registration register = new Registration(); + register.setFrom(connection.getUser()); + register.setTo(entityJID); + register.setType(IQ.Type.SET); + register.setRemove(true); + PacketCollector resultCollector = + connection.createPacketCollector(new PacketIDFilter(register.getPacketID())); + connection.sendPacket(register); + Packet result = resultCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + resultCollector.cancel(); + if(result!=null && result instanceof IQ){ + IQ resultIQ = (IQ)result; + if(resultIQ.getError()!=null){ + throw new XMPPException(resultIQ.getError()); + } + if(resultIQ.getType()==IQ.Type.ERROR){ + throw new XMPPException(resultIQ.getError()); + } + RosterEntry gatewayEntry = roster.getEntry(entityJID); + roster.removeEntry(gatewayEntry); + } + else{ + throw new XMPPException("Packet reply timeout"); + } + } + + /** + * Lets you login manually in this gateway. Normally a gateway logins you when it + * receives the first presence broadcasted by your server. But it is possible to + * manually login and logout by sending a directed presence. This method sends an + * empty available presence direct to the gateway. + */ + public void login(){ + Presence presence = new Presence(Presence.Type.available); + login(presence); + } + + /** + * This method lets you send the presence direct to the gateway. Type, To and From + * are modified. + * @param presence the presence used to login to gateway + */ + public void login(Presence presence){ + presence.setType(Presence.Type.available); + presence.setTo(entityJID); + presence.setFrom(connection.getUser()); + connection.sendPacket(presence); + } + + /** + * This method logs you out from this gateway by sending an unavailable presence + * to directly to this gateway. + */ + public void logout(){ + Presence presence = new Presence(Presence.Type.unavailable); + presence.setTo(entityJID); + presence.setFrom(connection.getUser()); + connection.sendPacket(presence); + } + + private class GatewayPresenceListener implements PacketListener{ + + public void processPacket(Packet packet) { + if(packet instanceof Presence){ + Presence presence = (Presence)packet; + if(entityJID.equals(presence.getFrom()) && + roster.contains(presence.getFrom()) && + presence.getType().equals(Presence.Type.subscribe)){ + Presence response = new Presence(Presence.Type.subscribed); + response.setTo(presence.getFrom()); + response.setFrom(StringUtils.parseBareAddress(connection.getUser())); + connection.sendPacket(response); + } + } + + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java new file mode 100644 index 0000000..eee9cfc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GatewayManager.java @@ -0,0 +1,199 @@ +package org.jivesoftware.smackx; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; +import org.jivesoftware.smackx.packet.DiscoverItems.Item; + +/** + * This class is the general entry point to gateway interaction (XEP-0100). + * This class discovers available gateways on the users servers, and + * can give you also a list of gateways the you user is registered with which + * are not on his server. All actual interaction with a gateway is handled in the + * class {@see Gateway}. + * @author Till Klocke + * + */ +public class GatewayManager { + + private static Map<Connection,GatewayManager> instances = + new HashMap<Connection,GatewayManager>(); + + private ServiceDiscoveryManager sdManager; + + private Map<String,Gateway> localGateways = new HashMap<String,Gateway>(); + + private Map<String,Gateway> nonLocalGateways = new HashMap<String,Gateway>(); + + private Map<String,Gateway> gateways = new HashMap<String,Gateway>(); + + private Connection connection; + + private Roster roster; + + private GatewayManager(){ + + } + + /** + * Creates a new instance of GatewayManager + * @param connection + * @throws XMPPException + */ + private GatewayManager(Connection connection) throws XMPPException{ + this.connection = connection; + this.roster = connection.getRoster(); + sdManager = ServiceDiscoveryManager.getInstanceFor(connection); + } + + /** + * Loads all gateways the users server offers + * @throws XMPPException + */ + private void loadLocalGateways() throws XMPPException{ + DiscoverItems items = sdManager.discoverItems(connection.getHost()); + Iterator<Item> iter = items.getItems(); + while(iter.hasNext()){ + String itemJID = iter.next().getEntityID(); + discoverGateway(itemJID); + } + } + + /** + * Discovers {@link DiscoveryInfo} and {@link DiscoveryInfo.Identity} of a gateway + * and creates a {@link Gateway} object representing this gateway. + * @param itemJID + * @throws XMPPException + */ + private void discoverGateway(String itemJID) throws XMPPException{ + DiscoverInfo info = sdManager.discoverInfo(itemJID); + Iterator<Identity> i = info.getIdentities(); + + while(i.hasNext()){ + Identity identity = i.next(); + String category = identity.getCategory(); + if(category.toLowerCase().equals("gateway")){ + gateways.put(itemJID, new Gateway(connection,itemJID)); + if(itemJID.contains(connection.getHost())){ + localGateways.put(itemJID, + new Gateway(connection,itemJID,info,identity)); + } + else{ + nonLocalGateways.put(itemJID, + new Gateway(connection,itemJID,info,identity)); + } + break; + } + } + } + + /** + * Loads all getways which are in the users roster, but are not supplied by the + * users server + * @throws XMPPException + */ + private void loadNonLocalGateways() throws XMPPException{ + if(roster!=null){ + for(RosterEntry entry : roster.getEntries()){ + if(entry.getUser().equalsIgnoreCase(StringUtils.parseServer(entry.getUser())) && + !entry.getUser().contains(connection.getHost())){ + discoverGateway(entry.getUser()); + } + } + } + } + + /** + * Returns an instance of GatewayManager for the given connection. If no instance for + * this connection exists a new one is created and stored in a Map. + * @param connection + * @return an instance of GatewayManager + * @throws XMPPException + */ + public GatewayManager getInstanceFor(Connection connection) throws XMPPException{ + synchronized(instances){ + if(instances.containsKey(connection)){ + return instances.get(connection); + } + GatewayManager instance = new GatewayManager(connection); + instances.put(connection, instance); + return instance; + } + } + + /** + * Returns a list of gateways which are offered by the users server, wether the + * user is registered to them or not. + * @return a List of Gateways + * @throws XMPPException + */ + public List<Gateway> getLocalGateways() throws XMPPException{ + if(localGateways.size()==0){ + loadLocalGateways(); + } + return new ArrayList<Gateway>(localGateways.values()); + } + + /** + * Returns a list of gateways the user has in his roster, but which are offered by + * remote servers. But note that this list isn't automatically refreshed. You have to + * refresh is manually if needed. + * @return a list of gateways + * @throws XMPPException + */ + public List<Gateway> getNonLocalGateways() throws XMPPException{ + if(nonLocalGateways.size()==0){ + loadNonLocalGateways(); + } + return new ArrayList<Gateway>(nonLocalGateways.values()); + } + + /** + * Refreshes the list of gateways offered by remote servers. + * @throws XMPPException + */ + public void refreshNonLocalGateways() throws XMPPException{ + loadNonLocalGateways(); + } + + /** + * Returns a Gateway object for a given JID. Please note that it is not checked if + * the JID belongs to valid gateway. If this JID doesn't belong to valid gateway + * all operations on this Gateway object should fail with a XMPPException. But there is + * no guarantee for that. + * @param entityJID + * @return a Gateway object + */ + public Gateway getGateway(String entityJID){ + if(localGateways.containsKey(entityJID)){ + return localGateways.get(entityJID); + } + if(nonLocalGateways.containsKey(entityJID)){ + return nonLocalGateways.get(entityJID); + } + if(gateways.containsKey(entityJID)){ + return gateways.get(entityJID); + } + Gateway gateway = new Gateway(connection,entityJID); + if(entityJID.contains(connection.getHost())){ + localGateways.put(entityJID, gateway); + } + else{ + nonLocalGateways.put(entityJID, gateway); + } + gateways.put(entityJID, gateway); + return gateway; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java new file mode 100644 index 0000000..a9ed35e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/GroupChatInvitation.java @@ -0,0 +1,115 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A group chat invitation packet extension, which is used to invite other + * users to a group chat room. To invite a user to a group chat room, address + * a new message to the user and set the room name appropriately, as in the + * following code example: + * + * <pre> + * Message message = new Message("user@chat.example.com"); + * message.setBody("Join me for a group chat!"); + * message.addExtension(new GroupChatInvitation("room@chat.example.com");); + * con.sendPacket(message); + * </pre> + * + * To listen for group chat invitations, use a PacketExtensionFilter for the + * <tt>x</tt> element name and <tt>jabber:x:conference</tt> namespace, as in the + * following code example: + * + * <pre> + * PacketFilter filter = new PacketExtensionFilter("x", "jabber:x:conference"); + * // Create a packet collector or packet listeners using the filter... + * </pre> + * + * <b>Note</b>: this protocol is outdated now that the Multi-User Chat (MUC) JEP is available + * (<a href="http://www.jabber.org/jeps/jep-0045.html">JEP-45</a>). However, most + * existing clients still use this older protocol. Once MUC support becomes more + * widespread, this API may be deprecated. + * + * @author Matt Tucker + */ +public class GroupChatInvitation implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "x"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "jabber:x:conference"; + + private String roomAddress; + + /** + * Creates a new group chat invitation to the specified room address. + * GroupChat room addresses are in the form <tt>room@service</tt>, + * where <tt>service</tt> is the name of groupchat server, such as + * <tt>chat.example.com</tt>. + * + * @param roomAddress the address of the group chat room. + */ + public GroupChatInvitation(String roomAddress) { + this.roomAddress = roomAddress; + } + + /** + * Returns the address of the group chat room. GroupChat room addresses + * are in the form <tt>room@service</tt>, where <tt>service</tt> is + * the name of groupchat server, such as <tt>chat.example.com</tt>. + * + * @return the address of the group chat room. + */ + public String getRoomAddress() { + return roomAddress; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<x xmlns=\"jabber:x:conference\" jid=\"").append(roomAddress).append("\"/>"); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + public PacketExtension parseExtension (XmlPullParser parser) throws Exception { + String roomAddress = parser.getAttributeValue("", "jid"); + // Advance to end of extension. + parser.next(); + return new GroupChatInvitation(roomAddress); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java new file mode 100644 index 0000000..07f40f2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LLServiceDiscoveryManager.java @@ -0,0 +1,634 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.LLPresence; +import org.jivesoftware.smack.LLPresenceListener; +import org.jivesoftware.smack.LLService; +import org.jivesoftware.smack.LLServiceConnectionListener; +import org.jivesoftware.smack.LLServiceListener; +import org.jivesoftware.smack.LLServiceStateListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.XMPPLLConnection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.entitycaps.CapsVerListener; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * LLServiceDiscoveryManager acts as a wrapper around ServiceDiscoveryManager + * as ServiceDiscoveryManager only creates an interface for requesting service + * information on existing connections. Simply said it creates new connections + * when needed, uses already active connections when appropriate and applies + * values to new connections. + * + * @author Jonas Ã…dahl + */ +public class LLServiceDiscoveryManager implements ServiceDiscoveryManagerInterface { + private static Map<LLService,LLServiceDiscoveryManager> serviceManagers = + new ConcurrentHashMap<LLService,LLServiceDiscoveryManager>(); + + private Map<String, NodeInformationProvider> nodeInformationProviders = + new ConcurrentHashMap<String,NodeInformationProvider>(); + private final List<String> features = + new ArrayList<String>(); + private DataForm extendedInfo = null; + + private LLService service; + private EntityCapsManager capsManager; + + /*static { + XMPPLLConnection.addLLConnectionListener(new ConnectionServiceMaintainer()); + }*/ + + public static void addServiceListener() { + LLService.addLLServiceListener(new LLServiceListener() { + public void serviceCreated(LLService service) { + addLLServiceDiscoveryManager( + new LLServiceDiscoveryManager(service)); + } + }); + } + + private LLServiceDiscoveryManager(LLService llservice) { + this.service = llservice; + + // Add LLService state listener + service.addServiceStateListener(new LLServiceStateListener() { + private void removeEntry() { + removeLLServiceDiscoveryManager(service); + } + + public void serviceClosed() { + removeEntry(); + } + + public void serviceClosedOnError(Exception e) { + removeEntry(); + } + + public void unknownOriginMessage(Message e) { + // ignore + } + + public void serviceNameChanged(String n, String o) { + // Remove entries + capsManager.removeUserCapsNode(n); + capsManager.removeUserCapsNode(o); + LLPresence np = service.getPresenceByServiceName(n); + LLPresence op = service.getPresenceByServiceName(o); + + // Add existing values, if any + if (np != null && np.getNode() != null && np.getVer() != null) + capsManager.addUserCapsNode(n, np.getNode() + "#" + np.getVer()); + if (op != null && op.getNode() != null && op.getVer() != null) + capsManager.addUserCapsNode(o, op.getNode() + "#" + op.getVer()); + } + }); + + // Entity Capabilities + capsManager = new EntityCapsManager(this); + capsManager.addCapsVerListener(new CapsPresenceRenewer()); + capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), + ServiceDiscoveryManager.getIdentityType(), + ServiceDiscoveryManager.getIdentityName(), + extendedInfo); + + // Add presence listener. The presence listener will gather + // entity caps data + service.addPresenceListener(new LLPresenceListener() { + public void presenceNew(LLPresence presence) { + if (presence.getHash() != null && + presence.getNode() != null && + presence.getVer() != null) { + // Add presence to caps manager + capsManager.addUserCapsNode(presence.getServiceName(), + presence.getNode() + "#" + presence.getVer()); + } + } + + public void presenceRemove(LLPresence presence) { + + } + }); + + service.addLLServiceConnectionListener(new ConnectionServiceMaintainer()); + } + + /** + * Add LLServiceDiscoveryManager to the map of existing ones. + */ + private static void addLLServiceDiscoveryManager(LLServiceDiscoveryManager manager) { + serviceManagers.put(manager.service, manager); + } + + /** + * Remove LLServiceDiscoveryManager from the map of existing ones. + */ + private static void removeLLServiceDiscoveryManager(LLService service) { + serviceManagers.remove(service); + } + + /** + * Get the LLServiceDiscoveryManager instance for a specific Link-local service. + * + * @param service + */ + public static LLServiceDiscoveryManager getInstanceFor(LLService service) { + return serviceManagers.get(service); + } + + /** + * Returns the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @return the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static String getIdentityName() { + return ServiceDiscoveryManager.getIdentityName(); + } + + /** + * Sets the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @param name the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static void setIdentityName(String name) { + ServiceDiscoveryManager.setIdentityType(name); + } + + /** + * Returns the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @return the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static String getIdentityType() { + return ServiceDiscoveryManager.getIdentityType(); + } + + /** + * Sets the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://www.jabber.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @param type the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static void setIdentityType(String type) { + ServiceDiscoveryManager.setIdentityType(type); + } + + /** + * Add discover info response data. + * + * @param response the discover info response packet + */ + public void addDiscoverInfoTo(DiscoverInfo response) { + // Set this client identity + DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", + getIdentityName()); + identity.setType(getIdentityType()); + response.addIdentity(identity); + // Add the registered features to the response + synchronized (features) { + // Add Entity Capabilities (XEP-0115) feature node. + response.addFeature("http://jabber.org/protocol/caps"); + + for (Iterator<String> it = getFeatures(); it.hasNext();) { + response.addFeature(it.next()); + } + if (extendedInfo != null) { + response.addExtension(extendedInfo); + } + } + } + + /** + * Get a DiscoverInfo for the current entity caps node. + * + * @return a DiscoverInfo for the current entity caps node + */ + public DiscoverInfo getOwnDiscoverInfo() { + DiscoverInfo di = new DiscoverInfo(); + di.setType(IQ.Type.RESULT); + di.setNode(capsManager.getNode() + "#" + getEntityCapsVersion()); + + // Add discover info + addDiscoverInfoTo(di); + + for (String feature : features) { + di.addFeature(feature); + } + + return di; + } + + /** + * Returns a new or already established connection to the given service name. + * + * @param serviceName remote service to which we wish to be connected to. + * @returns an established connection to the given service name. + */ + private XMPPLLConnection getConnection(String serviceName) throws XMPPException { + return service.getConnection(serviceName); + } + + /** + * Returns a ServiceDiscoveryManager instance for a new or already established + * connection to the given service name. + * + * @param serviceName the name of the service we wish to get the ServiceDiscoveryManager instance for. + * @returns the ServiceDiscoveryManager instance. + */ + private ServiceDiscoveryManager getInstance(String serviceName) throws XMPPException { + return ServiceDiscoveryManager.getInstanceFor(getConnection(serviceName)); + } + + /** + * Registers extended discovery information of this XMPP entity. When this + * client is queried for its information this data form will be returned as + * specified by XEP-0128. + * <p> + * + * Since no packet is actually sent to the server it is safe to perform this + * operation before logging to the server. In fact, you may want to + * configure the extended info before logging to the server so that the + * information is already available if it is required upon login. + * + * @param info + * the data form that contains the extend service discovery + * information. + */ + public void setExtendedInfo(DataForm info) { + extendedInfo = info; + + // set for already active connections + for (XMPPLLConnection connection : service.getConnections()) + ServiceDiscoveryManager.getInstanceFor(connection).setExtendedInfo(info); + + renewEntityCapsVersion(); + } + + /** + * Removes the dataform containing extended service discovery information + * from the information returned by this XMPP entity.<p> + * + * Since no packet is actually sent to the server it is safe to perform this + * operation before logging to the server. + */ + public void removeExtendedInfo() { + extendedInfo = null; + + // remove for already active connections + for (XMPPLLConnection connection : service.getConnections()) + ServiceDiscoveryManager.getInstanceFor(connection).removeExtendedInfo(); + + renewEntityCapsVersion(); + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String serviceName) throws XMPPException { + DiscoverInfo info = capsManager.getDiscoverInfoByUser(serviceName); + + // If there is no cached information retrieve new one + if (info == null) { + // If the caps node is known, use it in the request. + String node = null; + + if (capsManager != null) { + // Get the newest node#version + node = capsManager.getNodeVersionByUser(serviceName); + } + + return discoverInfo(serviceName, node); + } + else { + return info; + } + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String serviceName, String node) throws XMPPException { + // Discover the entity's info + DiscoverInfo disco = new DiscoverInfo(); + disco.setType(IQ.Type.GET); + disco.setTo(serviceName); + disco.setNode(node); + + IQ result = service.getIQResponse(disco); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + if (result instanceof DiscoverInfo) { + return (DiscoverInfo) result; + } + throw new XMPPException("Result was not a disco info reply."); + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String entityID) throws XMPPException { + return discoverItems(entityID, null); + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered items. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String serviceName, String node) throws XMPPException { + // Discover the entity's items + DiscoverItems disco = new DiscoverItems(); + disco.setType(IQ.Type.GET); + disco.setTo(serviceName); + disco.setNode(node); + + IQ result = service.getIQResponse(disco); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + if (result instanceof DiscoverItems) { + return (DiscoverItems) result; + } + throw new XMPPException("Result was not a disco items reply."); + } + + /** + * Sets the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. Every time this client receives a disco request + * regarding the items of a given node, the provider associated to that node will be the + * responsible for providing the requested information.<p> + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node whose items will be provided by the NodeInformationProvider. + * @param listener the NodeInformationProvider responsible for providing items related + * to the node. + */ + public void setNodeInformationProvider(String node, + NodeInformationProvider listener) { + // store this NodeInformationProvider so we can add it to new connections. + nodeInformationProviders.put(node, listener); + + // set for already active connections + for (XMPPLLConnection connection : service.getConnections()) + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node, listener); + } + + /** + * Removes the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. This means that no more information will be + * available for the specified node. + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node to remove the associated NodeInformationProvider. + */ + public void removeNodeInformationProvider(String node) { + // remove from wrapper class + nodeInformationProviders.remove(node); + + // remove from existing connections + for (XMPPLLConnection connection : service.getConnections()) + ServiceDiscoveryManager.getInstanceFor(connection).removeNodeInformationProvider(node); + } + + /** + * Returns the supported features by this XMPP entity. + * + * @return an Iterator on the supported features by this XMPP entity. + */ + public Iterator<String> getFeatures() { + synchronized (features) { + return Collections.unmodifiableList(new ArrayList<String>(features)).iterator(); + } + } + + /** + * Registers that a new feature is supported by this XMPP entity. When this client is + * queried for its information the registered features will be answered.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. In fact, you may want to configure the supported features + * before logging to the server so that the information is already available if it is required + * upon login. + * + * @param feature the feature to register as supported. + */ + public void addFeature(String feature) { + synchronized (features) { + features.add(feature); + } + + renewEntityCapsVersion(); + } + + /** + * Removes the specified feature from the supported features by this XMPP entity.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. + * + * @param feature the feature to remove from the supported features. + */ + public void removeFeature(String feature) { + synchronized (features) { + features.remove(feature); + for (XMPPLLConnection connection : service.getConnections()) + ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(feature); + } + + renewEntityCapsVersion(); + } + + /** + * Returns true if the specified feature is registered in the ServiceDiscoveryManager. + * + * @param feature the feature to look for. + * @return a boolean indicating if the specified featured is registered or not. + */ + public boolean includesFeature(String feature) { + synchronized (features) { + return features.contains(feature); + } + } + + /** + * Returns true if the server supports publishing of items. A client may wish to publish items + * to the server so that the server can provide items associated to the client. These items will + * be returned by the server whenever the server receives a disco request targeted to the bare + * address of the client (i.e. user@host.com). + * + * @param entityID the address of the XMPP entity. + * @return true if the server supports publishing of items. + * @throws XMPPException if the operation failed for some reason. + */ + public boolean canPublishItems(String entityID) throws XMPPException { + DiscoverInfo info = discoverInfo(entityID); + return ServiceDiscoveryManager.canPublishItems(info); + } + + /** + * Publishes new items to a parent entity. The item elements to publish MUST have at least + * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, DiscoverItems discoverItems) + throws XMPPException { + publishItems(entityID, null, discoverItems); + } + + /** + * Publishes new items to a parent entity and node. The item elements to publish MUST have at + * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, String node, DiscoverItems discoverItems) + throws XMPPException { + getInstance(entityID).publishItems(entityID, node, discoverItems); + } + + private void renewEntityCapsVersion() { + if (capsManager != null) { + capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), + ServiceDiscoveryManager.getIdentityType(), + ServiceDiscoveryManager.getIdentityName(), + extendedInfo); + } + } + + private String getEntityCapsVersion() { + if (capsManager != null) { + return capsManager.getCapsVersion(); + } + else { + return null; + } + } + + + /** + * In case that a connection is unavailable we create a new connection + * and push the service discovery procedure until the new connection is + * established. + */ + private class ConnectionServiceMaintainer implements LLServiceConnectionListener { + + public void connectionCreated(XMPPLLConnection connection) { + // Add service discovery for Link-local connections.\ + ServiceDiscoveryManager manager = + new ServiceDiscoveryManager(connection); + + // Set Entity Capabilities Manager + manager.setEntityCapsManager(capsManager); + + // Set extended info + manager.setExtendedInfo(extendedInfo); + + // Set node information providers + for (Map.Entry<String,NodeInformationProvider> entry : + nodeInformationProviders.entrySet()) { + manager.setNodeInformationProvider(entry.getKey(), entry.getValue()); + } + + // add features + synchronized (features) { + for (String feature : features) { + manager.addFeature(feature); + } + } + } + } + + private class CapsPresenceRenewer implements CapsVerListener { + public void capsVerUpdated(String ver) { + synchronized (service) { + try { + LLPresence presence = service.getLocalPresence(); + presence.setHash(EntityCapsManager.HASH_METHOD); + presence.setNode(capsManager.getNode()); + presence.setVer(ver); + service.updatePresence(presence); + } + catch (XMPPException xe) { + // ignore + } + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java new file mode 100644 index 0000000..9e231c7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/LastActivityManager.java @@ -0,0 +1,172 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.LastActivity; + +/** + * A last activity manager for handling information about the last activity associated + * with a Jabber ID. A manager handles incoming LastActivity requests of existing + * Connections. It also allows to request last activity information of other users.<p> + * + * LastActivity (JEP-012) based on the sending JID's type allows for retrieval of: + * <ol> + * <li>How long a particular user has been idle + * <li>How long a particular user has been logged-out and the message the specified when doing so. + * <li>How long a host has been up. + * </ol> + * <p/> + * + * For example to get the idle time of a user logged in a resource, simple send the + * LastActivity packet to them, as in the following code:<p> + * + * <pre> + * Connection con = new XMPPConnection("jabber.org"); + * con.login("john", "doe"); + * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org/Smack"); + * </pre> + * + * To get the lapsed time since the last user logout is the same as above but with + * out the resource: + * <pre> + * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org"); + * </pre> + * + * To get the uptime of a host, you simple send the LastActivity packet to it, as in the + * following code example:<p> + * + * <pre> + * LastActivity activity = LastActivity.getLastActivity(con, "jabber.org"); + * </pre> + * + * @author Gabriel Guardincerri + */ + +public class LastActivityManager { + + private long lastMessageSent; + + private Connection connection; + + // Enable the LastActivity support on every established connection + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + new LastActivityManager(connection); + } + }); + } + + /** + * Creates a last activity manager to response last activity requests. + * + * @param connection The Connection that the last activity requests will use. + */ + private LastActivityManager(Connection connection) { + this.connection = connection; + + // Listen to all the sent messages to reset the idle time on each one + connection.addPacketSendingListener(new PacketListener() { + public void processPacket(Packet packet) { + resetIdleTime(); + } + }, null); + + // Register a listener for a last activity query + connection.addPacketListener(new PacketListener() { + + public void processPacket(Packet packet) { + LastActivity message = new LastActivity(); + message.setType(IQ.Type.RESULT); + message.setTo(packet.getFrom()); + message.setFrom(packet.getTo()); + message.setPacketID(packet.getPacketID()); + message.setLastActivity(getIdleTime()); + + LastActivityManager.this.connection.sendPacket(message); + } + + }, new AndFilter(new IQTypeFilter(IQ.Type.GET), new PacketTypeFilter(LastActivity.class))); + } + + /** + * Resets the idle time to 0, this should be invoked when a new message is + * sent. + */ + private void resetIdleTime() { + lastMessageSent = System.currentTimeMillis(); + } + + /** + * The idle time is the lapsed time between the last message sent and now. + * + * @return the lapsed time between the last message sent and now. + */ + private long getIdleTime() { + long now = System.currentTimeMillis(); + return ((now - lastMessageSent) / 1000); + } + + /** + * Returns the last activity of a particular jid. If the jid is a full JID + * (i.e., a JID of the form of 'user@host/resource') then the last activity + * is the idle time of that connected resource. On the other hand, when the + * jid is a bare JID (e.g. 'user@host') then the last activity is the lapsed + * time since the last logout or 0 if the user is currently logged in. Moreover, + * when the jid is a server or component (e.g., a JID of the form 'host') the + * last activity is the uptime. + * + * @param con the current Connection. + * @param jid the JID of the user. + * @return the LastActivity packet of the jid. + * @throws XMPPException thrown if a server error has occured. + */ + public static LastActivity getLastActivity(Connection con, String jid) + throws XMPPException { + LastActivity activity = new LastActivity(); + activity.setTo(jid); + + PacketCollector collector = + con.createPacketCollector(new PacketIDFilter(activity.getPacketID())); + con.sendPacket(activity); + + LastActivity response = + (LastActivity) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java new file mode 100644 index 0000000..f8f553a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventManager.java @@ -0,0 +1,310 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.MessageEvent; + +/** + * Manages message events requests and notifications. A MessageEventManager provides a high + * level access to request for notifications and send event notifications. It also provides + * an easy way to hook up custom logic when requests or notifications are received. + * + * @author Gaston Dombiak + */ +public class MessageEventManager { + + private List<MessageEventNotificationListener> messageEventNotificationListeners = new ArrayList<MessageEventNotificationListener>(); + private List<MessageEventRequestListener> messageEventRequestListeners = new ArrayList<MessageEventRequestListener>(); + + private Connection con; + + private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:event"); + private PacketListener packetListener; + + /** + * Creates a new message event manager. + * + * @param con a Connection to a XMPP server. + */ + public MessageEventManager(Connection con) { + this.con = con; + init(); + } + + /** + * Adds event notification requests to a message. For each event type that + * the user wishes event notifications from the message recepient for, <tt>true</tt> + * should be passed in to this method. + * + * @param message the message to add the requested notifications. + * @param offline specifies if the offline event is requested. + * @param delivered specifies if the delivered event is requested. + * @param displayed specifies if the displayed event is requested. + * @param composing specifies if the composing event is requested. + */ + public static void addNotificationsRequests(Message message, boolean offline, + boolean delivered, boolean displayed, boolean composing) + { + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setOffline(offline); + messageEvent.setDelivered(delivered); + messageEvent.setDisplayed(displayed); + messageEvent.setComposing(composing); + message.addExtension(messageEvent); + } + + /** + * Adds a message event request listener. The listener will be fired anytime a request for + * event notification is received. + * + * @param messageEventRequestListener a message event request listener. + */ + public void addMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { + synchronized (messageEventRequestListeners) { + if (!messageEventRequestListeners.contains(messageEventRequestListener)) { + messageEventRequestListeners.add(messageEventRequestListener); + } + } + } + + /** + * Removes a message event request listener. The listener will be fired anytime a request for + * event notification is received. + * + * @param messageEventRequestListener a message event request listener. + */ + public void removeMessageEventRequestListener(MessageEventRequestListener messageEventRequestListener) { + synchronized (messageEventRequestListeners) { + messageEventRequestListeners.remove(messageEventRequestListener); + } + } + + /** + * Adds a message event notification listener. The listener will be fired anytime a notification + * event is received. + * + * @param messageEventNotificationListener a message event notification listener. + */ + public void addMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { + synchronized (messageEventNotificationListeners) { + if (!messageEventNotificationListeners.contains(messageEventNotificationListener)) { + messageEventNotificationListeners.add(messageEventNotificationListener); + } + } + } + + /** + * Removes a message event notification listener. The listener will be fired anytime a notification + * event is received. + * + * @param messageEventNotificationListener a message event notification listener. + */ + public void removeMessageEventNotificationListener(MessageEventNotificationListener messageEventNotificationListener) { + synchronized (messageEventNotificationListeners) { + messageEventNotificationListeners.remove(messageEventNotificationListener); + } + } + + /** + * Fires message event request listeners. + */ + private void fireMessageEventRequestListeners( + String from, + String packetID, + String methodName) { + MessageEventRequestListener[] listeners = null; + Method method; + synchronized (messageEventRequestListeners) { + listeners = new MessageEventRequestListener[messageEventRequestListeners.size()]; + messageEventRequestListeners.toArray(listeners); + } + try { + method = + MessageEventRequestListener.class.getDeclaredMethod( + methodName, + new Class[] { String.class, String.class, MessageEventManager.class }); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], new Object[] { from, packetID, this }); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Fires message event notification listeners. + */ + private void fireMessageEventNotificationListeners( + String from, + String packetID, + String methodName) { + MessageEventNotificationListener[] listeners = null; + Method method; + synchronized (messageEventNotificationListeners) { + listeners = + new MessageEventNotificationListener[messageEventNotificationListeners.size()]; + messageEventNotificationListeners.toArray(listeners); + } + try { + method = + MessageEventNotificationListener.class.getDeclaredMethod( + methodName, + new Class[] { String.class, String.class }); + for (int i = 0; i < listeners.length; i++) { + method.invoke(listeners[i], new Object[] { from, packetID }); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void init() { + // Listens for all message event packets and fire the proper message event listeners. + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + MessageEvent messageEvent = + (MessageEvent) message.getExtension("x", "jabber:x:event"); + if (messageEvent.isMessageEventRequest()) { + // Fire event for requests of message events + for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) + fireMessageEventRequestListeners( + message.getFrom(), + message.getPacketID(), + ((String) it.next()).concat("NotificationRequested")); + } else + // Fire event for notifications of message events + for (Iterator it = messageEvent.getEventTypes(); it.hasNext();) + fireMessageEventNotificationListeners( + message.getFrom(), + messageEvent.getPacketID(), + ((String) it.next()).concat("Notification")); + + }; + + }; + con.addPacketListener(packetListener, packetFilter); + } + + /** + * Sends the notification that the message was delivered to the sender of the original message + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendDeliveredNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setDelivered(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the message was displayed to the sender of the original message + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendDisplayedNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setDisplayed(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the receiver of the message is composing a reply + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendComposingNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setComposing(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + /** + * Sends the notification that the receiver of the message has cancelled composing a reply. + * + * @param to the recipient of the notification. + * @param packetID the id of the message to send. + */ + public void sendCancelledNotification(String to, String packetID) { + // Create the message to send + Message msg = new Message(to); + // Create a MessageEvent Package and add it to the message + MessageEvent messageEvent = new MessageEvent(); + messageEvent.setCancelled(true); + messageEvent.setPacketID(packetID); + msg.addExtension(messageEvent); + // Send the packet + con.sendPacket(msg); + } + + public void destroy() { + if (con != null) { + con.removePacketListener(packetListener); + } + } + + protected void finalize() throws Throwable { + destroy(); + super.finalize(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java new file mode 100644 index 0000000..335dae2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventNotificationListener.java @@ -0,0 +1,74 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +/** + * + * A listener that is fired anytime a message event notification is received. + * Message event notifications are received as a consequence of the request + * to receive notifications when sending a message. + * + * @author Gaston Dombiak + */ +public interface MessageEventNotificationListener { + + /** + * Called when a notification of message delivered is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void deliveredNotification(String from, String packetID); + + /** + * Called when a notification of message displayed is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void displayedNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message is composing a reply is + * received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void composingNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message is offline is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void offlineNotification(String from, String packetID); + + /** + * Called when a notification that the receiver of the message cancelled the reply + * is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + */ + public void cancelledNotification(String from, String packetID); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java new file mode 100644 index 0000000..86e0808 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MessageEventRequestListener.java @@ -0,0 +1,86 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +/** + * + * A listener that is fired anytime a message event request is received. + * Message event requests are received when the received message includes an extension + * like this: + * + * <pre> + * <x xmlns='jabber:x:event'> + * <offline/> + * <delivered/> + * <composing/> + * </x> + * </pre> + * + * In this example you can see that the sender of the message requests to be notified + * when the user couldn't receive the message because he/she is offline, the message + * was delivered or when the receiver of the message is composing a reply. + * + * @author Gaston Dombiak + */ +public interface MessageEventRequestListener { + + /** + * Called when a request for message delivered notification is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void deliveredNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request for message displayed notification is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void displayedNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request that the receiver of the message is composing a reply notification is + * received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void composingNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + + /** + * Called when a request that the receiver of the message is offline is received. + * + * @param from the user that sent the notification. + * @param packetID the id of the message that was sent. + * @param messageEventManager the messageEventManager that fired the listener. + */ + public void offlineNotificationRequested(String from, String packetID, + MessageEventManager messageEventManager); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java new file mode 100644 index 0000000..a9ac0f8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientInfo.java @@ -0,0 +1,98 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.MultipleAddresses; + +import java.util.List; + +/** + * MultipleRecipientInfo keeps information about the multiple recipients extension included + * in a received packet. Among the information we can find the list of TO and CC addresses. + * + * @author Gaston Dombiak + */ +public class MultipleRecipientInfo { + + MultipleAddresses extension; + + MultipleRecipientInfo(MultipleAddresses extension) { + this.extension = extension; + } + + /** + * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address} + * that were the primary recipients of the packet. + * + * @return list of primary recipients of the packet. + */ + public List getTOAddresses() { + return extension.getAddressesOfType(MultipleAddresses.TO); + } + + /** + * Returns the list of {@link org.jivesoftware.smackx.packet.MultipleAddresses.Address} + * that were the secondary recipients of the packet. + * + * @return list of secondary recipients of the packet. + */ + public List getCCAddresses() { + return extension.getAddressesOfType(MultipleAddresses.CC); + } + + /** + * Returns the JID of a MUC room to which responses should be sent or <tt>null</tt> if + * no specific address was provided. When no specific address was provided then the reply + * can be sent to any or all recipients. Otherwise, the user should join the specified room + * and send the reply to the room. + * + * @return the JID of a MUC room to which responses should be sent or <tt>null</tt> if + * no specific address was provided. + */ + public String getReplyRoom() { + List replyRoom = extension.getAddressesOfType(MultipleAddresses.REPLY_ROOM); + return replyRoom.isEmpty() ? null : ((MultipleAddresses.Address) replyRoom.get(0)).getJid(); + } + + /** + * Returns true if the received packet should not be replied. Use + * {@link MultipleRecipientManager#reply(org.jivesoftware.smack.Connection, org.jivesoftware.smack.packet.Message, org.jivesoftware.smack.packet.Message)} + * to send replies. + * + * @return true if the received packet should not be replied. + */ + public boolean shouldNotReply() { + return !extension.getAddressesOfType(MultipleAddresses.NO_REPLY).isEmpty(); + } + + /** + * Returns the address to which all replies are requested to be sent or <tt>null</tt> if + * no specific address was provided. When no specific address was provided then the reply + * can be sent to any or all recipients. + * + * @return the address to which all replies are requested to be sent or <tt>null</tt> if + * no specific address was provided. + */ + public MultipleAddresses.Address getReplyAddress() { + List replyTo = extension.getAddressesOfType(MultipleAddresses.REPLY_TO); + return replyTo.isEmpty() ? null : (MultipleAddresses.Address) replyTo.get(0); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java new file mode 100644 index 0000000..e8de33a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/MultipleRecipientManager.java @@ -0,0 +1,353 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.Cache; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.MultipleAddresses; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * A MultipleRecipientManager allows to send packets to multiple recipients by making use of + * <a href="http://www.jabber.org/jeps/jep-0033.html">JEP-33: Extended Stanza Addressing</a>. + * It also allows to send replies to packets that were sent to multiple recipients. + * + * @author Gaston Dombiak + */ +public class MultipleRecipientManager { + + /** + * Create a cache to hold the 100 most recently accessed elements for a period of + * 24 hours. + */ + private static Cache services = new Cache(100, 24 * 60 * 60 * 1000); + + /** + * Sends the specified packet to the list of specified recipients using the + * specified connection. If the server has support for JEP-33 then only one + * packet is going to be sent to the server with the multiple recipient instructions. + * However, if JEP-33 is not supported by the server then the client is going to send + * the packet to each recipient. + * + * @param connection the connection to use to send the packet. + * @param packet the packet to send to the list of recipients. + * @param to the list of JIDs to include in the TO list or <tt>null</tt> if no TO + * list exists. + * @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC + * list exists. + * @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC + * list exists. + * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and + * some JEP-33 specific features were requested. + */ + public static void send(Connection connection, Packet packet, List to, List cc, List bcc) + throws XMPPException { + send(connection, packet, to, cc, bcc, null, null, false); + } + + /** + * Sends the specified packet to the list of specified recipients using the + * specified connection. If the server has support for JEP-33 then only one + * packet is going to be sent to the server with the multiple recipient instructions. + * However, if JEP-33 is not supported by the server then the client is going to send + * the packet to each recipient. + * + * @param connection the connection to use to send the packet. + * @param packet the packet to send to the list of recipients. + * @param to the list of JIDs to include in the TO list or <tt>null</tt> if no TO + * list exists. + * @param cc the list of JIDs to include in the CC list or <tt>null</tt> if no CC + * list exists. + * @param bcc the list of JIDs to include in the BCC list or <tt>null</tt> if no BCC + * list exists. + * @param replyTo address to which all replies are requested to be sent or <tt>null</tt> + * indicating that they can reply to any address. + * @param replyRoom JID of a MUC room to which responses should be sent or <tt>null</tt> + * indicating that they can reply to any address. + * @param noReply true means that receivers should not reply to the message. + * @throws XMPPException if server does not support JEP-33: Extended Stanza Addressing and + * some JEP-33 specific features were requested. + */ + public static void send(Connection connection, Packet packet, List to, List cc, List bcc, + String replyTo, String replyRoom, boolean noReply) throws XMPPException { + String serviceAddress = getMultipleRecipienServiceAddress(connection); + if (serviceAddress != null) { + // Send packet to target users using multiple recipient service provided by the server + sendThroughService(connection, packet, to, cc, bcc, replyTo, replyRoom, noReply, + serviceAddress); + } + else { + // Server does not support JEP-33 so try to send the packet to each recipient + if (noReply || (replyTo != null && replyTo.trim().length() > 0) || + (replyRoom != null && replyRoom.trim().length() > 0)) { + // Some specified JEP-33 features were requested so throw an exception alerting + // the user that this features are not available + throw new XMPPException("Extended Stanza Addressing not supported by server"); + } + // Send the packet to each individual recipient + sendToIndividualRecipients(connection, packet, to, cc, bcc); + } + } + + /** + * Sends a reply to a previously received packet that was sent to multiple recipients. Before + * attempting to send the reply message some checkings are performed. If any of those checkings + * fail then an XMPPException is going to be thrown with the specific error detail. + * + * @param connection the connection to use to send the reply. + * @param original the previously received packet that was sent to multiple recipients. + * @param reply the new message to send as a reply. + * @throws XMPPException if the original message was not sent to multiple recipients, or the + * original message cannot be replied or reply should be sent to a room. + */ + public static void reply(Connection connection, Message original, Message reply) + throws XMPPException { + MultipleRecipientInfo info = getMultipleRecipientInfo(original); + if (info == null) { + throw new XMPPException("Original message does not contain multiple recipient info"); + } + if (info.shouldNotReply()) { + throw new XMPPException("Original message should not be replied"); + } + if (info.getReplyRoom() != null) { + throw new XMPPException("Reply should be sent through a room"); + } + // Any <thread/> element from the initial message MUST be copied into the reply. + if (original.getThread() != null) { + reply.setThread(original.getThread()); + } + MultipleAddresses.Address replyAddress = info.getReplyAddress(); + if (replyAddress != null && replyAddress.getJid() != null) { + // Send reply to the reply_to address + reply.setTo(replyAddress.getJid()); + connection.sendPacket(reply); + } + else { + // Send reply to multiple recipients + List to = new ArrayList(); + List cc = new ArrayList(); + for (Iterator it = info.getTOAddresses().iterator(); it.hasNext();) { + String jid = ((MultipleAddresses.Address) it.next()).getJid(); + to.add(jid); + } + for (Iterator it = info.getCCAddresses().iterator(); it.hasNext();) { + String jid = ((MultipleAddresses.Address) it.next()).getJid(); + cc.add(jid); + } + // Add original sender as a 'to' address (if not already present) + if (!to.contains(original.getFrom()) && !cc.contains(original.getFrom())) { + to.add(original.getFrom()); + } + // Remove the sender from the TO/CC list (try with bare JID too) + String from = connection.getUser(); + if (!to.remove(from) && !cc.remove(from)) { + String bareJID = StringUtils.parseBareAddress(from); + to.remove(bareJID); + cc.remove(bareJID); + } + + String serviceAddress = getMultipleRecipienServiceAddress(connection); + if (serviceAddress != null) { + // Send packet to target users using multiple recipient service provided by the server + sendThroughService(connection, reply, to, cc, null, null, null, false, + serviceAddress); + } + else { + // Server does not support JEP-33 so try to send the packet to each recipient + sendToIndividualRecipients(connection, reply, to, cc, null); + } + } + } + + /** + * Returns the {@link MultipleRecipientInfo} contained in the specified packet or + * <tt>null</tt> if none was found. Only packets sent to multiple recipients will + * contain such information. + * + * @param packet the packet to check. + * @return the MultipleRecipientInfo contained in the specified packet or <tt>null</tt> + * if none was found. + */ + public static MultipleRecipientInfo getMultipleRecipientInfo(Packet packet) { + MultipleAddresses extension = (MultipleAddresses) packet + .getExtension("addresses", "http://jabber.org/protocol/address"); + return extension == null ? null : new MultipleRecipientInfo(extension); + } + + private static void sendToIndividualRecipients(Connection connection, Packet packet, + List to, List cc, List bcc) { + if (to != null) { + for (Iterator it = to.iterator(); it.hasNext();) { + String jid = (String) it.next(); + packet.setTo(jid); + connection.sendPacket(new PacketCopy(packet.toXML())); + } + } + if (cc != null) { + for (Iterator it = cc.iterator(); it.hasNext();) { + String jid = (String) it.next(); + packet.setTo(jid); + connection.sendPacket(new PacketCopy(packet.toXML())); + } + } + if (bcc != null) { + for (Iterator it = bcc.iterator(); it.hasNext();) { + String jid = (String) it.next(); + packet.setTo(jid); + connection.sendPacket(new PacketCopy(packet.toXML())); + } + } + } + + private static void sendThroughService(Connection connection, Packet packet, List to, + List cc, List bcc, String replyTo, String replyRoom, boolean noReply, + String serviceAddress) { + // Create multiple recipient extension + MultipleAddresses multipleAddresses = new MultipleAddresses(); + if (to != null) { + for (Iterator it = to.iterator(); it.hasNext();) { + String jid = (String) it.next(); + multipleAddresses.addAddress(MultipleAddresses.TO, jid, null, null, false, null); + } + } + if (cc != null) { + for (Iterator it = cc.iterator(); it.hasNext();) { + String jid = (String) it.next(); + multipleAddresses.addAddress(MultipleAddresses.CC, jid, null, null, false, null); + } + } + if (bcc != null) { + for (Iterator it = bcc.iterator(); it.hasNext();) { + String jid = (String) it.next(); + multipleAddresses.addAddress(MultipleAddresses.BCC, jid, null, null, false, null); + } + } + if (noReply) { + multipleAddresses.setNoReply(); + } + else { + if (replyTo != null && replyTo.trim().length() > 0) { + multipleAddresses + .addAddress(MultipleAddresses.REPLY_TO, replyTo, null, null, false, null); + } + if (replyRoom != null && replyRoom.trim().length() > 0) { + multipleAddresses.addAddress(MultipleAddresses.REPLY_ROOM, replyRoom, null, null, + false, null); + } + } + // Set the multiple recipient service address as the target address + packet.setTo(serviceAddress); + // Add extension to packet + packet.addExtension(multipleAddresses); + // Send the packet + connection.sendPacket(packet); + } + + /** + * Returns the address of the multiple recipients service. To obtain such address service + * discovery is going to be used on the connected server and if none was found then another + * attempt will be tried on the server items. The discovered information is going to be + * cached for 24 hours. + * + * @param connection the connection to use for disco. The connected server is going to be + * queried. + * @return the address of the multiple recipients service or <tt>null</tt> if none was found. + */ + private static String getMultipleRecipienServiceAddress(Connection connection) { + String serviceName = connection.getServiceName(); + String serviceAddress = (String) services.get(serviceName); + if (serviceAddress == null) { + synchronized (services) { + serviceAddress = (String) services.get(serviceName); + if (serviceAddress == null) { + + // Send the disco packet to the server itself + try { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection) + .discoverInfo(serviceName); + // Check if the server supports JEP-33 + if (info.containsFeature("http://jabber.org/protocol/address")) { + serviceAddress = serviceName; + } + else { + // Get the disco items and send the disco packet to each server item + DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection) + .discoverItems(serviceName); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + info = ServiceDiscoveryManager.getInstanceFor(connection) + .discoverInfo(item.getEntityID(), item.getNode()); + if (info.containsFeature("http://jabber.org/protocol/address")) { + serviceAddress = serviceName; + break; + } + } + + } + // Cache the discovered information + services.put(serviceName, serviceAddress == null ? "" : serviceAddress); + } + catch (XMPPException e) { + e.printStackTrace(); + } + } + } + } + + return "".equals(serviceAddress) ? null : serviceAddress; + } + + /** + * Packet that holds the XML stanza to send. This class is useful when the same packet + * is needed to be sent to different recipients. Since using the same packet is not possible + * (i.e. cannot change the TO address of a queues packet to be sent) then this class was + * created to keep the XML stanza to send. + */ + private static class PacketCopy extends Packet { + + private String text; + + /** + * Create a copy of a packet with the text to send. The passed text must be a valid text to + * send to the server, no validation will be done on the passed text. + * + * @param text the whole text of the packet to send + */ + public PacketCopy(String text) { + this.text = text; + } + + public String toXML() { + return text; + } + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java new file mode 100644 index 0000000..816be4d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/NodeInformationProvider.java @@ -0,0 +1,68 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; + +import java.util.List; + + +/** + * The NodeInformationProvider is responsible for providing supported indentities, features + * and hosted items (i.e. DiscoverItems.Item) about a given node. This information will be + * requested each time this XMPPP client receives a disco info or items requests on the + * given node. each time this XMPPP client receives a disco info or items requests on the + * given node. + * + * @author Gaston Dombiak + */ +public interface NodeInformationProvider { + + /** + * Returns a list of the Items {@link org.jivesoftware.smackx.packet.DiscoverItems.Item} + * defined in the node. For example, the MUC protocol specifies that an XMPP client should + * answer an Item for each joined room when asked for the rooms where the use has joined. + * + * @return a list of the Items defined in the node. + */ + public abstract List<DiscoverItems.Item> getNodeItems(); + + /** + * Returns a list of the features defined in the node. For + * example, the entity caps protocol specifies that an XMPP client + * should answer with each feature supported by the client version + * or extension. + * + * @return a list of the feature strings defined in the node. + */ + public abstract List<String> getNodeFeatures(); + + /** + * Returns a list of the indentites defined in the node. For + * example, the x-command protocol must provide an identity of + * category automation and type command-node for each command. + * + * @return a list of the Identities defined in the node. + */ + public abstract List<DiscoverInfo.Identity> getNodeIdentities(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java new file mode 100644 index 0000000..55fd149 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageHeader.java @@ -0,0 +1,85 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * The OfflineMessageHeader holds header information of an offline message. The header + * information was retrieved using the {@link OfflineMessageManager} class.<p> + * + * Each offline message is identified by the target user of the offline message and a unique stamp. + * Use {@link OfflineMessageManager#getMessages(java.util.List)} to retrieve the whole message. + * + * @author Gaston Dombiak + */ +public class OfflineMessageHeader { + /** + * Bare JID of the user that was offline when the message was sent. + */ + private String user; + /** + * Full JID of the user that sent the message. + */ + private String jid; + /** + * Stamp that uniquely identifies the offline message. This stamp will be used for + * getting the specific message or delete it. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + */ + private String stamp; + + public OfflineMessageHeader(DiscoverItems.Item item) { + super(); + user = item.getEntityID(); + jid = item.getName(); + stamp = item.getNode(); + } + + /** + * Returns the bare JID of the user that was offline when the message was sent. + * + * @return the bare JID of the user that was offline when the message was sent. + */ + public String getUser() { + return user; + } + + /** + * Returns the full JID of the user that sent the message. + * + * @return the full JID of the user that sent the message. + */ + public String getJid() { + return jid; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp will + * be used for getting the specific message or delete it. The stamp may be of the + * form UTC timestamps but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getStamp() { + return stamp; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java new file mode 100644 index 0000000..ea1c3ca --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/OfflineMessageManager.java @@ -0,0 +1,284 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.OfflineMessageInfo; +import org.jivesoftware.smackx.packet.OfflineMessageRequest; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * The OfflineMessageManager helps manage offline messages even before the user has sent an + * available presence. When a user asks for his offline messages before sending an available + * presence then the server will not send a flood with all the offline messages when the user + * becomes online. The server will not send a flood with all the offline messages to the session + * that made the offline messages request or to any other session used by the user that becomes + * online.<p> + * + * Once the session that made the offline messages request has been closed and the user becomes + * offline in all the resources then the server will resume storing the messages offline and will + * send all the offline messages to the user when he becomes online. Therefore, the server will + * flood the user when he becomes online unless the user uses this class to manage his offline + * messages. + * + * @author Gaston Dombiak + */ +public class OfflineMessageManager { + + private final static String namespace = "http://jabber.org/protocol/offline"; + + private Connection connection; + + private PacketFilter packetFilter; + + public OfflineMessageManager(Connection connection) { + this.connection = connection; + packetFilter = + new AndFilter(new PacketExtensionFilter("offline", namespace), + new PacketTypeFilter(Message.class)); + } + + /** + * Returns true if the server supports Flexible Offline Message Retrieval. When the server + * supports Flexible Offline Message Retrieval it is possible to get the header of the offline + * messages, get specific messages, delete specific messages, etc. + * + * @return a boolean indicating if the server supports Flexible Offline Message Retrieval. + * @throws XMPPException If the user is not allowed to make this request. + */ + public boolean supportsFlexibleRetrieval() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(connection.getServiceName()); + return info.containsFeature(namespace); + } + + /** + * Returns the number of offline messages for the user of the connection. + * + * @return the number of offline messages for the user of the connection. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public int getMessageCount() throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(null, + namespace); + Form extendedInfo = Form.getFormFrom(info); + if (extendedInfo != null) { + String value = extendedInfo.getField("number_of_messages").getValues().next(); + return Integer.parseInt(value); + } + return 0; + } + + /** + * Returns an iterator on <tt>OfflineMessageHeader</tt> that keep information about the + * offline message. The OfflineMessageHeader includes a stamp that could be used to retrieve + * the complete message or delete the specific message. + * + * @return an iterator on <tt>OfflineMessageHeader</tt> that keep information about the offline + * message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator<OfflineMessageHeader> getHeaders() throws XMPPException { + List<OfflineMessageHeader> answer = new ArrayList<OfflineMessageHeader>(); + DiscoverItems items = ServiceDiscoveryManager.getInstanceFor(connection).discoverItems( + null, namespace); + for (Iterator it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = (DiscoverItems.Item) it.next(); + answer.add(new OfflineMessageHeader(item)); + } + return answer.iterator(); + } + + /** + * Returns an Iterator with the offline <tt>Messages</tt> whose stamp matches the specified + * request. The request will include the list of stamps that uniquely identifies + * the offline messages to retrieve. The returned offline messages will not be deleted + * from the server. Use {@link #deleteMessages(java.util.List)} to delete the messages. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @return an Iterator with the offline <tt>Messages</tt> that were received as part of + * this request. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator<Message> getMessages(final List<String> nodes) throws XMPPException { + List<Message> messages = new ArrayList<Message>(); + OfflineMessageRequest request = new OfflineMessageRequest(); + for (String node : nodes) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); + item.setAction("view"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketFilter messageFilter = new AndFilter(packetFilter, new PacketFilter() { + public boolean accept(Packet packet) { + OfflineMessageInfo info = (OfflineMessageInfo) packet.getExtension("offline", + namespace); + return nodes.contains(info.getNode()); + } + }); + PacketCollector messageCollector = connection.createPacketCollector(messageFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Returns an Iterator with all the offline <tt>Messages</tt> of the user. The returned offline + * messages will not be deleted from the server. Use {@link #deleteMessages(java.util.List)} + * to delete the messages. + * + * @return an Iterator with all the offline <tt>Messages</tt> of the user. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public Iterator<Message> getMessages() throws XMPPException { + List<Message> messages = new ArrayList<Message>(); + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setFetch(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Filter offline messages that were requested by this request + PacketCollector messageCollector = connection.createPacketCollector(packetFilter); + // Send the retrieval request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + + // Collect the received offline messages + Message message = (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + while (message != null) { + messages.add(message); + message = + (Message) messageCollector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + } + // Stop queuing offline messages + messageCollector.cancel(); + return messages.iterator(); + } + + /** + * Deletes the specified list of offline messages. The request will include the list of + * stamps that uniquely identifies the offline messages to delete. + * + * @param nodes the list of stamps that uniquely identifies offline message. + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages(List<String> nodes) throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + for (String node : nodes) { + OfflineMessageRequest.Item item = new OfflineMessageRequest.Item(node); + item.setAction("remove"); + request.addItem(item); + } + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Deletes all offline messages of the user. + * + * @throws XMPPException If the user is not allowed to make this request or the server does + * not support offline message retrieval. + */ + public void deleteMessages() throws XMPPException { + OfflineMessageRequest request = new OfflineMessageRequest(); + request.setPurge(true); + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(request.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the deletion request to the server. + connection.sendPacket(request); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java new file mode 100644 index 0000000..1d39484 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPListener.java @@ -0,0 +1,42 @@ +/** + * $RCSfile: PEPListener.java,v $ + * $Revision: 1.1 $ + * $Date: 2007/11/03 00:14:32 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smackx.packet.PEPEvent; + + +/** + * + * A listener that is fired anytime a PEP event message is received. + * + * @author Jeff Williams + */ +public interface PEPListener { + + /** + * Called when PEP events are received as part of a presence subscribe or message filter. + * + * @param from the user that sent the entries. + * @param event the event contained in the message. + */ + public void eventReceived(String from, PEPEvent event); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java new file mode 100644 index 0000000..857f1c4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PEPManager.java @@ -0,0 +1,160 @@ +/** + * $RCSfile: PEPManager.java,v $ + * $Revision: 1.4 $ + * $Date: 2007/11/06 21:43:40 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.ArrayList; +import java.util.List; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smackx.packet.PEPEvent; +import org.jivesoftware.smackx.packet.PEPItem; +import org.jivesoftware.smackx.packet.PEPPubSub; + +/** + * + * Manages Personal Event Publishing (XEP-163). A PEPManager provides a high level access to + * pubsub personal events. It also provides an easy way + * to hook up custom logic when events are received from another XMPP client through PEPListeners. + * + * Use example: + * + * <pre> + * PEPManager pepManager = new PEPManager(smackConnection); + * pepManager.addPEPListener(new PEPListener() { + * public void eventReceived(String inFrom, PEPEvent inEvent) { + * LOGGER.debug("Event received: " + inEvent); + * } + * }); + * + * PEPProvider pepProvider = new PEPProvider(); + * pepProvider.registerPEPParserExtension("http://jabber.org/protocol/tune", new TuneProvider()); + * ProviderManager.getInstance().addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", pepProvider); + * + * Tune tune = new Tune("jeff", "1", "CD", "My Title", "My Track"); + * pepManager.publish(tune); + * </pre> + * + * @author Jeff Williams + */ +public class PEPManager { + + private List<PEPListener> pepListeners = new ArrayList<PEPListener>(); + + private Connection connection; + + private PacketFilter packetFilter = new PacketExtensionFilter("event", "http://jabber.org/protocol/pubsub#event"); + private PacketListener packetListener; + + /** + * Creates a new PEP exchange manager. + * + * @param connection a Connection which is used to send and receive messages. + */ + public PEPManager(Connection connection) { + this.connection = connection; + init(); + } + + /** + * Adds a listener to PEPs. The listener will be fired anytime PEP events + * are received from remote XMPP clients. + * + * @param pepListener a roster exchange listener. + */ + public void addPEPListener(PEPListener pepListener) { + synchronized (pepListeners) { + if (!pepListeners.contains(pepListener)) { + pepListeners.add(pepListener); + } + } + } + + /** + * Removes a listener from PEP events. + * + * @param pepListener a roster exchange listener. + */ + public void removePEPListener(PEPListener pepListener) { + synchronized (pepListeners) { + pepListeners.remove(pepListener); + } + } + + /** + * Publish an event. + * + * @param item the item to publish. + */ + public void publish(PEPItem item) { + // Create a new message to publish the event. + PEPPubSub pubSub = new PEPPubSub(item); + pubSub.setType(Type.SET); + //pubSub.setFrom(connection.getUser()); + + // Send the message that contains the roster + connection.sendPacket(pubSub); + } + + /** + * Fires roster exchange listeners. + */ + private void firePEPListeners(String from, PEPEvent event) { + PEPListener[] listeners = null; + synchronized (pepListeners) { + listeners = new PEPListener[pepListeners.size()]; + pepListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].eventReceived(from, event); + } + } + + private void init() { + // Listens for all roster exchange packets and fire the roster exchange listeners. + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + PEPEvent event = (PEPEvent) message.getExtension("event", "http://jabber.org/protocol/pubsub#event"); + // Fire event for roster exchange listeners + firePEPListeners(message.getFrom(), event); + }; + + }; + connection.addPacketListener(packetListener, packetFilter); + } + + public void destroy() { + if (connection != null) + connection.removePacketListener(packetListener); + + } + + protected void finalize() throws Throwable { + destroy(); + super.finalize(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java new file mode 100644 index 0000000..03ba448 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/PrivateDataManager.java @@ -0,0 +1,359 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.DefaultPrivateData; +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Hashtable; +import java.util.Map; + +/** + * Manages private data, which is a mechanism to allow users to store arbitrary XML + * data on an XMPP server. Each private data chunk is defined by a element name and + * XML namespace. Example private data: + * + * <pre> + * <color xmlns="http://example.com/xmpp/color"> + * <favorite>blue</blue> + * <leastFavorite>puce</leastFavorite> + * </color> + * </pre> + * + * {@link PrivateDataProvider} instances are responsible for translating the XML into objects. + * If no PrivateDataProvider is registered for a given element name and namespace, then + * a {@link DefaultPrivateData} instance will be returned.<p> + * + * Warning: this is an non-standard protocol documented by + * <a href="http://www.jabber.org/jeps/jep-0049.html">JEP-49</a>. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Matt Tucker + */ +public class PrivateDataManager { + + /** + * Map of provider instances. + */ + private static Map privateDataProviders = new Hashtable(); + + /** + * Returns the private data provider registered to the specified XML element name and namespace. + * For example, if a provider was registered to the element name "prefs" and the + * namespace "http://www.xmppclient.com/prefs", then the following packet would trigger + * the provider: + * + * <pre> + * <iq type='result' to='joe@example.com' from='mary@example.com' id='time_1'> + * <query xmlns='jabber:iq:private'> + * <prefs xmlns='http://www.xmppclient.com/prefs'> + * <value1>ABC</value1> + * <value2>XYZ</value2> + * </prefs> + * </query> + * </iq></pre> + * + * <p>Note: this method is generally only called by the internal Smack classes. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @return the PrivateData provider. + */ + public static PrivateDataProvider getPrivateDataProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + return (PrivateDataProvider)privateDataProviders.get(key); + } + + /** + * Adds a private data provider with the specified element name and name space. The provider + * will override any providers loaded through the classpath. + * + * @param elementName the XML element name. + * @param namespace the XML namespace. + * @param provider the private data provider. + */ + public static void addPrivateDataProvider(String elementName, String namespace, + PrivateDataProvider provider) + { + String key = getProviderKey(elementName, namespace); + privateDataProviders.put(key, provider); + } + + /** + * Removes a private data provider with the specified element name and namespace. + * + * @param elementName The XML element name. + * @param namespace The XML namespace. + */ + public static void removePrivateDataProvider(String elementName, String namespace) { + String key = getProviderKey(elementName, namespace); + privateDataProviders.remove(key); + } + + + private Connection connection; + + /** + * The user to get and set private data for. In most cases, this value should + * be <tt>null</tt>, as the typical use of private data is to get and set + * your own private data and not others. + */ + private String user; + + /** + * Creates a new private data manager. The connection must have + * undergone a successful login before being used to construct an instance of + * this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + */ + public PrivateDataManager(Connection connection) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + } + + /** + * Creates a new private data manager for a specific user (special case). Most + * servers only support getting and setting private data for the user that + * authenticated via the connection. However, some servers support the ability + * to get and set private data for other users (for example, if you are the + * administrator). The connection must have undergone a successful login before + * being used to construct an instance of this class. + * + * @param connection an XMPP connection which must have already undergone a + * successful login. + * @param user the XMPP address of the user to get and set private data for. + */ + public PrivateDataManager(Connection connection, String user) { + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must be logged in to XMPP server."); + } + this.connection = connection; + this.user = user; + } + + /** + * Returns the private data specified by the given element name and namespace. Each chunk + * of private data is uniquely identified by an element name and namespace pair.<p> + * + * If a PrivateDataProvider is registered for the specified element name/namespace pair then + * that provider will determine the specific object type that is returned. If no provider + * is registered, a {@link DefaultPrivateData} instance will be returned. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return the private data. + * @throws XMPPException if an error occurs getting the private data. + */ + public PrivateData getPrivateData(final String elementName, final String namespace) + throws XMPPException + { + // Create an IQ packet to get the private data. + IQ privateDataGet = new IQ() { + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\"/>"); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataGet.setType(IQ.Type.GET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataGet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataGet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataGet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + return ((PrivateDataResult)response).getPrivateData(); + } + + /** + * Sets a private data value. Each chunk of private data is uniquely identified by an + * element name and namespace pair. If private data has already been set with the + * element name and namespace, then the new private data will overwrite the old value. + * + * @param privateData the private data. + * @throws XMPPException if setting the private data fails. + */ + public void setPrivateData(final PrivateData privateData) throws XMPPException { + // Create an IQ packet to set the private data. + IQ privateDataSet = new IQ() { + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + buf.append(privateData.toXML()); + buf.append("</query>"); + return buf.toString(); + } + }; + privateDataSet.setType(IQ.Type.SET); + // Address the packet to the other account if user has been set. + if (user != null) { + privateDataSet.setTo(user); + } + + // Setup a listener for the reply to the set operation. + String packetID = privateDataSet.getPacketID(); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(packetID)); + + // Send the private data. + connection.sendPacket(privateDataSet); + + // Wait up to five seconds for a response from the server. + IQ response = (IQ)collector.nextResult(5000); + // Stop queuing results + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + // If the server replied with an error, throw an exception. + else if (response.getType() == IQ.Type.ERROR) { + throw new XMPPException(response.getError()); + } + } + + /** + * Returns a String key for a given element name and namespace. + * + * @param elementName the element name. + * @param namespace the namespace. + * @return a unique key for the element name and namespace pair. + */ + private static String getProviderKey(String elementName, String namespace) { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(elementName).append("/><").append(namespace).append("/>"); + return buf.toString(); + } + + /** + * An IQ provider to parse IQ results containing private data. + */ + public static class PrivateDataIQProvider implements IQProvider { + public IQ parseIQ(XmlPullParser parser) throws Exception { + PrivateData privateData = null; + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + String elementName = parser.getName(); + String namespace = parser.getNamespace(); + // See if any objects are registered to handle this private data type. + PrivateDataProvider provider = getPrivateDataProvider(elementName, namespace); + // If there is a registered provider, use it. + if (provider != null) { + privateData = provider.parsePrivateData(parser); + } + // Otherwise, use a DefaultPrivateData instance to store the private data. + else { + DefaultPrivateData data = new DefaultPrivateData(elementName, namespace); + boolean finished = false; + while (!finished) { + int event = parser.next(); + if (event == XmlPullParser.START_TAG) { + String name = parser.getName(); + // If an empty element, set the value with the empty string. + if (parser.isEmptyElementTag()) { + data.setValue(name,""); + } + // Otherwise, get the the element text. + else { + event = parser.next(); + if (event == XmlPullParser.TEXT) { + String value = parser.getText(); + data.setValue(name, value); + } + } + } + else if (event == XmlPullParser.END_TAG) { + if (parser.getName().equals(elementName)) { + finished = true; + } + } + } + privateData = data; + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + return new PrivateDataResult(privateData); + } + } + + /** + * An IQ packet to hold PrivateData GET results. + */ + private static class PrivateDataResult extends IQ { + + private PrivateData privateData; + + PrivateDataResult(PrivateData privateData) { + this.privateData = privateData; + } + + public PrivateData getPrivateData() { + return privateData; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:private\">"); + if (privateData != null) { + privateData.toXML(); + } + buf.append("</query>"); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java new file mode 100644 index 0000000..acf0b0b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RemoteRosterEntry.java @@ -0,0 +1,114 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.*; + +/** + * Represents a roster item, which consists of a JID and , their name and + * the groups the roster item belongs to. This roster item does not belong + * to the local roster. Therefore, it does not persist in the server.<p> + * + * The idea of a RemoteRosterEntry is to be used as part of a roster exchange. + * + * @author Gaston Dombiak + */ +public class RemoteRosterEntry { + + private String user; + private String name; + private final List<String> groupNames = new ArrayList<String>(); + + /** + * Creates a new remote roster entry. + * + * @param user the user. + * @param name the user's name. + * @param groups the list of group names the entry will belong to, or <tt>null</tt> if the + * the roster entry won't belong to a group. + */ + public RemoteRosterEntry(String user, String name, String [] groups) { + this.user = user; + this.name = name; + if (groups != null) { + groupNames.addAll(Arrays.asList(groups)); + } + } + + /** + * Returns the user. + * + * @return the user. + */ + public String getUser() { + return user; + } + + /** + * Returns the user's name. + * + * @return the user's name. + */ + public String getName() { + return name; + } + + /** + * Returns an Iterator for the group names (as Strings) that the roster entry + * belongs to. + * + * @return an Iterator for the group names. + */ + public Iterator getGroupNames() { + synchronized (groupNames) { + return Collections.unmodifiableList(groupNames).iterator(); + } + } + + /** + * Returns a String array for the group names that the roster entry + * belongs to. + * + * @return a String[] for the group names. + */ + public String[] getGroupArrayNames() { + synchronized (groupNames) { + return Collections.unmodifiableList(groupNames).toArray(new String[groupNames.size()]); + } + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<item jid=\"").append(user).append("\""); + if (name != null) { + buf.append(" name=\"").append(name).append("\""); + } + buf.append(">"); + synchronized (groupNames) { + for (String groupName : groupNames) { + buf.append("<group>").append(groupName).append("</group>"); + } + } + buf.append("</item>"); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java new file mode 100644 index 0000000..0d7b760 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ReportedData.java @@ -0,0 +1,281 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.DataForm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a set of data results returned as part of a search. The report is structured + * in columns and rows. + * + * @author Gaston Dombiak + */ +public class ReportedData { + + private List<Column> columns = new ArrayList<Column>(); + private List<Row> rows = new ArrayList<Row>(); + private String title = ""; + + /** + * Returns a new ReportedData if the packet is used for reporting data and includes an + * extension that matches the elementName and namespace "x","jabber:x:data". + * + * @param packet the packet used for reporting data. + */ + public static ReportedData getReportedDataFrom(Packet packet) { + // Check if the packet includes the DataForm extension + PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); + if (packetExtension != null) { + // Check if the existing DataForm is a result of a search + DataForm dataForm = (DataForm) packetExtension; + if (dataForm.getReportedData() != null) + return new ReportedData(dataForm); + } + // Otherwise return null + return null; + } + + + /** + * Creates a new ReportedData based on the returned dataForm from a search + *(namespace "jabber:iq:search"). + * + * @param dataForm the dataForm returned from a search (namespace "jabber:iq:search"). + */ + private ReportedData(DataForm dataForm) { + // Add the columns to the report based on the reported data fields + for (Iterator fields = dataForm.getReportedData().getFields(); fields.hasNext();) { + FormField field = (FormField)fields.next(); + columns.add(new Column(field.getLabel(), field.getVariable(), field.getType())); + } + + // Add the rows to the report based on the form's items + for (Iterator items = dataForm.getItems(); items.hasNext();) { + DataForm.Item item = (DataForm.Item)items.next(); + List<Field> fieldList = new ArrayList<Field>(columns.size()); + FormField field; + for (Iterator fields = item.getFields(); fields.hasNext();) { + field = (FormField) fields.next(); + // The field is created with all the values of the data form's field + List<String> values = new ArrayList<String>(); + for (Iterator<String> it=field.getValues(); it.hasNext();) { + values.add(it.next()); + } + fieldList.add(new Field(field.getVariable(), values)); + } + rows.add(new Row(fieldList)); + } + + // Set the report's title + this.title = dataForm.getTitle(); + } + + + public ReportedData(){ + // Allow for model creation of ReportedData. + } + + /** + * Adds a new <code>Row</code>. + * @param row the new row to add. + */ + public void addRow(Row row){ + rows.add(row); + } + + /** + * Adds a new <code>Column</code> + * @param column the column to add. + */ + public void addColumn(Column column){ + columns.add(column); + } + + + /** + * Returns an Iterator for the rows returned from a search. + * + * @return an Iterator for the rows returned from a search. + */ + public Iterator<Row> getRows() { + return Collections.unmodifiableList(new ArrayList<Row>(rows)).iterator(); + } + + /** + * Returns an Iterator for the columns returned from a search. + * + * @return an Iterator for the columns returned from a search. + */ + public Iterator<Column> getColumns() { + return Collections.unmodifiableList(new ArrayList<Column>(columns)).iterator(); + } + + + /** + * Returns the report's title. It is similar to the title on a web page or an X + * window. + * + * @return title of the report. + */ + public String getTitle() { + return title; + } + + /** + * + * Represents the columns definition of the reported data. + * + * @author Gaston Dombiak + */ + public static class Column { + private String label; + private String variable; + private String type; + + /** + * Creates a new column with the specified definition. + * + * @param label the columns's label. + * @param variable the variable name of the column. + * @param type the format for the returned data. + */ + public Column(String label, String variable, String type) { + this.label = label; + this.variable = variable; + this.type = type; + } + + /** + * Returns the column's label. + * + * @return label of the column. + */ + public String getLabel() { + return label; + } + + + /** + * Returns the column's data format. Valid formats are: + * + * <ul> + * <li>text-single -> single line or word of text + * <li>text-private -> instead of showing the user what they typed, you show ***** to + * protect it + * <li>text-multi -> multiple lines of text entry + * <li>list-single -> given a list of choices, pick one + * <li>list-multi -> given a list of choices, pick one or more + * <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0 + * <li>fixed -> fixed for putting in text to show sections, or just advertise your web + * site in the middle of the form + * <li>hidden -> is not given to the user at all, but returned with the questionnaire + * <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based + * on the rules for a JID. + * <li>jid-multi -> multiple entries for JIDs + * </ul> + * + * @return format for the returned data. + */ + public String getType() { + return type; + } + + + /** + * Returns the variable name that the column is showing. + * + * @return the variable name of the column. + */ + public String getVariable() { + return variable; + } + + + } + + public static class Row { + private List<Field> fields = new ArrayList<Field>(); + + public Row(List<Field> fields) { + this.fields = fields; + } + + /** + * Returns the values of the field whose variable matches the requested variable. + * + * @param variable the variable to match. + * @return the values of the field whose variable matches the requested variable. + */ + public Iterator getValues(String variable) { + for(Iterator<Field> it=getFields();it.hasNext();) { + Field field = it.next(); + if (variable.equalsIgnoreCase(field.getVariable())) { + return field.getValues(); + } + } + return null; + } + + /** + * Returns the fields that define the data that goes with the item. + * + * @return the fields that define the data that goes with the item. + */ + private Iterator<Field> getFields() { + return Collections.unmodifiableList(new ArrayList<Field>(fields)).iterator(); + } + } + + public static class Field { + private String variable; + private List<String> values; + + public Field(String variable, List<String> values) { + this.variable = variable; + this.values = values; + } + + /** + * Returns the variable name that the field represents. + * + * @return the variable name of the field. + */ + public String getVariable() { + return variable; + } + + /** + * Returns an iterator on the values reported as part of the search. + * + * @return the returned values of the search. + */ + public Iterator<String> getValues() { + return Collections.unmodifiableList(values).iterator(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java new file mode 100644 index 0000000..6c21458 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeListener.java @@ -0,0 +1,42 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.Iterator; + +/** + * + * A listener that is fired anytime a roster exchange is received. + * + * @author Gaston Dombiak + */ +public interface RosterExchangeListener { + + /** + * Called when roster entries are received as part of a roster exchange. + * + * @param from the user that sent the entries. + * @param remoteRosterEntries the entries sent by the user. The entries are instances of + * RemoteRosterEntry. + */ + public void entriesReceived(String from, Iterator remoteRosterEntries); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java new file mode 100644 index 0000000..abb03ab --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/RosterExchangeManager.java @@ -0,0 +1,187 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.RosterExchange; + +/** + * + * Manages Roster exchanges. A RosterExchangeManager provides a high level access to send + * rosters, roster groups and roster entries to XMPP clients. It also provides an easy way + * to hook up custom logic when entries are received from another XMPP client through + * RosterExchangeListeners. + * + * @author Gaston Dombiak + */ +public class RosterExchangeManager { + + private List<RosterExchangeListener> rosterExchangeListeners = new ArrayList<RosterExchangeListener>(); + + private Connection con; + + private PacketFilter packetFilter = new PacketExtensionFilter("x", "jabber:x:roster"); + private PacketListener packetListener; + + /** + * Creates a new roster exchange manager. + * + * @param con a Connection which is used to send and receive messages. + */ + public RosterExchangeManager(Connection con) { + this.con = con; + init(); + } + + /** + * Adds a listener to roster exchanges. The listener will be fired anytime roster entries + * are received from remote XMPP clients. + * + * @param rosterExchangeListener a roster exchange listener. + */ + public void addRosterListener(RosterExchangeListener rosterExchangeListener) { + synchronized (rosterExchangeListeners) { + if (!rosterExchangeListeners.contains(rosterExchangeListener)) { + rosterExchangeListeners.add(rosterExchangeListener); + } + } + } + + /** + * Removes a listener from roster exchanges. The listener will be fired anytime roster + * entries are received from remote XMPP clients. + * + * @param rosterExchangeListener a roster exchange listener.. + */ + public void removeRosterListener(RosterExchangeListener rosterExchangeListener) { + synchronized (rosterExchangeListeners) { + rosterExchangeListeners.remove(rosterExchangeListener); + } + } + + /** + * Sends a roster to userID. All the entries of the roster will be sent to the + * target user. + * + * @param roster the roster to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(Roster roster, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(roster); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Sends a roster entry to userID. + * + * @param rosterEntry the roster entry to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(RosterEntry rosterEntry, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(); + rosterExchange.addRosterEntry(rosterEntry); + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Sends a roster group to userID. All the entries of the group will be sent to the + * target user. + * + * @param rosterGroup the roster group to send + * @param targetUserID the user that will receive the roster entries + */ + public void send(RosterGroup rosterGroup, String targetUserID) { + // Create a new message to send the roster + Message msg = new Message(targetUserID); + // Create a RosterExchange Package and add it to the message + RosterExchange rosterExchange = new RosterExchange(); + for (RosterEntry entry : rosterGroup.getEntries()) { + rosterExchange.addRosterEntry(entry); + } + msg.addExtension(rosterExchange); + + // Send the message that contains the roster + con.sendPacket(msg); + } + + /** + * Fires roster exchange listeners. + */ + private void fireRosterExchangeListeners(String from, Iterator remoteRosterEntries) { + RosterExchangeListener[] listeners = null; + synchronized (rosterExchangeListeners) { + listeners = new RosterExchangeListener[rosterExchangeListeners.size()]; + rosterExchangeListeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + listeners[i].entriesReceived(from, remoteRosterEntries); + } + } + + private void init() { + // Listens for all roster exchange packets and fire the roster exchange listeners. + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + Message message = (Message) packet; + RosterExchange rosterExchange = + (RosterExchange) message.getExtension("x", "jabber:x:roster"); + // Fire event for roster exchange listeners + fireRosterExchangeListeners(message.getFrom(), rosterExchange.getRosterEntries()); + }; + + }; + con.addPacketListener(packetListener, packetFilter); + } + + public void destroy() { + if (con != null) + con.removePacketListener(packetListener); + + } + protected void finalize() throws Throwable { + destroy(); + super.finalize(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java new file mode 100644 index 0000000..fc9cace --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManager.java @@ -0,0 +1,789 @@ +/** + * $RCSfile$ + * $Revision: 9920 $ + * $Date: 2008-02-16 00:32:46 +0800 (Sat, 16 Feb 2008) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.entitycaps.CapsVerListener; +import org.jivesoftware.smackx.entitycaps.EntityCapsManager; +import org.jivesoftware.smackx.packet.CapsExtension; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.DataForm; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages discovery of services in XMPP entities. This class provides: + * <ol> + * <li>A registry of supported features in this XMPP entity. + * <li>Automatic response when this XMPP entity is queried for information. + * <li>Ability to discover items and information of remote XMPP entities. + * <li>Ability to publish publicly available items. + * </ol> + * + * @author Gaston Dombiak + */ +public class ServiceDiscoveryManager implements ServiceDiscoveryManagerInterface { + + private static String identityName = "Smack"; + private static String identityType = "pc"; + + private static boolean cacheNonCaps=true; + + private boolean sendPresence = false; + + private Map<String,DiscoverInfo> nonCapsCache = + new ConcurrentHashMap<String,DiscoverInfo>(); + + private EntityCapsManager capsManager; + + private static Map<Connection, ServiceDiscoveryManager> instances = + new ConcurrentHashMap<Connection, ServiceDiscoveryManager>(); + + private Connection connection; + private final List<String> features = new ArrayList<String>(); + private DataForm extendedInfo = null; + private Map<String, NodeInformationProvider> nodeInformationProviders = + new ConcurrentHashMap<String, NodeInformationProvider>(); + + // Create a new ServiceDiscoveryManager on every established connection + static { + // Add service discovery for normal XMPP c2s connections + XMPPConnection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + new ServiceDiscoveryManager(connection); + } + }); + } + + /** + * Creates a new ServiceDiscoveryManager for a given connection. This means that the + * service manager will respond to any service discovery request that the connection may + * receive. + * + * @param connection the connection to which a ServiceDiscoveryManager is going to be created. + */ + public ServiceDiscoveryManager(Connection connection) { + this.connection = connection; + + // For every XMPPConnection, add one EntityCapsManager. + if (connection instanceof XMPPConnection + && ((XMPPConnection)connection).isEntityCapsEnabled()) { + setEntityCapsManager(new EntityCapsManager(this)); + capsManager.addCapsVerListener(new CapsPresenceRenewer()); + } + + renewEntityCapsVersion(); + + init(); + } + + /** + * Returns the ServiceDiscoveryManager instance associated with a given Connection. + * + * @param connection the connection used to look for the proper ServiceDiscoveryManager. + * @return the ServiceDiscoveryManager associated with a given Connection. + */ + public static ServiceDiscoveryManager getInstanceFor(Connection connection) { + return instances.get(connection); + } + + /** + * Returns the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @return the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static String getIdentityName() { + return identityName; + } + + /** + * Sets the name of the client that will be returned when asked for the client identity + * in a disco request. The name could be any value you need to identity this client. + * + * @param name the name of the client that will be returned when asked for the client identity + * in a disco request. + */ + public static void setIdentityName(String name) { + identityName = name; + } + + /** + * Returns the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @return the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static String getIdentityType() { + return identityType; + } + + /** + * Sets the type of client that will be returned when asked for the client identity in a + * disco request. The valid types are defined by the category client. Follow this link to learn + * the possible types: <a href="http://xmpp.org/registrar/disco-categories.html#client">Jabber::Registrar</a>. + * + * @param type the type of client that will be returned when asked for the client identity in a + * disco request. + */ + public static void setIdentityType(String type) { + identityType = type; + } + + /** + * Enables caching of non caps entities to reduce traffic. If enabled discover infos of + * entities without xep-0115 are store in a String,DiscoverInfo map and + * discoverInfo(String) queries this map before sending a real discover info to the + * remote entity. Enabled by default. + */ + public static void setNonCapsCaching(boolean set){ + cacheNonCaps = set; + } + + /** + * Check if caching of non caps entities is enabled + */ + + public static boolean isNonCapsCachingEnabled(){ + return cacheNonCaps; + } + + /** + * Add discover info response data. + * + * @param response the discover info response packet + */ + public void addDiscoverInfoTo(DiscoverInfo response) { + // Set this client identity + DiscoverInfo.Identity identity = new DiscoverInfo.Identity("client", + getIdentityName()); + identity.setType(getIdentityType()); + response.addIdentity(identity); + // Add the registered features to the response + synchronized (features) { + for (Iterator<String> it = getFeatures(); it.hasNext();) { + response.addFeature(it.next()); + } + if (extendedInfo != null) { + response.addExtension(extendedInfo); + } + } + } + + /** + * Get a DiscoverInfo for the current entity caps node. + * + * @return a DiscoverInfo for the current entity caps node + */ + public DiscoverInfo getOwnDiscoverInfo() { + DiscoverInfo di = new DiscoverInfo(); + di.setType(IQ.Type.RESULT); + di.setNode(capsManager.getNode() + "#" + getEntityCapsVersion()); + + // Add discover info + addDiscoverInfoTo(di); + + return di; + } + + /** + * Initializes the packet listeners of the connection that will answer to any + * service discovery request. + */ + private void init() { + // Register the new instance and associate it with the connection + instances.put(connection, this); + // Add a listener to the connection that removes the registered instance when + // the connection is closed + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + + public void connectionClosedOnError(Exception e) { + // ignore + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + }); + + // Intercept presence packages and add caps data when intended. + // XEP-0115 specifies that a client SHOULD include entity capabilities + // with every presence notification it sends. + PacketFilter capsPacketFilter = new PacketTypeFilter(Presence.class); + PacketInterceptor packetInterceptor = new PacketInterceptor() { + public void interceptPacket(Packet packet) { + if (capsManager != null) { + String ver = getEntityCapsVersion(); + CapsExtension caps = new CapsExtension(capsManager.getNode(), ver, "sha-1"); + packet.addExtension(caps); + } + } + }; + connection.addPacketInterceptor(packetInterceptor, capsPacketFilter); + + // Listen for disco#items requests and answer with an empty result + PacketFilter packetFilter = new PacketTypeFilter(DiscoverItems.class); + PacketListener packetListener = new PacketListener() { + public void processPacket(Packet packet) { + DiscoverItems discoverItems = (DiscoverItems) packet; + // Send back the items defined in the client if the request is of type GET + if (discoverItems != null && discoverItems.getType() == IQ.Type.GET) { + DiscoverItems response = new DiscoverItems(); + response.setType(IQ.Type.RESULT); + response.setTo(discoverItems.getFrom()); + response.setPacketID(discoverItems.getPacketID()); + response.setNode(discoverItems.getNode()); + + // Add the defined items related to the requested node. Look for + // the NodeInformationProvider associated with the requested node. + NodeInformationProvider nodeInformationProvider = + getNodeInformationProvider(discoverItems.getNode()); + if (nodeInformationProvider != null) { + // Specified node was found + List<DiscoverItems.Item> items = nodeInformationProvider.getNodeItems(); + if (items != null) { + for (DiscoverItems.Item item : items) { + response.addItem(item); + } + } + } else if(discoverItems.getNode() != null) { + // Return <item-not-found/> error since client doesn't contain + // the specified node + response.setType(IQ.Type.ERROR); + response.setError(new XMPPError(XMPPError.Condition.item_not_found)); + } + connection.sendPacket(response); + } + } + }; + connection.addPacketListener(packetListener, packetFilter); + + // Listen for disco#info requests and answer the client's supported features + // To add a new feature as supported use the #addFeature message + packetFilter = new PacketTypeFilter(DiscoverInfo.class); + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + DiscoverInfo discoverInfo = (DiscoverInfo) packet; + // Answer the client's supported features if the request is of the GET type + if (discoverInfo != null && discoverInfo.getType() == IQ.Type.GET) { + DiscoverInfo response = new DiscoverInfo(); + response.setType(IQ.Type.RESULT); + response.setTo(discoverInfo.getFrom()); + response.setPacketID(discoverInfo.getPacketID()); + response.setNode(discoverInfo.getNode()); + // Add the client's identity and features if "node" is + // null or our entity caps version. + if (discoverInfo.getNode() == null || + capsManager == null || + (capsManager.getNode() + "#" + + getEntityCapsVersion()).equals(discoverInfo.getNode())) { + addDiscoverInfoTo(response); + } + else { + // Disco#info was sent to a node. Check if we have information of the + // specified node + NodeInformationProvider nodeInformationProvider = + getNodeInformationProvider(discoverInfo.getNode()); + if (nodeInformationProvider != null) { + // Node was found. Add node features + List<String> features = nodeInformationProvider.getNodeFeatures(); + if (features != null) { + for(String feature : features) { + response.addFeature(feature); + } + } + // Add node identities + List<DiscoverInfo.Identity> identities = + nodeInformationProvider.getNodeIdentities(); + if (identities != null) { + for (DiscoverInfo.Identity identity : identities) { + response.addIdentity(identity); + } + } + } + else { + // Return <item-not-found/> error since specified node was not found + response.setType(IQ.Type.ERROR); + response.setError(new XMPPError(XMPPError.Condition.item_not_found)); + } + } + connection.sendPacket(response); + } + } + }; + connection.addPacketListener(packetListener, packetFilter); + } + + /** + * Returns the NodeInformationProvider responsible for providing information + * (ie items) related to a given node or <tt>null</null> if none.<p> + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node that contains items associated with an entity not addressable as a JID. + * @return the NodeInformationProvider responsible for providing information related + * to a given node. + */ + private NodeInformationProvider getNodeInformationProvider(String node) { + if (node == null) { + return null; + } + return nodeInformationProviders.get(node); + } + + /** + * Sets the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. Every time this client receives a disco request + * regarding the items of a given node, the provider associated to that node will be the + * responsible for providing the requested information.<p> + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node whose items will be provided by the NodeInformationProvider. + * @param listener the NodeInformationProvider responsible for providing items related + * to the node. + */ + public void setNodeInformationProvider(String node, NodeInformationProvider listener) { + nodeInformationProviders.put(node, listener); + } + + /** + * Removes the NodeInformationProvider responsible for providing information + * (ie items) related to a given node. This means that no more information will be + * available for the specified node. + * + * In MUC, a node could be 'http://jabber.org/protocol/muc#rooms' which means that the + * NodeInformationProvider will provide information about the rooms where the user has joined. + * + * @param node the node to remove the associated NodeInformationProvider. + */ + public void removeNodeInformationProvider(String node) { + nodeInformationProviders.remove(node); + } + + /** + * Returns the supported features by this XMPP entity. + * + * @return an Iterator on the supported features by this XMPP entity. + */ + public Iterator<String> getFeatures() { + synchronized (features) { + return Collections.unmodifiableList(new ArrayList<String>(features)).iterator(); + } + } + + /** + * Registers that a new feature is supported by this XMPP entity. When this client is + * queried for its information the registered features will be answered.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. In fact, you may want to configure the supported features + * before logging to the server so that the information is already available if it is required + * upon login. + * + * @param feature the feature to register as supported. + */ + public void addFeature(String feature) { + synchronized (features) { + features.add(feature); + renewEntityCapsVersion(); + } + } + + /** + * Removes the specified feature from the supported features by this XMPP entity.<p> + * + * Since no packet is actually sent to the server it is safe to perform this operation + * before logging to the server. + * + * @param feature the feature to remove from the supported features. + */ + public void removeFeature(String feature) { + synchronized (features) { + features.remove(feature); + renewEntityCapsVersion(); + } + } + + /** + * Returns true if the specified feature is registered in the ServiceDiscoveryManager. + * + * @param feature the feature to look for. + * @return a boolean indicating if the specified featured is registered or not. + */ + public boolean includesFeature(String feature) { + synchronized (features) { + return features.contains(feature); + } + } + + /** + * Registers extended discovery information of this XMPP entity. When this + * client is queried for its information this data form will be returned as + * specified by XEP-0128. + * <p> + * + * Since no packet is actually sent to the server it is safe to perform this + * operation before logging to the server. In fact, you may want to + * configure the extended info before logging to the server so that the + * information is already available if it is required upon login. + * + * @param info + * the data form that contains the extend service discovery + * information. + */ + public void setExtendedInfo(DataForm info) { + extendedInfo = info; + renewEntityCapsVersion(); + } + + /** + * Removes the dataform containing extended service discovery information + * from the information returned by this XMPP entity.<p> + * + * Since no packet is actually sent to the server it is safe to perform this + * operation before logging to the server. + */ + public void removeExtendedInfo() { + extendedInfo = null; + renewEntityCapsVersion(); + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID + * if it's known by the entity caps manager. + * + * @param entityID the address of the XMPP entity + * @return the disovered info or null if no such info is available from the + * entity caps manager. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfoByCaps(String entityID) throws XMPPException { + DiscoverInfo info = capsManager.getDiscoverInfoByUser(entityID); + + if (info != null) { + DiscoverInfo newInfo = cloneDiscoverInfo(info); + newInfo.setFrom(entityID); + return newInfo; + } + else { + return null; + } + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String entityID) throws XMPPException { + // Check if the have it cached in the Entity Capabilities Manager + DiscoverInfo info = discoverInfoByCaps(entityID); + + if (info != null) { + return info; + } else { + // If the caps node is known, use it in the request. + String node = null; + + if (capsManager != null) { + // Get the newest node#version + node = capsManager.getNodeVersionByUser(entityID); + } + + // Check if we cached DiscoverInfo for nonCaps entity + if (cacheNonCaps + && node == null + && nonCapsCache.containsKey(entityID)) { + return nonCapsCache.get(entityID); + } + // Discover by requesting from the remote client + info = discoverInfo(entityID, node); + + // If the node version is known, store the new entry. + if (node != null && capsManager != null) { + EntityCapsManager.addDiscoverInfoByNode(node, info); + } + // If this is a non caps entity store the discover in nonCapsCache map + else if (cacheNonCaps && node == null) { + nonCapsCache.put(entityID, info); + } + return info; + } + } + + /** + * Returns the discovered information of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverInfo discoverInfo(String entityID, String node) throws XMPPException { + // Discover the entity's info + DiscoverInfo disco = new DiscoverInfo(); + disco.setType(IQ.Type.GET); + disco.setTo(entityID); + disco.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); + + connection.sendPacket(disco); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return (DiscoverInfo) result; + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID. + * + * @param entityID the address of the XMPP entity. + * @return the discovered information. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String entityID) throws XMPPException { + return discoverItems(entityID, null); + } + + /** + * Returns the discovered items of a given XMPP entity addressed by its JID and + * note attribute. Use this message only when trying to query information which is not + * directly addressable. + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @return the discovered items. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverItems(String entityID, String node) throws XMPPException { + // Discover the entity's items + DiscoverItems disco = new DiscoverItems(); + disco.setType(IQ.Type.GET); + disco.setTo(entityID); + disco.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(disco.getPacketID())); + + connection.sendPacket(disco); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return (DiscoverItems) result; + } + + /** + * Returns true if the server supports publishing of items. A client may wish to publish items + * to the server so that the server can provide items associated to the client. These items will + * be returned by the server whenever the server receives a disco request targeted to the bare + * address of the client (i.e. user@host.com). + * + * @param entityID the address of the XMPP entity. + * @return true if the server supports publishing of items. + * @throws XMPPException if the operation failed for some reason. + */ + public boolean canPublishItems(String entityID) throws XMPPException { + DiscoverInfo info = discoverInfo(entityID); + return canPublishItems(info); + } + + /** + * Returns true if the server supports publishing of items. A client may wish to publish items + * to the server so that the server can provide items associated to the client. These items will + * be returned by the server whenever the server receives a disco request targeted to the bare + * address of the client (i.e. user@host.com). + * + * @param DiscoverInfo the discover info packet to check. + * @return true if the server supports publishing of items. + */ + public static boolean canPublishItems(DiscoverInfo info) { + return info.containsFeature("http://jabber.org/protocol/disco#publish"); + } + + /** + * Publishes new items to a parent entity. The item elements to publish MUST have at least + * a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, DiscoverItems discoverItems) + throws XMPPException { + publishItems(entityID, null, discoverItems); + } + + /** + * Publishes new items to a parent entity and node. The item elements to publish MUST have at + * least a 'jid' attribute specifying the Entity ID of the item, and an action attribute which + * specifies the action being taken for that item. Possible action values are: "update" and + * "remove". + * + * @param entityID the address of the XMPP entity. + * @param node the attribute that supplements the 'jid' attribute. + * @param discoverItems the DiscoveryItems to publish. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishItems(String entityID, String node, DiscoverItems discoverItems) + throws XMPPException { + discoverItems.setType(IQ.Type.SET); + discoverItems.setTo(entityID); + discoverItems.setNode(node); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(discoverItems.getPacketID())); + + connection.sendPacket(discoverItems); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + private DiscoverInfo cloneDiscoverInfo(DiscoverInfo disco) { + return disco.clone(); + } + + /** + * Entity Capabilities + */ + + public void setEntityCapsManager(EntityCapsManager manager) { + capsManager = manager; + if (connection.getCapsNode() != null + && connection.getHost() != null) { + capsManager.addUserCapsNode(connection.getHost(), connection.getCapsNode()); + } + capsManager.addPacketListener(connection); + } + + /** + * Updates the Entity Capabilities Verification String + * if EntityCaps is enabled + */ + private void renewEntityCapsVersion() { + // If a XMPPConnection is the managed one, see that the new + // version is updated + if (connection instanceof XMPPConnection) { + if (capsManager != null) { + capsManager.calculateEntityCapsVersion(getOwnDiscoverInfo(), + identityType, identityName, extendedInfo); + //capsManager.notifyCapsVerListeners(); + } + } + } + + private String getEntityCapsVersion() { + if (capsManager != null) { + return capsManager.getCapsVersion(); + } + else { + return null; + } + } + + public EntityCapsManager getEntityCapsManager(){ + return capsManager; + } + + private void setSendPresence() { + sendPresence = true; + } + + private boolean isSendPresence() { + return sendPresence; + } + + + private class CapsPresenceRenewer implements CapsVerListener { + public void capsVerUpdated(String ver) { + // Send an empty presence, and let the packet interceptor + // add a <c/> node to it. + if (((Connection)connection).isAuthenticated() && + (((Connection)connection).isSendPresence() || + isSendPresence())) { + Presence presence = new Presence(Presence.Type.available); + connection.sendPacket(presence); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java new file mode 100644 index 0000000..a59381a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/ServiceDiscoveryManagerInterface.java @@ -0,0 +1,5 @@ +package org.jivesoftware.smackx; + +public interface ServiceDiscoveryManagerInterface { + public void addFeature(String feature); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java new file mode 100644 index 0000000..c973730 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/SharedGroupManager.java @@ -0,0 +1,53 @@ +package org.jivesoftware.smackx; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.packet.SharedGroupsInfo; + +import java.util.List; + +/** + * A SharedGroupManager provides services for discovering the shared groups where a user belongs.<p> + * + * Important note: This functionality is not part of the XMPP spec and it will only work + * with Wildfire. + * + * @author Gaston Dombiak + */ +public class SharedGroupManager { + + /** + * Returns the collection that will contain the name of the shared groups where the user + * logged in with the specified session belongs. + * + * @param connection connection to use to get the user's shared groups. + * @return collection with the shared groups' name of the logged user. + */ + public static List getSharedGroups(Connection connection) throws XMPPException { + // Discover the shared groups of the logged user + SharedGroupsInfo info = new SharedGroupsInfo(); + info.setType(IQ.Type.GET); + + // Create a packet collector to listen for a response. + PacketCollector collector = + connection.createPacketCollector(new PacketIDFilter(info.getPacketID())); + + connection.sendPacket(info); + + // Wait up to 5 seconds for a result. + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from the server."); + } + if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return ((SharedGroupsInfo) result).getGroups(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java new file mode 100644 index 0000000..f0c102c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLManager.java @@ -0,0 +1,144 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.XHTMLExtension; + +import java.util.Iterator; + +/** + * Manages XHTML formatted texts within messages. A XHTMLManager provides a high level access to + * get and set XHTML bodies to messages, enable and disable XHTML support and check if remote XMPP + * clients support XHTML. + * + * @author Gaston Dombiak + */ +public class XHTMLManager { + + private final static String namespace = "http://jabber.org/protocol/xhtml-im"; + + // Enable the XHTML support on every established connection + // The ServiceDiscoveryManager class should have been already initialized + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + XHTMLManager.setServiceEnabled(connection, true); + } + }); + } + + /** + * Returns an Iterator for the XHTML bodies in the message. Returns null if + * the message does not contain an XHTML extension. + * + * @param message an XHTML message + * @return an Iterator for the bodies in the message or null if none. + */ + public static Iterator getBodies(Message message) { + XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); + if (xhtmlExtension != null) + return xhtmlExtension.getBodies(); + else + return null; + } + + /** + * Adds an XHTML body to the message. + * + * @param message the message that will receive the XHTML body + * @param body the string to add as an XHTML body to the message + */ + public static void addBody(Message message, String body) { + XHTMLExtension xhtmlExtension = (XHTMLExtension) message.getExtension("html", namespace); + if (xhtmlExtension == null) { + // Create an XHTMLExtension and add it to the message + xhtmlExtension = new XHTMLExtension(); + message.addExtension(xhtmlExtension); + } + // Add the required bodies to the message + xhtmlExtension.addBody(body); + } + + /** + * Returns true if the message contains an XHTML extension. + * + * @param message the message to check if contains an XHTML extentsion or not + * @return a boolean indicating whether the message is an XHTML message + */ + public static boolean isXHTMLMessage(Message message) { + return message.getExtension("html", namespace) != null; + } + + /** + * Enables or disables the XHTML support on a given connection.<p> + * + * Before starting to send XHTML messages to a user, check that the user can handle XHTML + * messages. Enable the XHTML support to indicate that this client handles XHTML messages. + * + * @param connection the connection where the service will be enabled or disabled + * @param enabled indicates if the service will be enabled or disabled + */ + public synchronized static void setServiceEnabled(Connection connection, boolean enabled) { + if (isServiceEnabled(connection) == enabled) + return; + + if (enabled) { + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(namespace); + } + else { + ServiceDiscoveryManager.getInstanceFor(connection).removeFeature(namespace); + } + } + + /** + * Returns true if the XHTML support is enabled for the given connection. + * + * @param connection the connection to look for XHTML support + * @return a boolean indicating if the XHTML support is enabled for the given connection + */ + public static boolean isServiceEnabled(Connection connection) { + return ServiceDiscoveryManager.getInstanceFor(connection).includesFeature(namespace); + } + + /** + * Returns true if the specified user handles XHTML messages. + * + * @param connection the connection to use to perform the service discovery + * @param userID the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com + * @return a boolean indicating whether the specified user handles XHTML messages + */ + public static boolean isServiceEnabled(Connection connection, String userID) { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(userID); + return result.containsFeature(namespace); + } + catch (XMPPException e) { + e.printStackTrace(); + return false; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java new file mode 100644 index 0000000..201e530 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XHTMLText.java @@ -0,0 +1,429 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * An XHTMLText represents formatted text. This class also helps to build valid + * XHTML tags. + * + * @author Gaston Dombiak + */ +public class XHTMLText { + + private StringBuilder text = new StringBuilder(30); + + /** + * Creates a new XHTMLText with body tag params. + * + * @param style the XHTML style of the body + * @param lang the language of the body + */ + public XHTMLText(String style, String lang) { + appendOpenBodyTag(style, lang); + } + + /** + * Appends a tag that indicates that an anchor section begins. + * + * @param href indicates the URL being linked to + * @param style the XHTML style of the anchor + */ + public void appendOpenAnchorTag(String href, String style) { + StringBuilder sb = new StringBuilder("<a"); + if (href != null) { + sb.append(" href=\""); + sb.append(href); + sb.append("\""); + } + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an anchor section ends. + * + */ + public void appendCloseAnchorTag() { + text.append("</a>"); + } + + /** + * Appends a tag that indicates that a blockquote section begins. + * + * @param style the XHTML style of the blockquote + */ + public void appendOpenBlockQuoteTag(String style) { + StringBuilder sb = new StringBuilder("<blockquote"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a blockquote section ends. + * + */ + public void appendCloseBlockQuoteTag() { + text.append("</blockquote>"); + } + + /** + * Appends a tag that indicates that a body section begins. + * + * @param style the XHTML style of the body + * @param lang the language of the body + */ + private void appendOpenBodyTag(String style, String lang) { + StringBuilder sb = new StringBuilder("<body"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + if (lang != null) { + sb.append(" xml:lang=\""); + sb.append(lang); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a body section ends. + * + */ + private String closeBodyTag() { + return "</body>"; + } + + /** + * Appends a tag that inserts a single carriage return. + * + */ + public void appendBrTag() { + text.append("<br/>"); + } + + /** + * Appends a tag that indicates a reference to work, such as a book, report or web site. + * + */ + public void appendOpenCiteTag() { + text.append("<cite>"); + } + + /** + * Appends a tag that indicates text that is the code for a program. + * + */ + public void appendOpenCodeTag() { + text.append("<code>"); + } + + /** + * Appends a tag that indicates end of text that is the code for a program. + * + */ + public void appendCloseCodeTag() { + text.append("</code>"); + } + + /** + * Appends a tag that indicates emphasis. + * + */ + public void appendOpenEmTag() { + text.append("<em>"); + } + + /** + * Appends a tag that indicates end of emphasis. + * + */ + public void appendCloseEmTag() { + text.append("</em>"); + } + + /** + * Appends a tag that indicates a header, a title of a section of the message. + * + * @param level the level of the Header. It should be a value between 1 and 3 + * @param style the XHTML style of the blockquote + */ + public void appendOpenHeaderTag(int level, String style) { + if (level > 3 || level < 1) { + return; + } + StringBuilder sb = new StringBuilder("<h"); + sb.append(level); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a header section ends. + * + * @param level the level of the Header. It should be a value between 1 and 3 + */ + public void appendCloseHeaderTag(int level) { + if (level > 3 || level < 1) { + return; + } + StringBuilder sb = new StringBuilder("</h"); + sb.append(level); + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates an image. + * + * @param align how text should flow around the picture + * @param alt the text to show if you don't show the picture + * @param height how tall is the picture + * @param src where to get the picture + * @param width how wide is the picture + */ + public void appendImageTag(String align, String alt, String height, String src, String width) { + StringBuilder sb = new StringBuilder("<img"); + if (align != null) { + sb.append(" align=\""); + sb.append(align); + sb.append("\""); + } + if (alt != null) { + sb.append(" alt=\""); + sb.append(alt); + sb.append("\""); + } + if (height != null) { + sb.append(" height=\""); + sb.append(height); + sb.append("\""); + } + if (src != null) { + sb.append(" src=\""); + sb.append(src); + sb.append("\""); + } + if (width != null) { + sb.append(" width=\""); + sb.append(width); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates the start of a new line item within a list. + * + * @param style the style of the line item + */ + public void appendLineItemTag(String style) { + StringBuilder sb = new StringBuilder("<li"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that creates an ordered list. "Ordered" means that the order of the items + * in the list is important. To show this, browsers automatically number the list. + * + * @param style the style of the ordered list + */ + public void appendOpenOrderedListTag(String style) { + StringBuilder sb = new StringBuilder("<ol"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an ordered list section ends. + * + */ + public void appendCloseOrderedListTag() { + text.append("</ol>"); + } + + /** + * Appends a tag that creates an unordered list. The unordered part means that the items + * in the list are not in any particular order. + * + * @param style the style of the unordered list + */ + public void appendOpenUnorderedListTag(String style) { + StringBuilder sb = new StringBuilder("<ul"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an unordered list section ends. + * + */ + public void appendCloseUnorderedListTag() { + text.append("</ul>"); + } + + /** + * Appends a tag that indicates the start of a new paragraph. This is usually rendered + * with two carriage returns, producing a single blank line in between the two paragraphs. + * + * @param style the style of the paragraph + */ + public void appendOpenParagraphTag(String style) { + StringBuilder sb = new StringBuilder("<p"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates the end of a new paragraph. This is usually rendered + * with two carriage returns, producing a single blank line in between the two paragraphs. + * + */ + public void appendCloseParagraphTag() { + text.append("</p>"); + } + + /** + * Appends a tag that indicates that an inlined quote section begins. + * + * @param style the style of the inlined quote + */ + public void appendOpenInlinedQuoteTag(String style) { + StringBuilder sb = new StringBuilder("<q"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that an inlined quote section ends. + * + */ + public void appendCloseInlinedQuoteTag() { + text.append("</q>"); + } + + /** + * Appends a tag that allows to set the fonts for a span of text. + * + * @param style the style for a span of text + */ + public void appendOpenSpanTag(String style) { + StringBuilder sb = new StringBuilder("<span"); + if (style != null) { + sb.append(" style=\""); + sb.append(style); + sb.append("\""); + } + sb.append(">"); + text.append(sb.toString()); + } + + /** + * Appends a tag that indicates that a span section ends. + * + */ + public void appendCloseSpanTag() { + text.append("</span>"); + } + + /** + * Appends a tag that indicates text which should be more forceful than surrounding text. + * + */ + public void appendOpenStrongTag() { + text.append("<strong>"); + } + + /** + * Appends a tag that indicates that a strong section ends. + * + */ + public void appendCloseStrongTag() { + text.append("</strong>"); + } + + /** + * Appends a given text to the XHTMLText. + * + * @param textToAppend the text to append + */ + public void append(String textToAppend) { + text.append(StringUtils.escapeForXML(textToAppend)); + } + + /** + * Returns the text of the XHTMLText. + * + * Note: Automatically adds the closing body tag. + * + * @return the text of the XHTMLText + */ + public String toString() { + return text.toString().concat(closeBodyTag()); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java new file mode 100644 index 0000000..1e836b1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/XmppStreamHandler.java @@ -0,0 +1,258 @@ +/** + * Copyright 2012 Miron Cuperman. + * + * All rights reserved. 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 org.jivesoftware.smackx; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.UnknownPacket; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.StringUtils; +import org.xmlpull.v1.XmlPullParser; + +public class XmppStreamHandler { + private static final String URN_SM_2 = "urn:xmpp:sm:2"; + private XMPPConnection mConnection; + private boolean isSmAvailable = false; + private boolean isSmEnabled = false; + private long previousIncomingStanzaCount = -1; + private String sessionId; + private long incomingStanzaCount = 0; + private long outgoingStanzaCount = 0; + private List<ManagedPacket> sendList = null; + + public static class ManagedPacket { + Packet packet; + long stanzaCount; + } + + public XmppStreamHandler(XMPPConnection connection) { + mConnection = connection; + startListening(); + } + + public boolean isResumePossible() { + return sessionId != null; + } + + public static void addExtensionProviders() { + addSimplePacketExtension("sm", URN_SM_2); + addSimplePacketExtension("r", URN_SM_2); + addSimplePacketExtension("a", URN_SM_2); + addSimplePacketExtension("enabled", URN_SM_2); + addSimplePacketExtension("resumed", URN_SM_2); + addSimplePacketExtension("failed", URN_SM_2); + } + + public void notifyInitialLogin() { + if (isSmAvailable) { + sendEnablePacket(); + } + } + + private void sendEnablePacket() { + if (sessionId != null) { + // TODO binding + StreamHandlingPacket resumePacket = new StreamHandlingPacket("resume", URN_SM_2); + resumePacket.addAttribute("h", String.valueOf(previousIncomingStanzaCount)); + resumePacket.addAttribute("previd", sessionId); + mConnection.sendPacket(resumePacket); + } + else { + StreamHandlingPacket enablePacket = new StreamHandlingPacket("enable", URN_SM_2); + enablePacket.addAttribute("resume", "true"); + mConnection.sendPacket(enablePacket); + } + } + + private void startListening() { + mConnection.addConnectionListener(new ConnectionListener() { + public void reconnectionSuccessful() { + if (isSmAvailable) { + sendEnablePacket(); + } + else { + isSmEnabled = false; + sessionId = null; + } + } + + public void reconnectionFailed(Exception e) { + } + + public void reconnectingIn(int seconds) { + } + + public void connectionClosedOnError(Exception e) { + if (isSmEnabled && sessionId != null) { + previousIncomingStanzaCount = incomingStanzaCount; + } + isSmEnabled = false; + isSmAvailable = false; + } + + public void connectionClosed() { + previousIncomingStanzaCount = -1; + } + }); + + mConnection.addPacketSendingListener(new PacketListener() { + public void processPacket(Packet packet) { + if (isSmAvailable) { + // for packets sent directly after <enable/>, before + // <enabled/> is received. + outgoingStanzaCount++; + } + if (isSmEnabled) { + StreamHandlingPacket reqPacket = new StreamHandlingPacket("r", URN_SM_2); + mConnection.sendPacket(reqPacket); + ManagedPacket managedPacket = new ManagedPacket(); + managedPacket.packet = packet; + managedPacket.stanzaCount = outgoingStanzaCount; + sendList.add(managedPacket); + } + } + }, new PacketFilter() { + public boolean accept(Packet packet) { + return (!(packet instanceof StreamHandlingPacket)); + } + }); + + mConnection.addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + incomingStanzaCount++; + if (packet instanceof StreamHandlingPacket) { + StreamHandlingPacket shPacket = (StreamHandlingPacket) packet; + String name = shPacket.getElementName(); + if ("sm".equals(name)) { + isSmAvailable = true; + } + else if ("r".equals(name)) { + incomingStanzaCount--; + StreamHandlingPacket ackPacket = new StreamHandlingPacket("a", URN_SM_2); + ackPacket.addAttribute("h", String.valueOf(incomingStanzaCount)); + mConnection.sendPacket(ackPacket); + } + else if ("a".equals(name)) { + for (ManagedPacket mp : sendList) { + long ackedCount = Long.valueOf(((StreamHandlingPacket) packet).getAttribute("h")); + if (mp.stanzaCount <= ackedCount) { + sendList.remove(mp); + } + } + } + else if ("enabled".equals(name)) { + incomingStanzaCount = 0; + isSmEnabled = true; + mConnection.getRoster().setOfflineOnError(false); + String resume = shPacket.getAttribute("resume"); + if ("true".equals(resume) || "1".equals(resume)) { + sessionId = shPacket.getAttribute("id"); + } + sendList = new ArrayList<ManagedPacket>(); + } + else if ("resumed".equals(name)) { + incomingStanzaCount = previousIncomingStanzaCount; + isSmEnabled = true; + } + else if ("failed".equals(name)) { + // Failed, shutdown and the parent will retry + mConnection.getRoster().setOfflineOnError(true); + mConnection.getRoster().setOfflinePresences(); + sessionId = null; + mConnection.quickShutdown(); + // isSmEnabled is already false + } + } + } + }, new PacketFilter() { + public boolean accept(Packet packet) { + return true; + } + }); + } + + private static void addSimplePacketExtension(final String name, final String namespace) { + ProviderManager.getInstance().addExtensionProvider(name, namespace, new PacketExtensionProvider() { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + StreamHandlingPacket packet = new StreamHandlingPacket(name, namespace); + int attributeCount = parser.getAttributeCount(); + for (int i = 0; i < attributeCount; i++) { + packet.addAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); + } + return packet; + } + }); + } + + static class StreamHandlingPacket extends UnknownPacket { + private String name; + private String namespace; + Map<String, String> attributes; + + StreamHandlingPacket(String name, String namespace) { + this.name = name; + this.namespace = namespace; + attributes = Collections.emptyMap(); + } + + public void addAttribute(String name, String value) { + if (attributes == Collections.EMPTY_MAP) + attributes = new HashMap<String, String>(); + attributes.put(name, value); + } + + public String getAttribute(String name) { + return attributes.get(name); + } + + public String getNamespace() { + return namespace; + } + + public String getElementName() { + return name; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()); + + // TODO Xmlns?? + if (getNamespace() != null) { + buf.append(" xmlns=\"").append(getNamespace()).append("\""); + } + for (String key : attributes.keySet()) { + buf.append(" ").append(key).append("=\"").append(StringUtils.escapeForXML(attributes.get(key))) + .append("\""); + } + buf.append("/>"); + return buf.toString(); + } + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java new file mode 100644 index 0000000..f85cc9c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkManager.java @@ -0,0 +1,224 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.bookmark; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.PrivateDataManager; + +import java.util.*; + +/** + * Provides methods to manage bookmarks in accordance with JEP-0048. Methods for managing URLs and + * Conferences are provided. + * </p> + * It should be noted that some extensions have been made to the JEP. There is an attribute on URLs + * that marks a url as a news feed and also a sub-element can be added to either a URL or conference + * indicated that it is shared amongst all users on a server. + * + * @author Alexander Wenckus + */ +public class BookmarkManager { + private static final Map<Connection, BookmarkManager> bookmarkManagerMap = new HashMap<Connection, BookmarkManager>(); + static { + PrivateDataManager.addPrivateDataProvider("storage", "storage:bookmarks", + new Bookmarks.Provider()); + } + + /** + * Returns the <i>BookmarkManager</i> for a connection, if it doesn't exist it is created. + * + * @param connection the connection for which the manager is desired. + * @return Returns the <i>BookmarkManager</i> for a connection, if it doesn't + * exist it is created. + * @throws XMPPException Thrown if the connection is null or has not yet been authenticated. + */ + public synchronized static BookmarkManager getBookmarkManager(Connection connection) + throws XMPPException + { + BookmarkManager manager = (BookmarkManager) bookmarkManagerMap.get(connection); + if(manager == null) { + manager = new BookmarkManager(connection); + bookmarkManagerMap.put(connection, manager); + } + return manager; + } + + private PrivateDataManager privateDataManager; + private Bookmarks bookmarks; + private final Object bookmarkLock = new Object(); + + /** + * Default constructor. Registers the data provider with the private data manager in the + * storage:bookmarks namespace. + * + * @param connection the connection for persisting and retrieving bookmarks. + * @throws XMPPException thrown when the connection is null or has not been authenticated. + */ + private BookmarkManager(Connection connection) throws XMPPException { + if(connection == null || !connection.isAuthenticated()) { + throw new XMPPException("Invalid connection."); + } + this.privateDataManager = new PrivateDataManager(connection); + } + + /** + * Returns all currently bookmarked conferences. + * + * @return returns all currently bookmarked conferences + * @throws XMPPException thrown when there was an error retrieving the current bookmarks from + * the server. + * @see BookmarkedConference + */ + public Collection<BookmarkedConference> getBookmarkedConferences() throws XMPPException { + retrieveBookmarks(); + return Collections.unmodifiableCollection(bookmarks.getBookmarkedConferences()); + } + + /** + * Adds or updates a conference in the bookmarks. + * + * @param name the name of the conference + * @param jid the jid of the conference + * @param isAutoJoin whether or not to join this conference automatically on login + * @param nickname the nickname to use for the user when joining the conference + * @param password the password to use for the user when joining the conference + * @throws XMPPException thrown when there is an issue retrieving the current bookmarks from + * the server. + */ + public void addBookmarkedConference(String name, String jid, boolean isAutoJoin, + String nickname, String password) throws XMPPException + { + retrieveBookmarks(); + BookmarkedConference bookmark + = new BookmarkedConference(name, jid, isAutoJoin, nickname, password); + List<BookmarkedConference> conferences = bookmarks.getBookmarkedConferences(); + if(conferences.contains(bookmark)) { + BookmarkedConference oldConference = conferences.get(conferences.indexOf(bookmark)); + if(oldConference.isShared()) { + throw new IllegalArgumentException("Cannot modify shared bookmark"); + } + oldConference.setAutoJoin(isAutoJoin); + oldConference.setName(name); + oldConference.setNickname(nickname); + oldConference.setPassword(password); + } + else { + bookmarks.addBookmarkedConference(bookmark); + } + privateDataManager.setPrivateData(bookmarks); + } + + /** + * Removes a conference from the bookmarks. + * + * @param jid the jid of the conference to be removed. + * @throws XMPPException thrown when there is a problem with the connection attempting to + * retrieve the bookmarks or persist the bookmarks. + * @throws IllegalArgumentException thrown when the conference being removed is a shared + * conference + */ + public void removeBookmarkedConference(String jid) throws XMPPException { + retrieveBookmarks(); + Iterator<BookmarkedConference> it = bookmarks.getBookmarkedConferences().iterator(); + while(it.hasNext()) { + BookmarkedConference conference = it.next(); + if(conference.getJid().equalsIgnoreCase(jid)) { + if(conference.isShared()) { + throw new IllegalArgumentException("Conference is shared and can't be removed"); + } + it.remove(); + privateDataManager.setPrivateData(bookmarks); + return; + } + } + } + + /** + * Returns an unmodifiable collection of all bookmarked urls. + * + * @return returns an unmodifiable collection of all bookmarked urls. + * @throws XMPPException thrown when there is a problem retriving bookmarks from the server. + */ + public Collection<BookmarkedURL> getBookmarkedURLs() throws XMPPException { + retrieveBookmarks(); + return Collections.unmodifiableCollection(bookmarks.getBookmarkedURLS()); + } + + /** + * Adds a new url or updates an already existing url in the bookmarks. + * + * @param URL the url of the bookmark + * @param name the name of the bookmark + * @param isRSS whether or not the url is an rss feed + * @throws XMPPException thrown when there is an error retriving or saving bookmarks from or to + * the server + */ + public void addBookmarkedURL(String URL, String name, boolean isRSS) throws XMPPException { + retrieveBookmarks(); + BookmarkedURL bookmark = new BookmarkedURL(URL, name, isRSS); + List<BookmarkedURL> urls = bookmarks.getBookmarkedURLS(); + if(urls.contains(bookmark)) { + BookmarkedURL oldURL = urls.get(urls.indexOf(bookmark)); + if(oldURL.isShared()) { + throw new IllegalArgumentException("Cannot modify shared bookmarks"); + } + oldURL.setName(name); + oldURL.setRss(isRSS); + } + else { + bookmarks.addBookmarkedURL(bookmark); + } + privateDataManager.setPrivateData(bookmarks); + } + + /** + * Removes a url from the bookmarks. + * + * @param bookmarkURL the url of the bookmark to remove + * @throws XMPPException thrown if there is an error retriving or saving bookmarks from or to + * the server. + */ + public void removeBookmarkedURL(String bookmarkURL) throws XMPPException { + retrieveBookmarks(); + Iterator<BookmarkedURL> it = bookmarks.getBookmarkedURLS().iterator(); + while(it.hasNext()) { + BookmarkedURL bookmark = it.next(); + if(bookmark.getURL().equalsIgnoreCase(bookmarkURL)) { + if(bookmark.isShared()) { + throw new IllegalArgumentException("Cannot delete a shared bookmark."); + } + it.remove(); + privateDataManager.setPrivateData(bookmarks); + return; + } + } + } + + private Bookmarks retrieveBookmarks() throws XMPPException { + synchronized(bookmarkLock) { + if(bookmarks == null) { + bookmarks = (Bookmarks) privateDataManager.getPrivateData("storage", + "storage:bookmarks"); + } + return bookmarks; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java new file mode 100644 index 0000000..5dac202 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedConference.java @@ -0,0 +1,130 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.bookmark; + +/** + * Respresents a Conference Room bookmarked on the server using JEP-0048 Bookmark Storage JEP. + * + * @author Derek DeMoro + */ +public class BookmarkedConference implements SharedBookmark { + + private String name; + private boolean autoJoin; + private final String jid; + + private String nickname; + private String password; + private boolean isShared; + + protected BookmarkedConference(String jid) { + this.jid = jid; + } + + protected BookmarkedConference(String name, String jid, boolean autoJoin, String nickname, + String password) + { + this.name = name; + this.jid = jid; + this.autoJoin = autoJoin; + this.nickname = nickname; + this.password = password; + } + + + /** + * Returns the display label representing the Conference room. + * + * @return the name of the conference room. + */ + public String getName() { + return name; + } + + protected void setName(String name) { + this.name = name; + } + + /** + * Returns true if this conference room should be auto-joined on startup. + * + * @return true if room should be joined on startup, otherwise false. + */ + public boolean isAutoJoin() { + return autoJoin; + } + + protected void setAutoJoin(boolean autoJoin) { + this.autoJoin = autoJoin; + } + + /** + * Returns the full JID of this conference room. (ex.dev@conference.jivesoftware.com) + * + * @return the full JID of this conference room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the nickname to use when joining this conference room. This is an optional + * value and may return null. + * + * @return the nickname to use when joining, null may be returned. + */ + public String getNickname() { + return nickname; + } + + protected void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * Returns the password to use when joining this conference room. This is an optional + * value and may return null. + * + * @return the password to use when joining this conference room, null may be returned. + */ + public String getPassword() { + return password; + } + + protected void setPassword(String password) { + this.password = password; + } + + public boolean equals(Object obj) { + if(obj == null || !(obj instanceof BookmarkedConference)) { + return false; + } + BookmarkedConference conference = (BookmarkedConference)obj; + return conference.getJid().equalsIgnoreCase(jid); + } + + protected void setShared(boolean isShared) { + this.isShared = isShared; + } + + public boolean isShared() { + return isShared; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java new file mode 100644 index 0000000..f3d6d9d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/BookmarkedURL.java @@ -0,0 +1,104 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.bookmark; + +/** + * Respresents one instance of a URL defined using JEP-0048 Bookmark Storage JEP. + * + * @author Derek DeMoro + */ +public class BookmarkedURL implements SharedBookmark { + + private String name; + private final String URL; + private boolean isRss; + private boolean isShared; + + protected BookmarkedURL(String URL) { + this.URL = URL; + } + + protected BookmarkedURL(String URL, String name, boolean isRss) { + this.URL = URL; + this.name = name; + this.isRss = isRss; + } + + /** + * Returns the name representing the URL (eg. Jive Software). This can be used in as a label, or + * identifer in applications. + * + * @return the name reprenting the URL. + */ + public String getName() { + return name; + } + + /** + * Sets the name representing the URL. + * + * @param name the name. + */ + protected void setName(String name) { + this.name = name; + } + + /** + * Returns the URL. + * + * @return the url. + */ + public String getURL() { + return URL; + } + /** + * Set to true if this URL is an RSS or news feed. + * + * @param isRss True if the URL is a news feed and false if it is not. + */ + protected void setRss(boolean isRss) { + this.isRss = isRss; + } + + /** + * Returns true if this URL is a news feed. + * + * @return Returns true if this URL is a news feed. + */ + public boolean isRss() { + return isRss; + } + + public boolean equals(Object obj) { + if(!(obj instanceof BookmarkedURL)) { + return false; + } + BookmarkedURL url = (BookmarkedURL)obj; + return url.getURL().equalsIgnoreCase(URL); + } + + protected void setShared(boolean shared) { + this.isShared = shared; + } + + public boolean isShared() { + return isShared; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java new file mode 100644 index 0000000..100fa46 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/Bookmarks.java @@ -0,0 +1,310 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.bookmark; + +import org.jivesoftware.smackx.packet.PrivateData; +import org.jivesoftware.smackx.provider.PrivateDataProvider; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Bookmarks is used for storing and retrieving URLS and Conference rooms. + * Bookmark Storage (JEP-0048) defined a protocol for the storage of bookmarks to conference rooms and other entities + * in a Jabber user's account. + * See the following code sample for saving Bookmarks: + * <p/> + * <pre> + * Connection con = new XMPPConnection("jabber.org"); + * con.login("john", "doe"); + * Bookmarks bookmarks = new Bookmarks(); + * <p/> + * // Bookmark a URL + * BookmarkedURL url = new BookmarkedURL(); + * url.setName("Google"); + * url.setURL("http://www.jivesoftware.com"); + * bookmarks.addURL(url); + * // Bookmark a Conference room. + * BookmarkedConference conference = new BookmarkedConference(); + * conference.setName("My Favorite Room"); + * conference.setAutoJoin("true"); + * conference.setJID("dev@conference.jivesoftware.com"); + * bookmarks.addConference(conference); + * // Save Bookmarks using PrivateDataManager. + * PrivateDataManager manager = new PrivateDataManager(con); + * manager.setPrivateData(bookmarks); + * <p/> + * <p/> + * LastActivity activity = LastActivity.getLastActivity(con, "xray@jabber.org"); + * </pre> + * + * @author Derek DeMoro + */ +public class Bookmarks implements PrivateData { + + private List<BookmarkedURL> bookmarkedURLS; + private List<BookmarkedConference> bookmarkedConferences; + + /** + * Required Empty Constructor to use Bookmarks. + */ + public Bookmarks() { + bookmarkedURLS = new ArrayList<BookmarkedURL>(); + bookmarkedConferences = new ArrayList<BookmarkedConference>(); + } + + /** + * Adds a BookmarkedURL. + * + * @param bookmarkedURL the bookmarked bookmarkedURL. + */ + public void addBookmarkedURL(BookmarkedURL bookmarkedURL) { + bookmarkedURLS.add(bookmarkedURL); + } + + /** + * Removes a bookmarked bookmarkedURL. + * + * @param bookmarkedURL the bookmarked bookmarkedURL to remove. + */ + public void removeBookmarkedURL(BookmarkedURL bookmarkedURL) { + bookmarkedURLS.remove(bookmarkedURL); + } + + /** + * Removes all BookmarkedURLs from user's bookmarks. + */ + public void clearBookmarkedURLS() { + bookmarkedURLS.clear(); + } + + /** + * Add a BookmarkedConference to bookmarks. + * + * @param bookmarkedConference the conference to remove. + */ + public void addBookmarkedConference(BookmarkedConference bookmarkedConference) { + bookmarkedConferences.add(bookmarkedConference); + } + + /** + * Removes a BookmarkedConference. + * + * @param bookmarkedConference the BookmarkedConference to remove. + */ + public void removeBookmarkedConference(BookmarkedConference bookmarkedConference) { + bookmarkedConferences.remove(bookmarkedConference); + } + + /** + * Removes all BookmarkedConferences from Bookmarks. + */ + public void clearBookmarkedConferences() { + bookmarkedConferences.clear(); + } + + /** + * Returns a Collection of all Bookmarked URLs for this user. + * + * @return a collection of all Bookmarked URLs. + */ + public List<BookmarkedURL> getBookmarkedURLS() { + return bookmarkedURLS; + } + + /** + * Returns a Collection of all Bookmarked Conference for this user. + * + * @return a collection of all Bookmarked Conferences. + */ + public List<BookmarkedConference> getBookmarkedConferences() { + return bookmarkedConferences; + } + + + /** + * Returns the root element name. + * + * @return the element name. + */ + public String getElementName() { + return "storage"; + } + + /** + * Returns the root element XML namespace. + * + * @return the namespace. + */ + public String getNamespace() { + return "storage:bookmarks"; + } + + /** + * Returns the XML reppresentation of the PrivateData. + * + * @return the private data as XML. + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<storage xmlns=\"storage:bookmarks\">"); + + final Iterator<BookmarkedURL> urls = getBookmarkedURLS().iterator(); + while (urls.hasNext()) { + BookmarkedURL urlStorage = urls.next(); + if(urlStorage.isShared()) { + continue; + } + buf.append("<url name=\"").append(urlStorage.getName()). + append("\" url=\"").append(urlStorage.getURL()).append("\""); + if(urlStorage.isRss()) { + buf.append(" rss=\"").append(true).append("\""); + } + buf.append(" />"); + } + + // Add Conference additions + final Iterator<BookmarkedConference> conferences = getBookmarkedConferences().iterator(); + while (conferences.hasNext()) { + BookmarkedConference conference = conferences.next(); + if(conference.isShared()) { + continue; + } + buf.append("<conference "); + buf.append("name=\"").append(conference.getName()).append("\" "); + buf.append("autojoin=\"").append(conference.isAutoJoin()).append("\" "); + buf.append("jid=\"").append(conference.getJid()).append("\" "); + buf.append(">"); + + if (conference.getNickname() != null) { + buf.append("<nick>").append(conference.getNickname()).append("</nick>"); + } + + + if (conference.getPassword() != null) { + buf.append("<password>").append(conference.getPassword()).append("</password>"); + } + buf.append("</conference>"); + } + + + buf.append("</storage>"); + return buf.toString(); + } + + /** + * The IQ Provider for BookmarkStorage. + * + * @author Derek DeMoro + */ + public static class Provider implements PrivateDataProvider { + + /** + * Empty Constructor for PrivateDataProvider. + */ + public Provider() { + super(); + } + + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception { + Bookmarks storage = new Bookmarks(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "url".equals(parser.getName())) { + final BookmarkedURL urlStorage = getURLStorage(parser); + if (urlStorage != null) { + storage.addBookmarkedURL(urlStorage); + } + } + else if (eventType == XmlPullParser.START_TAG && + "conference".equals(parser.getName())) + { + final BookmarkedConference conference = getConferenceStorage(parser); + storage.addBookmarkedConference(conference); + } + else if (eventType == XmlPullParser.END_TAG && "storage".equals(parser.getName())) + { + done = true; + } + } + + + return storage; + } + } + + private static BookmarkedURL getURLStorage(XmlPullParser parser) throws IOException, XmlPullParserException { + String name = parser.getAttributeValue("", "name"); + String url = parser.getAttributeValue("", "url"); + String rssString = parser.getAttributeValue("", "rss"); + boolean rss = rssString != null && "true".equals(rssString); + + BookmarkedURL urlStore = new BookmarkedURL(url, name, rss); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if(eventType == XmlPullParser.START_TAG + && "shared_bookmark".equals(parser.getName())) { + urlStore.setShared(true); + } + else if (eventType == XmlPullParser.END_TAG && "url".equals(parser.getName())) { + done = true; + } + } + return urlStore; + } + + private static BookmarkedConference getConferenceStorage(XmlPullParser parser) throws Exception { + String name = parser.getAttributeValue("", "name"); + String autojoin = parser.getAttributeValue("", "autojoin"); + String jid = parser.getAttributeValue("", "jid"); + + BookmarkedConference conf = new BookmarkedConference(jid); + conf.setName(name); + conf.setAutoJoin(Boolean.valueOf(autojoin).booleanValue()); + + // Check for nickname + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && "nick".equals(parser.getName())) { + conf.setNickname(parser.nextText()); + } + else if (eventType == XmlPullParser.START_TAG && "password".equals(parser.getName())) { + conf.setPassword(parser.nextText()); + } + else if(eventType == XmlPullParser.START_TAG + && "shared_bookmark".equals(parser.getName())) { + conf.setShared(true); + } + else if (eventType == XmlPullParser.END_TAG && "conference".equals(parser.getName())) { + done = true; + } + } + + + return conf; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java new file mode 100644 index 0000000..f672bc1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bookmark/SharedBookmark.java @@ -0,0 +1,35 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.bookmark; + +/** + * Interface to indicate if a bookmark is shared across the server. + * + * @author Alexander Wenckus + */ +public interface SharedBookmark { + + /** + * Returns true if this bookmark is shared. + * + * @return returns true if this bookmark is shared. + */ + public boolean isShared(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java new file mode 100644 index 0000000..be78255 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamListener.java @@ -0,0 +1,47 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams; + +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamListener; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamListener; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; + +/** + * BytestreamListener are notified if a remote user wants to initiate a bytestream. Implement this + * interface to handle incoming bytestream requests. + * <p> + * BytestreamListener can be registered at the {@link Socks5BytestreamManager} or the + * {@link InBandBytestreamManager}. + * <p> + * There are two ways to add this listener. See + * {@link BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and + * {@link BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for further + * details. + * <p> + * {@link Socks5BytestreamListener} or {@link InBandBytestreamListener} provide a more specific + * interface of the BytestreamListener. + * + * @author Henning Staib + */ +public interface BytestreamListener { + + /** + * This listener is notified if a bytestream request from another user has been received. + * + * @param request the incoming bytestream request + */ + public void incomingBytestreamRequest(BytestreamRequest request); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java new file mode 100644 index 0000000..ca6bbc6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamManager.java @@ -0,0 +1,114 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams; + +import java.io.IOException; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; + +/** + * BytestreamManager provides a generic interface for bytestream managers. + * <p> + * There are two implementations of the interface. See {@link Socks5BytestreamManager} and + * {@link InBandBytestreamManager}. + * + * @author Henning Staib + */ +public interface BytestreamManager { + + /** + * Adds {@link BytestreamListener} that is called for every incoming bytestream request unless + * there is a user specific {@link BytestreamListener} registered. + * <p> + * See {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and + * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener)} for further + * details. + * + * @param listener the listener to register + */ + public void addIncomingBytestreamListener(BytestreamListener listener); + + /** + * Removes the given listener from the list of listeners for all incoming bytestream requests. + * + * @param listener the listener to remove + */ + public void removeIncomingBytestreamListener(BytestreamListener listener); + + /** + * Adds {@link BytestreamListener} that is called for every incoming bytestream request unless + * there is a user specific {@link BytestreamListener} registered. + * <p> + * Use this method if you are awaiting an incoming bytestream request from a specific user. + * <p> + * See {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} + * and {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} + * for further details. + * + * @param listener the listener to register + * @param initiatorJID the JID of the user that wants to establish a bytestream + */ + public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID); + + /** + * Removes the listener for the given user. + * + * @param initiatorJID the JID of the user the listener should be removed + */ + public void removeIncomingBytestreamListener(String initiatorJID); + + /** + * Establishes a bytestream with the given user and returns the session to send/receive data + * to/from the user. + * <p> + * Use this method to establish bytestreams to users accepting all incoming bytestream requests + * since this method doesn't provide a way to tell the user something about the data to be sent. + * <p> + * To establish a bytestream after negotiation the kind of data to be sent (e.g. file transfer) + * use {@link #establishSession(String, String)}. + * <p> + * See {@link Socks5BytestreamManager#establishSession(String)} and + * {@link InBandBytestreamManager#establishSession(String)} for further details. + * + * @param targetJID the JID of the user a bytestream should be established + * @return the session to send/receive data to/from the user + * @throws XMPPException if an error occurred while establishing the session + * @throws IOException if an IO error occurred while establishing the session + * @throws InterruptedException if the thread was interrupted while waiting in a blocking + * operation + */ + public BytestreamSession establishSession(String targetJID) throws XMPPException, IOException, + InterruptedException; + + /** + * Establishes a bytestream with the given user and returns the session to send/receive data + * to/from the user. + * <p> + * See {@link Socks5BytestreamManager#establishSession(String)} and + * {@link InBandBytestreamManager#establishSession(String)} for further details. + * + * @param targetJID the JID of the user a bytestream should be established + * @param sessionID the session ID for the bytestream request + * @return the session to send/receive data to/from the user + * @throws XMPPException if an error occurred while establishing the session + * @throws IOException if an IO error occurred while establishing the session + * @throws InterruptedException if the thread was interrupted while waiting in a blocking + * operation + */ + public BytestreamSession establishSession(String targetJID, String sessionID) + throws XMPPException, IOException, InterruptedException; + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java new file mode 100644 index 0000000..e368bad --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamRequest.java @@ -0,0 +1,59 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; + +/** + * BytestreamRequest provides an interface to handle incoming bytestream requests. + * <p> + * There are two implementations of the interface. See {@link Socks5BytestreamRequest} and + * {@link InBandBytestreamRequest}. + * + * @author Henning Staib + */ +public interface BytestreamRequest { + + /** + * Returns the sender of the bytestream open request. + * + * @return the sender of the bytestream open request + */ + public String getFrom(); + + /** + * Returns the session ID of the bytestream open request. + * + * @return the session ID of the bytestream open request + */ + public String getSessionID(); + + /** + * Accepts the bytestream open request and returns the session to send/receive data. + * + * @return the session to send/receive data + * @throws XMPPException if an error occurred while accepting the bytestream request + * @throws InterruptedException if the thread was interrupted while waiting in a blocking + * operation + */ + public BytestreamSession accept() throws XMPPException, InterruptedException; + + /** + * Rejects the bytestream request by sending a reject error to the initiator. + */ + public void reject(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java new file mode 100644 index 0000000..7aafc35 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/BytestreamSession.java @@ -0,0 +1,81 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; + +/** + * BytestreamSession provides an interface for established bytestream sessions. + * <p> + * There are two implementations of the interface. See {@link Socks5BytestreamSession} and + * {@link InBandBytestreamSession}. + * + * @author Henning Staib + */ +public interface BytestreamSession { + + /** + * Returns the InputStream associated with this session to send data. + * + * @return the InputStream associated with this session to send data + * @throws IOException if an error occurs while retrieving the input stream + */ + public InputStream getInputStream() throws IOException; + + /** + * Returns the OutputStream associated with this session to receive data. + * + * @return the OutputStream associated with this session to receive data + * @throws IOException if an error occurs while retrieving the output stream + */ + public OutputStream getOutputStream() throws IOException; + + /** + * Closes the bytestream session. + * <p> + * Closing the session will also close the input stream and the output stream associated to this + * session. + * + * @throws IOException if an error occurs while closing the session + */ + public void close() throws IOException; + + /** + * Returns the timeout for read operations of the input stream associated with this session. 0 + * returns implies that the option is disabled (i.e., timeout of infinity). Default is 0. + * + * @return the timeout for read operations + * @throws IOException if there is an error in the underlying protocol + */ + public int getReadTimeout() throws IOException; + + /** + * Sets the specified timeout, in milliseconds. With this option set to a non-zero timeout, a + * read() call on the input stream associated with this session will block for only this amount + * of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the + * session is still valid. The option must be enabled prior to entering the blocking operation + * to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite + * timeout. Default is 0. + * + * @param timeout the specified timeout, in milliseconds + * @throws IOException if there is an error in the underlying protocol + */ + public void setReadTimeout(int timeout) throws IOException; + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java new file mode 100644 index 0000000..7690e95 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/CloseListener.java @@ -0,0 +1,75 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; + +/** + * CloseListener handles all In-Band Bytestream close requests. + * <p> + * If a close request is received it looks if a stored In-Band Bytestream + * session exists and closes it. If no session with the given session ID exists + * an <item-not-found/> error is returned to the sender. + * + * @author Henning Staib + */ +class CloseListener implements PacketListener { + + /* manager containing the listeners and the XMPP connection */ + private final InBandBytestreamManager manager; + + /* packet filter for all In-Band Bytestream close requests */ + private final PacketFilter closeFilter = new AndFilter(new PacketTypeFilter( + Close.class), new IQTypeFilter(IQ.Type.SET)); + + /** + * Constructor. + * + * @param manager the In-Band Bytestream manager + */ + protected CloseListener(InBandBytestreamManager manager) { + this.manager = manager; + } + + public void processPacket(Packet packet) { + Close closeRequest = (Close) packet; + InBandBytestreamSession ibbSession = this.manager.getSessions().get( + closeRequest.getSessionID()); + if (ibbSession == null) { + this.manager.replyItemNotFoundPacket(closeRequest); + } + else { + ibbSession.closeByPeer(closeRequest); + this.manager.getSessions().remove(closeRequest.getSessionID()); + } + + } + + /** + * Returns the packet filter for In-Band Bytestream close requests. + * + * @return the packet filter for In-Band Bytestream close requests + */ + protected PacketFilter getFilter() { + return this.closeFilter; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java new file mode 100644 index 0000000..166c146 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/DataListener.java @@ -0,0 +1,73 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; + +/** + * DataListener handles all In-Band Bytestream IQ stanzas containing a data + * packet extension that don't belong to an existing session. + * <p> + * If a data packet is received it looks if a stored In-Band Bytestream session + * exists. If no session with the given session ID exists an + * <item-not-found/> error is returned to the sender. + * <p> + * Data packets belonging to a running In-Band Bytestream session are processed + * by more specific listeners registered when an {@link InBandBytestreamSession} + * is created. + * + * @author Henning Staib + */ +class DataListener implements PacketListener { + + /* manager containing the listeners and the XMPP connection */ + private final InBandBytestreamManager manager; + + /* packet filter for all In-Band Bytestream data packets */ + private final PacketFilter dataFilter = new AndFilter( + new PacketTypeFilter(Data.class)); + + /** + * Constructor. + * + * @param manager the In-Band Bytestream manager + */ + public DataListener(InBandBytestreamManager manager) { + this.manager = manager; + } + + public void processPacket(Packet packet) { + Data data = (Data) packet; + InBandBytestreamSession ibbSession = this.manager.getSessions().get( + data.getDataPacketExtension().getSessionID()); + if (ibbSession == null) { + this.manager.replyItemNotFoundPacket(data); + } + } + + /** + * Returns the packet filter for In-Band Bytestream data packets. + * + * @return the packet filter for In-Band Bytestream data packets + */ + protected PacketFilter getFilter() { + return this.dataFilter; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java new file mode 100644 index 0000000..68791a6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamListener.java @@ -0,0 +1,46 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.BytestreamRequest; + +/** + * InBandBytestreamListener are informed if a remote user wants to initiate an In-Band Bytestream. + * Implement this interface to handle incoming In-Band Bytestream requests. + * <p> + * There are two ways to add this listener. See + * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and + * {@link InBandBytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for + * further details. + * + * @author Henning Staib + */ +public abstract class InBandBytestreamListener implements BytestreamListener { + + + + public void incomingBytestreamRequest(BytestreamRequest request) { + incomingBytestreamRequest((InBandBytestreamRequest) request); + } + + /** + * This listener is notified if an In-Band Bytestream request from another user has been + * received. + * + * @param request the incoming In-Band Bytestream request + */ + public abstract void incomingBytestreamRequest(InBandBytestreamRequest request); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java new file mode 100644 index 0000000..ef52531 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamManager.java @@ -0,0 +1,546 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.AbstractConnectionListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.SyncPacketSend; +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.BytestreamManager; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; +import org.jivesoftware.smackx.filetransfer.FileTransferManager; + +/** + * The InBandBytestreamManager class handles establishing In-Band Bytestreams as specified in the <a + * href="http://xmpp.org/extensions/xep-0047.html">XEP-0047</a>. + * <p> + * The In-Band Bytestreams (IBB) enables two entities to establish a virtual bytestream over which + * they can exchange Base64-encoded chunks of data over XMPP itself. It is the fall-back mechanism + * in case the Socks5 bytestream method of transferring data is not available. + * <p> + * There are two ways to send data over an In-Band Bytestream. It could either use IQ stanzas to + * send data packets or message stanzas. If IQ stanzas are used every data packet is acknowledged by + * the receiver. This is the recommended way to avoid possible rate-limiting penalties. Message + * stanzas are not acknowledged because most XMPP server implementation don't support stanza + * flow-control method like <a href="http://xmpp.org/extensions/xep-0079.html">Advanced Message + * Processing</a>. To set the stanza that should be used invoke {@link #setStanza(StanzaType)}. + * <p> + * To establish an In-Band Bytestream invoke the {@link #establishSession(String)} method. This will + * negotiate an in-band bytestream with the given target JID and return a session. + * <p> + * If a session ID for the In-Band Bytestream was already negotiated (e.g. while negotiating a file + * transfer) invoke {@link #establishSession(String, String)}. + * <p> + * To handle incoming In-Band Bytestream requests add an {@link InBandBytestreamListener} to the + * manager. There are two ways to add this listener. If you want to be informed about incoming + * In-Band Bytestreams from a specific user add the listener by invoking + * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should + * respond to all In-Band Bytestream requests invoke + * {@link #addIncomingBytestreamListener(BytestreamListener)}. + * <p> + * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming + * In-Band bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * <p> + * If no {@link InBandBytestreamListener}s are registered, all incoming In-Band bytestream requests + * will be rejected by returning a <not-acceptable/> error to the initiator. + * + * @author Henning Staib + */ +public class InBandBytestreamManager implements BytestreamManager { + + /** + * Stanzas that can be used to encapsulate In-Band Bytestream data packets. + */ + public enum StanzaType { + + /** + * IQ stanza. + */ + IQ, + + /** + * Message stanza. + */ + MESSAGE + } + + /* + * create a new InBandBytestreamManager and register its shutdown listener on every established + * connection + */ + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + final InBandBytestreamManager manager; + manager = InBandBytestreamManager.getByteStreamManager(connection); + + // register shutdown listener + connection.addConnectionListener(new AbstractConnectionListener() { + + public void connectionClosed() { + manager.disableService(); + } + + }); + + } + }); + } + + /** + * The XMPP namespace of the In-Band Bytestream + */ + public static final String NAMESPACE = "http://jabber.org/protocol/ibb"; + + /** + * Maximum block size that is allowed for In-Band Bytestreams + */ + public static final int MAXIMUM_BLOCK_SIZE = 65535; + + /* prefix used to generate session IDs */ + private static final String SESSION_ID_PREFIX = "jibb_"; + + /* random generator to create session IDs */ + private final static Random randomGenerator = new Random(); + + /* stores one InBandBytestreamManager for each XMPP connection */ + private final static Map<Connection, InBandBytestreamManager> managers = new HashMap<Connection, InBandBytestreamManager>(); + + /* XMPP connection */ + private final Connection connection; + + /* + * assigns a user to a listener that is informed if an In-Band Bytestream request for this user + * is received + */ + private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); + + /* + * list of listeners that respond to all In-Band Bytestream requests if there are no user + * specific listeners for that request + */ + private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); + + /* listener that handles all incoming In-Band Bytestream requests */ + private final InitiationListener initiationListener; + + /* listener that handles all incoming In-Band Bytestream IQ data packets */ + private final DataListener dataListener; + + /* listener that handles all incoming In-Band Bytestream close requests */ + private final CloseListener closeListener; + + /* assigns a session ID to the In-Band Bytestream session */ + private final Map<String, InBandBytestreamSession> sessions = new ConcurrentHashMap<String, InBandBytestreamSession>(); + + /* block size used for new In-Band Bytestreams */ + private int defaultBlockSize = 4096; + + /* maximum block size allowed for this connection */ + private int maximumBlockSize = MAXIMUM_BLOCK_SIZE; + + /* the stanza used to send data packets */ + private StanzaType stanza = StanzaType.IQ; + + /* + * list containing session IDs of In-Band Bytestream open packets that should be ignored by the + * InitiationListener + */ + private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); + + /** + * Returns the InBandBytestreamManager to handle In-Band Bytestreams for a given + * {@link Connection}. + * + * @param connection the XMPP connection + * @return the InBandBytestreamManager for the given XMPP connection + */ + public static synchronized InBandBytestreamManager getByteStreamManager(Connection connection) { + if (connection == null) + return null; + InBandBytestreamManager manager = managers.get(connection); + if (manager == null) { + manager = new InBandBytestreamManager(connection); + managers.put(connection, manager); + } + return manager; + } + + /** + * Constructor. + * + * @param connection the XMPP connection + */ + private InBandBytestreamManager(Connection connection) { + this.connection = connection; + + // register bytestream open packet listener + this.initiationListener = new InitiationListener(this); + this.connection.addPacketListener(this.initiationListener, + this.initiationListener.getFilter()); + + // register bytestream data packet listener + this.dataListener = new DataListener(this); + this.connection.addPacketListener(this.dataListener, this.dataListener.getFilter()); + + // register bytestream close packet listener + this.closeListener = new CloseListener(this); + this.connection.addPacketListener(this.closeListener, this.closeListener.getFilter()); + + } + + /** + * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request + * unless there is a user specific InBandBytestreamListener registered. + * <p> + * If no listeners are registered all In-Band Bytestream request are rejected with a + * <not-acceptable/> error. + * <p> + * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming + * Socks5 bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * + * @param listener the listener to register + */ + public void addIncomingBytestreamListener(BytestreamListener listener) { + this.allRequestListeners.add(listener); + } + + /** + * Removes the given listener from the list of listeners for all incoming In-Band Bytestream + * requests. + * + * @param listener the listener to remove + */ + public void removeIncomingBytestreamListener(BytestreamListener listener) { + this.allRequestListeners.remove(listener); + } + + /** + * Adds InBandBytestreamListener that is called for every incoming in-band bytestream request + * from the given user. + * <p> + * Use this method if you are awaiting an incoming Socks5 bytestream request from a specific + * user. + * <p> + * If no listeners are registered all In-Band Bytestream request are rejected with a + * <not-acceptable/> error. + * <p> + * Note that the registered {@link InBandBytestreamListener} will NOT be notified on incoming + * Socks5 bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * + * @param listener the listener to register + * @param initiatorJID the JID of the user that wants to establish an In-Band Bytestream + */ + public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { + this.userListeners.put(initiatorJID, listener); + } + + /** + * Removes the listener for the given user. + * + * @param initiatorJID the JID of the user the listener should be removed + */ + public void removeIncomingBytestreamListener(String initiatorJID) { + this.userListeners.remove(initiatorJID); + } + + /** + * Use this method to ignore the next incoming In-Band Bytestream request containing the given + * session ID. No listeners will be notified for this request and and no error will be returned + * to the initiator. + * <p> + * This method should be used if you are awaiting an In-Band Bytestream request as a reply to + * another packet (e.g. file transfer). + * + * @param sessionID to be ignored + */ + public void ignoreBytestreamRequestOnce(String sessionID) { + this.ignoredBytestreamRequests.add(sessionID); + } + + /** + * Returns the default block size that is used for all outgoing in-band bytestreams for this + * connection. + * <p> + * The recommended default block size is 4096 bytes. See <a + * href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a> Section 5. + * + * @return the default block size + */ + public int getDefaultBlockSize() { + return defaultBlockSize; + } + + /** + * Sets the default block size that is used for all outgoing in-band bytestreams for this + * connection. + * <p> + * The default block size must be between 1 and 65535 bytes. The recommended default block size + * is 4096 bytes. See <a href="http://xmpp.org/extensions/xep-0047.html#usage">XEP-0047</a> + * Section 5. + * + * @param defaultBlockSize the default block size to set + */ + public void setDefaultBlockSize(int defaultBlockSize) { + if (defaultBlockSize <= 0 || defaultBlockSize > MAXIMUM_BLOCK_SIZE) { + throw new IllegalArgumentException("Default block size must be between 1 and " + + MAXIMUM_BLOCK_SIZE); + } + this.defaultBlockSize = defaultBlockSize; + } + + /** + * Returns the maximum block size that is allowed for In-Band Bytestreams for this connection. + * <p> + * Incoming In-Band Bytestream open request will be rejected with an + * <resource-constraint/> error if the block size is greater then the maximum allowed + * block size. + * <p> + * The default maximum block size is 65535 bytes. + * + * @return the maximum block size + */ + public int getMaximumBlockSize() { + return maximumBlockSize; + } + + /** + * Sets the maximum block size that is allowed for In-Band Bytestreams for this connection. + * <p> + * The maximum block size must be between 1 and 65535 bytes. + * <p> + * Incoming In-Band Bytestream open request will be rejected with an + * <resource-constraint/> error if the block size is greater then the maximum allowed + * block size. + * + * @param maximumBlockSize the maximum block size to set + */ + public void setMaximumBlockSize(int maximumBlockSize) { + if (maximumBlockSize <= 0 || maximumBlockSize > MAXIMUM_BLOCK_SIZE) { + throw new IllegalArgumentException("Maximum block size must be between 1 and " + + MAXIMUM_BLOCK_SIZE); + } + this.maximumBlockSize = maximumBlockSize; + } + + /** + * Returns the stanza used to send data packets. + * <p> + * Default is {@link StanzaType#IQ}. See <a + * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4. + * + * @return the stanza used to send data packets + */ + public StanzaType getStanza() { + return stanza; + } + + /** + * Sets the stanza used to send data packets. + * <p> + * The use of {@link StanzaType#IQ} is recommended. See <a + * href="http://xmpp.org/extensions/xep-0047.html#message">XEP-0047</a> Section 4. + * + * @param stanza the stanza to set + */ + public void setStanza(StanzaType stanza) { + this.stanza = stanza; + } + + /** + * Establishes an In-Band Bytestream with the given user and returns the session to send/receive + * data to/from the user. + * <p> + * Use this method to establish In-Band Bytestreams to users accepting all incoming In-Band + * Bytestream requests since this method doesn't provide a way to tell the user something about + * the data to be sent. + * <p> + * To establish an In-Band Bytestream after negotiation the kind of data to be sent (e.g. file + * transfer) use {@link #establishSession(String, String)}. + * + * @param targetJID the JID of the user an In-Band Bytestream should be established + * @return the session to send/receive data to/from the user + * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the + * user prefers smaller block sizes + */ + public InBandBytestreamSession establishSession(String targetJID) throws XMPPException { + String sessionID = getNextSessionID(); + return establishSession(targetJID, sessionID); + } + + /** + * Establishes an In-Band Bytestream with the given user using the given session ID and returns + * the session to send/receive data to/from the user. + * + * @param targetJID the JID of the user an In-Band Bytestream should be established + * @param sessionID the session ID for the In-Band Bytestream request + * @return the session to send/receive data to/from the user + * @throws XMPPException if the user doesn't support or accept in-band bytestreams, or if the + * user prefers smaller block sizes + */ + public InBandBytestreamSession establishSession(String targetJID, String sessionID) + throws XMPPException { + Open byteStreamRequest = new Open(sessionID, this.defaultBlockSize, this.stanza); + byteStreamRequest.setTo(targetJID); + + // sending packet will throw exception on timeout or error reply + SyncPacketSend.getReply(this.connection, byteStreamRequest); + + InBandBytestreamSession inBandBytestreamSession = new InBandBytestreamSession( + this.connection, byteStreamRequest, targetJID); + this.sessions.put(sessionID, inBandBytestreamSession); + + return inBandBytestreamSession; + } + + /** + * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream is + * not accepted. + * + * @param request IQ packet that should be answered with a not-acceptable error + */ + protected void replyRejectPacket(IQ request) { + XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); + IQ error = IQ.createErrorResponse(request, xmppError); + this.connection.sendPacket(error); + } + + /** + * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream open + * request is rejected because its block size is greater than the maximum allowed block size. + * + * @param request IQ packet that should be answered with a resource-constraint error + */ + protected void replyResourceConstraintPacket(IQ request) { + XMPPError xmppError = new XMPPError(XMPPError.Condition.resource_constraint); + IQ error = IQ.createErrorResponse(request, xmppError); + this.connection.sendPacket(error); + } + + /** + * Responses to the given IQ packet's sender with an XMPP error that an In-Band Bytestream + * session could not be found. + * + * @param request IQ packet that should be answered with a item-not-found error + */ + protected void replyItemNotFoundPacket(IQ request) { + XMPPError xmppError = new XMPPError(XMPPError.Condition.item_not_found); + IQ error = IQ.createErrorResponse(request, xmppError); + this.connection.sendPacket(error); + } + + /** + * Returns a new unique session ID. + * + * @return a new unique session ID + */ + private String getNextSessionID() { + StringBuilder buffer = new StringBuilder(); + buffer.append(SESSION_ID_PREFIX); + buffer.append(Math.abs(randomGenerator.nextLong())); + return buffer.toString(); + } + + /** + * Returns the XMPP connection. + * + * @return the XMPP connection + */ + protected Connection getConnection() { + return this.connection; + } + + /** + * Returns the {@link InBandBytestreamListener} that should be informed if a In-Band Bytestream + * request from the given initiator JID is received. + * + * @param initiator the initiator's JID + * @return the listener + */ + protected BytestreamListener getUserListener(String initiator) { + return this.userListeners.get(initiator); + } + + /** + * Returns a list of {@link InBandBytestreamListener} that are informed if there are no + * listeners for a specific initiator. + * + * @return list of listeners + */ + protected List<BytestreamListener> getAllRequestListeners() { + return this.allRequestListeners; + } + + /** + * Returns the sessions map. + * + * @return the sessions map + */ + protected Map<String, InBandBytestreamSession> getSessions() { + return sessions; + } + + /** + * Returns the list of session IDs that should be ignored by the InitialtionListener + * + * @return list of session IDs + */ + protected List<String> getIgnoredBytestreamRequests() { + return ignoredBytestreamRequests; + } + + /** + * Disables the InBandBytestreamManager by removing its packet listeners and resetting its + * internal status. + */ + private void disableService() { + + // remove manager from static managers map + managers.remove(connection); + + // remove all listeners registered by this manager + this.connection.removePacketListener(this.initiationListener); + this.connection.removePacketListener(this.dataListener); + this.connection.removePacketListener(this.closeListener); + + // shutdown threads + this.initiationListener.shutdown(); + + // reset internal status + this.userListeners.clear(); + this.allRequestListeners.clear(); + this.sessions.clear(); + this.ignoredBytestreamRequests.clear(); + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java new file mode 100644 index 0000000..5bc689a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamRequest.java @@ -0,0 +1,92 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.BytestreamRequest; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; + +/** + * InBandBytestreamRequest class handles incoming In-Band Bytestream requests. + * + * @author Henning Staib + */ +public class InBandBytestreamRequest implements BytestreamRequest { + + /* the bytestream initialization request */ + private final Open byteStreamRequest; + + /* + * In-Band Bytestream manager containing the XMPP connection and helper + * methods + */ + private final InBandBytestreamManager manager; + + protected InBandBytestreamRequest(InBandBytestreamManager manager, + Open byteStreamRequest) { + this.manager = manager; + this.byteStreamRequest = byteStreamRequest; + } + + /** + * Returns the sender of the In-Band Bytestream open request. + * + * @return the sender of the In-Band Bytestream open request + */ + public String getFrom() { + return this.byteStreamRequest.getFrom(); + } + + /** + * Returns the session ID of the In-Band Bytestream open request. + * + * @return the session ID of the In-Band Bytestream open request + */ + public String getSessionID() { + return this.byteStreamRequest.getSessionID(); + } + + /** + * Accepts the In-Band Bytestream open request and returns the session to + * send/receive data. + * + * @return the session to send/receive data + * @throws XMPPException if stream is invalid. + */ + public InBandBytestreamSession accept() throws XMPPException { + Connection connection = this.manager.getConnection(); + + // create In-Band Bytestream session and store it + InBandBytestreamSession ibbSession = new InBandBytestreamSession(connection, + this.byteStreamRequest, this.byteStreamRequest.getFrom()); + this.manager.getSessions().put(this.byteStreamRequest.getSessionID(), ibbSession); + + // acknowledge request + IQ resultIQ = IQ.createResultIQ(this.byteStreamRequest); + connection.sendPacket(resultIQ); + + return ibbSession; + } + + /** + * Rejects the In-Band Bytestream request by sending a reject error to the + * initiator. + */ + public void reject() { + this.manager.replyRejectPacket(this.byteStreamRequest); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java new file mode 100644 index 0000000..1cced78 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InBandBytestreamSession.java @@ -0,0 +1,795 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.SocketTimeoutException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.SyncPacketSend; +import org.jivesoftware.smackx.bytestreams.BytestreamSession; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; +import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; + +/** + * InBandBytestreamSession class represents an In-Band Bytestream session. + * <p> + * In-band bytestreams are bidirectional and this session encapsulates the streams for both + * directions. + * <p> + * Note that closing the In-Band Bytestream session will close both streams. If both streams are + * closed individually the session will be closed automatically once the second stream is closed. + * Use the {@link #setCloseBothStreamsEnabled(boolean)} method if both streams should be closed + * automatically if one of them is closed. + * + * @author Henning Staib + */ +public class InBandBytestreamSession implements BytestreamSession { + + /* XMPP connection */ + private final Connection connection; + + /* the In-Band Bytestream open request for this session */ + private final Open byteStreamRequest; + + /* + * the input stream for this session (either IQIBBInputStream or MessageIBBInputStream) + */ + private IBBInputStream inputStream; + + /* + * the output stream for this session (either IQIBBOutputStream or MessageIBBOutputStream) + */ + private IBBOutputStream outputStream; + + /* JID of the remote peer */ + private String remoteJID; + + /* flag to close both streams if one of them is closed */ + private boolean closeBothStreamsEnabled = false; + + /* flag to indicate if session is closed */ + private boolean isClosed = false; + + /** + * Constructor. + * + * @param connection the XMPP connection + * @param byteStreamRequest the In-Band Bytestream open request for this session + * @param remoteJID JID of the remote peer + */ + protected InBandBytestreamSession(Connection connection, Open byteStreamRequest, + String remoteJID) { + this.connection = connection; + this.byteStreamRequest = byteStreamRequest; + this.remoteJID = remoteJID; + + // initialize streams dependent to the uses stanza type + switch (byteStreamRequest.getStanza()) { + case IQ: + this.inputStream = new IQIBBInputStream(); + this.outputStream = new IQIBBOutputStream(); + break; + case MESSAGE: + this.inputStream = new MessageIBBInputStream(); + this.outputStream = new MessageIBBOutputStream(); + break; + } + + } + + public InputStream getInputStream() { + return this.inputStream; + } + + public OutputStream getOutputStream() { + return this.outputStream; + } + + public int getReadTimeout() { + return this.inputStream.readTimeout; + } + + public void setReadTimeout(int timeout) { + if (timeout < 0) { + throw new IllegalArgumentException("Timeout must be >= 0"); + } + this.inputStream.readTimeout = timeout; + } + + /** + * Returns whether both streams should be closed automatically if one of the streams is closed. + * Default is <code>false</code>. + * + * @return <code>true</code> if both streams will be closed if one of the streams is closed, + * <code>false</code> if both streams can be closed independently. + */ + public boolean isCloseBothStreamsEnabled() { + return closeBothStreamsEnabled; + } + + /** + * Sets whether both streams should be closed automatically if one of the streams is closed. + * Default is <code>false</code>. + * + * @param closeBothStreamsEnabled <code>true</code> if both streams should be closed if one of + * the streams is closed, <code>false</code> if both streams should be closed + * independently + */ + public void setCloseBothStreamsEnabled(boolean closeBothStreamsEnabled) { + this.closeBothStreamsEnabled = closeBothStreamsEnabled; + } + + public void close() throws IOException { + closeByLocal(true); // close input stream + closeByLocal(false); // close output stream + } + + /** + * This method is invoked if a request to close the In-Band Bytestream has been received. + * + * @param closeRequest the close request from the remote peer + */ + protected void closeByPeer(Close closeRequest) { + + /* + * close streams without flushing them, because stream is already considered closed on the + * remote peers side + */ + this.inputStream.closeInternal(); + this.inputStream.cleanup(); + this.outputStream.closeInternal(false); + + // acknowledge close request + IQ confirmClose = IQ.createResultIQ(closeRequest); + this.connection.sendPacket(confirmClose); + + } + + /** + * This method is invoked if one of the streams has been closed locally, if an error occurred + * locally or if the whole session should be closed. + * + * @throws IOException if an error occurs while sending the close request + */ + protected synchronized void closeByLocal(boolean in) throws IOException { + if (this.isClosed) { + return; + } + + if (this.closeBothStreamsEnabled) { + this.inputStream.closeInternal(); + this.outputStream.closeInternal(true); + } + else { + if (in) { + this.inputStream.closeInternal(); + } + else { + // close stream but try to send any data left + this.outputStream.closeInternal(true); + } + } + + if (this.inputStream.isClosed && this.outputStream.isClosed) { + this.isClosed = true; + + // send close request + Close close = new Close(this.byteStreamRequest.getSessionID()); + close.setTo(this.remoteJID); + try { + SyncPacketSend.getReply(this.connection, close); + } + catch (XMPPException e) { + throw new IOException("Error while closing stream: " + e.getMessage()); + } + + this.inputStream.cleanup(); + + // remove session from manager + InBandBytestreamManager.getByteStreamManager(this.connection).getSessions().remove(this); + } + + } + + /** + * IBBInputStream class is the base implementation of an In-Band Bytestream input stream. + * Subclasses of this input stream must provide a packet listener along with a packet filter to + * collect the In-Band Bytestream data packets. + */ + private abstract class IBBInputStream extends InputStream { + + /* the data packet listener to fill the data queue */ + private final PacketListener dataPacketListener; + + /* queue containing received In-Band Bytestream data packets */ + protected final BlockingQueue<DataPacketExtension> dataQueue = new LinkedBlockingQueue<DataPacketExtension>(); + + /* buffer containing the data from one data packet */ + private byte[] buffer; + + /* pointer to the next byte to read from buffer */ + private int bufferPointer = -1; + + /* data packet sequence (range from 0 to 65535) */ + private long seq = -1; + + /* flag to indicate if input stream is closed */ + private boolean isClosed = false; + + /* flag to indicate if close method was invoked */ + private boolean closeInvoked = false; + + /* timeout for read operations */ + private int readTimeout = 0; + + /** + * Constructor. + */ + public IBBInputStream() { + // add data packet listener to connection + this.dataPacketListener = getDataPacketListener(); + connection.addPacketListener(this.dataPacketListener, getDataPacketFilter()); + } + + /** + * Returns the packet listener that processes In-Band Bytestream data packets. + * + * @return the data packet listener + */ + protected abstract PacketListener getDataPacketListener(); + + /** + * Returns the packet filter that accepts In-Band Bytestream data packets. + * + * @return the data packet filter + */ + protected abstract PacketFilter getDataPacketFilter(); + + public synchronized int read() throws IOException { + checkClosed(); + + // if nothing read yet or whole buffer has been read fill buffer + if (bufferPointer == -1 || bufferPointer >= buffer.length) { + // if no data available and stream was closed return -1 + if (!loadBuffer()) { + return -1; + } + } + + // return byte and increment buffer pointer + return (int) buffer[bufferPointer++]; + } + + public synchronized int read(byte[] b, int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return 0; + } + + checkClosed(); + + // if nothing read yet or whole buffer has been read fill buffer + if (bufferPointer == -1 || bufferPointer >= buffer.length) { + // if no data available and stream was closed return -1 + if (!loadBuffer()) { + return -1; + } + } + + // if more bytes wanted than available return all available + int bytesAvailable = buffer.length - bufferPointer; + if (len > bytesAvailable) { + len = bytesAvailable; + } + + System.arraycopy(buffer, bufferPointer, b, off, len); + bufferPointer += len; + return len; + } + + public synchronized int read(byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * This method blocks until a data packet is received, the stream is closed or the current + * thread is interrupted. + * + * @return <code>true</code> if data was received, otherwise <code>false</code> + * @throws IOException if data packets are out of sequence + */ + private synchronized boolean loadBuffer() throws IOException { + + // wait until data is available or stream is closed + DataPacketExtension data = null; + try { + if (this.readTimeout == 0) { + while (data == null) { + if (isClosed && this.dataQueue.isEmpty()) { + return false; + } + data = this.dataQueue.poll(1000, TimeUnit.MILLISECONDS); + } + } + else { + data = this.dataQueue.poll(this.readTimeout, TimeUnit.MILLISECONDS); + if (data == null) { + throw new SocketTimeoutException(); + } + } + } + catch (InterruptedException e) { + // Restore the interrupted status + Thread.currentThread().interrupt(); + return false; + } + + // handle sequence overflow + if (this.seq == 65535) { + this.seq = -1; + } + + // check if data packets sequence is successor of last seen sequence + long seq = data.getSeq(); + if (seq - 1 != this.seq) { + // packets out of order; close stream/session + InBandBytestreamSession.this.close(); + throw new IOException("Packets out of sequence"); + } + else { + this.seq = seq; + } + + // set buffer to decoded data + buffer = data.getDecodedData(); + bufferPointer = 0; + return true; + } + + /** + * Checks if this stream is closed and throws an IOException if necessary + * + * @throws IOException if stream is closed and no data should be read anymore + */ + private void checkClosed() throws IOException { + /* throw no exception if there is data available, but not if close method was invoked */ + if ((isClosed && this.dataQueue.isEmpty()) || closeInvoked) { + // clear data queue in case additional data was received after stream was closed + this.dataQueue.clear(); + throw new IOException("Stream is closed"); + } + } + + public boolean markSupported() { + return false; + } + + public void close() throws IOException { + if (isClosed) { + return; + } + + this.closeInvoked = true; + + InBandBytestreamSession.this.closeByLocal(true); + } + + /** + * This method sets the close flag and removes the data packet listener. + */ + private void closeInternal() { + if (isClosed) { + return; + } + isClosed = true; + } + + /** + * Invoked if the session is closed. + */ + private void cleanup() { + connection.removePacketListener(this.dataPacketListener); + } + + } + + /** + * IQIBBInputStream class implements IBBInputStream to be used with IQ stanzas encapsulating the + * data packets. + */ + private class IQIBBInputStream extends IBBInputStream { + + protected PacketListener getDataPacketListener() { + return new PacketListener() { + + private long lastSequence = -1; + + public void processPacket(Packet packet) { + // get data packet extension + DataPacketExtension data = (DataPacketExtension) packet.getExtension( + DataPacketExtension.ELEMENT_NAME, + InBandBytestreamManager.NAMESPACE); + + /* + * check if sequence was not used already (see XEP-0047 Section 2.2) + */ + if (data.getSeq() <= this.lastSequence) { + IQ unexpectedRequest = IQ.createErrorResponse((IQ) packet, new XMPPError( + XMPPError.Condition.unexpected_request)); + connection.sendPacket(unexpectedRequest); + return; + + } + + // check if encoded data is valid (see XEP-0047 Section 2.2) + if (data.getDecodedData() == null) { + // data is invalid; respond with bad-request error + IQ badRequest = IQ.createErrorResponse((IQ) packet, new XMPPError( + XMPPError.Condition.bad_request)); + connection.sendPacket(badRequest); + return; + } + + // data is valid; add to data queue + dataQueue.offer(data); + + // confirm IQ + IQ confirmData = IQ.createResultIQ((IQ) packet); + connection.sendPacket(confirmData); + + // set last seen sequence + this.lastSequence = data.getSeq(); + if (this.lastSequence == 65535) { + this.lastSequence = -1; + } + + } + + }; + } + + protected PacketFilter getDataPacketFilter() { + /* + * filter all IQ stanzas having type 'SET' (represented by Data class), containing a + * data packet extension, matching session ID and recipient + */ + return new AndFilter(new PacketTypeFilter(Data.class), new IBBDataPacketFilter()); + } + + } + + /** + * MessageIBBInputStream class implements IBBInputStream to be used with message stanzas + * encapsulating the data packets. + */ + private class MessageIBBInputStream extends IBBInputStream { + + protected PacketListener getDataPacketListener() { + return new PacketListener() { + + public void processPacket(Packet packet) { + // get data packet extension + DataPacketExtension data = (DataPacketExtension) packet.getExtension( + DataPacketExtension.ELEMENT_NAME, + InBandBytestreamManager.NAMESPACE); + + // check if encoded data is valid + if (data.getDecodedData() == null) { + /* + * TODO once a majority of XMPP server implementation support XEP-0079 + * Advanced Message Processing the invalid message could be answered with an + * appropriate error. For now we just ignore the packet. Subsequent packets + * with an increased sequence will cause the input stream to close the + * stream/session. + */ + return; + } + + // data is valid; add to data queue + dataQueue.offer(data); + + // TODO confirm packet once XMPP servers support XEP-0079 + } + + }; + } + + @Override + protected PacketFilter getDataPacketFilter() { + /* + * filter all message stanzas containing a data packet extension, matching session ID + * and recipient + */ + return new AndFilter(new PacketTypeFilter(Message.class), new IBBDataPacketFilter()); + } + + } + + /** + * IBBDataPacketFilter class filters all packets from the remote peer of this session, + * containing an In-Band Bytestream data packet extension whose session ID matches this sessions + * ID. + */ + private class IBBDataPacketFilter implements PacketFilter { + + public boolean accept(Packet packet) { + // sender equals remote peer + if (!packet.getFrom().equalsIgnoreCase(remoteJID)) { + return false; + } + + // stanza contains data packet extension + PacketExtension packetExtension = packet.getExtension(DataPacketExtension.ELEMENT_NAME, + InBandBytestreamManager.NAMESPACE); + if (packetExtension == null || !(packetExtension instanceof DataPacketExtension)) { + return false; + } + + // session ID equals this session ID + DataPacketExtension data = (DataPacketExtension) packetExtension; + if (!data.getSessionID().equals(byteStreamRequest.getSessionID())) { + return false; + } + + return true; + } + + } + + /** + * IBBOutputStream class is the base implementation of an In-Band Bytestream output stream. + * Subclasses of this output stream must provide a method to send data over XMPP stream. + */ + private abstract class IBBOutputStream extends OutputStream { + + /* buffer with the size of this sessions block size */ + protected final byte[] buffer; + + /* pointer to next byte to write to buffer */ + protected int bufferPointer = 0; + + /* data packet sequence (range from 0 to 65535) */ + protected long seq = 0; + + /* flag to indicate if output stream is closed */ + protected boolean isClosed = false; + + /** + * Constructor. + */ + public IBBOutputStream() { + this.buffer = new byte[(byteStreamRequest.getBlockSize()/4)*3]; + } + + /** + * Writes the given data packet to the XMPP stream. + * + * @param data the data packet + * @throws IOException if an I/O error occurred while sending or if the stream is closed + */ + protected abstract void writeToXML(DataPacketExtension data) throws IOException; + + public synchronized void write(int b) throws IOException { + if (this.isClosed) { + throw new IOException("Stream is closed"); + } + + // if buffer is full flush buffer + if (bufferPointer >= buffer.length) { + flushBuffer(); + } + + buffer[bufferPointer++] = (byte) b; + } + + public synchronized void write(byte b[], int off, int len) throws IOException { + if (b == null) { + throw new NullPointerException(); + } + else if ((off < 0) || (off > b.length) || (len < 0) || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } + else if (len == 0) { + return; + } + + if (this.isClosed) { + throw new IOException("Stream is closed"); + } + + // is data to send greater than buffer size + if (len >= buffer.length) { + + // "byte" off the first chunk to write out + writeOut(b, off, buffer.length); + + // recursively call this method with the lesser amount + write(b, off + buffer.length, len - buffer.length); + } + else { + writeOut(b, off, len); + } + } + + public synchronized void write(byte[] b) throws IOException { + write(b, 0, b.length); + } + + /** + * Fills the buffer with the given data and sends it over the XMPP stream if the buffers + * capacity has been reached. This method is only called from this class so it is assured + * that the amount of data to send is <= buffer capacity + * + * @param b the data + * @param off the data + * @param len the number of bytes to write + * @throws IOException if an I/O error occurred while sending or if the stream is closed + */ + private synchronized void writeOut(byte b[], int off, int len) throws IOException { + if (this.isClosed) { + throw new IOException("Stream is closed"); + } + + // set to 0 in case the next 'if' block is not executed + int available = 0; + + // is data to send greater that buffer space left + if (len > buffer.length - bufferPointer) { + // fill buffer to capacity and send it + available = buffer.length - bufferPointer; + System.arraycopy(b, off, buffer, bufferPointer, available); + bufferPointer += available; + flushBuffer(); + } + + // copy the data left to buffer + System.arraycopy(b, off + available, buffer, bufferPointer, len - available); + bufferPointer += len - available; + } + + public synchronized void flush() throws IOException { + if (this.isClosed) { + throw new IOException("Stream is closed"); + } + flushBuffer(); + } + + private synchronized void flushBuffer() throws IOException { + + // do nothing if no data to send available + if (bufferPointer == 0) { + return; + } + + // create data packet + String enc = StringUtils.encodeBase64(buffer, 0, bufferPointer, false); + DataPacketExtension data = new DataPacketExtension(byteStreamRequest.getSessionID(), + this.seq, enc); + + // write to XMPP stream + writeToXML(data); + + // reset buffer pointer + bufferPointer = 0; + + // increment sequence, considering sequence overflow + this.seq = (this.seq + 1 == 65535 ? 0 : this.seq + 1); + + } + + public void close() throws IOException { + if (isClosed) { + return; + } + InBandBytestreamSession.this.closeByLocal(false); + } + + /** + * Sets the close flag and optionally flushes the stream. + * + * @param flush if <code>true</code> flushes the stream + */ + protected void closeInternal(boolean flush) { + if (this.isClosed) { + return; + } + this.isClosed = true; + + try { + if (flush) { + flushBuffer(); + } + } + catch (IOException e) { + /* + * ignore, because writeToXML() will not throw an exception if stream is already + * closed + */ + } + } + + } + + /** + * IQIBBOutputStream class implements IBBOutputStream to be used with IQ stanzas encapsulating + * the data packets. + */ + private class IQIBBOutputStream extends IBBOutputStream { + + @Override + protected synchronized void writeToXML(DataPacketExtension data) throws IOException { + // create IQ stanza containing data packet + IQ iq = new Data(data); + iq.setTo(remoteJID); + + try { + SyncPacketSend.getReply(connection, iq); + } + catch (XMPPException e) { + // close session unless it is already closed + if (!this.isClosed) { + InBandBytestreamSession.this.close(); + throw new IOException("Error while sending Data: " + e.getMessage()); + } + } + + } + + } + + /** + * MessageIBBOutputStream class implements IBBOutputStream to be used with message stanzas + * encapsulating the data packets. + */ + private class MessageIBBOutputStream extends IBBOutputStream { + + @Override + protected synchronized void writeToXML(DataPacketExtension data) { + // create message stanza containing data packet + Message message = new Message(remoteJID); + message.addExtension(data); + + connection.sendPacket(message); + + } + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java new file mode 100644 index 0000000..0ecb081 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/InitiationListener.java @@ -0,0 +1,127 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; + +/** + * InitiationListener handles all incoming In-Band Bytestream open requests. If there are no + * listeners for a In-Band Bytestream request InitiationListener will always refuse the request and + * reply with a <not-acceptable/> error (<a + * href="http://xmpp.org/extensions/xep-0047.html#example-5" >XEP-0047</a> Section 2.1). + * <p> + * All In-Band Bytestream request having a block size greater than the maximum allowed block size + * for this connection are rejected with an <resource-constraint/> error. The maximum block + * size can be set by invoking {@link InBandBytestreamManager#setMaximumBlockSize(int)}. + * + * @author Henning Staib + */ +class InitiationListener implements PacketListener { + + /* manager containing the listeners and the XMPP connection */ + private final InBandBytestreamManager manager; + + /* packet filter for all In-Band Bytestream requests */ + private final PacketFilter initFilter = new AndFilter(new PacketTypeFilter(Open.class), + new IQTypeFilter(IQ.Type.SET)); + + /* executor service to process incoming requests concurrently */ + private final ExecutorService initiationListenerExecutor; + + /** + * Constructor. + * + * @param manager the In-Band Bytestream manager + */ + protected InitiationListener(InBandBytestreamManager manager) { + this.manager = manager; + initiationListenerExecutor = Executors.newCachedThreadPool(); + } + + public void processPacket(final Packet packet) { + initiationListenerExecutor.execute(new Runnable() { + + public void run() { + processRequest(packet); + } + }); + } + + private void processRequest(Packet packet) { + Open ibbRequest = (Open) packet; + + // validate that block size is within allowed range + if (ibbRequest.getBlockSize() > this.manager.getMaximumBlockSize()) { + this.manager.replyResourceConstraintPacket(ibbRequest); + return; + } + + // ignore request if in ignore list + if (this.manager.getIgnoredBytestreamRequests().remove(ibbRequest.getSessionID())) + return; + + // build bytestream request from packet + InBandBytestreamRequest request = new InBandBytestreamRequest(this.manager, ibbRequest); + + // notify listeners for bytestream initiation from a specific user + BytestreamListener userListener = this.manager.getUserListener(ibbRequest.getFrom()); + if (userListener != null) { + userListener.incomingBytestreamRequest(request); + + } + else if (!this.manager.getAllRequestListeners().isEmpty()) { + /* + * if there is no user specific listener inform listeners for all initiation requests + */ + for (BytestreamListener listener : this.manager.getAllRequestListeners()) { + listener.incomingBytestreamRequest(request); + } + + } + else { + /* + * if there is no listener for this initiation request, reply with reject message + */ + this.manager.replyRejectPacket(ibbRequest); + } + } + + /** + * Returns the packet filter for In-Band Bytestream open requests. + * + * @return the packet filter for In-Band Bytestream open requests + */ + protected PacketFilter getFilter() { + return this.initFilter; + } + + /** + * Shuts down the listeners executor service. + */ + protected void shutdown() { + this.initiationListenerExecutor.shutdownNow(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java new file mode 100644 index 0000000..9a78d73 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Close.java @@ -0,0 +1,65 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; + +/** + * Represents a request to close an In-Band Bytestream. + * + * @author Henning Staib + */ +public class Close extends IQ { + + /* unique session ID identifying this In-Band Bytestream */ + private final String sessionID; + + /** + * Creates a new In-Band Bytestream close request packet. + * + * @param sessionID unique session ID identifying this In-Band Bytestream + */ + public Close(String sessionID) { + if (sessionID == null || "".equals(sessionID)) { + throw new IllegalArgumentException("Session ID must not be null or empty"); + } + this.sessionID = sessionID; + setType(Type.SET); + } + + /** + * Returns the unique session ID identifying this In-Band Bytestream. + * + * @return the unique session ID identifying this In-Band Bytestream + */ + public String getSessionID() { + return sessionID; + } + + @Override + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<close "); + buf.append("xmlns=\""); + buf.append(InBandBytestreamManager.NAMESPACE); + buf.append("\" "); + buf.append("sid=\""); + buf.append(sessionID); + buf.append("\""); + buf.append("/>"); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java new file mode 100644 index 0000000..696fa75 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Data.java @@ -0,0 +1,64 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.packet; + +import org.jivesoftware.smack.packet.IQ; + +/** + * Represents a chunk of data sent over an In-Band Bytestream encapsulated in an + * IQ stanza. + * + * @author Henning Staib + */ +public class Data extends IQ { + + /* the data packet extension */ + private final DataPacketExtension dataPacketExtension; + + /** + * Constructor. + * + * @param data data packet extension containing the encoded data + */ + public Data(DataPacketExtension data) { + if (data == null) { + throw new IllegalArgumentException("Data must not be null"); + } + this.dataPacketExtension = data; + + /* + * also set as packet extension so that data packet extension can be + * retrieved from IQ stanza and message stanza in the same way + */ + addExtension(data); + setType(IQ.Type.SET); + } + + /** + * Returns the data packet extension. + * <p> + * Convenience method for <code>packet.getExtension("data", + * "http://jabber.org/protocol/ibb")</code>. + * + * @return the data packet extension + */ + public DataPacketExtension getDataPacketExtension() { + return this.dataPacketExtension; + } + + public String getChildElementXML() { + return this.dataPacketExtension.toXML(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java new file mode 100644 index 0000000..80ed1e1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/DataPacketExtension.java @@ -0,0 +1,149 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; + +/** + * Represents a chunk of data of an In-Band Bytestream within an IQ stanza or a + * message stanza + * + * @author Henning Staib + */ +public class DataPacketExtension implements PacketExtension { + + /** + * The element name of the data packet extension. + */ + public final static String ELEMENT_NAME = "data"; + + /* unique session ID identifying this In-Band Bytestream */ + private final String sessionID; + + /* sequence of this packet in regard to the other data packets */ + private final long seq; + + /* the data contained in this packet */ + private final String data; + + private byte[] decodedData; + + /** + * Creates a new In-Band Bytestream data packet. + * + * @param sessionID unique session ID identifying this In-Band Bytestream + * @param seq sequence of this packet in regard to the other data packets + * @param data the base64 encoded data contained in this packet + */ + public DataPacketExtension(String sessionID, long seq, String data) { + if (sessionID == null || "".equals(sessionID)) { + throw new IllegalArgumentException("Session ID must not be null or empty"); + } + if (seq < 0 || seq > 65535) { + throw new IllegalArgumentException("Sequence must not be between 0 and 65535"); + } + if (data == null) { + throw new IllegalArgumentException("Data must not be null"); + } + this.sessionID = sessionID; + this.seq = seq; + this.data = data; + } + + /** + * Returns the unique session ID identifying this In-Band Bytestream. + * + * @return the unique session ID identifying this In-Band Bytestream + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the sequence of this packet in regard to the other data packets. + * + * @return the sequence of this packet in regard to the other data packets. + */ + public long getSeq() { + return seq; + } + + /** + * Returns the data contained in this packet. + * + * @return the data contained in this packet. + */ + public String getData() { + return data; + } + + /** + * Returns the decoded data or null if data could not be decoded. + * <p> + * The encoded data is invalid if it contains bad Base64 input characters or + * if it contains the pad ('=') character on a position other than the last + * character(s) of the data. See <a + * href="http://xmpp.org/extensions/xep-0047.html#sec">XEP-0047</a> Section + * 6. + * + * @return the decoded data + */ + public byte[] getDecodedData() { + // return cached decoded data + if (this.decodedData != null) { + return this.decodedData; + } + + // data must not contain the pad (=) other than end of data + if (data.matches(".*={1,2}+.+")) { + return null; + } + + // decodeBase64 will return null if bad characters are included + this.decodedData = StringUtils.decodeBase64(data); + return this.decodedData; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return InBandBytestreamManager.NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<"); + buf.append(getElementName()); + buf.append(" "); + buf.append("xmlns=\""); + buf.append(InBandBytestreamManager.NAMESPACE); + buf.append("\" "); + buf.append("seq=\""); + buf.append(seq); + buf.append("\" "); + buf.append("sid=\""); + buf.append(sessionID); + buf.append("\">"); + buf.append(data); + buf.append("</"); + buf.append(getElementName()); + buf.append(">"); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java new file mode 100644 index 0000000..94a7a9b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/packet/Open.java @@ -0,0 +1,126 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType; + +/** + * Represents a request to open an In-Band Bytestream. + * + * @author Henning Staib + */ +public class Open extends IQ { + + /* unique session ID identifying this In-Band Bytestream */ + private final String sessionID; + + /* block size in which the data will be fragmented */ + private final int blockSize; + + /* stanza type used to encapsulate the data */ + private final StanzaType stanza; + + /** + * Creates a new In-Band Bytestream open request packet. + * <p> + * The data sent over this In-Band Bytestream will be fragmented in blocks + * with the given block size. The block size should not be greater than + * 65535. A recommended default value is 4096. + * <p> + * The data can be sent using IQ stanzas or message stanzas. + * + * @param sessionID unique session ID identifying this In-Band Bytestream + * @param blockSize block size in which the data will be fragmented + * @param stanza stanza type used to encapsulate the data + */ + public Open(String sessionID, int blockSize, StanzaType stanza) { + if (sessionID == null || "".equals(sessionID)) { + throw new IllegalArgumentException("Session ID must not be null or empty"); + } + if (blockSize <= 0) { + throw new IllegalArgumentException("Block size must be greater than zero"); + } + + this.sessionID = sessionID; + this.blockSize = blockSize; + this.stanza = stanza; + setType(Type.SET); + } + + /** + * Creates a new In-Band Bytestream open request packet. + * <p> + * The data sent over this In-Band Bytestream will be fragmented in blocks + * with the given block size. The block size should not be greater than + * 65535. A recommended default value is 4096. + * <p> + * The data will be sent using IQ stanzas. + * + * @param sessionID unique session ID identifying this In-Band Bytestream + * @param blockSize block size in which the data will be fragmented + */ + public Open(String sessionID, int blockSize) { + this(sessionID, blockSize, StanzaType.IQ); + } + + /** + * Returns the unique session ID identifying this In-Band Bytestream. + * + * @return the unique session ID identifying this In-Band Bytestream + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the block size in which the data will be fragmented. + * + * @return the block size in which the data will be fragmented + */ + public int getBlockSize() { + return blockSize; + } + + /** + * Returns the stanza type used to encapsulate the data. + * + * @return the stanza type used to encapsulate the data + */ + public StanzaType getStanza() { + return stanza; + } + + @Override + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<open "); + buf.append("xmlns=\""); + buf.append(InBandBytestreamManager.NAMESPACE); + buf.append("\" "); + buf.append("block-size=\""); + buf.append(blockSize); + buf.append("\" "); + buf.append("sid=\""); + buf.append(sessionID); + buf.append("\" "); + buf.append("stanza=\""); + buf.append(stanza.toString().toLowerCase()); + buf.append("\""); + buf.append("/>"); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java new file mode 100644 index 0000000..566724c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/CloseIQProvider.java @@ -0,0 +1,33 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Close; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses a close In-Band Bytestream packet. + * + * @author Henning Staib + */ +public class CloseIQProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + String sid = parser.getAttributeValue("", "sid"); + return new Close(sid); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java new file mode 100644 index 0000000..5abed08 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/DataPacketProvider.java @@ -0,0 +1,45 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Data; +import org.jivesoftware.smackx.bytestreams.ibb.packet.DataPacketExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses an In-Band Bytestream data packet which can be a packet extension of + * either an IQ stanza or a message stanza. + * + * @author Henning Staib + */ +public class DataPacketProvider implements PacketExtensionProvider, IQProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + String sessionID = parser.getAttributeValue("", "sid"); + long seq = Long.parseLong(parser.getAttributeValue("", "seq")); + String data = parser.nextText(); + return new DataPacketExtension(sessionID, seq, data); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + DataPacketExtension data = (DataPacketExtension) parseExtension(parser); + IQ iq = new Data(data); + return iq; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java new file mode 100644 index 0000000..3cc725a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/ibb/provider/OpenIQProvider.java @@ -0,0 +1,45 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.ibb.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager.StanzaType; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses an In-Band Bytestream open packet. + * + * @author Henning Staib + */ +public class OpenIQProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + String sessionID = parser.getAttributeValue("", "sid"); + int blockSize = Integer.parseInt(parser.getAttributeValue("", "block-size")); + + String stanzaValue = parser.getAttributeValue("", "stanza"); + StanzaType stanza = null; + if (stanzaValue == null) { + stanza = StanzaType.IQ; + } + else { + stanza = StanzaType.valueOf(stanzaValue.toUpperCase()); + } + + return new Open(sessionID, blockSize, stanza); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java new file mode 100644 index 0000000..2a78250 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/InitiationListener.java @@ -0,0 +1,119 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; + +/** + * InitiationListener handles all incoming SOCKS5 Bytestream initiation requests. If there are no + * listeners for a SOCKS5 bytestream request InitiationListener will always refuse the request and + * reply with a <not-acceptable/> error (<a + * href="http://xmpp.org/extensions/xep-0065.html#usecase-alternate">XEP-0065</a> Section 5.2.A2). + * + * @author Henning Staib + */ +final class InitiationListener implements PacketListener { + + /* manager containing the listeners and the XMPP connection */ + private final Socks5BytestreamManager manager; + + /* packet filter for all SOCKS5 Bytestream requests */ + private final PacketFilter initFilter = new AndFilter(new PacketTypeFilter(Bytestream.class), + new IQTypeFilter(IQ.Type.SET)); + + /* executor service to process incoming requests concurrently */ + private final ExecutorService initiationListenerExecutor; + + /** + * Constructor + * + * @param manager the SOCKS5 Bytestream manager + */ + protected InitiationListener(Socks5BytestreamManager manager) { + this.manager = manager; + initiationListenerExecutor = Executors.newCachedThreadPool(); + } + + public void processPacket(final Packet packet) { + initiationListenerExecutor.execute(new Runnable() { + + public void run() { + processRequest(packet); + } + }); + } + + private void processRequest(Packet packet) { + Bytestream byteStreamRequest = (Bytestream) packet; + + // ignore request if in ignore list + if (this.manager.getIgnoredBytestreamRequests().remove(byteStreamRequest.getSessionID())) { + return; + } + + // build bytestream request from packet + Socks5BytestreamRequest request = new Socks5BytestreamRequest(this.manager, + byteStreamRequest); + + // notify listeners for bytestream initiation from a specific user + BytestreamListener userListener = this.manager.getUserListener(byteStreamRequest.getFrom()); + if (userListener != null) { + userListener.incomingBytestreamRequest(request); + + } + else if (!this.manager.getAllRequestListeners().isEmpty()) { + /* + * if there is no user specific listener inform listeners for all initiation requests + */ + for (BytestreamListener listener : this.manager.getAllRequestListeners()) { + listener.incomingBytestreamRequest(request); + } + + } + else { + /* + * if there is no listener for this initiation request, reply with reject message + */ + this.manager.replyRejectPacket(byteStreamRequest); + } + } + + /** + * Returns the packet filter for SOCKS5 Bytestream initialization requests. + * + * @return the packet filter for SOCKS5 Bytestream initialization requests + */ + protected PacketFilter getFilter() { + return this.initFilter; + } + + /** + * Shuts down the listeners executor service. + */ + protected void shutdown() { + this.initiationListenerExecutor.shutdownNow(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java new file mode 100644 index 0000000..1430b1d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamListener.java @@ -0,0 +1,43 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.BytestreamRequest; + +/** + * Socks5BytestreamListener are informed if a remote user wants to initiate a SOCKS5 Bytestream. + * Implement this interface to handle incoming SOCKS5 Bytestream requests. + * <p> + * There are two ways to add this listener. See + * {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener)} and + * {@link Socks5BytestreamManager#addIncomingBytestreamListener(BytestreamListener, String)} for + * further details. + * + * @author Henning Staib + */ +public abstract class Socks5BytestreamListener implements BytestreamListener { + + public void incomingBytestreamRequest(BytestreamRequest request) { + incomingBytestreamRequest((Socks5BytestreamRequest) request); + } + + /** + * This listener is notified if a SOCKS5 Bytestream request from another user has been received. + * + * @param request the incoming SOCKS5 Bytestream request + */ + public abstract void incomingBytestreamRequest(Socks5BytestreamRequest request); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java new file mode 100644 index 0000000..41826df --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamManager.java @@ -0,0 +1,760 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.IOException; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeoutException; + +import org.jivesoftware.smack.AbstractConnectionListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.SyncPacketSend; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.bytestreams.BytestreamListener; +import org.jivesoftware.smackx.bytestreams.BytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHostUsed; +import org.jivesoftware.smackx.filetransfer.FileTransferManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; +import org.jivesoftware.smackx.packet.DiscoverItems.Item; + +/** + * The Socks5BytestreamManager class handles establishing SOCKS5 Bytestreams as specified in the <a + * href="http://xmpp.org/extensions/xep-0065.html">XEP-0065</a>. + * <p> + * A SOCKS5 Bytestream is negotiated partly over the XMPP XML stream and partly over a separate + * socket. The actual transfer though takes place over a separately created socket. + * <p> + * A SOCKS5 Bytestream generally has three parties, the initiator, the target, and the stream host. + * The stream host is a specialized SOCKS5 proxy setup on a server, or, the initiator can act as the + * stream host. + * <p> + * To establish a SOCKS5 Bytestream invoke the {@link #establishSession(String)} method. This will + * negotiate a SOCKS5 Bytestream with the given target JID and return a socket. + * <p> + * If a session ID for the SOCKS5 Bytestream was already negotiated (e.g. while negotiating a file + * transfer) invoke {@link #establishSession(String, String)}. + * <p> + * To handle incoming SOCKS5 Bytestream requests add an {@link Socks5BytestreamListener} to the + * manager. There are two ways to add this listener. If you want to be informed about incoming + * SOCKS5 Bytestreams from a specific user add the listener by invoking + * {@link #addIncomingBytestreamListener(BytestreamListener, String)}. If the listener should + * respond to all SOCKS5 Bytestream requests invoke + * {@link #addIncomingBytestreamListener(BytestreamListener)}. + * <p> + * Note that the registered {@link Socks5BytestreamListener} will NOT be notified on incoming Socks5 + * bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * <p> + * If no {@link Socks5BytestreamListener}s are registered, all incoming SOCKS5 Bytestream requests + * will be rejected by returning a <not-acceptable/> error to the initiator. + * + * @author Henning Staib + */ +public final class Socks5BytestreamManager implements BytestreamManager { + + /* + * create a new Socks5BytestreamManager and register a shutdown listener on every established + * connection + */ + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + + public void connectionCreated(Connection connection) { + final Socks5BytestreamManager manager; + manager = Socks5BytestreamManager.getBytestreamManager(connection); + + // register shutdown listener + connection.addConnectionListener(new AbstractConnectionListener() { + + public void connectionClosed() { + manager.disableService(); + } + + }); + } + + }); + } + + /** + * The XMPP namespace of the SOCKS5 Bytestream + */ + public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams"; + + /* prefix used to generate session IDs */ + private static final String SESSION_ID_PREFIX = "js5_"; + + /* random generator to create session IDs */ + private final static Random randomGenerator = new Random(); + + /* stores one Socks5BytestreamManager for each XMPP connection */ + private final static Map<Connection, Socks5BytestreamManager> managers = new HashMap<Connection, Socks5BytestreamManager>(); + + /* XMPP connection */ + private final Connection connection; + + /* + * assigns a user to a listener that is informed if a bytestream request for this user is + * received + */ + private final Map<String, BytestreamListener> userListeners = new ConcurrentHashMap<String, BytestreamListener>(); + + /* + * list of listeners that respond to all bytestream requests if there are not user specific + * listeners for that request + */ + private final List<BytestreamListener> allRequestListeners = Collections.synchronizedList(new LinkedList<BytestreamListener>()); + + /* listener that handles all incoming bytestream requests */ + private final InitiationListener initiationListener; + + /* timeout to wait for the response to the SOCKS5 Bytestream initialization request */ + private int targetResponseTimeout = 10000; + + /* timeout for connecting to the SOCKS5 proxy selected by the target */ + private int proxyConnectionTimeout = 10000; + + /* blacklist of errornous SOCKS5 proxies */ + private final List<String> proxyBlacklist = Collections.synchronizedList(new LinkedList<String>()); + + /* remember the last proxy that worked to prioritize it */ + private String lastWorkingProxy = null; + + /* flag to enable/disable prioritization of last working proxy */ + private boolean proxyPrioritizationEnabled = true; + + /* + * list containing session IDs of SOCKS5 Bytestream initialization packets that should be + * ignored by the InitiationListener + */ + private List<String> ignoredBytestreamRequests = Collections.synchronizedList(new LinkedList<String>()); + + /** + * Returns the Socks5BytestreamManager to handle SOCKS5 Bytestreams for a given + * {@link Connection}. + * <p> + * If no manager exists a new is created and initialized. + * + * @param connection the XMPP connection or <code>null</code> if given connection is + * <code>null</code> + * @return the Socks5BytestreamManager for the given XMPP connection + */ + public static synchronized Socks5BytestreamManager getBytestreamManager(Connection connection) { + if (connection == null) { + return null; + } + Socks5BytestreamManager manager = managers.get(connection); + if (manager == null) { + manager = new Socks5BytestreamManager(connection); + managers.put(connection, manager); + manager.activate(); + } + return manager; + } + + /** + * Private constructor. + * + * @param connection the XMPP connection + */ + private Socks5BytestreamManager(Connection connection) { + this.connection = connection; + this.initiationListener = new InitiationListener(this); + } + + /** + * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request unless + * there is a user specific BytestreamListener registered. + * <p> + * If no listeners are registered all SOCKS5 Bytestream request are rejected with a + * <not-acceptable/> error. + * <p> + * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 + * bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * + * @param listener the listener to register + */ + public void addIncomingBytestreamListener(BytestreamListener listener) { + this.allRequestListeners.add(listener); + } + + /** + * Removes the given listener from the list of listeners for all incoming SOCKS5 Bytestream + * requests. + * + * @param listener the listener to remove + */ + public void removeIncomingBytestreamListener(BytestreamListener listener) { + this.allRequestListeners.remove(listener); + } + + /** + * Adds BytestreamListener that is called for every incoming SOCKS5 Bytestream request from the + * given user. + * <p> + * Use this method if you are awaiting an incoming SOCKS5 Bytestream request from a specific + * user. + * <p> + * If no listeners are registered all SOCKS5 Bytestream request are rejected with a + * <not-acceptable/> error. + * <p> + * Note that the registered {@link BytestreamListener} will NOT be notified on incoming Socks5 + * bytestream requests sent in the context of <a + * href="http://xmpp.org/extensions/xep-0096.html">XEP-0096</a> file transfer. (See + * {@link FileTransferManager}) + * + * @param listener the listener to register + * @param initiatorJID the JID of the user that wants to establish a SOCKS5 Bytestream + */ + public void addIncomingBytestreamListener(BytestreamListener listener, String initiatorJID) { + this.userListeners.put(initiatorJID, listener); + } + + /** + * Removes the listener for the given user. + * + * @param initiatorJID the JID of the user the listener should be removed + */ + public void removeIncomingBytestreamListener(String initiatorJID) { + this.userListeners.remove(initiatorJID); + } + + /** + * Use this method to ignore the next incoming SOCKS5 Bytestream request containing the given + * session ID. No listeners will be notified for this request and and no error will be returned + * to the initiator. + * <p> + * This method should be used if you are awaiting a SOCKS5 Bytestream request as a reply to + * another packet (e.g. file transfer). + * + * @param sessionID to be ignored + */ + public void ignoreBytestreamRequestOnce(String sessionID) { + this.ignoredBytestreamRequests.add(sessionID); + } + + /** + * Disables the SOCKS5 Bytestream manager by removing the SOCKS5 Bytestream feature from the + * service discovery, disabling the listener for SOCKS5 Bytestream initiation requests and + * resetting its internal state. + * <p> + * To re-enable the SOCKS5 Bytestream feature invoke {@link #getBytestreamManager(Connection)}. + * Using the file transfer API will automatically re-enable the SOCKS5 Bytestream feature. + */ + public synchronized void disableService() { + + // remove initiation packet listener + this.connection.removePacketListener(this.initiationListener); + + // shutdown threads + this.initiationListener.shutdown(); + + // clear listeners + this.allRequestListeners.clear(); + this.userListeners.clear(); + + // reset internal state + this.lastWorkingProxy = null; + this.proxyBlacklist.clear(); + this.ignoredBytestreamRequests.clear(); + + // remove manager from static managers map + managers.remove(this.connection); + + // shutdown local SOCKS5 proxy if there are no more managers for other connections + if (managers.size() == 0) { + Socks5Proxy.getSocks5Proxy().stop(); + } + + // remove feature from service discovery + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); + + // check if service discovery is not already disposed by connection shutdown + if (serviceDiscoveryManager != null) { + serviceDiscoveryManager.removeFeature(NAMESPACE); + } + + } + + /** + * Returns the timeout to wait for the response to the SOCKS5 Bytestream initialization request. + * Default is 10000ms. + * + * @return the timeout to wait for the response to the SOCKS5 Bytestream initialization request + */ + public int getTargetResponseTimeout() { + if (this.targetResponseTimeout <= 0) { + this.targetResponseTimeout = 10000; + } + return targetResponseTimeout; + } + + /** + * Sets the timeout to wait for the response to the SOCKS5 Bytestream initialization request. + * Default is 10000ms. + * + * @param targetResponseTimeout the timeout to set + */ + public void setTargetResponseTimeout(int targetResponseTimeout) { + this.targetResponseTimeout = targetResponseTimeout; + } + + /** + * Returns the timeout for connecting to the SOCKS5 proxy selected by the target. Default is + * 10000ms. + * + * @return the timeout for connecting to the SOCKS5 proxy selected by the target + */ + public int getProxyConnectionTimeout() { + if (this.proxyConnectionTimeout <= 0) { + this.proxyConnectionTimeout = 10000; + } + return proxyConnectionTimeout; + } + + /** + * Sets the timeout for connecting to the SOCKS5 proxy selected by the target. Default is + * 10000ms. + * + * @param proxyConnectionTimeout the timeout to set + */ + public void setProxyConnectionTimeout(int proxyConnectionTimeout) { + this.proxyConnectionTimeout = proxyConnectionTimeout; + } + + /** + * Returns if the prioritization of the last working SOCKS5 proxy on successive SOCKS5 + * Bytestream connections is enabled. Default is <code>true</code>. + * + * @return <code>true</code> if prioritization is enabled, <code>false</code> otherwise + */ + public boolean isProxyPrioritizationEnabled() { + return proxyPrioritizationEnabled; + } + + /** + * Enable/disable the prioritization of the last working SOCKS5 proxy on successive SOCKS5 + * Bytestream connections. + * + * @param proxyPrioritizationEnabled enable/disable the prioritization of the last working + * SOCKS5 proxy + */ + public void setProxyPrioritizationEnabled(boolean proxyPrioritizationEnabled) { + this.proxyPrioritizationEnabled = proxyPrioritizationEnabled; + } + + /** + * Establishes a SOCKS5 Bytestream with the given user and returns the Socket to send/receive + * data to/from the user. + * <p> + * Use this method to establish SOCKS5 Bytestreams to users accepting all incoming Socks5 + * bytestream requests since this method doesn't provide a way to tell the user something about + * the data to be sent. + * <p> + * To establish a SOCKS5 Bytestream after negotiation the kind of data to be sent (e.g. file + * transfer) use {@link #establishSession(String, String)}. + * + * @param targetJID the JID of the user a SOCKS5 Bytestream should be established + * @return the Socket to send/receive data to/from the user + * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 + * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies + * @throws IOException if the bytestream could not be established + * @throws InterruptedException if the current thread was interrupted while waiting + */ + public Socks5BytestreamSession establishSession(String targetJID) throws XMPPException, + IOException, InterruptedException { + String sessionID = getNextSessionID(); + return establishSession(targetJID, sessionID); + } + + /** + * Establishes a SOCKS5 Bytestream with the given user using the given session ID and returns + * the Socket to send/receive data to/from the user. + * + * @param targetJID the JID of the user a SOCKS5 Bytestream should be established + * @param sessionID the session ID for the SOCKS5 Bytestream request + * @return the Socket to send/receive data to/from the user + * @throws XMPPException if the user doesn't support or accept SOCKS5 Bytestreams, if no Socks5 + * Proxy could be found, if the user couldn't connect to any of the SOCKS5 Proxies + * @throws IOException if the bytestream could not be established + * @throws InterruptedException if the current thread was interrupted while waiting + */ + public Socks5BytestreamSession establishSession(String targetJID, String sessionID) + throws XMPPException, IOException, InterruptedException { + + // check if target supports SOCKS5 Bytestream + if (!supportsSocks5(targetJID)) { + throw new XMPPException(targetJID + " doesn't support SOCKS5 Bytestream"); + } + + // determine SOCKS5 proxies from XMPP-server + List<String> proxies = determineProxies(); + + // determine address and port of each proxy + List<StreamHost> streamHosts = determineStreamHostInfos(proxies); + + // compute digest + String digest = Socks5Utils.createDigest(sessionID, this.connection.getUser(), targetJID); + + if (streamHosts.isEmpty()) { + throw new XMPPException("no SOCKS5 proxies available"); + } + + // prioritize last working SOCKS5 proxy if exists + if (this.proxyPrioritizationEnabled && this.lastWorkingProxy != null) { + StreamHost selectedStreamHost = null; + for (StreamHost streamHost : streamHosts) { + if (streamHost.getJID().equals(this.lastWorkingProxy)) { + selectedStreamHost = streamHost; + break; + } + } + if (selectedStreamHost != null) { + streamHosts.remove(selectedStreamHost); + streamHosts.add(0, selectedStreamHost); + } + + } + + Socks5Proxy socks5Proxy = Socks5Proxy.getSocks5Proxy(); + try { + + // add transfer digest to local proxy to make transfer valid + socks5Proxy.addTransfer(digest); + + // create initiation packet + Bytestream initiation = createBytestreamInitiation(sessionID, targetJID, streamHosts); + + // send initiation packet + Packet response = SyncPacketSend.getReply(this.connection, initiation, + getTargetResponseTimeout()); + + // extract used stream host from response + StreamHostUsed streamHostUsed = ((Bytestream) response).getUsedHost(); + StreamHost usedStreamHost = initiation.getStreamHost(streamHostUsed.getJID()); + + if (usedStreamHost == null) { + throw new XMPPException("Remote user responded with unknown host"); + } + + // build SOCKS5 client + Socks5Client socks5Client = new Socks5ClientForInitiator(usedStreamHost, digest, + this.connection, sessionID, targetJID); + + // establish connection to proxy + Socket socket = socks5Client.getSocket(getProxyConnectionTimeout()); + + // remember last working SOCKS5 proxy to prioritize it for next request + this.lastWorkingProxy = usedStreamHost.getJID(); + + // negotiation successful, return the output stream + return new Socks5BytestreamSession(socket, usedStreamHost.getJID().equals( + this.connection.getUser())); + + } + catch (TimeoutException e) { + throw new IOException("Timeout while connecting to SOCKS5 proxy"); + } + finally { + + // remove transfer digest if output stream is returned or an exception + // occurred + socks5Proxy.removeTransfer(digest); + + } + } + + /** + * Returns <code>true</code> if the given target JID supports feature SOCKS5 Bytestream. + * + * @param targetJID the target JID + * @return <code>true</code> if the given target JID supports feature SOCKS5 Bytestream + * otherwise <code>false</code> + * @throws XMPPException if there was an error querying target for supported features + */ + private boolean supportsSocks5(String targetJID) throws XMPPException { + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); + DiscoverInfo discoverInfo = serviceDiscoveryManager.discoverInfo(targetJID); + return discoverInfo.containsFeature(NAMESPACE); + } + + /** + * Returns a list of JIDs of SOCKS5 proxies by querying the XMPP server. The SOCKS5 proxies are + * in the same order as returned by the XMPP server. + * + * @return list of JIDs of SOCKS5 proxies + * @throws XMPPException if there was an error querying the XMPP server for SOCKS5 proxies + */ + private List<String> determineProxies() throws XMPPException { + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection); + + List<String> proxies = new ArrayList<String>(); + + // get all items form XMPP server + DiscoverItems discoverItems = serviceDiscoveryManager.discoverItems(this.connection.getServiceName()); + Iterator<Item> itemIterator = discoverItems.getItems(); + + // query all items if they are SOCKS5 proxies + while (itemIterator.hasNext()) { + Item item = itemIterator.next(); + + // skip blacklisted servers + if (this.proxyBlacklist.contains(item.getEntityID())) { + continue; + } + + try { + DiscoverInfo proxyInfo; + proxyInfo = serviceDiscoveryManager.discoverInfo(item.getEntityID()); + Iterator<Identity> identities = proxyInfo.getIdentities(); + + // item must have category "proxy" and type "bytestream" + while (identities.hasNext()) { + Identity identity = identities.next(); + + if ("proxy".equalsIgnoreCase(identity.getCategory()) + && "bytestreams".equalsIgnoreCase(identity.getType())) { + proxies.add(item.getEntityID()); + break; + } + + /* + * server is not a SOCKS5 proxy, blacklist server to skip next time a Socks5 + * bytestream should be established + */ + this.proxyBlacklist.add(item.getEntityID()); + + } + } + catch (XMPPException e) { + // blacklist errornous server + this.proxyBlacklist.add(item.getEntityID()); + } + } + + return proxies; + } + + /** + * Returns a list of stream hosts containing the IP address an the port for the given list of + * SOCKS5 proxy JIDs. The order of the returned list is the same as the given list of JIDs + * excluding all SOCKS5 proxies who's network settings could not be determined. If a local + * SOCKS5 proxy is running it will be the first item in the list returned. + * + * @param proxies a list of SOCKS5 proxy JIDs + * @return a list of stream hosts containing the IP address an the port + */ + private List<StreamHost> determineStreamHostInfos(List<String> proxies) { + List<StreamHost> streamHosts = new ArrayList<StreamHost>(); + + // add local proxy on first position if exists + List<StreamHost> localProxies = getLocalStreamHost(); + if (localProxies != null) { + streamHosts.addAll(localProxies); + } + + // query SOCKS5 proxies for network settings + for (String proxy : proxies) { + Bytestream streamHostRequest = createStreamHostRequest(proxy); + try { + Bytestream response = (Bytestream) SyncPacketSend.getReply(this.connection, + streamHostRequest); + streamHosts.addAll(response.getStreamHosts()); + } + catch (XMPPException e) { + // blacklist errornous proxies + this.proxyBlacklist.add(proxy); + } + } + + return streamHosts; + } + + /** + * Returns a IQ packet to query a SOCKS5 proxy its network settings. + * + * @param proxy the proxy to query + * @return IQ packet to query a SOCKS5 proxy its network settings + */ + private Bytestream createStreamHostRequest(String proxy) { + Bytestream request = new Bytestream(); + request.setType(IQ.Type.GET); + request.setTo(proxy); + return request; + } + + /** + * Returns the stream host information of the local SOCKS5 proxy containing the IP address and + * the port or null if local SOCKS5 proxy is not running. + * + * @return the stream host information of the local SOCKS5 proxy or null if local SOCKS5 proxy + * is not running + */ + private List<StreamHost> getLocalStreamHost() { + + // get local proxy singleton + Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); + + if (socks5Server.isRunning()) { + List<String> addresses = socks5Server.getLocalAddresses(); + int port = socks5Server.getPort(); + + if (addresses.size() >= 1) { + List<StreamHost> streamHosts = new ArrayList<StreamHost>(); + for (String address : addresses) { + StreamHost streamHost = new StreamHost(this.connection.getUser(), address); + streamHost.setPort(port); + streamHosts.add(streamHost); + } + return streamHosts; + } + + } + + // server is not running or local address could not be determined + return null; + } + + /** + * Returns a SOCKS5 Bytestream initialization request packet with the given session ID + * containing the given stream hosts for the given target JID. + * + * @param sessionID the session ID for the SOCKS5 Bytestream + * @param targetJID the target JID of SOCKS5 Bytestream request + * @param streamHosts a list of SOCKS5 proxies the target should connect to + * @return a SOCKS5 Bytestream initialization request packet + */ + private Bytestream createBytestreamInitiation(String sessionID, String targetJID, + List<StreamHost> streamHosts) { + Bytestream initiation = new Bytestream(sessionID); + + // add all stream hosts + for (StreamHost streamHost : streamHosts) { + initiation.addStreamHost(streamHost); + } + + initiation.setType(IQ.Type.SET); + initiation.setTo(targetJID); + + return initiation; + } + + /** + * Responses to the given packet's sender with a XMPP error that a SOCKS5 Bytestream is not + * accepted. + * + * @param packet Packet that should be answered with a not-acceptable error + */ + protected void replyRejectPacket(IQ packet) { + XMPPError xmppError = new XMPPError(XMPPError.Condition.no_acceptable); + IQ errorIQ = IQ.createErrorResponse(packet, xmppError); + this.connection.sendPacket(errorIQ); + } + + /** + * Activates the Socks5BytestreamManager by registering the SOCKS5 Bytestream initialization + * listener and enabling the SOCKS5 Bytestream feature. + */ + private void activate() { + // register bytestream initiation packet listener + this.connection.addPacketListener(this.initiationListener, + this.initiationListener.getFilter()); + + // enable SOCKS5 feature + enableService(); + } + + /** + * Adds the SOCKS5 Bytestream feature to the service discovery. + */ + private void enableService() { + ServiceDiscoveryManager manager = ServiceDiscoveryManager.getInstanceFor(this.connection); + if (!manager.includesFeature(NAMESPACE)) { + manager.addFeature(NAMESPACE); + } + } + + /** + * Returns a new unique session ID. + * + * @return a new unique session ID + */ + private String getNextSessionID() { + StringBuilder buffer = new StringBuilder(); + buffer.append(SESSION_ID_PREFIX); + buffer.append(Math.abs(randomGenerator.nextLong())); + return buffer.toString(); + } + + /** + * Returns the XMPP connection. + * + * @return the XMPP connection + */ + protected Connection getConnection() { + return this.connection; + } + + /** + * Returns the {@link BytestreamListener} that should be informed if a SOCKS5 Bytestream request + * from the given initiator JID is received. + * + * @param initiator the initiator's JID + * @return the listener + */ + protected BytestreamListener getUserListener(String initiator) { + return this.userListeners.get(initiator); + } + + /** + * Returns a list of {@link BytestreamListener} that are informed if there are no listeners for + * a specific initiator. + * + * @return list of listeners + */ + protected List<BytestreamListener> getAllRequestListeners() { + return this.allRequestListeners; + } + + /** + * Returns the list of session IDs that should be ignored by the InitialtionListener + * + * @return list of session IDs + */ + protected List<String> getIgnoredBytestreamRequests() { + return ignoredBytestreamRequests; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java new file mode 100644 index 0000000..0b2fdeb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamRequest.java @@ -0,0 +1,316 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.IOException; +import java.net.Socket; +import java.util.Collection; +import java.util.concurrent.TimeoutException; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.Cache; +import org.jivesoftware.smackx.bytestreams.BytestreamRequest; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; + +/** + * Socks5BytestreamRequest class handles incoming SOCKS5 Bytestream requests. + * + * @author Henning Staib + */ +public class Socks5BytestreamRequest implements BytestreamRequest { + + /* lifetime of an Item in the blacklist */ + private static final long BLACKLIST_LIFETIME = 60 * 1000 * 120; + + /* size of the blacklist */ + private static final int BLACKLIST_MAX_SIZE = 100; + + /* blacklist of addresses of SOCKS5 proxies */ + private static final Cache<String, Integer> ADDRESS_BLACKLIST = new Cache<String, Integer>( + BLACKLIST_MAX_SIZE, BLACKLIST_LIFETIME); + + /* + * The number of connection failures it takes for a particular SOCKS5 proxy to be blacklisted. + * When a proxy is blacklisted no more connection attempts will be made to it for a period of 2 + * hours. + */ + private static int CONNECTION_FAILURE_THRESHOLD = 2; + + /* the bytestream initialization request */ + private Bytestream bytestreamRequest; + + /* SOCKS5 Bytestream manager containing the XMPP connection and helper methods */ + private Socks5BytestreamManager manager; + + /* timeout to connect to all SOCKS5 proxies */ + private int totalConnectTimeout = 10000; + + /* minimum timeout to connect to one SOCKS5 proxy */ + private int minimumConnectTimeout = 2000; + + /** + * Returns the number of connection failures it takes for a particular SOCKS5 proxy to be + * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a + * period of 2 hours. Default is 2. + * + * @return the number of connection failures it takes for a particular SOCKS5 proxy to be + * blacklisted + */ + public static int getConnectFailureThreshold() { + return CONNECTION_FAILURE_THRESHOLD; + } + + /** + * Sets the number of connection failures it takes for a particular SOCKS5 proxy to be + * blacklisted. When a proxy is blacklisted no more connection attempts will be made to it for a + * period of 2 hours. Default is 2. + * <p> + * Setting the connection failure threshold to zero disables the blacklisting. + * + * @param connectFailureThreshold the number of connection failures it takes for a particular + * SOCKS5 proxy to be blacklisted + */ + public static void setConnectFailureThreshold(int connectFailureThreshold) { + CONNECTION_FAILURE_THRESHOLD = connectFailureThreshold; + } + + /** + * Creates a new Socks5BytestreamRequest. + * + * @param manager the SOCKS5 Bytestream manager + * @param bytestreamRequest the SOCKS5 Bytestream initialization packet + */ + protected Socks5BytestreamRequest(Socks5BytestreamManager manager, Bytestream bytestreamRequest) { + this.manager = manager; + this.bytestreamRequest = bytestreamRequest; + } + + /** + * Returns the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms. + * <p> + * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given + * by the initiator until a connection is established. This timeout divided by the number of + * SOCKS5 proxies determines the timeout for every connection attempt. + * <p> + * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking + * {@link #setMinimumConnectTimeout(int)}. + * + * @return the maximum timeout to connect to SOCKS5 proxies + */ + public int getTotalConnectTimeout() { + if (this.totalConnectTimeout <= 0) { + return 10000; + } + return this.totalConnectTimeout; + } + + /** + * Sets the maximum timeout to connect to SOCKS5 proxies. Default is 10000ms. + * <p> + * When accepting a SOCKS5 Bytestream request Smack tries to connect to all SOCKS5 proxies given + * by the initiator until a connection is established. This timeout divided by the number of + * SOCKS5 proxies determines the timeout for every connection attempt. + * <p> + * You can set the minimum timeout for establishing a connection to one SOCKS5 proxy by invoking + * {@link #setMinimumConnectTimeout(int)}. + * + * @param totalConnectTimeout the maximum timeout to connect to SOCKS5 proxies + */ + public void setTotalConnectTimeout(int totalConnectTimeout) { + this.totalConnectTimeout = totalConnectTimeout; + } + + /** + * Returns the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream + * request. Default is 2000ms. + * + * @return the timeout to connect to one SOCKS5 proxy + */ + public int getMinimumConnectTimeout() { + if (this.minimumConnectTimeout <= 0) { + return 2000; + } + return this.minimumConnectTimeout; + } + + /** + * Sets the timeout to connect to one SOCKS5 proxy while accepting the SOCKS5 Bytestream + * request. Default is 2000ms. + * + * @param minimumConnectTimeout the timeout to connect to one SOCKS5 proxy + */ + public void setMinimumConnectTimeout(int minimumConnectTimeout) { + this.minimumConnectTimeout = minimumConnectTimeout; + } + + /** + * Returns the sender of the SOCKS5 Bytestream initialization request. + * + * @return the sender of the SOCKS5 Bytestream initialization request. + */ + public String getFrom() { + return this.bytestreamRequest.getFrom(); + } + + /** + * Returns the session ID of the SOCKS5 Bytestream initialization request. + * + * @return the session ID of the SOCKS5 Bytestream initialization request. + */ + public String getSessionID() { + return this.bytestreamRequest.getSessionID(); + } + + /** + * Accepts the SOCKS5 Bytestream initialization request and returns the socket to send/receive + * data. + * <p> + * Before accepting the SOCKS5 Bytestream request you can set timeouts by invoking + * {@link #setTotalConnectTimeout(int)} and {@link #setMinimumConnectTimeout(int)}. + * + * @return the socket to send/receive data + * @throws XMPPException if connection to all SOCKS5 proxies failed or if stream is invalid. + * @throws InterruptedException if the current thread was interrupted while waiting + */ + public Socks5BytestreamSession accept() throws XMPPException, InterruptedException { + Collection<StreamHost> streamHosts = this.bytestreamRequest.getStreamHosts(); + + // throw exceptions if request contains no stream hosts + if (streamHosts.size() == 0) { + cancelRequest(); + } + + StreamHost selectedHost = null; + Socket socket = null; + + String digest = Socks5Utils.createDigest(this.bytestreamRequest.getSessionID(), + this.bytestreamRequest.getFrom(), this.manager.getConnection().getUser()); + + /* + * determine timeout for each connection attempt; each SOCKS5 proxy has the same amount of + * time so that the first does not consume the whole timeout + */ + int timeout = Math.max(getTotalConnectTimeout() / streamHosts.size(), + getMinimumConnectTimeout()); + + for (StreamHost streamHost : streamHosts) { + String address = streamHost.getAddress() + ":" + streamHost.getPort(); + + // check to see if this address has been blacklisted + int failures = getConnectionFailures(address); + if (CONNECTION_FAILURE_THRESHOLD > 0 && failures >= CONNECTION_FAILURE_THRESHOLD) { + continue; + } + + // establish socket + try { + + // build SOCKS5 client + final Socks5Client socks5Client = new Socks5Client(streamHost, digest); + + // connect to SOCKS5 proxy with a timeout + socket = socks5Client.getSocket(timeout); + + // set selected host + selectedHost = streamHost; + break; + + } + catch (TimeoutException e) { + incrementConnectionFailures(address); + } + catch (IOException e) { + incrementConnectionFailures(address); + } + catch (XMPPException e) { + incrementConnectionFailures(address); + } + + } + + // throw exception if connecting to all SOCKS5 proxies failed + if (selectedHost == null || socket == null) { + cancelRequest(); + } + + // send used-host confirmation + Bytestream response = createUsedHostResponse(selectedHost); + this.manager.getConnection().sendPacket(response); + + return new Socks5BytestreamSession(socket, selectedHost.getJID().equals( + this.bytestreamRequest.getFrom())); + + } + + /** + * Rejects the SOCKS5 Bytestream request by sending a reject error to the initiator. + */ + public void reject() { + this.manager.replyRejectPacket(this.bytestreamRequest); + } + + /** + * Cancels the SOCKS5 Bytestream request by sending an error to the initiator and building a + * XMPP exception. + * + * @throws XMPPException XMPP exception containing the XMPP error + */ + private void cancelRequest() throws XMPPException { + String errorMessage = "Could not establish socket with any provided host"; + XMPPError error = new XMPPError(XMPPError.Condition.item_not_found, errorMessage); + IQ errorIQ = IQ.createErrorResponse(this.bytestreamRequest, error); + this.manager.getConnection().sendPacket(errorIQ); + throw new XMPPException(errorMessage, error); + } + + /** + * Returns the response to the SOCKS5 Bytestream request containing the SOCKS5 proxy used. + * + * @param selectedHost the used SOCKS5 proxy + * @return the response to the SOCKS5 Bytestream request + */ + private Bytestream createUsedHostResponse(StreamHost selectedHost) { + Bytestream response = new Bytestream(this.bytestreamRequest.getSessionID()); + response.setTo(this.bytestreamRequest.getFrom()); + response.setType(IQ.Type.RESULT); + response.setPacketID(this.bytestreamRequest.getPacketID()); + response.setUsedHost(selectedHost.getJID()); + return response; + } + + /** + * Increments the connection failure counter by one for the given address. + * + * @param address the address the connection failure counter should be increased + */ + private void incrementConnectionFailures(String address) { + Integer count = ADDRESS_BLACKLIST.get(address); + ADDRESS_BLACKLIST.put(address, count == null ? 1 : count + 1); + } + + /** + * Returns how often the connection to the given address failed. + * + * @param address the address + * @return number of connection failures + */ + private int getConnectionFailures(String address) { + Integer count = ADDRESS_BLACKLIST.get(address); + return count != null ? count : 0; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java new file mode 100644 index 0000000..20fbdb1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5BytestreamSession.java @@ -0,0 +1,81 @@ +package org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.net.SocketException; + +import org.jivesoftware.smackx.bytestreams.BytestreamSession; + +/** + * Socks5BytestreamSession class represents a SOCKS5 Bytestream session. + * + * @author Henning Staib + */ +public class Socks5BytestreamSession implements BytestreamSession { + + /* the underlying socket of the SOCKS5 Bytestream */ + private final Socket socket; + + /* flag to indicate if this session is a direct or mediated connection */ + private final boolean isDirect; + + protected Socks5BytestreamSession(Socket socket, boolean isDirect) { + this.socket = socket; + this.isDirect = isDirect; + } + + /** + * Returns <code>true</code> if the session is established through a direct connection between + * the initiator and target, <code>false</code> if the session is mediated over a SOCKS proxy. + * + * @return <code>true</code> if session is a direct connection, <code>false</code> if session is + * mediated over a SOCKS5 proxy + */ + public boolean isDirect() { + return this.isDirect; + } + + /** + * Returns <code>true</code> if the session is mediated over a SOCKS proxy, <code>false</code> + * if this session is established through a direct connection between the initiator and target. + * + * @return <code>true</code> if session is mediated over a SOCKS5 proxy, <code>false</code> if + * session is a direct connection + */ + public boolean isMediated() { + return !this.isDirect; + } + + public InputStream getInputStream() throws IOException { + return this.socket.getInputStream(); + } + + public OutputStream getOutputStream() throws IOException { + return this.socket.getOutputStream(); + } + + public int getReadTimeout() throws IOException { + try { + return this.socket.getSoTimeout(); + } + catch (SocketException e) { + throw new IOException("Error on underlying Socket"); + } + } + + public void setReadTimeout(int timeout) throws IOException { + try { + this.socket.setSoTimeout(timeout); + } + catch (SocketException e) { + throw new IOException("Error on underlying Socket"); + } + } + + public void close() throws IOException { + this.socket.close(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java new file mode 100644 index 0000000..664ea59 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Client.java @@ -0,0 +1,204 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; + +/** + * The SOCKS5 client class handles establishing a connection to a SOCKS5 proxy. Connecting to a + * SOCKS5 proxy requires authentication. This implementation only supports the no-authentication + * authentication method. + * + * @author Henning Staib + */ +class Socks5Client { + + /* stream host containing network settings and name of the SOCKS5 proxy */ + protected StreamHost streamHost; + + /* SHA-1 digest identifying the SOCKS5 stream */ + protected String digest; + + /** + * Constructor for a SOCKS5 client. + * + * @param streamHost containing network settings of the SOCKS5 proxy + * @param digest identifying the SOCKS5 Bytestream + */ + public Socks5Client(StreamHost streamHost, String digest) { + this.streamHost = streamHost; + this.digest = digest; + } + + /** + * Returns the initialized socket that can be used to transfer data between peers via the SOCKS5 + * proxy. + * + * @param timeout timeout to connect to SOCKS5 proxy in milliseconds + * @return socket the initialized socket + * @throws IOException if initializing the socket failed due to a network error + * @throws XMPPException if establishing connection to SOCKS5 proxy failed + * @throws TimeoutException if connecting to SOCKS5 proxy timed out + * @throws InterruptedException if the current thread was interrupted while waiting + */ + public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException, + TimeoutException { + + // wrap connecting in future for timeout + FutureTask<Socket> futureTask = new FutureTask<Socket>(new Callable<Socket>() { + + public Socket call() throws Exception { + + // initialize socket + Socket socket = new Socket(); + SocketAddress socketAddress = new InetSocketAddress(streamHost.getAddress(), + streamHost.getPort()); + socket.connect(socketAddress); + + // initialize connection to SOCKS5 proxy + if (!establish(socket)) { + + // initialization failed, close socket + socket.close(); + throw new XMPPException("establishing connection to SOCKS5 proxy failed"); + + } + + return socket; + } + + }); + Thread executor = new Thread(futureTask); + executor.start(); + + // get connection to initiator with timeout + try { + return futureTask.get(timeout, TimeUnit.MILLISECONDS); + } + catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause != null) { + // case exceptions to comply with method signature + if (cause instanceof IOException) { + throw (IOException) cause; + } + if (cause instanceof XMPPException) { + throw (XMPPException) cause; + } + } + + // throw generic IO exception if unexpected exception was thrown + throw new IOException("Error while connection to SOCKS5 proxy"); + } + + } + + /** + * Initializes the connection to the SOCKS5 proxy by negotiating authentication method and + * requesting a stream for the given digest. Currently only the no-authentication method is + * supported by the Socks5Client. + * <p> + * Returns <code>true</code> if a stream could be established, otherwise <code>false</code>. If + * <code>false</code> is returned the given Socket should be closed. + * + * @param socket connected to a SOCKS5 proxy + * @return <code>true</code> if if a stream could be established, otherwise <code>false</code>. + * If <code>false</code> is returned the given Socket should be closed. + * @throws IOException if a network error occurred + */ + protected boolean establish(Socket socket) throws IOException { + + /* + * use DataInputStream/DataOutpuStream to assure read and write is completed in a single + * statement + */ + DataInputStream in = new DataInputStream(socket.getInputStream()); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + + // authentication negotiation + byte[] cmd = new byte[3]; + + cmd[0] = (byte) 0x05; // protocol version 5 + cmd[1] = (byte) 0x01; // number of authentication methods supported + cmd[2] = (byte) 0x00; // authentication method: no-authentication required + + out.write(cmd); + out.flush(); + + byte[] response = new byte[2]; + in.readFully(response); + + // check if server responded with correct version and no-authentication method + if (response[0] != (byte) 0x05 || response[1] != (byte) 0x00) { + return false; + } + + // request SOCKS5 connection with given address/digest + byte[] connectionRequest = createSocks5ConnectRequest(); + out.write(connectionRequest); + out.flush(); + + // receive response + byte[] connectionResponse; + try { + connectionResponse = Socks5Utils.receiveSocks5Message(in); + } + catch (XMPPException e) { + return false; // server answered in an unsupported way + } + + // verify response + connectionRequest[1] = (byte) 0x00; // set expected return status to 0 + return Arrays.equals(connectionRequest, connectionResponse); + } + + /** + * Returns a SOCKS5 connection request message. It contains the command "connect", the address + * type "domain" and the digest as address. + * <p> + * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>) + * + * @return SOCKS5 connection request message + */ + private byte[] createSocks5ConnectRequest() { + byte addr[] = this.digest.getBytes(); + + byte[] data = new byte[7 + addr.length]; + data[0] = (byte) 0x05; // version (SOCKS5) + data[1] = (byte) 0x01; // command (1 - connect) + data[2] = (byte) 0x00; // reserved byte (always 0) + data[3] = (byte) 0x03; // address type (3 - domain name) + data[4] = (byte) addr.length; // address length + System.arraycopy(addr, 0, data, 5, addr.length); // address + data[data.length - 2] = (byte) 0; // address port (2 bytes always 0) + data[data.length - 1] = (byte) 0; + + return data; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java new file mode 100644 index 0000000..0d90791 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5ClientForInitiator.java @@ -0,0 +1,117 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.TimeoutException; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.SyncPacketSend; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream.StreamHost; + +/** + * Implementation of a SOCKS5 client used on the initiators side. This is needed because connecting + * to the local SOCKS5 proxy differs form the regular way to connect to a SOCKS5 proxy. Additionally + * a remote SOCKS5 proxy has to be activated by the initiator before data can be transferred between + * the peers. + * + * @author Henning Staib + */ +class Socks5ClientForInitiator extends Socks5Client { + + /* the XMPP connection used to communicate with the SOCKS5 proxy */ + private Connection connection; + + /* the session ID used to activate SOCKS5 stream */ + private String sessionID; + + /* the target JID used to activate SOCKS5 stream */ + private String target; + + /** + * Creates a new SOCKS5 client for the initiators side. + * + * @param streamHost containing network settings of the SOCKS5 proxy + * @param digest identifying the SOCKS5 Bytestream + * @param connection the XMPP connection + * @param sessionID the session ID of the SOCKS5 Bytestream + * @param target the target JID of the SOCKS5 Bytestream + */ + public Socks5ClientForInitiator(StreamHost streamHost, String digest, Connection connection, + String sessionID, String target) { + super(streamHost, digest); + this.connection = connection; + this.sessionID = sessionID; + this.target = target; + } + + public Socket getSocket(int timeout) throws IOException, XMPPException, InterruptedException, + TimeoutException { + Socket socket = null; + + // check if stream host is the local SOCKS5 proxy + if (this.streamHost.getJID().equals(this.connection.getUser())) { + Socks5Proxy socks5Server = Socks5Proxy.getSocks5Proxy(); + socket = socks5Server.getSocket(this.digest); + if (socket == null) { + throw new XMPPException("target is not connected to SOCKS5 proxy"); + } + } + else { + socket = super.getSocket(timeout); + + try { + activate(); + } + catch (XMPPException e) { + socket.close(); + throw new XMPPException("activating SOCKS5 Bytestream failed", e); + } + + } + + return socket; + } + + /** + * Activates the SOCKS5 Bytestream by sending a XMPP SOCKS5 Bytestream activation packet to the + * SOCKS5 proxy. + */ + private void activate() throws XMPPException { + Bytestream activate = createStreamHostActivation(); + // if activation fails #getReply throws an exception + SyncPacketSend.getReply(this.connection, activate); + } + + /** + * Returns a SOCKS5 Bytestream activation packet. + * + * @return SOCKS5 Bytestream activation packet + */ + private Bytestream createStreamHostActivation() { + Bytestream activate = new Bytestream(this.sessionID); + activate.setMode(null); + activate.setType(IQ.Type.SET); + activate.setTo(this.streamHost.getJID()); + + activate.setToActivate(this.target); + + return activate; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java new file mode 100644 index 0000000..11ef7a9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Proxy.java @@ -0,0 +1,423 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.XMPPException; + +/** + * The Socks5Proxy class represents a local SOCKS5 proxy server. It can be enabled/disabled by + * setting the <code>localSocks5ProxyEnabled</code> flag in the <code>smack-config.xml</code> or by + * invoking {@link SmackConfiguration#setLocalSocks5ProxyEnabled(boolean)}. The proxy is enabled by + * default. + * <p> + * The port of the local SOCKS5 proxy can be configured by setting <code>localSocks5ProxyPort</code> + * in the <code>smack-config.xml</code> or by invoking + * {@link SmackConfiguration#setLocalSocks5ProxyPort(int)}. Default port is 7777. If you set the + * port to a negative value Smack tries to the absolute value and all following until it finds an + * open port. + * <p> + * If your application is running on a machine with multiple network interfaces or if you want to + * provide your public address in case you are behind a NAT router, invoke + * {@link #addLocalAddress(String)} or {@link #replaceLocalAddresses(List)} to modify the list of + * local network addresses used for outgoing SOCKS5 Bytestream requests. + * <p> + * The local SOCKS5 proxy server refuses all connections except the ones that are explicitly allowed + * in the process of establishing a SOCKS5 Bytestream ( + * {@link Socks5BytestreamManager#establishSession(String)}). + * <p> + * This Implementation has the following limitations: + * <ul> + * <li>only supports the no-authentication authentication method</li> + * <li>only supports the <code>connect</code> command and will not answer correctly to other + * commands</li> + * <li>only supports requests with the domain address type and will not correctly answer to requests + * with other address types</li> + * </ul> + * (see <a href="http://tools.ietf.org/html/rfc1928">RFC 1928</a>) + * + * @author Henning Staib + */ +public class Socks5Proxy { + + /* SOCKS5 proxy singleton */ + private static Socks5Proxy socks5Server; + + /* reusable implementation of a SOCKS5 proxy server process */ + private Socks5ServerProcess serverProcess; + + /* thread running the SOCKS5 server process */ + private Thread serverThread; + + /* server socket to accept SOCKS5 connections */ + private ServerSocket serverSocket; + + /* assigns a connection to a digest */ + private final Map<String, Socket> connectionMap = new ConcurrentHashMap<String, Socket>(); + + /* list of digests connections should be stored */ + private final List<String> allowedConnections = Collections.synchronizedList(new LinkedList<String>()); + + private final Set<String> localAddresses = Collections.synchronizedSet(new LinkedHashSet<String>()); + + /** + * Private constructor. + */ + private Socks5Proxy() { + this.serverProcess = new Socks5ServerProcess(); + + // add default local address + try { + this.localAddresses.add(InetAddress.getLocalHost().getHostAddress()); + } + catch (UnknownHostException e) { + // do nothing + } + + } + + /** + * Returns the local SOCKS5 proxy server. + * + * @return the local SOCKS5 proxy server + */ + public static synchronized Socks5Proxy getSocks5Proxy() { + if (socks5Server == null) { + socks5Server = new Socks5Proxy(); + } + if (SmackConfiguration.isLocalSocks5ProxyEnabled()) { + socks5Server.start(); + } + return socks5Server; + } + + /** + * Starts the local SOCKS5 proxy server. If it is already running, this method does nothing. + */ + public synchronized void start() { + if (isRunning()) { + return; + } + try { + if (SmackConfiguration.getLocalSocks5ProxyPort() < 0) { + int port = Math.abs(SmackConfiguration.getLocalSocks5ProxyPort()); + for (int i = 0; i < 65535 - port; i++) { + try { + this.serverSocket = new ServerSocket(port + i); + break; + } + catch (IOException e) { + // port is used, try next one + } + } + } + else { + this.serverSocket = new ServerSocket(SmackConfiguration.getLocalSocks5ProxyPort()); + } + + if (this.serverSocket != null) { + this.serverThread = new Thread(this.serverProcess); + this.serverThread.start(); + } + } + catch (IOException e) { + // couldn't setup server + System.err.println("couldn't setup local SOCKS5 proxy on port " + + SmackConfiguration.getLocalSocks5ProxyPort() + ": " + e.getMessage()); + } + } + + /** + * Stops the local SOCKS5 proxy server. If it is not running this method does nothing. + */ + public synchronized void stop() { + if (!isRunning()) { + return; + } + + try { + this.serverSocket.close(); + } + catch (IOException e) { + // do nothing + } + + if (this.serverThread != null && this.serverThread.isAlive()) { + try { + this.serverThread.interrupt(); + this.serverThread.join(); + } + catch (InterruptedException e) { + // do nothing + } + } + this.serverThread = null; + this.serverSocket = null; + + } + + /** + * Adds the given address to the list of local network addresses. + * <p> + * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request. + * This may be necessary if your application is running on a machine with multiple network + * interfaces or if you want to provide your public address in case you are behind a NAT router. + * <p> + * The order of the addresses used is determined by the order you add addresses. + * <p> + * Note that the list of addresses initially contains the address returned by + * <code>InetAddress.getLocalHost().getHostAddress()</code>. You can replace the list of + * addresses by invoking {@link #replaceLocalAddresses(List)}. + * + * @param address the local network address to add + */ + public void addLocalAddress(String address) { + if (address == null) { + throw new IllegalArgumentException("address may not be null"); + } + this.localAddresses.add(address); + } + + /** + * Removes the given address from the list of local network addresses. This address will then no + * longer be used of outgoing SOCKS5 Bytestream requests. + * + * @param address the local network address to remove + */ + public void removeLocalAddress(String address) { + this.localAddresses.remove(address); + } + + /** + * Returns an unmodifiable list of the local network addresses that will be used for streamhost + * candidates of outgoing SOCKS5 Bytestream requests. + * + * @return unmodifiable list of the local network addresses + */ + public List<String> getLocalAddresses() { + return Collections.unmodifiableList(new ArrayList<String>(this.localAddresses)); + } + + /** + * Replaces the list of local network addresses. + * <p> + * Use this method if you want to provide multiple addresses in a SOCKS5 Bytestream request and + * want to define their order. This may be necessary if your application is running on a machine + * with multiple network interfaces or if you want to provide your public address in case you + * are behind a NAT router. + * + * @param addresses the new list of local network addresses + */ + public void replaceLocalAddresses(List<String> addresses) { + if (addresses == null) { + throw new IllegalArgumentException("list must not be null"); + } + this.localAddresses.clear(); + this.localAddresses.addAll(addresses); + + } + + /** + * Returns the port of the local SOCKS5 proxy server. If it is not running -1 will be returned. + * + * @return the port of the local SOCKS5 proxy server or -1 if proxy is not running + */ + public int getPort() { + if (!isRunning()) { + return -1; + } + return this.serverSocket.getLocalPort(); + } + + /** + * Returns the socket for the given digest. A socket will be returned if the given digest has + * been in the list of allowed transfers (see {@link #addTransfer(String)}) while the peer + * connected to the SOCKS5 proxy. + * + * @param digest identifying the connection + * @return socket or null if there is no socket for the given digest + */ + protected Socket getSocket(String digest) { + return this.connectionMap.get(digest); + } + + /** + * Add the given digest to the list of allowed transfers. Only connections for allowed transfers + * are stored and can be retrieved by invoking {@link #getSocket(String)}. All connections to + * the local SOCKS5 proxy that don't contain an allowed digest are discarded. + * + * @param digest to be added to the list of allowed transfers + */ + protected void addTransfer(String digest) { + this.allowedConnections.add(digest); + } + + /** + * Removes the given digest from the list of allowed transfers. After invoking this method + * already stored connections with the given digest will be removed. + * <p> + * The digest should be removed after establishing the SOCKS5 Bytestream is finished, an error + * occurred while establishing the connection or if the connection is not allowed anymore. + * + * @param digest to be removed from the list of allowed transfers + */ + protected void removeTransfer(String digest) { + this.allowedConnections.remove(digest); + this.connectionMap.remove(digest); + } + + /** + * Returns <code>true</code> if the local SOCKS5 proxy server is running, otherwise + * <code>false</code>. + * + * @return <code>true</code> if the local SOCKS5 proxy server is running, otherwise + * <code>false</code> + */ + public boolean isRunning() { + return this.serverSocket != null; + } + + /** + * Implementation of a simplified SOCKS5 proxy server. + */ + private class Socks5ServerProcess implements Runnable { + + public void run() { + while (true) { + Socket socket = null; + + try { + + if (Socks5Proxy.this.serverSocket.isClosed() + || Thread.currentThread().isInterrupted()) { + return; + } + + // accept connection + socket = Socks5Proxy.this.serverSocket.accept(); + + // initialize connection + establishConnection(socket); + + } + catch (SocketException e) { + /* + * do nothing, if caused by closing the server socket, thread will terminate in + * next loop + */ + } + catch (Exception e) { + try { + if (socket != null) { + socket.close(); + } + } + catch (IOException e1) { + /* do nothing */ + } + } + } + + } + + /** + * Negotiates a SOCKS5 connection and stores it on success. + * + * @param socket connection to the client + * @throws XMPPException if client requests a connection in an unsupported way + * @throws IOException if a network error occurred + */ + private void establishConnection(Socket socket) throws XMPPException, IOException { + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + DataInputStream in = new DataInputStream(socket.getInputStream()); + + // first byte is version should be 5 + int b = in.read(); + if (b != 5) { + throw new XMPPException("Only SOCKS5 supported"); + } + + // second byte number of authentication methods supported + b = in.read(); + + // read list of supported authentication methods + byte[] auth = new byte[b]; + in.readFully(auth); + + byte[] authMethodSelectionResponse = new byte[2]; + authMethodSelectionResponse[0] = (byte) 0x05; // protocol version + + // only authentication method 0, no authentication, supported + boolean noAuthMethodFound = false; + for (int i = 0; i < auth.length; i++) { + if (auth[i] == (byte) 0x00) { + noAuthMethodFound = true; + break; + } + } + + if (!noAuthMethodFound) { + authMethodSelectionResponse[1] = (byte) 0xFF; // no acceptable methods + out.write(authMethodSelectionResponse); + out.flush(); + throw new XMPPException("Authentication method not supported"); + } + + authMethodSelectionResponse[1] = (byte) 0x00; // no-authentication method + out.write(authMethodSelectionResponse); + out.flush(); + + // receive connection request + byte[] connectionRequest = Socks5Utils.receiveSocks5Message(in); + + // extract digest + String responseDigest = new String(connectionRequest, 5, connectionRequest[4]); + + // return error if digest is not allowed + if (!Socks5Proxy.this.allowedConnections.contains(responseDigest)) { + connectionRequest[1] = (byte) 0x05; // set return status to 5 (connection refused) + out.write(connectionRequest); + out.flush(); + + throw new XMPPException("Connection is not allowed"); + } + + connectionRequest[1] = (byte) 0x00; // set return status to 0 (success) + out.write(connectionRequest); + out.flush(); + + // store connection + Socks5Proxy.this.connectionMap.put(responseDigest, socket); + } + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java new file mode 100644 index 0000000..9c92563 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/Socks5Utils.java @@ -0,0 +1,73 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5; + +import java.io.DataInputStream; +import java.io.IOException; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.util.StringUtils; + +/** + * A collection of utility methods for SOcKS5 messages. + * + * @author Henning Staib + */ +class Socks5Utils { + + /** + * Returns a SHA-1 digest of the given parameters as specified in <a + * href="http://xmpp.org/extensions/xep-0065.html#impl-socks5">XEP-0065</a>. + * + * @param sessionID for the SOCKS5 Bytestream + * @param initiatorJID JID of the initiator of a SOCKS5 Bytestream + * @param targetJID JID of the target of a SOCKS5 Bytestream + * @return SHA-1 digest of the given parameters + */ + public static String createDigest(String sessionID, String initiatorJID, String targetJID) { + StringBuilder b = new StringBuilder(); + b.append(sessionID).append(initiatorJID).append(targetJID); + return StringUtils.hash(b.toString()); + } + + /** + * Reads a SOCKS5 message from the given InputStream. The message can either be a SOCKS5 request + * message or a SOCKS5 response message. + * <p> + * (see <a href="http://tools.ietf.org/html/rfc1928">RFC1928</a>) + * + * @param in the DataInputStream to read the message from + * @return the SOCKS5 message + * @throws IOException if a network error occurred + * @throws XMPPException if the SOCKS5 message contains an unsupported address type + */ + public static byte[] receiveSocks5Message(DataInputStream in) throws IOException, XMPPException { + byte[] header = new byte[5]; + in.readFully(header, 0, 5); + + if (header[3] != (byte) 0x03) { + throw new XMPPException("Unsupported SOCKS5 address type"); + } + + int addressLength = header[4]; + + byte[] response = new byte[7 + addressLength]; + System.arraycopy(header, 0, response, 0, header.length); + + in.readFully(response, header.length, addressLength + 2); + + return response; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java new file mode 100644 index 0000000..9e07fc3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/packet/Bytestream.java @@ -0,0 +1,474 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5.packet; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * A packet representing part of a SOCKS5 Bytestream negotiation. + * + * @author Alexander Wenckus + */ +public class Bytestream extends IQ { + + private String sessionID; + + private Mode mode = Mode.tcp; + + private final List<StreamHost> streamHosts = new ArrayList<StreamHost>(); + + private StreamHostUsed usedHost; + + private Activate toActivate; + + /** + * The default constructor + */ + public Bytestream() { + super(); + } + + /** + * A constructor where the session ID can be specified. + * + * @param SID The session ID related to the negotiation. + * @see #setSessionID(String) + */ + public Bytestream(final String SID) { + super(); + setSessionID(SID); + } + + /** + * Set the session ID related to the bytestream. The session ID is a unique identifier used to + * differentiate between stream negotiations. + * + * @param sessionID the unique session ID that identifies the transfer. + */ + public void setSessionID(final String sessionID) { + this.sessionID = sessionID; + } + + /** + * Returns the session ID related to the bytestream negotiation. + * + * @return Returns the session ID related to the bytestream negotiation. + * @see #setSessionID(String) + */ + public String getSessionID() { + return sessionID; + } + + /** + * Set the transport mode. This should be put in the initiation of the interaction. + * + * @param mode the transport mode, either UDP or TCP + * @see Mode + */ + public void setMode(final Mode mode) { + this.mode = mode; + } + + /** + * Returns the transport mode. + * + * @return Returns the transport mode. + * @see #setMode(Mode) + */ + public Mode getMode() { + return mode; + } + + /** + * Adds a potential stream host that the remote user can connect to to receive the file. + * + * @param JID The JID of the stream host. + * @param address The internet address of the stream host. + * @return The added stream host. + */ + public StreamHost addStreamHost(final String JID, final String address) { + return addStreamHost(JID, address, 0); + } + + /** + * Adds a potential stream host that the remote user can connect to to receive the file. + * + * @param JID The JID of the stream host. + * @param address The internet address of the stream host. + * @param port The port on which the remote host is seeking connections. + * @return The added stream host. + */ + public StreamHost addStreamHost(final String JID, final String address, final int port) { + StreamHost host = new StreamHost(JID, address); + host.setPort(port); + addStreamHost(host); + + return host; + } + + /** + * Adds a potential stream host that the remote user can transfer the file through. + * + * @param host The potential stream host. + */ + public void addStreamHost(final StreamHost host) { + streamHosts.add(host); + } + + /** + * Returns the list of stream hosts contained in the packet. + * + * @return Returns the list of stream hosts contained in the packet. + */ + public Collection<StreamHost> getStreamHosts() { + return Collections.unmodifiableCollection(streamHosts); + } + + /** + * Returns the stream host related to the given JID, or null if there is none. + * + * @param JID The JID of the desired stream host. + * @return Returns the stream host related to the given JID, or null if there is none. + */ + public StreamHost getStreamHost(final String JID) { + if (JID == null) { + return null; + } + for (StreamHost host : streamHosts) { + if (host.getJID().equals(JID)) { + return host; + } + } + + return null; + } + + /** + * Returns the count of stream hosts contained in this packet. + * + * @return Returns the count of stream hosts contained in this packet. + */ + public int countStreamHosts() { + return streamHosts.size(); + } + + /** + * Upon connecting to the stream host the target of the stream replies to the initiator with the + * JID of the SOCKS5 host that they used. + * + * @param JID The JID of the used host. + */ + public void setUsedHost(final String JID) { + this.usedHost = new StreamHostUsed(JID); + } + + /** + * Returns the SOCKS5 host connected to by the remote user. + * + * @return Returns the SOCKS5 host connected to by the remote user. + */ + public StreamHostUsed getUsedHost() { + return usedHost; + } + + /** + * Returns the activate element of the packet sent to the proxy host to verify the identity of + * the initiator and match them to the appropriate stream. + * + * @return Returns the activate element of the packet sent to the proxy host to verify the + * identity of the initiator and match them to the appropriate stream. + */ + public Activate getToActivate() { + return toActivate; + } + + /** + * Upon the response from the target of the used host the activate packet is sent to the SOCKS5 + * proxy. The proxy will activate the stream or return an error after verifying the identity of + * the initiator, using the activate packet. + * + * @param targetID The JID of the target of the file transfer. + */ + public void setToActivate(final String targetID) { + this.toActivate = new Activate(targetID); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<query xmlns=\"http://jabber.org/protocol/bytestreams\""); + if (this.getType().equals(IQ.Type.SET)) { + if (getSessionID() != null) { + buf.append(" sid=\"").append(getSessionID()).append("\""); + } + if (getMode() != null) { + buf.append(" mode = \"").append(getMode()).append("\""); + } + buf.append(">"); + if (getToActivate() == null) { + for (StreamHost streamHost : getStreamHosts()) { + buf.append(streamHost.toXML()); + } + } + else { + buf.append(getToActivate().toXML()); + } + } + else if (this.getType().equals(IQ.Type.RESULT)) { + buf.append(">"); + if (getUsedHost() != null) { + buf.append(getUsedHost().toXML()); + } + // A result from the server can also contain stream hosts + else if (countStreamHosts() > 0) { + for (StreamHost host : streamHosts) { + buf.append(host.toXML()); + } + } + } + else if (this.getType().equals(IQ.Type.GET)) { + return buf.append("/>").toString(); + } + else { + return null; + } + buf.append("</query>"); + + return buf.toString(); + } + + /** + * Packet extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts + * are forwarded to the target of the file transfer who then chooses and connects to one. + * + * @author Alexander Wenckus + */ + public static class StreamHost implements PacketExtension { + + public static String NAMESPACE = ""; + + public static String ELEMENTNAME = "streamhost"; + + private final String JID; + + private final String addy; + + private int port = 0; + + /** + * Default constructor. + * + * @param JID The JID of the stream host. + * @param address The internet address of the stream host. + */ + public StreamHost(final String JID, final String address) { + this.JID = JID; + this.addy = address; + } + + /** + * Returns the JID of the stream host. + * + * @return Returns the JID of the stream host. + */ + public String getJID() { + return JID; + } + + /** + * Returns the internet address of the stream host. + * + * @return Returns the internet address of the stream host. + */ + public String getAddress() { + return addy; + } + + /** + * Sets the port of the stream host. + * + * @param port The port on which the potential stream host would accept the connection. + */ + public void setPort(final int port) { + this.port = port; + } + + /** + * Returns the port on which the potential stream host would accept the connection. + * + * @return Returns the port on which the potential stream host would accept the connection. + */ + public int getPort() { + return port; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getElementName() { + return ELEMENTNAME; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(getElementName()).append(" "); + buf.append("jid=\"").append(getJID()).append("\" "); + buf.append("host=\"").append(getAddress()).append("\" "); + if (getPort() != 0) { + buf.append("port=\"").append(getPort()).append("\""); + } + else { + buf.append("zeroconf=\"_jabber.bytestreams\""); + } + buf.append("/>"); + + return buf.toString(); + } + } + + /** + * After selected a SOCKS5 stream host and successfully connecting, the target of the file + * transfer returns a byte stream packet with the stream host used extension. + * + * @author Alexander Wenckus + */ + public static class StreamHostUsed implements PacketExtension { + + public String NAMESPACE = ""; + + public static String ELEMENTNAME = "streamhost-used"; + + private final String JID; + + /** + * Default constructor. + * + * @param JID The JID of the selected stream host. + */ + public StreamHostUsed(final String JID) { + this.JID = JID; + } + + /** + * Returns the JID of the selected stream host. + * + * @return Returns the JID of the selected stream host. + */ + public String getJID() { + return JID; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getElementName() { + return ELEMENTNAME; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" "); + buf.append("jid=\"").append(getJID()).append("\" "); + buf.append("/>"); + return buf.toString(); + } + } + + /** + * The packet sent by the stream initiator to the stream proxy to activate the connection. + * + * @author Alexander Wenckus + */ + public static class Activate implements PacketExtension { + + public String NAMESPACE = ""; + + public static String ELEMENTNAME = "activate"; + + private final String target; + + /** + * Default constructor specifying the target of the stream. + * + * @param target The target of the stream. + */ + public Activate(final String target) { + this.target = target; + } + + /** + * Returns the target of the activation. + * + * @return Returns the target of the activation. + */ + public String getTarget() { + return target; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getElementName() { + return ELEMENTNAME; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(">"); + buf.append(getTarget()); + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + } + + /** + * The stream can be either a TCP stream or a UDP stream. + * + * @author Alexander Wenckus + */ + public enum Mode { + + /** + * A TCP based stream. + */ + tcp, + + /** + * A UDP based stream. + */ + udp; + + public static Mode fromName(String name) { + Mode mode; + try { + mode = Mode.valueOf(name); + } + catch (Exception ex) { + mode = tcp; + } + + return mode; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java new file mode 100644 index 0000000..76f9b0c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/bytestreams/socks5/provider/BytestreamsProvider.java @@ -0,0 +1,82 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.bytestreams.socks5.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses a bytestream packet. + * + * @author Alexander Wenckus + */ +public class BytestreamsProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + boolean done = false; + + Bytestream toReturn = new Bytestream(); + + String id = parser.getAttributeValue("", "sid"); + String mode = parser.getAttributeValue("", "mode"); + + // streamhost + String JID = null; + String host = null; + String port = null; + + int eventType; + String elementName; + while (!done) { + eventType = parser.next(); + elementName = parser.getName(); + if (eventType == XmlPullParser.START_TAG) { + if (elementName.equals(Bytestream.StreamHost.ELEMENTNAME)) { + JID = parser.getAttributeValue("", "jid"); + host = parser.getAttributeValue("", "host"); + port = parser.getAttributeValue("", "port"); + } + else if (elementName.equals(Bytestream.StreamHostUsed.ELEMENTNAME)) { + toReturn.setUsedHost(parser.getAttributeValue("", "jid")); + } + else if (elementName.equals(Bytestream.Activate.ELEMENTNAME)) { + toReturn.setToActivate(parser.getAttributeValue("", "jid")); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (elementName.equals("streamhost")) { + if (port == null) { + toReturn.addStreamHost(JID, host); + } + else { + toReturn.addStreamHost(JID, host, Integer.parseInt(port)); + } + JID = null; + host = null; + port = null; + } + else if (elementName.equals("query")) { + done = true; + } + } + } + + toReturn.setMode((Bytestream.Mode.fromName(mode))); + toReturn.setSessionID(id); + return toReturn; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java new file mode 100755 index 0000000..c5d0963 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommand.java @@ -0,0 +1,450 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.commands; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.packet.AdHocCommandData; + +import java.util.List; + +/** + * An ad-hoc command is responsible for executing the provided service and + * storing the result of the execution. Each new request will create a new + * instance of the command, allowing information related to executions to be + * stored in it. For example suppose that a command that retrieves the list of + * users on a server is implemented. When the command is executed it gets that + * list and the result is stored as a form in the command instance, i.e. the + * <code>getForm</code> method retrieves a form with all the users. + * <p> + * Each command has a <tt>node</tt> that should be unique within a given JID. + * <p> + * Commands may have zero or more stages. Each stage is usually used for + * gathering information required for the command execution. Users are able to + * move forward or backward across the different stages. Commands may not be + * cancelled while they are being executed. However, users may request the + * "cancel" action when submitting a stage response indicating that the command + * execution should be aborted. Thus, releasing any collected information. + * Commands that require user interaction (i.e. have more than one stage) will + * have to provide the data forms the user must complete in each stage and the + * allowed actions the user might perform during each stage (e.g. go to the + * previous stage or go to the next stage). + * <p> + * All the actions may throw an XMPPException if there is a problem executing + * them. The <code>XMPPError</code> of that exception may have some specific + * information about the problem. The possible extensions are: + * + * <li><i>malformed-action</i>. Extension of a <i>bad-request</i> error.</li> + * <li><i>bad-action</i>. Extension of a <i>bad-request</i> error.</li> + * <li><i>bad-locale</i>. Extension of a <i>bad-request</i> error.</li> + * <li><i>bad-payload</i>. Extension of a <i>bad-request</i> error.</li> + * <li><i>bad-sessionid</i>. Extension of a <i>bad-request</i> error.</li> + * <li><i>session-expired</i>. Extension of a <i>not-allowed</i> error.</li> + * <p> + * See the <code>SpecificErrorCondition</code> class for detailed description + * of each one. + * <p> + * Use the <code>getSpecificErrorConditionFrom</code> to obtain the specific + * information from an <code>XMPPError</code>. + * + * @author Gabriel Guardincerri + * + */ +public abstract class AdHocCommand { + // TODO: Analyze the redesign of command by having an ExecutionResponse as a + // TODO: result to the execution of every action. That result should have all the + // TODO: information related to the execution, e.g. the form to fill. Maybe this + // TODO: design is more intuitive and simpler than the current one that has all in + // TODO: one class. + + private AdHocCommandData data; + + public AdHocCommand() { + super(); + data = new AdHocCommandData(); + } + + /** + * Returns the specific condition of the <code>error</code> or <tt>null</tt> if the + * error doesn't have any. + * + * @param error the error the get the specific condition from. + * @return the specific condition of this error, or null if it doesn't have + * any. + */ + public static SpecificErrorCondition getSpecificErrorCondition(XMPPError error) { + // This method is implemented to provide an easy way of getting a packet + // extension of the XMPPError. + for (SpecificErrorCondition condition : SpecificErrorCondition.values()) { + if (error.getExtension(condition.toString(), + AdHocCommandData.SpecificError.namespace) != null) { + return condition; + } + } + return null; + } + + /** + * Set the the human readable name of the command, usually used for + * displaying in a UI. + * + * @param name the name. + */ + public void setName(String name) { + data.setName(name); + } + + /** + * Returns the human readable name of the command. + * + * @return the human readable name of the command + */ + public String getName() { + return data.getName(); + } + + /** + * Sets the unique identifier of the command. This value must be unique for + * the <code>OwnerJID</code>. + * + * @param node the unique identifier of the command. + */ + public void setNode(String node) { + data.setNode(node); + } + + /** + * Returns the unique identifier of the command. It is unique for the + * <code>OwnerJID</code>. + * + * @return the unique identifier of the command. + */ + public String getNode() { + return data.getNode(); + } + + /** + * Returns the full JID of the owner of this command. This JID is the "to" of a + * execution request. + * + * @return the owner JID. + */ + public abstract String getOwnerJID(); + + /** + * Returns the notes that the command has at the current stage. + * + * @return a list of notes. + */ + public List<AdHocCommandNote> getNotes() { + return data.getNotes(); + } + + /** + * Adds a note to the current stage. This should be used when setting a + * response to the execution of an action. All the notes added here are + * returned by the {@link #getNotes} method during the current stage. + * Once the stage changes all the notes are discarded. + * + * @param note the note. + */ + protected void addNote(AdHocCommandNote note) { + data.addNote(note); + } + + public String getRaw() { + return data.getChildElementXML(); + } + + /** + * Returns the form of the current stage. Usually it is the form that must + * be answered to execute the next action. If that is the case it should be + * used by the requester to fill all the information that the executor needs + * to continue to the next stage. It can also be the result of the + * execution. + * + * @return the form of the current stage to fill out or the result of the + * execution. + */ + public Form getForm() { + if (data.getForm() == null) { + return null; + } + else { + return new Form(data.getForm()); + } + } + + /** + * Sets the form of the current stage. This should be used when setting a + * response. It could be a form to fill out the information needed to go to + * the next stage or the result of an execution. + * + * @param form the form of the current stage to fill out or the result of the + * execution. + */ + protected void setForm(Form form) { + data.setForm(form.getDataFormToSend()); + } + + /** + * Executes the command. This is invoked only on the first stage of the + * command. It is invoked on every command. If there is a problem executing + * the command it throws an XMPPException. + * + * @throws XMPPException if there is an error executing the command. + */ + public abstract void execute() throws XMPPException; + + /** + * Executes the next action of the command with the information provided in + * the <code>response</code>. This form must be the answer form of the + * previous stage. This method will be only invoked for commands that have one + * or more stages. If there is a problem executing the command it throws an + * XMPPException. + * + * @param response the form answer of the previous stage. + * @throws XMPPException if there is a problem executing the command. + */ + public abstract void next(Form response) throws XMPPException; + + /** + * Completes the command execution with the information provided in the + * <code>response</code>. This form must be the answer form of the + * previous stage. This method will be only invoked for commands that have one + * or more stages. If there is a problem executing the command it throws an + * XMPPException. + * + * @param response the form answer of the previous stage. + * @throws XMPPException if there is a problem executing the command. + */ + public abstract void complete(Form response) throws XMPPException; + + /** + * Goes to the previous stage. The requester is asking to re-send the + * information of the previous stage. The command must change it state to + * the previous one. If there is a problem executing the command it throws + * an XMPPException. + * + * @throws XMPPException if there is a problem executing the command. + */ + public abstract void prev() throws XMPPException; + + /** + * Cancels the execution of the command. This can be invoked on any stage of + * the execution. If there is a problem executing the command it throws an + * XMPPException. + * + * @throws XMPPException if there is a problem executing the command. + */ + public abstract void cancel() throws XMPPException; + + /** + * Returns a collection with the allowed actions based on the current stage. + * Possible actions are: {@link Action#prev prev}, {@link Action#next next} and + * {@link Action#complete complete}. This method will be only invoked for commands that + * have one or more stages. + * + * @return a collection with the allowed actions based on the current stage + * as defined in the SessionData. + */ + protected List<Action> getActions() { + return data.getActions(); + } + + /** + * Add an action to the current stage available actions. This should be used + * when creating a response. + * + * @param action the action. + */ + protected void addActionAvailable(Action action) { + data.addAction(action); + } + + /** + * Returns the action available for the current stage which is + * considered the equivalent to "execute". When the requester sends his + * reply, if no action was defined in the command then the action will be + * assumed "execute" thus assuming the action returned by this method. This + * method will never be invoked for commands that have no stages. + * + * @return the action available for the current stage which is considered + * the equivalent to "execute". + */ + protected Action getExecuteAction() { + return data.getExecuteAction(); + } + + /** + * Sets which of the actions available for the current stage is + * considered the equivalent to "execute". This should be used when setting + * a response. When the requester sends his reply, if no action was defined + * in the command then the action will be assumed "execute" thus assuming + * the action returned by this method. + * + * @param action the action. + */ + protected void setExecuteAction(Action action) { + data.setExecuteAction(action); + } + + /** + * Returns the status of the current stage. + * + * @return the current status. + */ + public Status getStatus() { + return data.getStatus(); + } + + /** + * Sets the data of the current stage. This should not used. + * + * @param data the data. + */ + void setData(AdHocCommandData data) { + this.data = data; + } + + /** + * Gets the data of the current stage. This should not used. + * + * @return the data. + */ + AdHocCommandData getData() { + return data; + } + + /** + * Returns true if the <code>action</code> is available in the current stage. + * The {@link Action#cancel cancel} action is always allowed. To define the + * available actions use the <code>addActionAvailable</code> method. + * + * @param action + * The action to check if it is available. + * @return True if the action is available for the current stage. + */ + protected boolean isValidAction(Action action) { + return getActions().contains(action) || Action.cancel.equals(action); + } + + /** + * The status of the stage in the adhoc command. + */ + public enum Status { + + /** + * The command is being executed. + */ + executing, + + /** + * The command has completed. The command session has ended. + */ + completed, + + /** + * The command has been canceled. The command session has ended. + */ + canceled + } + + public enum Action { + + /** + * The command should be executed or continue to be executed. This is + * the default value. + */ + execute, + + /** + * The command should be canceled. + */ + cancel, + + /** + * The command should be digress to the previous stage of execution. + */ + prev, + + /** + * The command should progress to the next stage of execution. + */ + next, + + /** + * The command should be completed (if possible). + */ + complete, + + /** + * The action is unknow. This is used when a recieved message has an + * unknown action. It must not be used to send an execution request. + */ + unknown + } + + public enum SpecificErrorCondition { + + /** + * The responding JID cannot accept the specified action. + */ + badAction("bad-action"), + + /** + * The responding JID does not understand the specified action. + */ + malformedAction("malformed-action"), + + /** + * The responding JID cannot accept the specified language/locale. + */ + badLocale("bad-locale"), + + /** + * The responding JID cannot accept the specified payload (e.g. the data + * form did not provide one or more required fields). + */ + badPayload("bad-payload"), + + /** + * The responding JID cannot accept the specified sessionid. + */ + badSessionid("bad-sessionid"), + + /** + * The requesting JID specified a sessionid that is no longer active + * (either because it was completed, canceled, or timed out). + */ + sessionExpired("session-expired"); + + private String value; + + SpecificErrorCondition(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java new file mode 100755 index 0000000..7496bf6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandManager.java @@ -0,0 +1,740 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2008 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.commands; + +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.NodeInformationProvider; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.commands.AdHocCommand.Action; +import org.jivesoftware.smackx.commands.AdHocCommand.Status; +import org.jivesoftware.smackx.packet.AdHocCommandData; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverInfo.Identity; +import org.jivesoftware.smackx.packet.DiscoverItems; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * An AdHocCommandManager is responsible for keeping the list of available + * commands offered by a service and for processing commands requests. + * + * Pass in a Connection instance to + * {@link #getAddHocCommandsManager(org.jivesoftware.smack.Connection)} in order to + * get an instance of this class. + * + * @author Gabriel Guardincerri + */ +public class AdHocCommandManager { + + private static final String DISCO_NAMESPACE = "http://jabber.org/protocol/commands"; + + private static final String discoNode = DISCO_NAMESPACE; + + /** + * The session time out in seconds. + */ + private static final int SESSION_TIMEOUT = 2 * 60; + + /** + * Map a Connection with it AdHocCommandManager. This map have a key-value + * pair for every active connection. + */ + private static Map<Connection, AdHocCommandManager> instances = + new ConcurrentHashMap<Connection, AdHocCommandManager>(); + + /** + * Register the listener for all the connection creations. When a new + * connection is created a new AdHocCommandManager is also created and + * related to that connection. + */ + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(Connection connection) { + new AdHocCommandManager(connection); + } + }); + } + + /** + * Returns the <code>AdHocCommandManager</code> related to the + * <code>connection</code>. + * + * @param connection the XMPP connection. + * @return the AdHocCommandManager associated with the connection. + */ + public static AdHocCommandManager getAddHocCommandsManager(Connection connection) { + return instances.get(connection); + } + + /** + * Thread that reaps stale sessions. + */ + private Thread sessionsSweeper; + + /** + * The Connection that this instances of AdHocCommandManager manages + */ + private Connection connection; + + /** + * Map a command node with its AdHocCommandInfo. Note: Key=command node, + * Value=command. Command node matches the node attribute sent by command + * requesters. + */ + private Map<String, AdHocCommandInfo> commands = new ConcurrentHashMap<String, AdHocCommandInfo>(); + + /** + * Map a command session ID with the instance LocalCommand. The LocalCommand + * is the an objects that has all the information of the current state of + * the command execution. Note: Key=session ID, Value=LocalCommand. Session + * ID matches the sessionid attribute sent by command responders. + */ + private Map<String, LocalCommand> executingCommands = new ConcurrentHashMap<String, LocalCommand>(); + + private AdHocCommandManager(Connection connection) { + super(); + this.connection = connection; + init(); + } + + /** + * Registers a new command with this command manager, which is related to a + * connection. The <tt>node</tt> is an unique identifier of that command for + * the connection related to this command manager. The <tt>name</tt> is the + * human readable name of the command. The <tt>class</tt> is the class of + * the command, which must extend {@link LocalCommand} and have a default + * constructor. + * + * @param node the unique identifier of the command. + * @param name the human readable name of the command. + * @param clazz the class of the command, which must extend {@link LocalCommand}. + */ + public void registerCommand(String node, String name, final Class clazz) { + registerCommand(node, name, new LocalCommandFactory() { + public LocalCommand getInstance() throws InstantiationException, IllegalAccessException { + return (LocalCommand)clazz.newInstance(); + } + }); + } + + /** + * Registers a new command with this command manager, which is related to a + * connection. The <tt>node</tt> is an unique identifier of that + * command for the connection related to this command manager. The <tt>name</tt> + * is the human readeale name of the command. The <tt>factory</tt> generates + * new instances of the command. + * + * @param node the unique identifier of the command. + * @param name the human readable name of the command. + * @param factory a factory to create new instances of the command. + */ + public void registerCommand(String node, final String name, LocalCommandFactory factory) { + AdHocCommandInfo commandInfo = new AdHocCommandInfo(node, name, connection.getUser(), factory); + + commands.put(node, commandInfo); + // Set the NodeInformationProvider that will provide information about + // the added command + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider(node, + new NodeInformationProvider() { + public List<DiscoverItems.Item> getNodeItems() { + return null; + } + + public List<String> getNodeFeatures() { + List<String> answer = new ArrayList<String>(); + answer.add(DISCO_NAMESPACE); + // TODO: check if this service is provided by the + // TODO: current connection. + answer.add("jabber:x:data"); + return answer; + } + + public List<DiscoverInfo.Identity> getNodeIdentities() { + List<DiscoverInfo.Identity> answer = new ArrayList<DiscoverInfo.Identity>(); + DiscoverInfo.Identity identity = new DiscoverInfo.Identity( + "automation", name); + identity.setType("command-node"); + answer.add(identity); + return answer; + } + + }); + } + + /** + * Discover the commands of an specific JID. The <code>jid</code> is a + * full JID. + * + * @param jid the full JID to retrieve the commands for. + * @return the discovered items. + * @throws XMPPException if the operation failed for some reason. + */ + public DiscoverItems discoverCommands(String jid) throws XMPPException { + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager + .getInstanceFor(connection); + return serviceDiscoveryManager.discoverItems(jid, discoNode); + } + + /** + * Publish the commands to an specific JID. + * + * @param jid the full JID to publish the commands to. + * @throws XMPPException if the operation failed for some reason. + */ + public void publishCommands(String jid) throws XMPPException { + ServiceDiscoveryManager serviceDiscoveryManager = ServiceDiscoveryManager + .getInstanceFor(connection); + + // Collects the commands to publish as items + DiscoverItems discoverItems = new DiscoverItems(); + Collection<AdHocCommandInfo> xCommandsList = getRegisteredCommands(); + + for (AdHocCommandInfo info : xCommandsList) { + DiscoverItems.Item item = new DiscoverItems.Item(info.getOwnerJID()); + item.setName(info.getName()); + item.setNode(info.getNode()); + discoverItems.addItem(item); + } + + serviceDiscoveryManager.publishItems(jid, discoNode, discoverItems); + } + + /** + * Returns a command that represents an instance of a command in a remote + * host. It is used to execute remote commands. The concept is similar to + * RMI. Every invocation on this command is equivalent to an invocation in + * the remote command. + * + * @param jid the full JID of the host of the remote command + * @param node the identifier of the command + * @return a local instance equivalent to the remote command. + */ + public RemoteCommand getRemoteCommand(String jid, String node) { + return new RemoteCommand(connection, node, jid); + } + + /** + * <ul> + * <li>Adds listeners to the connection</li> + * <li>Registers the ad-hoc command feature to the ServiceDiscoveryManager</li> + * <li>Registers the items of the feature</li> + * <li>Adds packet listeners to handle execution requests</li> + * <li>Creates and start the session sweeper</li> + * </ul> + */ + private void init() { + // Register the new instance and associate it with the connection + instances.put(connection, this); + + // Add a listener to the connection that removes the registered instance + // when the connection is closed + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + + public void connectionClosedOnError(Exception e) { + // Unregister this instance since the connection has been closed + instances.remove(connection); + } + + public void reconnectionSuccessful() { + // Register this instance since the connection has been + // reestablished + instances.put(connection, AdHocCommandManager.this); + } + + public void reconnectingIn(int seconds) { + // Nothing to do + } + + public void reconnectionFailed(Exception e) { + // Nothing to do + } + }); + + // Add the feature to the service discovery manage to show that this + // connection supports the AdHoc-Commands protocol. + // This information will be used when another client tries to + // discover whether this client supports AdHoc-Commands or not. + ServiceDiscoveryManager.getInstanceFor(connection).addFeature( + DISCO_NAMESPACE); + + // Set the NodeInformationProvider that will provide information about + // which AdHoc-Commands are registered, whenever a disco request is + // received + ServiceDiscoveryManager.getInstanceFor(connection) + .setNodeInformationProvider(discoNode, + new NodeInformationProvider() { + public List<DiscoverItems.Item> getNodeItems() { + + List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); + Collection<AdHocCommandInfo> commandsList = getRegisteredCommands(); + + for (AdHocCommandInfo info : commandsList) { + DiscoverItems.Item item = new DiscoverItems.Item( + info.getOwnerJID()); + item.setName(info.getName()); + item.setNode(info.getNode()); + answer.add(item); + } + + return answer; + } + + public List<String> getNodeFeatures() { + return null; + } + + public List<Identity> getNodeIdentities() { + return null; + } + }); + + // The packet listener and the filter for processing some AdHoc Commands + // Packets + PacketListener listener = new PacketListener() { + public void processPacket(Packet packet) { + AdHocCommandData requestData = (AdHocCommandData) packet; + processAdHocCommand(requestData); + } + }; + + PacketFilter filter = new PacketTypeFilter(AdHocCommandData.class); + connection.addPacketListener(listener, filter); + + sessionsSweeper = null; + } + + /** + * Process the AdHoc-Command packet that request the execution of some + * action of a command. If this is the first request, this method checks, + * before executing the command, if: + * <ul> + * <li>The requested command exists</li> + * <li>The requester has permissions to execute it</li> + * <li>The command has more than one stage, if so, it saves the command and + * session ID for further use</li> + * </ul> + * + * <br> + * <br> + * If this is not the first request, this method checks, before executing + * the command, if: + * <ul> + * <li>The session ID of the request was stored</li> + * <li>The session life do not exceed the time out</li> + * <li>The action to execute is one of the available actions</li> + * </ul> + * + * @param requestData + * the packet to process. + */ + private void processAdHocCommand(AdHocCommandData requestData) { + // Only process requests of type SET + if (requestData.getType() != IQ.Type.SET) { + return; + } + + // Creates the response with the corresponding data + AdHocCommandData response = new AdHocCommandData(); + response.setTo(requestData.getFrom()); + response.setPacketID(requestData.getPacketID()); + response.setNode(requestData.getNode()); + response.setId(requestData.getTo()); + + String sessionId = requestData.getSessionID(); + String commandNode = requestData.getNode(); + + if (sessionId == null) { + // A new execution request has been received. Check that the + // command exists + if (!commands.containsKey(commandNode)) { + // Requested command does not exist so return + // item_not_found error. + respondError(response, XMPPError.Condition.item_not_found); + return; + } + + // Create new session ID + sessionId = StringUtils.randomString(15); + + try { + // Create a new instance of the command with the + // corresponding sessioid + LocalCommand command = newInstanceOfCmd(commandNode, sessionId); + + response.setType(IQ.Type.RESULT); + command.setData(response); + + // Check that the requester has enough permission. + // Answer forbidden error if requester permissions are not + // enough to execute the requested command + if (!command.hasPermission(requestData.getFrom())) { + respondError(response, XMPPError.Condition.forbidden); + return; + } + + Action action = requestData.getAction(); + + // If the action is unknown then respond an error. + if (action != null && action.equals(Action.unknown)) { + respondError(response, XMPPError.Condition.bad_request, + AdHocCommand.SpecificErrorCondition.malformedAction); + return; + } + + // If the action is not execute, then it is an invalid action. + if (action != null && !action.equals(Action.execute)) { + respondError(response, XMPPError.Condition.bad_request, + AdHocCommand.SpecificErrorCondition.badAction); + return; + } + + // Increase the state number, so the command knows in witch + // stage it is + command.incrementStage(); + // Executes the command + command.execute(); + + if (command.isLastStage()) { + // If there is only one stage then the command is completed + response.setStatus(Status.completed); + } + else { + // Else it is still executing, and is registered to be + // available for the next call + response.setStatus(Status.executing); + executingCommands.put(sessionId, command); + // See if the session reaping thread is started. If not, start it. + if (sessionsSweeper == null) { + sessionsSweeper = new Thread(new Runnable() { + public void run() { + while (true) { + for (String sessionId : executingCommands.keySet()) { + LocalCommand command = executingCommands.get(sessionId); + // Since the command could be removed in the meanwhile + // of getting the key and getting the value - by a + // processed packet. We must check if it still in the + // map. + if (command != null) { + long creationStamp = command.getCreationDate(); + // Check if the Session data has expired (default is + // 10 minutes) + // To remove it from the session list it waits for + // the double of the of time out time. This is to + // let + // the requester know why his execution request is + // not accepted. If the session is removed just + // after the time out, then whe the user request to + // continue the execution he will recieved an + // invalid session error and not a time out error. + if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000 * 2) { + // Remove the expired session + executingCommands.remove(sessionId); + } + } + } + try { + Thread.sleep(1000); + } + catch (InterruptedException ie) { + // Ignore. + } + } + } + + }); + sessionsSweeper.setDaemon(true); + sessionsSweeper.start(); + } + } + + // Sends the response packet + connection.sendPacket(response); + + } + catch (XMPPException e) { + // If there is an exception caused by the next, complete, + // prev or cancel method, then that error is returned to the + // requester. + XMPPError error = e.getXMPPError(); + + // If the error type is cancel, then the execution is + // canceled therefore the status must show that, and the + // command be removed from the executing list. + if (XMPPError.Type.CANCEL.equals(error.getType())) { + response.setStatus(Status.canceled); + executingCommands.remove(sessionId); + } + respondError(response, error); + e.printStackTrace(); + } + } + else { + LocalCommand command = executingCommands.get(sessionId); + + // Check that a command exists for the specified sessionID + // This also handles if the command was removed in the meanwhile + // of getting the key and the value of the map. + if (command == null) { + respondError(response, XMPPError.Condition.bad_request, + AdHocCommand.SpecificErrorCondition.badSessionid); + return; + } + + // Check if the Session data has expired (default is 10 minutes) + long creationStamp = command.getCreationDate(); + if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) { + // Remove the expired session + executingCommands.remove(sessionId); + + // Answer a not_allowed error (session-expired) + respondError(response, XMPPError.Condition.not_allowed, + AdHocCommand.SpecificErrorCondition.sessionExpired); + return; + } + + /* + * Since the requester could send two requests for the same + * executing command i.e. the same session id, all the execution of + * the action must be synchronized to avoid inconsistencies. + */ + synchronized (command) { + Action action = requestData.getAction(); + + // If the action is unknown the respond an error + if (action != null && action.equals(Action.unknown)) { + respondError(response, XMPPError.Condition.bad_request, + AdHocCommand.SpecificErrorCondition.malformedAction); + return; + } + + // If the user didn't specify an action or specify the execute + // action then follow the actual default execute action + if (action == null || Action.execute.equals(action)) { + action = command.getExecuteAction(); + } + + // Check that the specified action was previously + // offered + if (!command.isValidAction(action)) { + respondError(response, XMPPError.Condition.bad_request, + AdHocCommand.SpecificErrorCondition.badAction); + return; + } + + try { + // TODO: Check that all the requierd fields of the form are + // TODO: filled, if not throw an exception. This will simplify the + // TODO: construction of new commands + + // Since all errors were passed, the response is now a + // result + response.setType(IQ.Type.RESULT); + + // Set the new data to the command. + command.setData(response); + + if (Action.next.equals(action)) { + command.incrementStage(); + command.next(new Form(requestData.getForm())); + if (command.isLastStage()) { + // If it is the last stage then the command is + // completed + response.setStatus(Status.completed); + } + else { + // Otherwise it is still executing + response.setStatus(Status.executing); + } + } + else if (Action.complete.equals(action)) { + command.incrementStage(); + command.complete(new Form(requestData.getForm())); + response.setStatus(Status.completed); + // Remove the completed session + executingCommands.remove(sessionId); + } + else if (Action.prev.equals(action)) { + command.decrementStage(); + command.prev(); + } + else if (Action.cancel.equals(action)) { + command.cancel(); + response.setStatus(Status.canceled); + // Remove the canceled session + executingCommands.remove(sessionId); + } + + connection.sendPacket(response); + } + catch (XMPPException e) { + // If there is an exception caused by the next, complete, + // prev or cancel method, then that error is returned to the + // requester. + XMPPError error = e.getXMPPError(); + + // If the error type is cancel, then the execution is + // canceled therefore the status must show that, and the + // command be removed from the executing list. + if (XMPPError.Type.CANCEL.equals(error.getType())) { + response.setStatus(Status.canceled); + executingCommands.remove(sessionId); + } + respondError(response, error); + + e.printStackTrace(); + } + } + } + } + + /** + * Responds an error with an specific condition. + * + * @param response the response to send. + * @param condition the condition of the error. + */ + private void respondError(AdHocCommandData response, + XMPPError.Condition condition) { + respondError(response, new XMPPError(condition)); + } + + /** + * Responds an error with an specific condition. + * + * @param response the response to send. + * @param condition the condition of the error. + * @param specificCondition the adhoc command error condition. + */ + private void respondError(AdHocCommandData response, XMPPError.Condition condition, + AdHocCommand.SpecificErrorCondition specificCondition) + { + XMPPError error = new XMPPError(condition); + error.addExtension(new AdHocCommandData.SpecificError(specificCondition)); + respondError(response, error); + } + + /** + * Responds an error with an specific error. + * + * @param response the response to send. + * @param error the error to send. + */ + private void respondError(AdHocCommandData response, XMPPError error) { + response.setType(IQ.Type.ERROR); + response.setError(error); + connection.sendPacket(response); + } + + /** + * Creates a new instance of a command to be used by a new execution request + * + * @param commandNode the command node that identifies it. + * @param sessionID the session id of this execution. + * @return the command instance to execute. + * @throws XMPPException if there is problem creating the new instance. + */ + private LocalCommand newInstanceOfCmd(String commandNode, String sessionID) + throws XMPPException + { + AdHocCommandInfo commandInfo = commands.get(commandNode); + LocalCommand command; + try { + command = (LocalCommand) commandInfo.getCommandInstance(); + command.setSessionID(sessionID); + command.setName(commandInfo.getName()); + command.setNode(commandInfo.getNode()); + } + catch (InstantiationException e) { + e.printStackTrace(); + throw new XMPPException(new XMPPError( + XMPPError.Condition.interna_server_error)); + } + catch (IllegalAccessException e) { + e.printStackTrace(); + throw new XMPPException(new XMPPError( + XMPPError.Condition.interna_server_error)); + } + return command; + } + + /** + * Returns the registered commands of this command manager, which is related + * to a connection. + * + * @return the registered commands. + */ + private Collection<AdHocCommandInfo> getRegisteredCommands() { + return commands.values(); + } + + /** + * Stores ad-hoc command information. + */ + private static class AdHocCommandInfo { + + private String node; + private String name; + private String ownerJID; + private LocalCommandFactory factory; + + public AdHocCommandInfo(String node, String name, String ownerJID, + LocalCommandFactory factory) + { + this.node = node; + this.name = name; + this.ownerJID = ownerJID; + this.factory = factory; + } + + public LocalCommand getCommandInstance() throws InstantiationException, + IllegalAccessException + { + return factory.getInstance(); + } + + public String getName() { + return name; + } + + public String getNode() { + return node; + } + + public String getOwnerJID() { + return ownerJID; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java new file mode 100755 index 0000000..d3f487f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/AdHocCommandNote.java @@ -0,0 +1,86 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.commands; + +/** + * Notes can be added to a command execution response. A note has a type and value. + * + * @author Gabriel Guardincerri + */ +public class AdHocCommandNote { + + private Type type; + private String value; + + /** + * Creates a new adhoc command note with the specified type and value. + * + * @param type the type of the note. + * @param value the value of the note. + */ + public AdHocCommandNote(Type type, String value) { + this.type = type; + this.value = value; + } + + /** + * Returns the value or message of the note. + * + * @return the value or message of the note. + */ + public String getValue() { + return value; + } + + /** + * Return the type of the note. + * + * @return the type of the note. + */ + public Type getType() { + return type; + } + + /** + * Represents a note type. + */ + public enum Type { + + /** + * The note is informational only. This is not really an exceptional + * condition. + */ + info, + + /** + * The note indicates a warning. Possibly due to illogical (yet valid) + * data. + */ + warn, + + /** + * The note indicates an error. The text should indicate the reason for + * the error. + */ + error + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java new file mode 100755 index 0000000..36d5154 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommand.java @@ -0,0 +1,169 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.commands; + +import org.jivesoftware.smackx.packet.AdHocCommandData; + +/** + * Represents a command that can be executed locally from a remote location. This + * class must be extended to implement an specific ad-hoc command. This class + * provides some useful tools:<ul> + * <li>Node</li> + * <li>Name</li> + * <li>Session ID</li> + * <li>Current Stage</li> + * <li>Available actions</li> + * <li>Default action</li> + * </ul><p/> + * To implement a new command extend this class and implement all the abstract + * methods. When implementing the actions remember that they could be invoked + * several times, and that you must use the current stage number to know what to + * do. + * + * @author Gabriel Guardincerri + */ +public abstract class LocalCommand extends AdHocCommand { + + /** + * The time stamp of first invokation of the command. Used to implement the session timeout. + */ + private long creationDate; + + /** + * The unique ID of the execution of the command. + */ + private String sessionID; + + /** + * The full JID of the host of the command. + */ + private String ownerJID; + + /** + * The number of the current stage. + */ + private int currenStage; + + public LocalCommand() { + super(); + this.creationDate = System.currentTimeMillis(); + currenStage = -1; + } + + /** + * The sessionID is an unique identifier of an execution request. This is + * automatically handled and should not be called. + * + * @param sessionID the unique session id of this execution + */ + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + getData().setSessionID(sessionID); + } + + /** + * Returns the session ID of this execution. + * + * @return the unique session id of this execution + */ + public String getSessionID() { + return sessionID; + } + + /** + * Sets the JID of the command host. This is automatically handled and should + * not be called. + * + * @param ownerJID the JID of the owner. + */ + public void setOwnerJID(String ownerJID) { + this.ownerJID = ownerJID; + } + + @Override + public String getOwnerJID() { + return ownerJID; + } + + /** + * Returns the date the command was created. + * + * @return the date the command was created. + */ + public long getCreationDate() { + return creationDate; + } + + /** + * Returns true if the current stage is the last one. If it is then the + * execution of some action will complete the execution of the command. + * Commands that don't have multiple stages can always return <tt>true</tt>. + * + * @return true if the command is in the last stage. + */ + public abstract boolean isLastStage(); + + /** + * Returns true if the specified requester has permission to execute all the + * stages of this action. This is checked when the first request is received, + * if the permission is grant then the requester will be able to execute + * all the stages of the command. It is not checked again during the + * execution. + * + * @param jid the JID to check permissions on. + * @return true if the user has permission to execute this action. + */ + public abstract boolean hasPermission(String jid); + + /** + * Returns the currently executing stage number. The first stage number is + * 0. During the execution of the first action this method will answer 0. + * + * @return the current stage number. + */ + public int getCurrentStage() { + return currenStage; + } + + @Override + void setData(AdHocCommandData data) { + data.setSessionID(sessionID); + super.setData(data); + } + + /** + * Increase the current stage number. This is automatically handled and should + * not be called. + * + */ + void incrementStage() { + currenStage++; + } + + /** + * Decrease the current stage number. This is automatically handled and should + * not be called. + * + */ + void decrementStage() { + currenStage--; + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java new file mode 100644 index 0000000..11efd2b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/LocalCommandFactory.java @@ -0,0 +1,24 @@ +package org.jivesoftware.smackx.commands; + +/** + * A factory for creating local commands. It's useful in cases where instantiation + * of a command is more complicated than just using the default constructor. For example, + * when arguments must be passed into the constructor or when using a dependency injection + * framework. When a LocalCommandFactory isn't used, you can provide the AdHocCommandManager + * a Class object instead. For more details, see + * {@link AdHocCommandManager#registerCommand(String, String, LocalCommandFactory)}. + * + * @author Matt Tucker + */ +public interface LocalCommandFactory { + + /** + * Returns an instance of a LocalCommand. + * + * @return a LocalCommand instance. + * @throws InstantiationException if creating an instance failed. + * @throws IllegalAccessException if creating an instance is not allowed. + */ + public LocalCommand getInstance() throws InstantiationException, IllegalAccessException; + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java new file mode 100755 index 0000000..a32d267 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/commands/RemoteCommand.java @@ -0,0 +1,201 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.commands; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.packet.AdHocCommandData; + +/** + * Represents a command that is in a remote location. Invoking one of the + * {@link AdHocCommand.Action#execute execute}, {@link AdHocCommand.Action#next next}, + * {@link AdHocCommand.Action#prev prev}, {@link AdHocCommand.Action#cancel cancel} or + * {@link AdHocCommand.Action#complete complete} actions results in executing that + * action in the remote location. In response to that action the internal state + * of the this command instance will change. For example, if the command is a + * single stage command, then invoking the execute action will execute this + * action in the remote location. After that the local instance will have a + * state of "completed" and a form or notes that applies. + * + * @author Gabriel Guardincerri + * + */ +public class RemoteCommand extends AdHocCommand { + + /** + * The connection that is used to execute this command + */ + private Connection connection; + + /** + * The full JID of the command host + */ + private String jid; + + /** + * The session ID of this execution. + */ + private String sessionID; + + + /** + * The number of milliseconds to wait for a response from the server + * The default value is the default packet reply timeout (5000 ms). + */ + private long packetReplyTimeout; + + /** + * Creates a new RemoteCommand that uses an specific connection to execute a + * command identified by <code>node</code> in the host identified by + * <code>jid</code> + * + * @param connection the connection to use for the execution. + * @param node the identifier of the command. + * @param jid the JID of the host. + */ + protected RemoteCommand(Connection connection, String node, String jid) { + super(); + this.connection = connection; + this.jid = jid; + this.setNode(node); + this.packetReplyTimeout = SmackConfiguration.getPacketReplyTimeout(); + } + + @Override + public void cancel() throws XMPPException { + executeAction(Action.cancel, packetReplyTimeout); + } + + @Override + public void complete(Form form) throws XMPPException { + executeAction(Action.complete, form, packetReplyTimeout); + } + + @Override + public void execute() throws XMPPException { + executeAction(Action.execute, packetReplyTimeout); + } + + /** + * Executes the default action of the command with the information provided + * in the Form. This form must be the anwser form of the previous stage. If + * there is a problem executing the command it throws an XMPPException. + * + * @param form the form anwser of the previous stage. + * @throws XMPPException if an error occurs. + */ + public void execute(Form form) throws XMPPException { + executeAction(Action.execute, form, packetReplyTimeout); + } + + @Override + public void next(Form form) throws XMPPException { + executeAction(Action.next, form, packetReplyTimeout); + } + + @Override + public void prev() throws XMPPException { + executeAction(Action.prev, packetReplyTimeout); + } + + private void executeAction(Action action, long packetReplyTimeout) throws XMPPException { + executeAction(action, null, packetReplyTimeout); + } + + /** + * Executes the <code>action</codo> with the <code>form</code>. + * The action could be any of the available actions. The form must + * be the anwser of the previous stage. It can be <tt>null</tt> if it is the first stage. + * + * @param action the action to execute. + * @param form the form with the information. + * @param timeout the amount of time to wait for a reply. + * @throws XMPPException if there is a problem executing the command. + */ + private void executeAction(Action action, Form form, long timeout) throws XMPPException { + // TODO: Check that all the required fields of the form were filled, if + // TODO: not throw the corresponding exeption. This will make a faster response, + // TODO: since the request is stoped before it's sent. + AdHocCommandData data = new AdHocCommandData(); + data.setType(IQ.Type.SET); + data.setTo(getOwnerJID()); + data.setNode(getNode()); + data.setSessionID(sessionID); + data.setAction(action); + + if (form != null) { + data.setForm(form.getDataFormToSend()); + } + + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(data.getPacketID())); + + connection.sendPacket(data); + + Packet response = collector.nextResult(timeout); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + + AdHocCommandData responseData = (AdHocCommandData) response; + this.sessionID = responseData.getSessionID(); + super.setData(responseData); + } + + @Override + public String getOwnerJID() { + return jid; + } + + /** + * Returns the number of milliseconds to wait for a respone. The + * {@link SmackConfiguration#getPacketReplyTimeout default} value + * should be adjusted for commands that can take a long time to execute. + * + * @return the number of milliseconds to wait for responses. + */ + public long getPacketReplyTimeout() { + return packetReplyTimeout; + } + + /** + * Returns the number of milliseconds to wait for a respone. The + * {@link SmackConfiguration#getPacketReplyTimeout default} value + * should be adjusted for commands that can take a long time to execute. + * + * @param packetReplyTimeout the number of milliseconds to wait for responses. + */ + public void setPacketReplyTimeout(long packetReplyTimeout) { + this.packetReplyTimeout = packetReplyTimeout; + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html new file mode 100644 index 0000000..8ea20e0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/debugger/package.html @@ -0,0 +1 @@ +<body>Smack optional Debuggers.</body> \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java new file mode 100644 index 0000000..a29de33 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsPacketListener.java @@ -0,0 +1,24 @@ +package org.jivesoftware.smackx.entitycaps; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.CapsExtension; + +class CapsPacketListener implements PacketListener { + + private EntityCapsManager manager; + + protected CapsPacketListener(EntityCapsManager manager) { + this.manager = manager; + } + + public void processPacket(Packet packet) { + CapsExtension ext = + (CapsExtension) packet.getExtension(CapsExtension.NODE_NAME, CapsExtension.XMLNS); + + String nodeVer = ext.getNode() + "#" + ext.getVersion(); + String user = packet.getFrom(); + + manager.addUserCapsNode(user, nodeVer); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java new file mode 100644 index 0000000..2a72dbb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/CapsVerListener.java @@ -0,0 +1,5 @@ +package org.jivesoftware.smackx.entitycaps; + +public interface CapsVerListener { + public void capsVerUpdated(String capsVer); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java new file mode 100644 index 0000000..036c7c7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsManager.java @@ -0,0 +1,326 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * Copyright 2011 Florian Schmaus + * + * All rights reserved. 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 org.jivesoftware.smackx.entitycaps; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.Base64; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.ServiceDiscoveryManagerInterface; +import org.jivesoftware.smackx.provider.CapsExtensionProvider; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.CapsExtension; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo.Feature; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArraySet; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +/** + * Keeps track of entity capabilities. + */ +public class EntityCapsManager { + + public static final String HASH_METHOD = "sha-1"; + public static final String HASH_METHOD_CAPS = "SHA-1"; + + // TODO entityNode should become a constant (final) + private static String entityNode = "http://www.igniterealtime.org/projects/smack/"; + private static EntityCapsPersistentCache persistentCache; + + /** + * Map of (node, hash algorithm) -> DiscoverInfo. + */ + private static Map<String,DiscoverInfo> caps = + new ConcurrentHashMap<String,DiscoverInfo>(); + + /** + * Map of Full JID -> DiscoverInfo/null. + * In case of c2s connection the key is formed as user@server/resource (resource is required) + * In case of link-local connection the key is formed as user@host (no resource) + * In case of a server or component the key is formed as domain + */ + private Map<String,String> userCaps = + new ConcurrentHashMap<String,String>(); + + // CapsVerListeners gets notified when the version string is changed. + private Set<CapsVerListener> capsVerListeners = + new CopyOnWriteArraySet<CapsVerListener>(); + + private String currentCapsVersion = null; + + static { + ProviderManager.getInstance().addExtensionProvider(CapsExtension.NODE_NAME, + CapsExtension.XMLNS, new CapsExtensionProvider()); + } + + /** + * Add DiscoverInfo to the database. + * + * @param node The node name. Could be for example "http://psi-im.org#q07IKJEyjvHSyhy//CH0CxmKi8w=". + * @param info DiscoverInfo for the specified node. + */ + public static void addDiscoverInfoByNode(String node, DiscoverInfo info) { + cleanupDicsoverInfo(info); + + caps.put(node, info); + + if (persistentCache != null) + persistentCache.addDiscoverInfoByNodePersistent(node, info); + } + + public EntityCapsManager(ServiceDiscoveryManagerInterface sdm) { + // Add Entity Capabilities (XEP-0115) feature node. + sdm.addFeature("http://jabber.org/protocol/caps"); + } + + /** + * Add a record telling what entity caps node a user has. The entity caps + * node has the format node#ver. + * + * @param user the user (Full JID) + * @param node the entity caps node#ver + */ + public void addUserCapsNode(String user, String node) { + if (user != null && node != null) { + userCaps.put(user, node); + } + } + + /** + * Remove a record telling what entity caps node a user has. + * + * @param user the user (Full JID) + */ + public void removeUserCapsNode(String user) { + userCaps.remove(user); + } + + /** + * Get the Node version (node#ver) of a user. + * + * @param user the user (Full JID) + * @return the node version. + */ + public String getNodeVersionByUser(String user) { + return userCaps.get(user); + } + + /** + * Get the discover info given a user name. The discover + * info is returned if the user has a node#ver associated with + * it and the node#ver has a discover info associated with it. + * + * @param user user name (Full JID) + * @return the discovered info + */ + public DiscoverInfo getDiscoverInfoByUser(String user) { + String capsNode = userCaps.get(user); + if (capsNode == null) + return null; + + return getDiscoverInfoByNode(capsNode); + } + + /** + * Get our own caps version. + * + * @return our own caps version + */ + public String getCapsVersion() { + return currentCapsVersion; + } + + /** + * Get our own entity node. + * + * @return our own entity node. + */ + public String getNode() { + return entityNode; + } + + /** + * Set our own entity node. + * + * @param node the new node + */ + public void setNode(String node) { + entityNode = node; + } + + /** + * Retrieve DiscoverInfo for a specific node. + * + * @param node The node name. + * @return The corresponding DiscoverInfo or null if none is known. + */ + public static DiscoverInfo getDiscoverInfoByNode(String node) { + return caps.get(node); + } + + private static void cleanupDicsoverInfo(DiscoverInfo info) { + info.setFrom(null); + info.setTo(null); + info.setPacketID(null); + } + + public void addPacketListener(Connection connection) { + PacketFilter f = + new AndFilter( + new PacketTypeFilter(Presence.class), + new PacketExtensionFilter(CapsExtension.NODE_NAME, CapsExtension.XMLNS)); + connection.addPacketListener(new CapsPacketListener(this), f); + } + + public void addCapsVerListener(CapsVerListener listener) { + capsVerListeners.add(listener); + + if (currentCapsVersion != null) + listener.capsVerUpdated(currentCapsVersion); + } + + public void removeCapsVerListener(CapsVerListener listener) { + capsVerListeners.remove(listener); + } + + private void notifyCapsVerListeners() { + for (CapsVerListener listener : capsVerListeners) { + listener.capsVerUpdated(currentCapsVersion); + } + } + + /** + * Calculate Entity Caps version string + * + * @param capsString + * @return + */ + private static String capsToHash(String capsString) { + try { + MessageDigest md = MessageDigest.getInstance(HASH_METHOD_CAPS); + byte[] digest = md.digest(capsString.getBytes()); + return Base64.encodeBytes(digest); + } + catch (NoSuchAlgorithmException nsae) { + return null; + } + } + + private static String formFieldValuesToCaps(Iterator<String> i) { + String s = ""; + SortedSet<String> fvs = new TreeSet<String>(); + for (; i.hasNext();) { + fvs.add(i.next()); + } + for (String fv : fvs) { + s += fv + "<"; + } + return s; + } + + public void calculateEntityCapsVersion(DiscoverInfo discoverInfo, + String identityType, + String identityName, + DataForm extendedInfo) { + String s = ""; + + // Add identity + // FIXME language + s += "client/" + identityType + "//" + identityName + "<"; + + // Add features + SortedSet<String> features = new TreeSet<String>(); + for (Iterator<Feature> it = discoverInfo.getFeatures(); it.hasNext();) + features.add(it.next().getVar()); + + for (String f : features) { + s += f + "<"; + } + + if (extendedInfo != null) { + synchronized (extendedInfo) { + SortedSet<FormField> fs = new TreeSet<FormField>( + new Comparator<FormField>() { + public int compare(FormField f1, FormField f2) { + return f1.getVariable().compareTo(f2.getVariable()); + } + }); + + FormField ft = null; + + for (Iterator<FormField> i = extendedInfo.getFields(); i.hasNext();) { + FormField f = i.next(); + if (!f.getVariable().equals("FORM_TYPE")) { + fs.add(f); + } + else { + ft = f; + } + } + + // Add FORM_TYPE values + if (ft != null) { + s += formFieldValuesToCaps(ft.getValues()); + } + + // Add the other values + for (FormField f : fs) { + s += f.getVariable() + "<"; + s += formFieldValuesToCaps(f.getValues()); + } + } + } + + + setCurrentCapsVersion(discoverInfo, capsToHash(s)); + } + + /** + * Set our own caps version. + * + * @param capsVersion the new caps version + */ + public void setCurrentCapsVersion(DiscoverInfo discoverInfo, String capsVersion) { + currentCapsVersion = capsVersion; + addDiscoverInfoByNode(getNode() + "#" + capsVersion, discoverInfo); + notifyCapsVerListeners(); + } + + public static void setPersistentCache(EntityCapsPersistentCache cache) { + if (persistentCache != null) + throw new IllegalStateException("Entity Caps Persistent Cache was already set"); + persistentCache = cache; + persistentCache.replay(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java new file mode 100644 index 0000000..6dcca6f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/EntityCapsPersistentCache.java @@ -0,0 +1,23 @@ +package org.jivesoftware.smackx.entitycaps; + +import org.jivesoftware.smackx.packet.DiscoverInfo; + +public interface EntityCapsPersistentCache { + /** + * Add an DiscoverInfo to the persistent Cache + * + * @param node + * @param info + */ + abstract void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info); + + /** + * Replay the Caches data into EntityCapsManager + */ + abstract void replay(); + + /** + * Empty the Cache + */ + abstract void emptyCache(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java new file mode 100644 index 0000000..e0a559a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/entitycaps/SimpleDirectoryPersistentCache.java @@ -0,0 +1,166 @@ +/** + * Copyright 2011 Florian Schmaus + * + * 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 org.jivesoftware.smackx.entitycaps; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.Base64; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * Simples implementation of an EntityCapsPersistentCache that uses a + * directory to store the Caps information for every known node. Every node + * is represented by an file. + * + * @author Florian Schmaus + * + */ +public class SimpleDirectoryPersistentCache implements + EntityCapsPersistentCache { + + private File cacheDir; + + /** + * Creates a new SimpleDirectoryPersistentCache Object. Make sure that the + * cacheDir exists and that it's an directory. + * + * @param cacheDir + */ + public SimpleDirectoryPersistentCache(File cacheDir) { + if (!cacheDir.exists()) + throw new IllegalStateException("Cache directory \"" + cacheDir + "\" does not exist"); + if (!cacheDir.isDirectory()) + throw new IllegalStateException("Cache directory \"" + cacheDir + "\" is not a directory"); + + this.cacheDir = cacheDir; + } + + @Override + public void addDiscoverInfoByNodePersistent(String node, DiscoverInfo info) { + String base64Node = Base64.encodeBytes(node.getBytes()); + File nodeFile = new File(cacheDir, base64Node); + + try { + if (nodeFile.createNewFile()) + writeInfoToFile(nodeFile, info); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void replay() { + File[] files = cacheDir.listFiles(); + for (File f : files) { + String nodeBase64 = f.getName(); + String node = new String(Base64.decode(nodeBase64)); + DiscoverInfo info; + try { + info = restoreInfoFromFile(f); + } catch (IOException e) { + e.printStackTrace(); + continue; + } + if (info == null) + continue; + + EntityCapsManager.addDiscoverInfoByNode(node, info); + } + } + + public void emptyCache() { + File[] files = cacheDir.listFiles(); + for (File f : files) { + f.delete(); + } + } + + /** + * Writes the DiscoverInfo packet to an file + * + * @param file + * @param info + * @throws IOException + */ + private static void writeInfoToFile(File file, DiscoverInfo info) throws IOException { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(file)); + dos.writeUTF(info.toXML()); + } + + /** + * Tries to restore an DiscoverInfo packet from a file. + * + * @param file + * @return + * @throws IOException + */ + private static DiscoverInfo restoreInfoFromFile(File file) throws IOException { + DataInputStream dis = new DataInputStream(new FileInputStream(file)); + String fileContent = dis.readUTF(); + Reader reader = new StringReader(fileContent); + XmlPullParser parser; + try { + parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(reader); + } catch (XmlPullParserException xppe) { + xppe.printStackTrace(); + return null; + } + + DiscoverInfo iqPacket; + IQProvider provider = (IQProvider) ProviderManager.getInstance().getIQProvider("query", "http://jabber.org/protocol/disco#info"); + + // Skip the first <iq id=....> tag + try { + parser.next(); + } catch (XmlPullParserException e1) { + e1.printStackTrace(); + return null; + } + + // Point parser to the query tag + try { + parser.next(); + } catch (XmlPullParserException e1) { + e1.printStackTrace(); + return null; + } + + try { + iqPacket = (DiscoverInfo) provider.parseIQ(parser); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + return iqPacket; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java new file mode 100644 index 0000000..ef18606 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FaultTolerantNegotiator.java @@ -0,0 +1,186 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.packet.StreamInitiation; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.*; +import java.util.List; +import java.util.ArrayList; + + +/** + * The fault tolerant negotiator takes two stream negotiators, the primary and the secondary + * negotiator. If the primary negotiator fails during the stream negotiaton process, the second + * negotiator is used. + */ +public class FaultTolerantNegotiator extends StreamNegotiator { + + private StreamNegotiator primaryNegotiator; + private StreamNegotiator secondaryNegotiator; + private Connection connection; + private PacketFilter primaryFilter; + private PacketFilter secondaryFilter; + + public FaultTolerantNegotiator(Connection connection, StreamNegotiator primary, + StreamNegotiator secondary) { + this.primaryNegotiator = primary; + this.secondaryNegotiator = secondary; + this.connection = connection; + } + + public PacketFilter getInitiationPacketFilter(String from, String streamID) { + if (primaryFilter == null || secondaryFilter == null) { + primaryFilter = primaryNegotiator.getInitiationPacketFilter(from, streamID); + secondaryFilter = secondaryNegotiator.getInitiationPacketFilter(from, streamID); + } + return new OrFilter(primaryFilter, secondaryFilter); + } + + InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException { + throw new UnsupportedOperationException("Negotiation only handled by create incoming " + + "stream method."); + } + + final Packet initiateIncomingStream(Connection connection, StreamInitiation initiation) { + throw new UnsupportedOperationException("Initiation handled by createIncomingStream " + + "method"); + } + + public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException { + PacketCollector collector = connection.createPacketCollector( + getInitiationPacketFilter(initiation.getFrom(), initiation.getSessionID())); + + connection.sendPacket(super.createInitiationAccept(initiation, getNamespaces())); + + ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(2); + CompletionService<InputStream> service + = new ExecutorCompletionService<InputStream>(threadPoolExecutor); + List<Future<InputStream>> futures = new ArrayList<Future<InputStream>>(); + InputStream stream = null; + XMPPException exception = null; + try { + futures.add(service.submit(new NegotiatorService(collector))); + futures.add(service.submit(new NegotiatorService(collector))); + + int i = 0; + while (stream == null && i < futures.size()) { + Future<InputStream> future; + try { + i++; + future = service.poll(10, TimeUnit.SECONDS); + } + catch (InterruptedException e) { + continue; + } + + if (future == null) { + continue; + } + + try { + stream = future.get(); + } + catch (InterruptedException e) { + /* Do Nothing */ + } + catch (ExecutionException e) { + exception = new XMPPException(e.getCause()); + } + } + } + finally { + for (Future<InputStream> future : futures) { + future.cancel(true); + } + collector.cancel(); + threadPoolExecutor.shutdownNow(); + } + if (stream == null) { + if (exception != null) { + throw exception; + } + else { + throw new XMPPException("File transfer negotiation failed."); + } + } + + return stream; + } + + private StreamNegotiator determineNegotiator(Packet streamInitiation) { + return primaryFilter.accept(streamInitiation) ? primaryNegotiator : secondaryNegotiator; + } + + public OutputStream createOutgoingStream(String streamID, String initiator, String target) + throws XMPPException { + OutputStream stream; + try { + stream = primaryNegotiator.createOutgoingStream(streamID, initiator, target); + } + catch (XMPPException ex) { + stream = secondaryNegotiator.createOutgoingStream(streamID, initiator, target); + } + + return stream; + } + + public String[] getNamespaces() { + String[] primary = primaryNegotiator.getNamespaces(); + String[] secondary = secondaryNegotiator.getNamespaces(); + + String[] namespaces = new String[primary.length + secondary.length]; + System.arraycopy(primary, 0, namespaces, 0, primary.length); + System.arraycopy(secondary, 0, namespaces, primary.length, secondary.length); + + return namespaces; + } + + public void cleanup() { + } + + private class NegotiatorService implements Callable<InputStream> { + + private PacketCollector collector; + + NegotiatorService(PacketCollector collector) { + this.collector = collector; + } + + public InputStream call() throws Exception { + Packet streamInitiation = collector.nextResult( + SmackConfiguration.getPacketReplyTimeout() * 2); + if (streamInitiation == null) { + throw new XMPPException("No response from remote client"); + } + StreamNegotiator negotiator = determineNegotiator(streamInitiation); + return negotiator.negotiateIncomingStream(streamInitiation); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java new file mode 100644 index 0000000..f7c2ad9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransfer.java @@ -0,0 +1,380 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.XMPPException; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Contains the generic file information and progress related to a particular + * file transfer. + * + * @author Alexander Wenckus + * + */ +public abstract class FileTransfer { + + private String fileName; + + private String filePath; + + private long fileSize; + + private String peer; + + private Status status = Status.initial; + + private final Object statusMonitor = new Object(); + + protected FileTransferNegotiator negotiator; + + protected String streamID; + + protected long amountWritten = -1; + + private Error error; + + private Exception exception; + + /** + * Buffer size between input and output + */ + private static final int BUFFER_SIZE = 8192; + + protected FileTransfer(String peer, String streamID, + FileTransferNegotiator negotiator) { + this.peer = peer; + this.streamID = streamID; + this.negotiator = negotiator; + } + + protected void setFileInfo(String fileName, long fileSize) { + this.fileName = fileName; + this.fileSize = fileSize; + } + + protected void setFileInfo(String path, String fileName, long fileSize) { + this.filePath = path; + this.fileName = fileName; + this.fileSize = fileSize; + } + + /** + * Returns the size of the file being transfered. + * + * @return Returns the size of the file being transfered. + */ + public long getFileSize() { + return fileSize; + } + + /** + * Returns the name of the file being transfered. + * + * @return Returns the name of the file being transfered. + */ + public String getFileName() { + return fileName; + } + + /** + * Returns the local path of the file. + * + * @return Returns the local path of the file. + */ + public String getFilePath() { + return filePath; + } + + /** + * Returns the JID of the peer for this file transfer. + * + * @return Returns the JID of the peer for this file transfer. + */ + public String getPeer() { + return peer; + } + + /** + * Returns the progress of the file transfer as a number between 0 and 1. + * + * @return Returns the progress of the file transfer as a number between 0 + * and 1. + */ + public double getProgress() { + if (amountWritten <= 0 || fileSize <= 0) { + return 0; + } + return (double) amountWritten / (double) fileSize; + } + + /** + * Returns true if the transfer has been cancelled, if it has stopped because + * of a an error, or the transfer completed succesfully. + * + * @return Returns true if the transfer has been cancelled, if it has stopped + * because of a an error, or the transfer completed succesfully. + */ + public boolean isDone() { + return status == Status.cancelled || status == Status.error + || status == Status.complete || status == Status.refused; + } + + /** + * Retuns the current status of the file transfer. + * + * @return Retuns the current status of the file transfer. + */ + public Status getStatus() { + return status; + } + + protected void setError(Error type) { + this.error = type; + } + + /** + * When {@link #getStatus()} returns that there was an {@link Status#error} + * during the transfer, the type of error can be retrieved through this + * method. + * + * @return Returns the type of error that occured if one has occured. + */ + public Error getError() { + return error; + } + + /** + * If an exception occurs asynchronously it will be stored for later + * retrival. If there is an error there maybe an exception set. + * + * @return The exception that occured or null if there was no exception. + * @see #getError() + */ + public Exception getException() { + return exception; + } + + public String getStreamID() { + return streamID; + } + + /** + * Cancels the file transfer. + */ + public abstract void cancel(); + + protected void setException(Exception exception) { + this.exception = exception; + } + + protected void setStatus(Status status) { + synchronized (statusMonitor) { + this.status = status; + } + } + + protected boolean updateStatus(Status oldStatus, Status newStatus) { + synchronized (statusMonitor) { + if (oldStatus != status) { + return false; + } + status = newStatus; + return true; + } + } + + protected void writeToStream(final InputStream in, final OutputStream out) + throws XMPPException + { + final byte[] b = new byte[BUFFER_SIZE]; + int count = 0; + amountWritten = 0; + + do { + // write to the output stream + try { + out.write(b, 0, count); + } catch (IOException e) { + throw new XMPPException("error writing to output stream", e); + } + + amountWritten += count; + + // read more bytes from the input stream + try { + count = in.read(b); + } catch (IOException e) { + throw new XMPPException("error reading from input stream", e); + } + } while (count != -1 && !getStatus().equals(Status.cancelled)); + + // the connection was likely terminated abrubtly if these are not equal + if (!getStatus().equals(Status.cancelled) && getError() == Error.none + && amountWritten != fileSize) { + setStatus(Status.error); + this.error = Error.connection; + } + } + + /** + * A class to represent the current status of the file transfer. + * + * @author Alexander Wenckus + * + */ + public enum Status { + + /** + * An error occured during the transfer. + * + * @see FileTransfer#getError() + */ + error("Error"), + + /** + * The initial status of the file transfer. + */ + initial("Initial"), + + /** + * The file transfer is being negotiated with the peer. The party + * recieving the file has the option to accept or refuse a file transfer + * request. If they accept, then the process of stream negotiation will + * begin. If they refuse the file will not be transfered. + * + * @see #negotiating_stream + */ + negotiating_transfer("Negotiating Transfer"), + + /** + * The peer has refused the file transfer request halting the file + * transfer negotiation process. + */ + refused("Refused"), + + /** + * The stream to transfer the file is being negotiated over the chosen + * stream type. After the stream negotiating process is complete the + * status becomes negotiated. + * + * @see #negotiated + */ + negotiating_stream("Negotiating Stream"), + + /** + * After the stream negotitation has completed the intermediate state + * between the time when the negotiation is finished and the actual + * transfer begins. + */ + negotiated("Negotiated"), + + /** + * The transfer is in progress. + * + * @see FileTransfer#getProgress() + */ + in_progress("In Progress"), + + /** + * The transfer has completed successfully. + */ + complete("Complete"), + + /** + * The file transfer was canceled + */ + cancelled("Cancelled"); + + private String status; + + private Status(String status) { + this.status = status; + } + + public String toString() { + return status; + } + } + + /** + * Return the length of bytes written out to the stream. + * @return the amount in bytes written out. + */ + public long getAmountWritten(){ + return amountWritten; + } + + public enum Error { + /** + * No error + */ + none("No error"), + + /** + * The peer did not find any of the provided stream mechanisms + * acceptable. + */ + not_acceptable("The peer did not find any of the provided stream mechanisms acceptable."), + + /** + * The provided file to transfer does not exist or could not be read. + */ + bad_file("The provided file to transfer does not exist or could not be read."), + + /** + * The remote user did not respond or the connection timed out. + */ + no_response("The remote user did not respond or the connection timed out."), + + /** + * An error occured over the socket connected to send the file. + */ + connection("An error occured over the socket connected to send the file."), + + /** + * An error occured while sending or recieving the file + */ + stream("An error occured while sending or recieving the file."); + + private final String msg; + + private Error(String msg) { + this.msg = msg; + } + + /** + * Returns a String representation of this error. + * + * @return Returns a String representation of this error. + */ + public String getMessage() { + return msg; + } + + public String toString() { + return msg; + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java new file mode 100644 index 0000000..69b5c10 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferListener.java @@ -0,0 +1,36 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +/** + * File transfers can cause several events to be raised. These events can be + * monitored through this interface. + * + * @author Alexander Wenckus + */ +public interface FileTransferListener { + /** + * A request to send a file has been recieved from another user. + * + * @param request + * The request from the other user. + */ + public void fileTransferRequest(final FileTransferRequest request); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java new file mode 100644 index 0000000..25498a6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferManager.java @@ -0,0 +1,178 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.IQTypeFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.packet.StreamInitiation; + +import java.util.ArrayList; +import java.util.List; + +/** + * The file transfer manager class handles the sending and recieving of files. + * To send a file invoke the {@link #createOutgoingFileTransfer(String)} method. + * <p> + * And to recieve a file add a file transfer listener to the manager. The + * listener will notify you when there is a new file transfer request. To create + * the {@link IncomingFileTransfer} object accept the transfer, or, if the + * transfer is not desirable reject it. + * + * @author Alexander Wenckus + * + */ +public class FileTransferManager { + + private final FileTransferNegotiator fileTransferNegotiator; + + private List<FileTransferListener> listeners; + + private Connection connection; + + /** + * Creates a file transfer manager to initiate and receive file transfers. + * + * @param connection + * The Connection that the file transfers will use. + */ + public FileTransferManager(Connection connection) { + this.connection = connection; + this.fileTransferNegotiator = FileTransferNegotiator + .getInstanceFor(connection); + } + + /** + * Add a file transfer listener to listen to incoming file transfer + * requests. + * + * @param li + * The listener + * @see #removeFileTransferListener(FileTransferListener) + * @see FileTransferListener + */ + public void addFileTransferListener(final FileTransferListener li) { + if (listeners == null) { + initListeners(); + } + synchronized (this.listeners) { + listeners.add(li); + } + } + + private void initListeners() { + listeners = new ArrayList<FileTransferListener>(); + + connection.addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + fireNewRequest((StreamInitiation) packet); + } + }, new AndFilter(new PacketTypeFilter(StreamInitiation.class), + new IQTypeFilter(IQ.Type.SET))); + } + + protected void fireNewRequest(StreamInitiation initiation) { + FileTransferListener[] listeners = null; + synchronized (this.listeners) { + listeners = new FileTransferListener[this.listeners.size()]; + this.listeners.toArray(listeners); + } + FileTransferRequest request = new FileTransferRequest(this, initiation); + for (int i = 0; i < listeners.length; i++) { + listeners[i].fileTransferRequest(request); + } + } + + /** + * Removes a file transfer listener. + * + * @param li + * The file transfer listener to be removed + * @see FileTransferListener + */ + public void removeFileTransferListener(final FileTransferListener li) { + if (listeners == null) { + return; + } + synchronized (this.listeners) { + listeners.remove(li); + } + } + + /** + * Creates an OutgoingFileTransfer to send a file to another user. + * + * @param userID + * The fully qualified jabber ID with resource of the user to + * send the file to. + * @return The send file object on which the negotiated transfer can be run. + */ + public OutgoingFileTransfer createOutgoingFileTransfer(String userID) { +// Why is this only accepting fully qualified JID? +// if (userID == null || StringUtils.parseName(userID).length() <= 0 +// || StringUtils.parseServer(userID).length() <= 0 +// || StringUtils.parseResource(userID).length() <= 0) { +// throw new IllegalArgumentException( +// "The provided user id was not fully qualified"); +// } + + return new OutgoingFileTransfer(connection.getUser(), userID, + fileTransferNegotiator.getNextStreamID(), + fileTransferNegotiator); + } + + /** + * When the file transfer request is acceptable, this method should be + * invoked. It will create an IncomingFileTransfer which allows the + * transmission of the file to procede. + * + * @param request + * The remote request that is being accepted. + * @return The IncomingFileTransfer which manages the download of the file + * from the transfer initiator. + */ + protected IncomingFileTransfer createIncomingFileTransfer( + FileTransferRequest request) { + if (request == null) { + throw new NullPointerException("RecieveRequest cannot be null"); + } + + IncomingFileTransfer transfer = new IncomingFileTransfer(request, + fileTransferNegotiator); + transfer.setFileInfo(request.getFileName(), request.getFileSize()); + + return transfer; + } + + protected void rejectIncomingFileTransfer(FileTransferRequest request) { + StreamInitiation initiation = request.getStreamInitiation(); + + IQ rejection = FileTransferNegotiator.createIQ( + initiation.getPacketID(), initiation.getFrom(), initiation + .getTo(), IQ.Type.ERROR); + rejection.setError(new XMPPError(XMPPError.Condition.no_acceptable)); + connection.sendPacket(rejection); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java new file mode 100644 index 0000000..290d69c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java @@ -0,0 +1,485 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.StreamInitiation; + +/** + * Manages the negotiation of file transfers according to JEP-0096. If a file is + * being sent the remote user chooses the type of stream under which the file + * will be sent. + * + * @author Alexander Wenckus + * @see <a href="http://xmpp.org/extensions/xep-0096.html">XEP-0096: SI File Transfer</a> + */ +public class FileTransferNegotiator { + + // Static + + private static final String[] NAMESPACE = { + "http://jabber.org/protocol/si/profile/file-transfer", + "http://jabber.org/protocol/si"}; + + private static final Map<Connection, FileTransferNegotiator> transferObject = + new ConcurrentHashMap<Connection, FileTransferNegotiator>(); + + private static final String STREAM_INIT_PREFIX = "jsi_"; + + protected static final String STREAM_DATA_FIELD_NAME = "stream-method"; + + private static final Random randomGenerator = new Random(); + + /** + * A static variable to use only offer IBB for file transfer. It is generally recommend to only + * set this variable to true for testing purposes as IBB is the backup file transfer method + * and shouldn't be used as the only transfer method in production systems. + */ + public static boolean IBB_ONLY = (System.getProperty("ibb") != null);//true; + + /** + * Returns the file transfer negotiator related to a particular connection. + * When this class is requested on a particular connection the file transfer + * service is automatically enabled. + * + * @param connection The connection for which the transfer manager is desired + * @return The IMFileTransferManager + */ + public static FileTransferNegotiator getInstanceFor( + final Connection connection) { + if (connection == null) { + throw new IllegalArgumentException("Connection cannot be null"); + } + if (!connection.isConnected()) { + return null; + } + + if (transferObject.containsKey(connection)) { + return transferObject.get(connection); + } + else { + FileTransferNegotiator transfer = new FileTransferNegotiator( + connection); + setServiceEnabled(connection, true); + transferObject.put(connection, transfer); + return transfer; + } + } + + /** + * Enable the Jabber services related to file transfer on the particular + * connection. + * + * @param connection The connection on which to enable or disable the services. + * @param isEnabled True to enable, false to disable. + */ + public static void setServiceEnabled(final Connection connection, + final boolean isEnabled) { + ServiceDiscoveryManager manager = ServiceDiscoveryManager + .getInstanceFor(connection); + + List<String> namespaces = new ArrayList<String>(); + namespaces.addAll(Arrays.asList(NAMESPACE)); + namespaces.add(InBandBytestreamManager.NAMESPACE); + if (!IBB_ONLY) { + namespaces.add(Socks5BytestreamManager.NAMESPACE); + } + + for (String namespace : namespaces) { + if (isEnabled) { + if (!manager.includesFeature(namespace)) { + manager.addFeature(namespace); + } + } else { + manager.removeFeature(namespace); + } + } + + } + + /** + * Checks to see if all file transfer related services are enabled on the + * connection. + * + * @param connection The connection to check + * @return True if all related services are enabled, false if they are not. + */ + public static boolean isServiceEnabled(final Connection connection) { + ServiceDiscoveryManager manager = ServiceDiscoveryManager + .getInstanceFor(connection); + + List<String> namespaces = new ArrayList<String>(); + namespaces.addAll(Arrays.asList(NAMESPACE)); + namespaces.add(InBandBytestreamManager.NAMESPACE); + if (!IBB_ONLY) { + namespaces.add(Socks5BytestreamManager.NAMESPACE); + } + + for (String namespace : namespaces) { + if (!manager.includesFeature(namespace)) { + return false; + } + } + return true; + } + + /** + * A convenience method to create an IQ packet. + * + * @param ID The packet ID of the + * @param to To whom the packet is addressed. + * @param from From whom the packet is sent. + * @param type The IQ type of the packet. + * @return The created IQ packet. + */ + public static IQ createIQ(final String ID, final String to, + final String from, final IQ.Type type) { + IQ iqPacket = new IQ() { + public String getChildElementXML() { + return null; + } + }; + iqPacket.setPacketID(ID); + iqPacket.setTo(to); + iqPacket.setFrom(from); + iqPacket.setType(type); + + return iqPacket; + } + + /** + * Returns a collection of the supported transfer protocols. + * + * @return Returns a collection of the supported transfer protocols. + */ + public static Collection<String> getSupportedProtocols() { + List<String> protocols = new ArrayList<String>(); + protocols.add(InBandBytestreamManager.NAMESPACE); + if (!IBB_ONLY) { + protocols.add(Socks5BytestreamManager.NAMESPACE); + } + return Collections.unmodifiableList(protocols); + } + + // non-static + + private final Connection connection; + + private final StreamNegotiator byteStreamTransferManager; + + private final StreamNegotiator inbandTransferManager; + + private FileTransferNegotiator(final Connection connection) { + configureConnection(connection); + + this.connection = connection; + byteStreamTransferManager = new Socks5TransferNegotiator(connection); + inbandTransferManager = new IBBTransferNegotiator(connection); + } + + private void configureConnection(final Connection connection) { + connection.addConnectionListener(new ConnectionListener() { + public void connectionClosed() { + cleanup(connection); + } + + public void connectionClosedOnError(Exception e) { + cleanup(connection); + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + + public void reconnectingIn(int seconds) { + // ignore + } + }); + } + + private void cleanup(final Connection connection) { + if (transferObject.remove(connection) != null) { + inbandTransferManager.cleanup(); + } + } + + /** + * Selects an appropriate stream negotiator after examining the incoming file transfer request. + * + * @param request The related file transfer request. + * @return The file transfer object that handles the transfer + * @throws XMPPException If there are either no stream methods contained in the packet, or + * there is not an appropriate stream method. + */ + public StreamNegotiator selectStreamNegotiator( + FileTransferRequest request) throws XMPPException { + StreamInitiation si = request.getStreamInitiation(); + FormField streamMethodField = getStreamMethodField(si + .getFeatureNegotiationForm()); + + if (streamMethodField == null) { + String errorMessage = "No stream methods contained in packet."; + XMPPError error = new XMPPError(XMPPError.Condition.bad_request, errorMessage); + IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), + IQ.Type.ERROR); + iqPacket.setError(error); + connection.sendPacket(iqPacket); + throw new XMPPException(errorMessage, error); + } + + // select the appropriate protocol + + StreamNegotiator selectedStreamNegotiator; + try { + selectedStreamNegotiator = getNegotiator(streamMethodField); + } + catch (XMPPException e) { + IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), + IQ.Type.ERROR); + iqPacket.setError(e.getXMPPError()); + connection.sendPacket(iqPacket); + throw e; + } + + // return the appropriate negotiator + + return selectedStreamNegotiator; + } + + private FormField getStreamMethodField(DataForm form) { + FormField field = null; + for (Iterator<FormField> it = form.getFields(); it.hasNext();) { + field = it.next(); + if (field.getVariable().equals(STREAM_DATA_FIELD_NAME)) { + break; + } + field = null; + } + return field; + } + + private StreamNegotiator getNegotiator(final FormField field) + throws XMPPException { + String variable; + boolean isByteStream = false; + boolean isIBB = false; + for (Iterator<FormField.Option> it = field.getOptions(); it.hasNext();) { + variable = it.next().getValue(); + if (variable.equals(Socks5BytestreamManager.NAMESPACE) && !IBB_ONLY) { + isByteStream = true; + } + else if (variable.equals(InBandBytestreamManager.NAMESPACE)) { + isIBB = true; + } + } + + if (!isByteStream && !isIBB) { + XMPPError error = new XMPPError(XMPPError.Condition.bad_request, + "No acceptable transfer mechanism"); + throw new XMPPException(error.getMessage(), error); + } + + //if (isByteStream && isIBB && field.getType().equals(FormField.TYPE_LIST_MULTI)) { + if (isByteStream && isIBB) { + return new FaultTolerantNegotiator(connection, + byteStreamTransferManager, + inbandTransferManager); + } + else if (isByteStream) { + return byteStreamTransferManager; + } + else { + return inbandTransferManager; + } + } + + /** + * Reject a stream initiation request from a remote user. + * + * @param si The Stream Initiation request to reject. + */ + public void rejectStream(final StreamInitiation si) { + XMPPError error = new XMPPError(XMPPError.Condition.forbidden, "Offer Declined"); + IQ iqPacket = createIQ(si.getPacketID(), si.getFrom(), si.getTo(), + IQ.Type.ERROR); + iqPacket.setError(error); + connection.sendPacket(iqPacket); + } + + /** + * Returns a new, unique, stream ID to identify a file transfer. + * + * @return Returns a new, unique, stream ID to identify a file transfer. + */ + public String getNextStreamID() { + StringBuilder buffer = new StringBuilder(); + buffer.append(STREAM_INIT_PREFIX); + buffer.append(Math.abs(randomGenerator.nextLong())); + + return buffer.toString(); + } + + /** + * Send a request to another user to send them a file. The other user has + * the option of, accepting, rejecting, or not responding to a received file + * transfer request. + * <p/> + * If they accept, the packet will contain the other user's chosen stream + * type to send the file across. The two choices this implementation + * provides to the other user for file transfer are <a + * href="http://www.jabber.org/jeps/jep-0065.html">SOCKS5 Bytestreams</a>, + * which is the preferred method of transfer, and <a + * href="http://www.jabber.org/jeps/jep-0047.html">In-Band Bytestreams</a>, + * which is the fallback mechanism. + * <p/> + * The other user may choose to decline the file request if they do not + * desire the file, their client does not support JEP-0096, or if there are + * no acceptable means to transfer the file. + * <p/> + * Finally, if the other user does not respond this method will return null + * after the specified timeout. + * + * @param userID The userID of the user to whom the file will be sent. + * @param streamID The unique identifier for this file transfer. + * @param fileName The name of this file. Preferably it should include an + * extension as it is used to determine what type of file it is. + * @param size The size, in bytes, of the file. + * @param desc A description of the file. + * @param responseTimeout The amount of time, in milliseconds, to wait for the remote + * user to respond. If they do not respond in time, this + * @return Returns the stream negotiator selected by the peer. + * @throws XMPPException Thrown if there is an error negotiating the file transfer. + */ + public StreamNegotiator negotiateOutgoingTransfer(final String userID, + final String streamID, final String fileName, final long size, + final String desc, int responseTimeout) throws XMPPException { + StreamInitiation si = new StreamInitiation(); + si.setSesssionID(streamID); + si.setMimeType(URLConnection.guessContentTypeFromName(fileName)); + + StreamInitiation.File siFile = new StreamInitiation.File(fileName, size); + siFile.setDesc(desc); + si.setFile(siFile); + + si.setFeatureNegotiationForm(createDefaultInitiationForm()); + + si.setFrom(connection.getUser()); + si.setTo(userID); + si.setType(IQ.Type.SET); + + PacketCollector collector = connection + .createPacketCollector(new PacketIDFilter(si.getPacketID())); + connection.sendPacket(si); + Packet siResponse = collector.nextResult(responseTimeout); + collector.cancel(); + + if (siResponse instanceof IQ) { + IQ iqResponse = (IQ) siResponse; + if (iqResponse.getType().equals(IQ.Type.RESULT)) { + StreamInitiation response = (StreamInitiation) siResponse; + return getOutgoingNegotiator(getStreamMethodField(response + .getFeatureNegotiationForm())); + + } + else if (iqResponse.getType().equals(IQ.Type.ERROR)) { + throw new XMPPException(iqResponse.getError()); + } + else { + throw new XMPPException("File transfer response unreadable"); + } + } + else { + return null; + } + } + + private StreamNegotiator getOutgoingNegotiator(final FormField field) + throws XMPPException { + String variable; + boolean isByteStream = false; + boolean isIBB = false; + for (Iterator<String> it = field.getValues(); it.hasNext();) { + variable = it.next(); + if (variable.equals(Socks5BytestreamManager.NAMESPACE) && !IBB_ONLY) { + isByteStream = true; + } + else if (variable.equals(InBandBytestreamManager.NAMESPACE)) { + isIBB = true; + } + } + + if (!isByteStream && !isIBB) { + XMPPError error = new XMPPError(XMPPError.Condition.bad_request, + "No acceptable transfer mechanism"); + throw new XMPPException(error.getMessage(), error); + } + + if (isByteStream && isIBB) { + return new FaultTolerantNegotiator(connection, + byteStreamTransferManager, inbandTransferManager); + } + else if (isByteStream) { + return byteStreamTransferManager; + } + else { + return inbandTransferManager; + } + } + + private DataForm createDefaultInitiationForm() { + DataForm form = new DataForm(Form.TYPE_FORM); + FormField field = new FormField(STREAM_DATA_FIELD_NAME); + field.setType(FormField.TYPE_LIST_SINGLE); + if (!IBB_ONLY) { + field.addOption(new FormField.Option(Socks5BytestreamManager.NAMESPACE)); + } + field.addOption(new FormField.Option(InBandBytestreamManager.NAMESPACE)); + form.addField(field); + return form; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java new file mode 100644 index 0000000..d202dca --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/FileTransferRequest.java @@ -0,0 +1,138 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smackx.packet.StreamInitiation; + +/** + * A request to send a file recieved from another user. + * + * @author Alexander Wenckus + * + */ +public class FileTransferRequest { + private final StreamInitiation streamInitiation; + + private final FileTransferManager manager; + + /** + * A recieve request is constructed from the Stream Initiation request + * received from the initator. + * + * @param manager + * The manager handling this file transfer + * + * @param si + * The Stream initiaton recieved from the initiator. + */ + public FileTransferRequest(FileTransferManager manager, StreamInitiation si) { + this.streamInitiation = si; + this.manager = manager; + } + + /** + * Returns the name of the file. + * + * @return Returns the name of the file. + */ + public String getFileName() { + return streamInitiation.getFile().getName(); + } + + /** + * Returns the size in bytes of the file. + * + * @return Returns the size in bytes of the file. + */ + public long getFileSize() { + return streamInitiation.getFile().getSize(); + } + + /** + * Returns the description of the file provided by the requestor. + * + * @return Returns the description of the file provided by the requestor. + */ + public String getDescription() { + return streamInitiation.getFile().getDesc(); + } + + /** + * Returns the mime-type of the file. + * + * @return Returns the mime-type of the file. + */ + public String getMimeType() { + return streamInitiation.getMimeType(); + } + + /** + * Returns the fully-qualified jabber ID of the user that requested this + * file transfer. + * + * @return Returns the fully-qualified jabber ID of the user that requested + * this file transfer. + */ + public String getRequestor() { + return streamInitiation.getFrom(); + } + + /** + * Returns the stream ID that uniquely identifies this file transfer. + * + * @return Returns the stream ID that uniquely identifies this file + * transfer. + */ + public String getStreamID() { + return streamInitiation.getSessionID(); + } + + /** + * Returns the stream initiation packet that was sent by the requestor which + * contains the parameters of the file transfer being transfer and also the + * methods available to transfer the file. + * + * @return Returns the stream initiation packet that was sent by the + * requestor which contains the parameters of the file transfer + * being transfer and also the methods available to transfer the + * file. + */ + protected StreamInitiation getStreamInitiation() { + return streamInitiation; + } + + /** + * Accepts this file transfer and creates the incoming file transfer. + * + * @return Returns the <b><i>IncomingFileTransfer</b></i> on which the + * file transfer can be carried out. + */ + public IncomingFileTransfer accept() { + return manager.createIncomingFileTransfer(this); + } + + /** + * Rejects the file transfer request. + */ + public void reject() { + manager.rejectIncomingFileTransfer(this); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java new file mode 100644 index 0000000..df0f67e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IBBTransferNegotiator.java @@ -0,0 +1,152 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import java.io.InputStream; +import java.io.OutputStream; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromContainsFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamManager; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamRequest; +import org.jivesoftware.smackx.bytestreams.ibb.InBandBytestreamSession; +import org.jivesoftware.smackx.bytestreams.ibb.packet.Open; +import org.jivesoftware.smackx.packet.StreamInitiation; + +/** + * The In-Band Bytestream file transfer method, or IBB for short, transfers the + * file over the same XML Stream used by XMPP. It is the fall-back mechanism in + * case the SOCKS5 bytestream method of transferring files is not available. + * + * @author Alexander Wenckus + * @author Henning Staib + * @see <a href="http://xmpp.org/extensions/xep-0047.html">XEP-0047: In-Band + * Bytestreams (IBB)</a> + */ +public class IBBTransferNegotiator extends StreamNegotiator { + + private Connection connection; + + private InBandBytestreamManager manager; + + /** + * The default constructor for the In-Band Bytestream Negotiator. + * + * @param connection The connection which this negotiator works on. + */ + protected IBBTransferNegotiator(Connection connection) { + this.connection = connection; + this.manager = InBandBytestreamManager.getByteStreamManager(connection); + } + + public OutputStream createOutgoingStream(String streamID, String initiator, + String target) throws XMPPException { + InBandBytestreamSession session = this.manager.establishSession(target, streamID); + session.setCloseBothStreamsEnabled(true); + return session.getOutputStream(); + } + + public InputStream createIncomingStream(StreamInitiation initiation) + throws XMPPException { + /* + * In-Band Bytestream initiation listener must ignore next in-band + * bytestream request with given session ID + */ + this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); + + Packet streamInitiation = initiateIncomingStream(this.connection, initiation); + return negotiateIncomingStream(streamInitiation); + } + + public PacketFilter getInitiationPacketFilter(String from, String streamID) { + /* + * this method is always called prior to #negotiateIncomingStream() so + * the In-Band Bytestream initiation listener must ignore the next + * In-Band Bytestream request with the given session ID + */ + this.manager.ignoreBytestreamRequestOnce(streamID); + + return new AndFilter(new FromContainsFilter(from), new IBBOpenSidFilter(streamID)); + } + + public String[] getNamespaces() { + return new String[] { InBandBytestreamManager.NAMESPACE }; + } + + InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException { + // build In-Band Bytestream request + InBandBytestreamRequest request = new ByteStreamRequest(this.manager, + (Open) streamInitiation); + + // always accept the request + InBandBytestreamSession session = request.accept(); + session.setCloseBothStreamsEnabled(true); + return session.getInputStream(); + } + + public void cleanup() { + } + + /** + * This PacketFilter accepts an incoming In-Band Bytestream open request + * with a specified session ID. + */ + private static class IBBOpenSidFilter extends PacketTypeFilter { + + private String sessionID; + + public IBBOpenSidFilter(String sessionID) { + super(Open.class); + if (sessionID == null) { + throw new IllegalArgumentException("StreamID cannot be null"); + } + this.sessionID = sessionID; + } + + public boolean accept(Packet packet) { + if (super.accept(packet)) { + Open bytestream = (Open) packet; + + // packet must by of type SET and contains the given session ID + return this.sessionID.equals(bytestream.getSessionID()) + && IQ.Type.SET.equals(bytestream.getType()); + } + return false; + } + } + + /** + * Derive from InBandBytestreamRequest to access protected constructor. + */ + private static class ByteStreamRequest extends InBandBytestreamRequest { + + private ByteStreamRequest(InBandBytestreamManager manager, Open byteStreamRequest) { + super(manager, byteStreamRequest); + } + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java new file mode 100644 index 0000000..3973d44 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/IncomingFileTransfer.java @@ -0,0 +1,215 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.XMPPException; + +import java.io.*; +import java.util.concurrent.*; + +/** + * An incoming file transfer is created when the + * {@link FileTransferManager#createIncomingFileTransfer(FileTransferRequest)} + * method is invoked. It is a file being sent to the local user from another + * user on the jabber network. There are two stages of the file transfer to be + * concerned with and they can be handled in different ways depending upon the + * method that is invoked on this class. + * <p/> + * The first way that a file is recieved is by calling the + * {@link #recieveFile()} method. This method, negotiates the appropriate stream + * method and then returns the <b><i>InputStream</b></i> to read the file + * data from. + * <p/> + * The second way that a file can be recieved through this class is by invoking + * the {@link #recieveFile(File)} method. This method returns immediatly and + * takes as its parameter a file on the local file system where the file + * recieved from the transfer will be put. + * + * @author Alexander Wenckus + */ +public class IncomingFileTransfer extends FileTransfer { + + private FileTransferRequest recieveRequest; + + private InputStream inputStream; + + protected IncomingFileTransfer(FileTransferRequest request, + FileTransferNegotiator transferNegotiator) { + super(request.getRequestor(), request.getStreamID(), transferNegotiator); + this.recieveRequest = request; + } + + /** + * Negotiates the stream method to transfer the file over and then returns + * the negotiated stream. + * + * @return The negotiated InputStream from which to read the data. + * @throws XMPPException If there is an error in the negotiation process an exception + * is thrown. + */ + public InputStream recieveFile() throws XMPPException { + if (inputStream != null) { + throw new IllegalStateException("Transfer already negotiated!"); + } + + try { + inputStream = negotiateStream(); + } + catch (XMPPException e) { + setException(e); + throw e; + } + + return inputStream; + } + + /** + * This method negotitates the stream and then transfer's the file over the + * negotiated stream. The transfered file will be saved at the provided + * location. + * <p/> + * This method will return immedialtly, file transfer progress can be + * monitored through several methods: + * <p/> + * <UL> + * <LI>{@link FileTransfer#getStatus()} + * <LI>{@link FileTransfer#getProgress()} + * <LI>{@link FileTransfer#isDone()} + * </UL> + * + * @param file The location to save the file. + * @throws XMPPException when the file transfer fails + * @throws IllegalArgumentException This exception is thrown when the the provided file is + * either null, or cannot be written to. + */ + public void recieveFile(final File file) throws XMPPException { + if (file != null) { + if (!file.exists()) { + try { + file.createNewFile(); + } + catch (IOException e) { + throw new XMPPException( + "Could not create file to write too", e); + } + } + if (!file.canWrite()) { + throw new IllegalArgumentException("Cannot write to provided file"); + } + } + else { + throw new IllegalArgumentException("File cannot be null"); + } + + Thread transferThread = new Thread(new Runnable() { + public void run() { + try { + inputStream = negotiateStream(); + } + catch (XMPPException e) { + handleXMPPException(e); + return; + } + + OutputStream outputStream = null; + try { + outputStream = new FileOutputStream(file); + setStatus(Status.in_progress); + writeToStream(inputStream, outputStream); + } + catch (XMPPException e) { + setStatus(Status.error); + setError(Error.stream); + setException(e); + } + catch (FileNotFoundException e) { + setStatus(Status.error); + setError(Error.bad_file); + setException(e); + } + + if (getStatus().equals(Status.in_progress)) { + setStatus(Status.complete); + } + if (inputStream != null) { + try { + inputStream.close(); + } + catch (Throwable io) { + /* Ignore */ + } + } + if (outputStream != null) { + try { + outputStream.close(); + } + catch (Throwable io) { + /* Ignore */ + } + } + } + }, "File Transfer " + streamID); + transferThread.start(); + } + + private void handleXMPPException(XMPPException e) { + setStatus(FileTransfer.Status.error); + setException(e); + } + + private InputStream negotiateStream() throws XMPPException { + setStatus(Status.negotiating_transfer); + final StreamNegotiator streamNegotiator = negotiator + .selectStreamNegotiator(recieveRequest); + setStatus(Status.negotiating_stream); + FutureTask<InputStream> streamNegotiatorTask = new FutureTask<InputStream>( + new Callable<InputStream>() { + + public InputStream call() throws Exception { + return streamNegotiator + .createIncomingStream(recieveRequest.getStreamInitiation()); + } + }); + streamNegotiatorTask.run(); + InputStream inputStream; + try { + inputStream = streamNegotiatorTask.get(15, TimeUnit.SECONDS); + } + catch (InterruptedException e) { + throw new XMPPException("Interruption while executing", e); + } + catch (ExecutionException e) { + throw new XMPPException("Error in execution", e); + } + catch (TimeoutException e) { + throw new XMPPException("Request timed out", e); + } + finally { + streamNegotiatorTask.cancel(true); + } + setStatus(Status.negotiated); + return inputStream; + } + + public void cancel() { + setStatus(Status.cancelled); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java new file mode 100644 index 0000000..0bc1d77 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/OutgoingFileTransfer.java @@ -0,0 +1,453 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.XMPPError; + +import java.io.*; + +/** + * Handles the sending of a file to another user. File transfer's in jabber have + * several steps and there are several methods in this class that handle these + * steps differently. + * + * @author Alexander Wenckus + * + */ +public class OutgoingFileTransfer extends FileTransfer { + + private static int RESPONSE_TIMEOUT = 60 * 1000; + private NegotiationProgress callback; + + /** + * Returns the time in milliseconds after which the file transfer + * negotiation process will timeout if the other user has not responded. + * + * @return Returns the time in milliseconds after which the file transfer + * negotiation process will timeout if the remote user has not + * responded. + */ + public static int getResponseTimeout() { + return RESPONSE_TIMEOUT; + } + + /** + * Sets the time in milliseconds after which the file transfer negotiation + * process will timeout if the other user has not responded. + * + * @param responseTimeout + * The timeout time in milliseconds. + */ + public static void setResponseTimeout(int responseTimeout) { + RESPONSE_TIMEOUT = responseTimeout; + } + + private OutputStream outputStream; + + private String initiator; + + private Thread transferThread; + + protected OutgoingFileTransfer(String initiator, String target, + String streamID, FileTransferNegotiator transferNegotiator) { + super(target, streamID, transferNegotiator); + this.initiator = initiator; + } + + protected void setOutputStream(OutputStream stream) { + if (outputStream == null) { + this.outputStream = stream; + } + } + + /** + * Returns the output stream connected to the peer to transfer the file. It + * is only available after it has been succesfully negotiated by the + * {@link StreamNegotiator}. + * + * @return Returns the output stream connected to the peer to transfer the + * file. + */ + protected OutputStream getOutputStream() { + if (getStatus().equals(FileTransfer.Status.negotiated)) { + return outputStream; + } else { + return null; + } + } + + /** + * This method handles the negotiation of the file transfer and the stream, + * it only returns the created stream after the negotiation has been completed. + * + * @param fileName + * The name of the file that will be transmitted. It is + * preferable for this name to have an extension as it will be + * used to determine the type of file it is. + * @param fileSize + * The size in bytes of the file that will be transmitted. + * @param description + * A description of the file that will be transmitted. + * @return The OutputStream that is connected to the peer to transmit the + * file. + * @throws XMPPException + * Thrown if an error occurs during the file transfer + * negotiation process. + */ + public synchronized OutputStream sendFile(String fileName, long fileSize, + String description) throws XMPPException { + if (isDone() || outputStream != null) { + throw new IllegalStateException( + "The negotation process has already" + + " been attempted on this file transfer"); + } + try { + this.outputStream = negotiateStream(fileName, fileSize, description); + } catch (XMPPException e) { + handleXMPPException(e); + throw e; + } + return outputStream; + } + + /** + * This methods handles the transfer and stream negotiation process. It + * returns immediately and its progress will be updated through the + * {@link NegotiationProgress} callback. + * + * @param fileName + * The name of the file that will be transmitted. It is + * preferable for this name to have an extension as it will be + * used to determine the type of file it is. + * @param fileSize + * The size in bytes of the file that will be transmitted. + * @param description + * A description of the file that will be transmitted. + * @param progress + * A callback to monitor the progress of the file transfer + * negotiation process and to retrieve the OutputStream when it + * is complete. + */ + public synchronized void sendFile(final String fileName, + final long fileSize, final String description, + final NegotiationProgress progress) + { + if(progress == null) { + throw new IllegalArgumentException("Callback progress cannot be null."); + } + checkTransferThread(); + if (isDone() || outputStream != null) { + throw new IllegalStateException( + "The negotation process has already" + + " been attempted for this file transfer"); + } + this.callback = progress; + transferThread = new Thread(new Runnable() { + public void run() { + try { + OutgoingFileTransfer.this.outputStream = negotiateStream( + fileName, fileSize, description); + progress.outputStreamEstablished(OutgoingFileTransfer.this.outputStream); + } + catch (XMPPException e) { + handleXMPPException(e); + } + } + }, "File Transfer Negotiation " + streamID); + transferThread.start(); + } + + private void checkTransferThread() { + if (transferThread != null && transferThread.isAlive() || isDone()) { + throw new IllegalStateException( + "File transfer in progress or has already completed."); + } + } + + /** + * This method handles the stream negotiation process and transmits the file + * to the remote user. It returns immediatly and the progress of the file + * transfer can be monitored through several methods: + * + * <UL> + * <LI>{@link FileTransfer#getStatus()} + * <LI>{@link FileTransfer#getProgress()} + * <LI>{@link FileTransfer#isDone()} + * </UL> + * + * @param file the file to transfer to the remote entity. + * @param description a description for the file to transfer. + * @throws XMPPException + * If there is an error during the negotiation process or the + * sending of the file. + */ + public synchronized void sendFile(final File file, final String description) + throws XMPPException { + checkTransferThread(); + if (file == null || !file.exists() || !file.canRead()) { + throw new IllegalArgumentException("Could not read file"); + } else { + setFileInfo(file.getAbsolutePath(), file.getName(), file.length()); + } + + transferThread = new Thread(new Runnable() { + public void run() { + try { + outputStream = negotiateStream(file.getName(), file + .length(), description); + } catch (XMPPException e) { + handleXMPPException(e); + return; + } + if (outputStream == null) { + return; + } + + if (!updateStatus(Status.negotiated, Status.in_progress)) { + return; + } + + InputStream inputStream = null; + try { + inputStream = new FileInputStream(file); + writeToStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + setStatus(FileTransfer.Status.error); + setError(Error.bad_file); + setException(e); + } catch (XMPPException e) { + setStatus(FileTransfer.Status.error); + setException(e); + } finally { + try { + if (inputStream != null) { + inputStream.close(); + } + + outputStream.flush(); + outputStream.close(); + } catch (IOException e) { + /* Do Nothing */ + } + } + updateStatus(Status.in_progress, FileTransfer.Status.complete); + } + + }, "File Transfer " + streamID); + transferThread.start(); + } + + /** + * This method handles the stream negotiation process and transmits the file + * to the remote user. It returns immediatly and the progress of the file + * transfer can be monitored through several methods: + * + * <UL> + * <LI>{@link FileTransfer#getStatus()} + * <LI>{@link FileTransfer#getProgress()} + * <LI>{@link FileTransfer#isDone()} + * </UL> + * + * @param in the stream to transfer to the remote entity. + * @param fileName the name of the file that is transferred + * @param fileSize the size of the file that is transferred + * @param description a description for the file to transfer. + */ + public synchronized void sendStream(final InputStream in, final String fileName, final long fileSize, final String description){ + checkTransferThread(); + + transferThread = new Thread(new Runnable() { + public void run() { + setFileInfo(fileName, fileSize); + //Create packet filter + try { + outputStream = negotiateStream(fileName, fileSize, description); + } catch (XMPPException e) { + handleXMPPException(e); + return; + } catch (IllegalStateException e) { + setStatus(FileTransfer.Status.error); + setException(e); + } + if (outputStream == null) { + return; + } + + if (!updateStatus(Status.negotiated, Status.in_progress)) { + return; + } + try { + writeToStream(in, outputStream); + } catch (XMPPException e) { + setStatus(FileTransfer.Status.error); + setException(e); + } catch (IllegalStateException e) { + setStatus(FileTransfer.Status.error); + setException(e); + } finally { + try { + if (in != null) { + in.close(); + } + + outputStream.flush(); + outputStream.close(); + } catch (IOException e) { + /* Do Nothing */ + } + } + updateStatus(Status.in_progress, FileTransfer.Status.complete); + } + + }, "File Transfer " + streamID); + transferThread.start(); + } + + private void handleXMPPException(XMPPException e) { + XMPPError error = e.getXMPPError(); + if (error != null) { + int code = error.getCode(); + if (code == 403) { + setStatus(Status.refused); + return; + } + else if (code == 400) { + setStatus(Status.error); + setError(Error.not_acceptable); + } + else { + setStatus(FileTransfer.Status.error); + } + } + + setException(e); + } + + /** + * Returns the amount of bytes that have been sent for the file transfer. Or + * -1 if the file transfer has not started. + * <p> + * Note: This method is only useful when the {@link #sendFile(File, String)} + * method is called, as it is the only method that actualy transmits the + * file. + * + * @return Returns the amount of bytes that have been sent for the file + * transfer. Or -1 if the file transfer has not started. + */ + public long getBytesSent() { + return amountWritten; + } + + private OutputStream negotiateStream(String fileName, long fileSize, + String description) throws XMPPException { + // Negotiate the file transfer profile + + if (!updateStatus(Status.initial, Status.negotiating_transfer)) { + throw new XMPPException("Illegal state change"); + } + StreamNegotiator streamNegotiator = negotiator.negotiateOutgoingTransfer( + getPeer(), streamID, fileName, fileSize, description, + RESPONSE_TIMEOUT); + + if (streamNegotiator == null) { + setStatus(Status.error); + setError(Error.no_response); + return null; + } + + // Negotiate the stream + if (!updateStatus(Status.negotiating_transfer, Status.negotiating_stream)) { + throw new XMPPException("Illegal state change"); + } + outputStream = streamNegotiator.createOutgoingStream(streamID, + initiator, getPeer()); + + if (!updateStatus(Status.negotiating_stream, Status.negotiated)) { + throw new XMPPException("Illegal state change"); + } + return outputStream; + } + + public void cancel() { + setStatus(Status.cancelled); + } + + @Override + protected boolean updateStatus(Status oldStatus, Status newStatus) { + boolean isUpdated = super.updateStatus(oldStatus, newStatus); + if(callback != null && isUpdated) { + callback.statusUpdated(oldStatus, newStatus); + } + return isUpdated; + } + + @Override + protected void setStatus(Status status) { + Status oldStatus = getStatus(); + super.setStatus(status); + if(callback != null) { + callback.statusUpdated(oldStatus, status); + } + } + + @Override + protected void setException(Exception exception) { + super.setException(exception); + if(callback != null) { + callback.errorEstablishingStream(exception); + } + } + + /** + * A callback class to retrive the status of an outgoing transfer + * negotiation process. + * + * @author Alexander Wenckus + * + */ + public interface NegotiationProgress { + + /** + * Called when the status changes + * + * @param oldStatus the previous status of the file transfer. + * @param newStatus the new status of the file transfer. + */ + void statusUpdated(Status oldStatus, Status newStatus); + + /** + * Once the negotiation process is completed the output stream can be + * retrieved. + * + * @param stream the established stream which can be used to transfer the file to the remote + * entity + */ + void outputStreamEstablished(OutputStream stream); + + /** + * Called when an exception occurs during the negotiation progress. + * + * @param e the exception that occured. + */ + void errorEstablishingStream(Exception e); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java new file mode 100644 index 0000000..3c07fdc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/Socks5TransferNegotiator.java @@ -0,0 +1,164 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PushbackInputStream; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromMatchesFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamManager; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamRequest; +import org.jivesoftware.smackx.bytestreams.socks5.Socks5BytestreamSession; +import org.jivesoftware.smackx.bytestreams.socks5.packet.Bytestream; +import org.jivesoftware.smackx.packet.StreamInitiation; + +/** + * Negotiates a SOCKS5 Bytestream to be used for file transfers. The implementation is based on the + * {@link Socks5BytestreamManager} and the {@link Socks5BytestreamRequest}. + * + * @author Henning Staib + * @see <a href="http://xmpp.org/extensions/xep-0065.html">XEP-0065: SOCKS5 Bytestreams</a> + */ +public class Socks5TransferNegotiator extends StreamNegotiator { + + private Connection connection; + + private Socks5BytestreamManager manager; + + Socks5TransferNegotiator(Connection connection) { + this.connection = connection; + this.manager = Socks5BytestreamManager.getBytestreamManager(this.connection); + } + + @Override + public OutputStream createOutgoingStream(String streamID, String initiator, String target) + throws XMPPException { + try { + return this.manager.establishSession(target, streamID).getOutputStream(); + } + catch (IOException e) { + throw new XMPPException("error establishing SOCKS5 Bytestream", e); + } + catch (InterruptedException e) { + throw new XMPPException("error establishing SOCKS5 Bytestream", e); + } + } + + @Override + public InputStream createIncomingStream(StreamInitiation initiation) throws XMPPException, + InterruptedException { + /* + * SOCKS5 initiation listener must ignore next SOCKS5 Bytestream request with given session + * ID + */ + this.manager.ignoreBytestreamRequestOnce(initiation.getSessionID()); + + Packet streamInitiation = initiateIncomingStream(this.connection, initiation); + return negotiateIncomingStream(streamInitiation); + } + + @Override + public PacketFilter getInitiationPacketFilter(final String from, String streamID) { + /* + * this method is always called prior to #negotiateIncomingStream() so the SOCKS5 + * InitiationListener must ignore the next SOCKS5 Bytestream request with the given session + * ID + */ + this.manager.ignoreBytestreamRequestOnce(streamID); + + return new AndFilter(new FromMatchesFilter(from), new BytestreamSIDFilter(streamID)); + } + + @Override + public String[] getNamespaces() { + return new String[] { Socks5BytestreamManager.NAMESPACE }; + } + + @Override + InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException, + InterruptedException { + // build SOCKS5 Bytestream request + Socks5BytestreamRequest request = new ByteStreamRequest(this.manager, + (Bytestream) streamInitiation); + + // always accept the request + Socks5BytestreamSession session = request.accept(); + + // test input stream + try { + PushbackInputStream stream = new PushbackInputStream(session.getInputStream()); + int firstByte = stream.read(); + stream.unread(firstByte); + return stream; + } + catch (IOException e) { + throw new XMPPException("Error establishing input stream", e); + } + } + + @Override + public void cleanup() { + /* do nothing */ + } + + /** + * This PacketFilter accepts an incoming SOCKS5 Bytestream request with a specified session ID. + */ + private static class BytestreamSIDFilter extends PacketTypeFilter { + + private String sessionID; + + public BytestreamSIDFilter(String sessionID) { + super(Bytestream.class); + if (sessionID == null) { + throw new IllegalArgumentException("StreamID cannot be null"); + } + this.sessionID = sessionID; + } + + @Override + public boolean accept(Packet packet) { + if (super.accept(packet)) { + Bytestream bytestream = (Bytestream) packet; + + // packet must by of type SET and contains the given session ID + return this.sessionID.equals(bytestream.getSessionID()) + && IQ.Type.SET.equals(bytestream.getType()); + } + return false; + } + + } + + /** + * Derive from Socks5BytestreamRequest to access protected constructor. + */ + private static class ByteStreamRequest extends Socks5BytestreamRequest { + + private ByteStreamRequest(Socks5BytestreamManager manager, Bytestream byteStreamRequest) { + super(manager, byteStreamRequest); + } + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java new file mode 100644 index 0000000..46ece9c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java @@ -0,0 +1,167 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.filetransfer; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.StreamInitiation; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * After the file transfer negotiation process is completed according to + * JEP-0096, the negotiation process is passed off to a particular stream + * negotiator. The stream negotiator will then negotiate the chosen stream and + * return the stream to transfer the file. + * + * @author Alexander Wenckus + */ +public abstract class StreamNegotiator { + + /** + * Creates the initiation acceptance packet to forward to the stream + * initiator. + * + * @param streamInitiationOffer The offer from the stream initiator to connect for a stream. + * @param namespaces The namespace that relates to the accepted means of transfer. + * @return The response to be forwarded to the initiator. + */ + public StreamInitiation createInitiationAccept( + StreamInitiation streamInitiationOffer, String[] namespaces) + { + StreamInitiation response = new StreamInitiation(); + response.setTo(streamInitiationOffer.getFrom()); + response.setFrom(streamInitiationOffer.getTo()); + response.setType(IQ.Type.RESULT); + response.setPacketID(streamInitiationOffer.getPacketID()); + + DataForm form = new DataForm(Form.TYPE_SUBMIT); + FormField field = new FormField( + FileTransferNegotiator.STREAM_DATA_FIELD_NAME); + for (String namespace : namespaces) { + field.addValue(namespace); + } + form.addField(field); + + response.setFeatureNegotiationForm(form); + return response; + } + + + public IQ createError(String from, String to, String packetID, XMPPError xmppError) { + IQ iq = FileTransferNegotiator.createIQ(packetID, to, from, IQ.Type.ERROR); + iq.setError(xmppError); + return iq; + } + + Packet initiateIncomingStream(Connection connection, StreamInitiation initiation) throws XMPPException { + StreamInitiation response = createInitiationAccept(initiation, + getNamespaces()); + + // establish collector to await response + PacketCollector collector = connection + .createPacketCollector(getInitiationPacketFilter(initiation.getFrom(), initiation.getSessionID())); + connection.sendPacket(response); + + Packet streamMethodInitiation = collector + .nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (streamMethodInitiation == null) { + throw new XMPPException("No response from file transfer initiator"); + } + + return streamMethodInitiation; + } + + /** + * Returns the packet filter that will return the initiation packet for the appropriate stream + * initiation. + * + * @param from The initiator of the file transfer. + * @param streamID The stream ID related to the transfer. + * @return The <b><i>PacketFilter</b></i> that will return the packet relatable to the stream + * initiation. + */ + public abstract PacketFilter getInitiationPacketFilter(String from, String streamID); + + + abstract InputStream negotiateIncomingStream(Packet streamInitiation) throws XMPPException, + InterruptedException; + + /** + * This method handles the file stream download negotiation process. The + * appropriate stream negotiator's initiate incoming stream is called after + * an appropriate file transfer method is selected. The manager will respond + * to the initiator with the selected means of transfer, then it will handle + * any negotiation specific to the particular transfer method. This method + * returns the InputStream, ready to transfer the file. + * + * @param initiation The initiation that triggered this download. + * @return After the negotiation process is complete, the InputStream to + * write a file to is returned. + * @throws XMPPException If an error occurs during this process an XMPPException is + * thrown. + * @throws InterruptedException If thread is interrupted. + */ + public abstract InputStream createIncomingStream(StreamInitiation initiation) + throws XMPPException, InterruptedException; + + /** + * This method handles the file upload stream negotiation process. The + * particular stream negotiator is determined during the file transfer + * negotiation process. This method returns the OutputStream to transmit the + * file to the remote user. + * + * @param streamID The streamID that uniquely identifies the file transfer. + * @param initiator The fully-qualified JID of the initiator of the file transfer. + * @param target The fully-qualified JID of the target or receiver of the file + * transfer. + * @return The negotiated stream ready for data. + * @throws XMPPException If an error occurs during the negotiation process an + * exception will be thrown. + */ + public abstract OutputStream createOutgoingStream(String streamID, + String initiator, String target) throws XMPPException; + + /** + * Returns the XMPP namespace reserved for this particular type of file + * transfer. + * + * @return Returns the XMPP namespace reserved for this particular type of + * file transfer. + */ + public abstract String[] getNamespaces(); + + /** + * Cleanup any and all resources associated with this negotiator. + */ + public abstract void cleanup(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java new file mode 100644 index 0000000..09a04f6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Affiliate.java @@ -0,0 +1,98 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.jivesoftware.smackx.packet.MUCOwner; + +/** + * Represents an affiliation of a user to a given room. The affiliate's information will always have + * the bare jid of the real user and its affiliation. If the affiliate is an occupant of the room + * then we will also have information about the role and nickname of the user in the room. + * + * @author Gaston Dombiak + */ +public class Affiliate { + // Fields that must have a value + private String jid; + private String affiliation; + + // Fields that may have a value + private String role; + private String nick; + + Affiliate(MUCOwner.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + Affiliate(MUCAdmin.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + /** + * Returns the bare JID of the affiliated user. This information will always be available. + * + * @return the bare JID of the affiliated user. + */ + public String getJid() { + return jid; + } + + /** + * Returns the affiliation of the afffiliated user. Possible affiliations are: "owner", "admin", + * "member", "outcast". This information will always be available. + * + * @return the affiliation of the afffiliated user. + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the current role of the affiliated user if the user is currently in the room. + * If the user is not present in the room then the answer will be null. + * + * @return the current role of the affiliated user in the room or null if the user is not in + * the room. + */ + public String getRole() { + return role; + } + + /** + * Returns the current nickname of the affiliated user if the user is currently in the room. + * If the user is not present in the room then the answer will be null. + * + * @return the current nickname of the affiliated user in the room or null if the user is not in + * the room. + */ + public String getNick() { + return nick; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java new file mode 100644 index 0000000..2a719f6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ConnectionDetachedPacketCollector.java @@ -0,0 +1,145 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.packet.Packet; + +import java.util.LinkedList; + +/** + * A variant of the {@link org.jivesoftware.smack.PacketCollector} class + * that does not force attachment to a <code>Connection</code> + * on creation and no filter is required. Used to collect message + * packets targeted to a group chat room. + * + * @author Larry Kirschner + */ +class ConnectionDetachedPacketCollector { + /** + * Max number of packets that any one collector can hold. After the max is + * reached, older packets will be automatically dropped from the queue as + * new packets are added. + */ + private int maxPackets = SmackConfiguration.getPacketCollectorSize(); + + private LinkedList<Packet> resultQueue; + + /** + * Creates a new packet collector. If the packet filter is <tt>null</tt>, then + * all packets will match this collector. + */ + public ConnectionDetachedPacketCollector() { + this.resultQueue = new LinkedList<Packet>(); + } + + /** + * Creates a new packet collector. If the packet filter is <tt>null</tt>, then + * all packets will match this collector. + */ + public ConnectionDetachedPacketCollector(int maxSize) { + this.resultQueue = new LinkedList<Packet>(); + maxPackets = maxSize; + } + + /** + * Polls to see if a packet is currently available and returns it, or + * immediately returns <tt>null</tt> if no packets are currently in the + * result queue. + * + * @return the next packet result, or <tt>null</tt> if there are no more + * results. + */ + public synchronized Packet pollResult() { + if (resultQueue.isEmpty()) { + return null; + } + else { + return resultQueue.removeLast(); + } + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available. + * + * @return the next available packet. + */ + public synchronized Packet nextResult() { + // Wait indefinitely until there is a result to return. + while (resultQueue.isEmpty()) { + try { + wait(); + } + catch (InterruptedException ie) { + // Ignore. + } + } + return resultQueue.removeLast(); + } + + /** + * Returns the next available packet. The method call will block (not return) + * until a packet is available or the <tt>timeout</tt> has elapased. If the + * timeout elapses without a result, <tt>null</tt> will be returned. + * + * @param timeout the amount of time to wait for the next packet (in milleseconds). + * @return the next available packet. + */ + public synchronized Packet nextResult(long timeout) { + // Wait up to the specified amount of time for a result. + if (resultQueue.isEmpty()) { + try { + wait(timeout); + } + catch (InterruptedException ie) { + // Ignore. + } + } + // If still no result, return null. + if (resultQueue.isEmpty()) { + return null; + } + else { + return resultQueue.removeLast(); + } + } + + /** + * Processes a packet to see if it meets the criteria for this packet collector. + * If so, the packet is added to the result queue. + * + * @param packet the packet to process. + */ + protected synchronized void processPacket(Packet packet) { + if (packet == null) { + return; + } + // If the max number of packets has been reached, remove the oldest one. + if (resultQueue.size() == maxPackets) { + resultQueue.removeLast(); + } + // Add the new packet. + resultQueue.addFirst(packet); + // Notify waiting threads a result is available. + notifyAll(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java new file mode 100644 index 0000000..c0351c9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DeafOccupantInterceptor.java @@ -0,0 +1,76 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.PacketInterceptor; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.Presence; + +/** + * Packet interceptor that will intercept presence packets sent to the MUC service to indicate + * that the user wants to be a deaf occupant. A user can only indicate that he wants to be a + * deaf occupant while joining the room. It is not possible to become deaf or stop being deaf + * after the user joined the room.<p> + * + * Deaf occupants will not get messages broadcasted to all room occupants. However, they will + * be able to get private messages, presences, IQ packets or room history. To use this + * functionality you will need to send the message + * {@link MultiUserChat#addPresenceInterceptor(org.jivesoftware.smack.PacketInterceptor)} and + * pass this interceptor as the parameter.<p> + * + * Note that this is a custom extension to the MUC service so it may not work with other servers + * than Wildfire. + * + * @author Gaston Dombiak + */ +public class DeafOccupantInterceptor implements PacketInterceptor { + + public void interceptPacket(Packet packet) { + Presence presence = (Presence) packet; + // Check if user is joining a room + if (Presence.Type.available == presence.getType() && + presence.getExtension("x", "http://jabber.org/protocol/muc") != null) { + // Add extension that indicates that user wants to be a deaf occupant + packet.addExtension(new DeafExtension()); + } + } + + private static class DeafExtension implements PacketExtension { + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "http://jivesoftware.org/protocol/muc"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()) + .append("\">"); + buf.append("<deaf-occupant/>"); + buf.append("</").append(getElementName()).append(">"); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java new file mode 100644 index 0000000..6eb9efa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultParticipantStatusListener.java @@ -0,0 +1,79 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * Default implementation of the ParticipantStatusListener interface.<p> + * + * This class does not provide any behavior by default. It just avoids having + * to implement all the inteface methods if the user is only interested in implementing + * some of the methods. + * + * @author Gaston Dombiak + */ +public class DefaultParticipantStatusListener implements ParticipantStatusListener { + + public void joined(String participant) { + } + + public void left(String participant) { + } + + public void kicked(String participant, String actor, String reason) { + } + + public void voiceGranted(String participant) { + } + + public void voiceRevoked(String participant) { + } + + public void banned(String participant, String actor, String reason) { + } + + public void membershipGranted(String participant) { + } + + public void membershipRevoked(String participant) { + } + + public void moderatorGranted(String participant) { + } + + public void moderatorRevoked(String participant) { + } + + public void ownershipGranted(String participant) { + } + + public void ownershipRevoked(String participant) { + } + + public void adminGranted(String participant) { + } + + public void adminRevoked(String participant) { + } + + public void nicknameChanged(String participant, String newNickname) { + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java new file mode 100644 index 0000000..de7cc87 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DefaultUserStatusListener.java @@ -0,0 +1,70 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * Default implementation of the UserStatusListener interface.<p> + * + * This class does not provide any behavior by default. It just avoids having + * to implement all the inteface methods if the user is only interested in implementing + * some of the methods. + * + * @author Gaston Dombiak + */ +public class DefaultUserStatusListener implements UserStatusListener { + + public void kicked(String actor, String reason) { + } + + public void voiceGranted() { + } + + public void voiceRevoked() { + } + + public void banned(String actor, String reason) { + } + + public void membershipGranted() { + } + + public void membershipRevoked() { + } + + public void moderatorGranted() { + } + + public void moderatorRevoked() { + } + + public void ownershipGranted() { + } + + public void ownershipRevoked() { + } + + public void adminGranted() { + } + + public void adminRevoked() { + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java new file mode 100644 index 0000000..036f6cb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/DiscussionHistory.java @@ -0,0 +1,173 @@ +/** + * $RCSfile$ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import java.util.Date; + +import org.jivesoftware.smackx.packet.MUCInitialPresence; + +/** + * The DiscussionHistory class controls the number of characters or messages to receive + * when entering a room. The room will decide the amount of history to return if you don't + * specify a DiscussionHistory while joining a room.<p> + * + * You can use some or all of these variable to control the amount of history to receive: + * <ul> + * <li>maxchars -> total number of characters to receive in the history. + * <li>maxstanzas -> total number of messages to receive in the history. + * <li>seconds -> only the messages received in the last "X" seconds will be included in the + * history. + * <li>since -> only the messages received since the datetime specified will be included in + * the history. + * </ul> + * + * Note: Setting maxchars to 0 indicates that the user requests to receive no history. + * + * @author Gaston Dombiak + */ +public class DiscussionHistory { + + private int maxChars = -1; + private int maxStanzas = -1; + private int seconds = -1; + private Date since; + + /** + * Returns the total number of characters to receive in the history. + * + * @return total number of characters to receive in the history. + */ + public int getMaxChars() { + return maxChars; + } + + /** + * Returns the total number of messages to receive in the history. + * + * @return the total number of messages to receive in the history. + */ + public int getMaxStanzas() { + return maxStanzas; + } + + /** + * Returns the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @return the number of seconds to use to filter the messages received during that time. + */ + public int getSeconds() { + return seconds; + } + + /** + * Returns the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @return the since date to use to filter the messages received during that time. + */ + public Date getSince() { + return since; + } + + /** + * Sets the total number of characters to receive in the history. + * + * @param maxChars the total number of characters to receive in the history. + */ + public void setMaxChars(int maxChars) { + this.maxChars = maxChars; + } + + /** + * Sets the total number of messages to receive in the history. + * + * @param maxStanzas the total number of messages to receive in the history. + */ + public void setMaxStanzas(int maxStanzas) { + this.maxStanzas = maxStanzas; + } + + /** + * Sets the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @param seconds the number of seconds to use to filter the messages received during + * that time. + */ + public void setSeconds(int seconds) { + this.seconds = seconds; + } + + /** + * Sets the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @param since the since date to use to filter the messages received during that time. + */ + public void setSince(Date since) { + this.since = since; + } + + /** + * Returns true if the history has been configured with some values. + * + * @return true if the history has been configured with some values. + */ + private boolean isConfigured() { + return maxChars > -1 || maxStanzas > -1 || seconds > -1 || since != null; + } + + /** + * Returns the History that manages the amount of discussion history provided on entering a + * room. + * + * @return the History that manages the amount of discussion history provided on entering a + * room. + */ + MUCInitialPresence.History getMUCHistory() { + // Return null if the history was not properly configured + if (!isConfigured()) { + return null; + } + + MUCInitialPresence.History mucHistory = new MUCInitialPresence.History(); + if (maxChars > -1) { + mucHistory.setMaxChars(maxChars); + } + if (maxStanzas > -1) { + mucHistory.setMaxStanzas(maxStanzas); + } + if (seconds > -1) { + mucHistory.setSeconds(seconds); + } + if (since != null) { + mucHistory.setSince(since); + } + return mucHistory; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java new file mode 100644 index 0000000..7cd580b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/HostedRoom.java @@ -0,0 +1,65 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.DiscoverItems; + +/** + * Hosted rooms by a chat service may be discovered if they are configured to appear in the room + * directory . The information that may be discovered is the XMPP address of the room and the room + * name. The address of the room may be used for obtaining more detailed information + * {@link org.jivesoftware.smackx.muc.MultiUserChat#getRoomInfo(org.jivesoftware.smack.Connection, String)} + * or could be used for joining the room + * {@link org.jivesoftware.smackx.muc.MultiUserChat#MultiUserChat(org.jivesoftware.smack.Connection, String)} + * and {@link org.jivesoftware.smackx.muc.MultiUserChat#join(String)}. + * + * @author Gaston Dombiak + */ +public class HostedRoom { + + private String jid; + + private String name; + + public HostedRoom(DiscoverItems.Item item) { + super(); + jid = item.getEntityID(); + name = item.getName(); + } + + /** + * Returns the XMPP address of the hosted room by the chat service. This address may be used + * when creating a <code>MultiUserChat</code> when joining a room. + * + * @return the XMPP address of the hosted room by the chat service. + */ + public String getJid() { + return jid; + } + + /** + * Returns the name of the room. + * + * @return the name of the room. + */ + public String getName() { + return name; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java new file mode 100644 index 0000000..34c915d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationListener.java @@ -0,0 +1,49 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.Message; + +/** + * A listener that is fired anytime an invitation to join a MUC room is received. + * + * @author Gaston Dombiak + */ +public interface InvitationListener { + + /** + * Called when the an invitation to join a MUC room is received.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param conn the Connection that received the invitation. + * @param room the room that invitation refers to. + * @param inviter the inviter that sent the invitation. (e.g. crone1@shakespeare.lit). + * @param reason the reason why the inviter sent the invitation. + * @param password the password to use when joining the room. + * @param message the message used by the inviter to send the invitation. + */ + public abstract void invitationReceived(Connection conn, String room, String inviter, String reason, + String password, Message message); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java new file mode 100644 index 0000000..1580c6f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/InvitationRejectionListener.java @@ -0,0 +1,38 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime an invitee declines or rejects an invitation. + * + * @author Gaston Dombiak + */ +public interface InvitationRejectionListener { + + /** + * Called when the invitee declines the invitation. + * + * @param invitee the invitee that declined the invitation. (e.g. hecate@shakespeare.lit). + * @param reason the reason why the invitee declined the invitation. + */ + public abstract void invitationDeclined(String invitee, String reason); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java new file mode 100644 index 0000000..aee90ea --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -0,0 +1,2717 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.Chat; +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.MessageListener; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.PacketInterceptor; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.FromMatchesFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.packet.Registration; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.NodeInformationProvider; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.jivesoftware.smackx.packet.MUCInitialPresence; +import org.jivesoftware.smackx.packet.MUCOwner; +import org.jivesoftware.smackx.packet.MUCUser; + +/** + * A MultiUserChat is a conversation that takes place among many users in a virtual + * room. A room could have many occupants with different affiliation and roles. + * Possible affiliatons are "owner", "admin", "member", and "outcast". Possible roles + * are "moderator", "participant", and "visitor". Each role and affiliation guarantees + * different privileges (e.g. Send messages to all occupants, Kick participants and visitors, + * Grant voice, Edit member list, etc.). + * + * @author Gaston Dombiak, Larry Kirschner + */ +public class MultiUserChat { + + private final static String discoNamespace = "http://jabber.org/protocol/muc"; + private final static String discoNode = "http://jabber.org/protocol/muc#rooms"; + + private static Map<Connection, List<String>> joinedRooms = + new WeakHashMap<Connection, List<String>>(); + + private Connection connection; + private String room; + private String subject; + private String nickname = null; + private boolean joined = false; + private Map<String, Presence> occupantsMap = new ConcurrentHashMap<String, Presence>(); + + private final List<InvitationRejectionListener> invitationRejectionListeners = + new ArrayList<InvitationRejectionListener>(); + private final List<SubjectUpdatedListener> subjectUpdatedListeners = + new ArrayList<SubjectUpdatedListener>(); + private final List<UserStatusListener> userStatusListeners = + new ArrayList<UserStatusListener>(); + private final List<ParticipantStatusListener> participantStatusListeners = + new ArrayList<ParticipantStatusListener>(); + + private PacketFilter presenceFilter; + private List<PacketInterceptor> presenceInterceptors = new ArrayList<PacketInterceptor>(); + private PacketFilter messageFilter; + private RoomListenerMultiplexor roomListenerMultiplexor; + private ConnectionDetachedPacketCollector messageCollector; + private List<PacketListener> connectionListeners = new ArrayList<PacketListener>(); + + static { + Connection.addConnectionCreationListener(new ConnectionCreationListener() { + public void connectionCreated(final Connection connection) { + // Set on every established connection that this client supports the Multi-User + // Chat protocol. This information will be used when another client tries to + // discover whether this client supports MUC or not. + ServiceDiscoveryManager.getInstanceFor(connection).addFeature(discoNamespace); + // Set the NodeInformationProvider that will provide information about the + // joined rooms whenever a disco request is received + ServiceDiscoveryManager.getInstanceFor(connection).setNodeInformationProvider( + discoNode, + new NodeInformationProvider() { + public List<DiscoverItems.Item> getNodeItems() { + List<DiscoverItems.Item> answer = new ArrayList<DiscoverItems.Item>(); + Iterator<String> rooms=MultiUserChat.getJoinedRooms(connection); + while (rooms.hasNext()) { + answer.add(new DiscoverItems.Item(rooms.next())); + } + return answer; + } + + public List<String> getNodeFeatures() { + return null; + } + + public List<DiscoverInfo.Identity> getNodeIdentities() { + return null; + } + }); + } + }); + } + + /** + * Creates a new multi user chat with the specified connection and room name. Note: no + * information is sent to or received from the server until you attempt to + * {@link #join(String) join} the chat room. On some server implementations, + * the room will not be created until the first person joins it.<p> + * + * Most XMPP servers use a sub-domain for the chat service (eg chat.example.com + * for the XMPP server example.com). You must ensure that the room address you're + * trying to connect to includes the proper chat sub-domain. + * + * @param connection the XMPP connection. + * @param room the name of the room in the form "roomName@service", where + * "service" is the hostname at which the multi-user chat + * service is running. Make sure to provide a valid JID. + */ + public MultiUserChat(Connection connection, String room) { + this.connection = connection; + this.room = room.toLowerCase(); + init(); + } + + /** + * Returns true if the specified user supports the Multi-User Chat protocol. + * + * @param connection the connection to use to perform the service discovery. + * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. + * @return a boolean indicating whether the specified user supports the MUC protocol. + */ + public static boolean isServiceEnabled(Connection connection, String user) { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(user); + return result.containsFeature(discoNamespace); + } + catch (XMPPException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Returns an Iterator on the rooms where the user has joined using a given connection. + * The Iterator will contain Strings where each String represents a room + * (e.g. room@muc.jabber.org). + * + * @param connection the connection used to join the rooms. + * @return an Iterator on the rooms where the user has joined using a given connection. + */ + private static Iterator<String> getJoinedRooms(Connection connection) { + List<String> rooms = joinedRooms.get(connection); + if (rooms != null) { + return rooms.iterator(); + } + // Return an iterator on an empty collection (i.e. the user never joined a room) + return new ArrayList<String>().iterator(); + } + + /** + * Returns an Iterator on the rooms where the requested user has joined. The Iterator will + * contain Strings where each String represents a room (e.g. room@muc.jabber.org). + * + * @param connection the connection to use to perform the service discovery. + * @param user the user to check. A fully qualified xmpp ID, e.g. jdoe@example.com. + * @return an Iterator on the rooms where the requested user has joined. + */ + public static Iterator<String> getJoinedRooms(Connection connection, String user) { + try { + ArrayList<String> answer = new ArrayList<String>(); + // Send the disco packet to the user + DiscoverItems result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverItems(user, discoNode); + // Collect the entityID for each returned item + for (Iterator<DiscoverItems.Item> items=result.getItems(); items.hasNext();) { + answer.add(items.next().getEntityID()); + } + return answer.iterator(); + } + catch (XMPPException e) { + e.printStackTrace(); + // Return an iterator on an empty collection + return new ArrayList<String>().iterator(); + } + } + + /** + * Returns the discovered information of a given room without actually having to join the room. + * The server will provide information only for rooms that are public. + * + * @param connection the XMPP connection to use for discovering information about the room. + * @param room the name of the room in the form "roomName@service" of which we want to discover + * its information. + * @return the discovered information of a given room without actually having to join the room. + * @throws XMPPException if an error occured while trying to discover information of a room. + */ + public static RoomInfo getRoomInfo(Connection connection, String room) + throws XMPPException { + DiscoverInfo info = ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo(room); + return new RoomInfo(info); + } + + /** + * Returns a collection with the XMPP addresses of the Multi-User Chat services. + * + * @param connection the XMPP connection to use for discovering Multi-User Chat services. + * @return a collection with the XMPP addresses of the Multi-User Chat services. + * @throws XMPPException if an error occured while trying to discover MUC services. + */ + public static Collection<String> getServiceNames(Connection connection) throws XMPPException { + final List<String> answer = new ArrayList<String>(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + DiscoverItems items = discoManager.discoverItems(connection.getServiceName()); + for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { + DiscoverItems.Item item = it.next(); + try { + DiscoverInfo info = discoManager.discoverInfo(item.getEntityID()); + if (info.containsFeature("http://jabber.org/protocol/muc")) { + answer.add(item.getEntityID()); + } + } + catch (XMPPException e) { + // Trouble finding info in some cases. This is a workaround for + // discovering info on remote servers. + } + } + return answer; + } + + /** + * Returns a collection of HostedRooms where each HostedRoom has the XMPP address of the room + * and the room's name. Once discovered the rooms hosted by a chat service it is possible to + * discover more detailed room information or join the room. + * + * @param connection the XMPP connection to use for discovering hosted rooms by the MUC service. + * @param serviceName the service that is hosting the rooms to discover. + * @return a collection of HostedRooms. + * @throws XMPPException if an error occured while trying to discover the information. + */ + public static Collection<HostedRoom> getHostedRooms(Connection connection, String serviceName) + throws XMPPException { + List<HostedRoom> answer = new ArrayList<HostedRoom>(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + DiscoverItems items = discoManager.discoverItems(serviceName); + for (Iterator<DiscoverItems.Item> it = items.getItems(); it.hasNext();) { + answer.add(new HostedRoom(it.next())); + } + return answer; + } + + /** + * Returns the name of the room this MultiUserChat object represents. + * + * @return the multi user chat room name. + */ + public String getRoom() { + return room; + } + + /** + * Creates the room according to some default configuration, assign the requesting user + * as the room owner, and add the owner to the room but not allow anyone else to enter + * the room (effectively "locking" the room). The requesting user will join the room + * under the specified nickname as soon as the room has been created.<p> + * + * To create an "Instant Room", that means a room with some default configuration that is + * available for immediate access, the room's owner should send an empty form after creating + * the room. {@link #sendConfigurationForm(Form)}<p> + * + * To create a "Reserved Room", that means a room manually configured by the room creator + * before anyone is allowed to enter, the room's owner should complete and send a form after + * creating the room. Once the completed configutation form is sent to the server, the server + * will unlock the room. {@link #sendConfigurationForm(Form)} + * + * @param nickname the nickname to use. + * @throws XMPPException if the room couldn't be created for some reason + * (e.g. room already exists; user already joined to an existant room or + * 405 error if the user is not allowed to create the room) + */ + public synchronized void create(String nickname) throws XMPPException { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // If we've already joined the room, leave it before joining under a new + // nickname. + if (joined) { + throw new IllegalStateException("Creation failed - User already joined the room."); + } + // We create a room by sending a presence packet to room@service/nick + // and signal support for MUC. The owner will be automatically logged into the room. + Presence joinPresence = new Presence(Presence.Type.available); + joinPresence.setTo(room + "/" + nickname); + // Indicate the the client supports MUC + joinPresence.addExtension(new MUCInitialPresence()); + // Invoke presence interceptors so that extra information can be dynamically added + for (PacketInterceptor packetInterceptor : presenceInterceptors) { + packetInterceptor.interceptPacket(joinPresence); + } + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room + "/" + nickname), + new PacketTypeFilter(Presence.class)); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send create & join packet. + connection.sendPacket(joinPresence); + // Wait up to a certain number of seconds for a reply. + Presence presence = + (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (presence == null) { + throw new XMPPException("No response from server."); + } + else if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + // Whether the room existed before or was created, the user has joined the room + this.nickname = nickname; + joined = true; + userHasJoined(); + + // Look for confirmation of room creation from the server + MUCUser mucUser = getMUCUserExtension(presence); + if (mucUser != null && mucUser.getStatus() != null) { + if ("201".equals(mucUser.getStatus().getCode())) { + // Room was created and the user has joined the room + return; + } + } + // We need to leave the room since it seems that the room already existed + leave(); + throw new XMPPException("Creation failed - Missing acknowledge of room creation."); + } + + /** + * Joins the chat room using the specified nickname. If already joined + * using another nickname, this method will first leave the room and then + * re-join using the new nickname. The default timeout of Smack for a reply + * from the group chat server that the join succeeded will be used. After + * joining the room, the room will decide the amount of history to send. + * + * @param nickname the nickname to use. + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public void join(String nickname) throws XMPPException { + join(nickname, null, null, SmackConfiguration.getPacketReplyTimeout()); + } + + /** + * Joins the chat room using the specified nickname and password. If already joined + * using another nickname, this method will first leave the room and then + * re-join using the new nickname. The default timeout of Smack for a reply + * from the group chat server that the join succeeded will be used. After + * joining the room, the room will decide the amount of history to send.<p> + * + * A password is required when joining password protected rooms. If the room does + * not require a password there is no need to provide one. + * + * @param nickname the nickname to use. + * @param password the password to use. + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public void join(String nickname, String password) throws XMPPException { + join(nickname, password, null, SmackConfiguration.getPacketReplyTimeout()); + } + + /** + * Joins the chat room using the specified nickname and password. If already joined + * using another nickname, this method will first leave the room and then + * re-join using the new nickname.<p> + * + * To control the amount of history to receive while joining a room you will need to provide + * a configured DiscussionHistory object.<p> + * + * A password is required when joining password protected rooms. If the room does + * not require a password there is no need to provide one.<p> + * + * If the room does not already exist when the user seeks to enter it, the server will + * decide to create a new room or not. + * + * @param nickname the nickname to use. + * @param password the password to use. + * @param history the amount of discussion history to receive while joining a room. + * @param timeout the amount of time to wait for a reply from the MUC service(in milleseconds). + * @throws XMPPException if an error occurs joining the room. In particular, a + * 401 error can occur if no password was provided and one is required; or a + * 403 error can occur if the user is banned; or a + * 404 error can occur if the room does not exist or is locked; or a + * 407 error can occur if user is not on the member list; or a + * 409 error can occur if someone is already in the group chat with the same nickname. + */ + public synchronized void join( + String nickname, + String password, + DiscussionHistory history, + long timeout) + throws XMPPException { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // If we've already joined the room, leave it before joining under a new + // nickname. + if (joined) { + leave(); + } + // We join a room by sending a presence packet where the "to" + // field is in the form "roomName@service/nickname" + Presence joinPresence = new Presence(Presence.Type.available); + joinPresence.setTo(room + "/" + nickname); + + // Indicate the the client supports MUC + MUCInitialPresence mucInitialPresence = new MUCInitialPresence(); + if (password != null) { + mucInitialPresence.setPassword(password); + } + if (history != null) { + mucInitialPresence.setHistory(history.getMUCHistory()); + } + joinPresence.addExtension(mucInitialPresence); + // Invoke presence interceptors so that extra information can be dynamically added + for (PacketInterceptor packetInterceptor : presenceInterceptors) { + packetInterceptor.interceptPacket(joinPresence); + } + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room + "/" + nickname), + new PacketTypeFilter(Presence.class)); + PacketCollector response = null; + Presence presence; + try { + response = connection.createPacketCollector(responseFilter); + // Send join packet. + connection.sendPacket(joinPresence); + // Wait up to a certain number of seconds for a reply. + presence = (Presence) response.nextResult(timeout); + } + finally { + // Stop queuing results + if (response != null) { + response.cancel(); + } + } + + if (presence == null) { + throw new XMPPException("No response from server."); + } + else if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + this.nickname = nickname; + joined = true; + userHasJoined(); + } + + /** + * Returns true if currently in the multi user chat (after calling the {@link + * #join(String)} method). + * + * @return true if currently in the multi user chat room. + */ + public boolean isJoined() { + return joined; + } + + /** + * Leave the chat room. + */ + public synchronized void leave() { + // If not joined already, do nothing. + if (!joined) { + return; + } + // We leave a room by sending a presence packet where the "to" + // field is in the form "roomName@service/nickname" + Presence leavePresence = new Presence(Presence.Type.unavailable); + leavePresence.setTo(room + "/" + nickname); + // Invoke presence interceptors so that extra information can be dynamically added + for (PacketInterceptor packetInterceptor : presenceInterceptors) { + packetInterceptor.interceptPacket(leavePresence); + } + connection.sendPacket(leavePresence); + // Reset occupant information. + occupantsMap.clear(); + nickname = null; + joined = false; + userHasLeft(); + } + + /** + * Returns the room's configuration form that the room's owner can use or <tt>null</tt> if + * no configuration is possible. The configuration form allows to set the room's language, + * enable logging, specify room's type, etc.. + * + * @return the Form that contains the fields to complete together with the instrucions or + * <tt>null</tt> if no configuration is possible. + * @throws XMPPException if an error occurs asking the configuration form for the room. + */ + public Form getConfigurationForm() throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Request the configuration form to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + return Form.getFormFrom(answer); + } + + /** + * Sends the completed configuration form to the server. The room will be configured + * with the new settings defined in the form. If the form is empty then the server + * will create an instant room (will use default configuration). + * + * @param form the form with the new settings. + * @throws XMPPException if an error occurs setting the new rooms' configuration. + */ + public void sendConfigurationForm(Form form) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + iq.addExtension(form.getDataFormToSend()); + + // Filter packets looking for an answer from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the completed configuration form to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Returns the room's registration form that an unaffiliated user, can use to become a member + * of the room or <tt>null</tt> if no registration is possible. Some rooms may restrict the + * privilege to register members and allow only room admins to add new members.<p> + * + * If the user requesting registration requirements is not allowed to register with the room + * (e.g. because that privilege has been restricted), the room will return a "Not Allowed" + * error to the user (error code 405). + * + * @return the registration Form that contains the fields to complete together with the + * instrucions or <tt>null</tt> if no registration is possible. + * @throws XMPPException if an error occurs asking the registration form for the room or a + * 405 error if the user is not allowed to register with the room. + */ + public Form getRegistrationForm() throws XMPPException { + Registration reg = new Registration(); + reg.setType(IQ.Type.GET); + reg.setTo(room); + + PacketFilter filter = + new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + return Form.getFormFrom(result); + } + + /** + * Sends the completed registration form to the server. After the user successfully submits + * the form, the room may queue the request for review by the room admins or may immediately + * add the user to the member list by changing the user's affiliation from "none" to "member.<p> + * + * If the desired room nickname is already reserved for that room, the room will return a + * "Conflict" error to the user (error code 409). If the room does not support registration, + * it will return a "Service Unavailable" error to the user (error code 503). + * + * @param form the completed registration form. + * @throws XMPPException if an error occurs submitting the registration form. In particular, a + * 409 error can occur if the desired room nickname is already reserved for that room; + * or a 503 error can occur if the room does not support registration. + */ + public void sendRegistrationForm(Form form) throws XMPPException { + Registration reg = new Registration(); + reg.setType(IQ.Type.SET); + reg.setTo(room); + reg.addExtension(form.getDataFormToSend()); + + PacketFilter filter = + new AndFilter(new PacketIDFilter(reg.getPacketID()), new PacketTypeFilter(IQ.class)); + PacketCollector collector = connection.createPacketCollector(filter); + connection.sendPacket(reg); + IQ result = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + collector.cancel(); + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getType() == IQ.Type.ERROR) { + throw new XMPPException(result.getError()); + } + } + + /** + * Sends a request to the server to destroy the room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error (403). + * + * @param reason the reason for the room destruction. + * @param alternateJID the JID of an alternate location. + * @throws XMPPException if an error occurs while trying to destroy the room. + * An error can occur which will be wrapped by an XMPPException -- + * XMPP error code 403. The error code can be used to present more + * appropiate error messages to end-users. + */ + public void destroy(String reason, String alternateJID) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + + // Create the reason for the room destruction + MUCOwner.Destroy destroy = new MUCOwner.Destroy(); + destroy.setReason(reason); + destroy.setJid(alternateJID); + iq.setDestroy(destroy); + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the room destruction request. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Reset occupant information. + occupantsMap.clear(); + nickname = null; + joined = false; + userHasLeft(); + } + + /** + * Invites another user to the room in which one is an occupant. The invitation + * will be sent to the room which in turn will forward the invitation to the invitee.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) + * @param reason the reason why the user is being invited. + */ + public void invite(String user, String reason) { + invite(new Message(), user, reason); + } + + /** + * Invites another user to the room in which one is an occupant using a given Message. The invitation + * will be sent to the room which in turn will forward the invitation to the invitee.<p> + * + * If the room is password-protected, the invitee will receive a password to use to join + * the room. If the room is members-only, the the invitee may be added to the member list. + * + * @param message the message to use for sending the invitation. + * @param user the user to invite to the room.(e.g. hecate@shakespeare.lit) + * @param reason the reason why the user is being invited. + */ + public void invite(Message message, String user, String reason) { + // TODO listen for 404 error code when inviter supplies a non-existent JID + message.setTo(room); + + // Create the MUCUser packet that will include the invitation + MUCUser mucUser = new MUCUser(); + MUCUser.Invite invite = new MUCUser.Invite(); + invite.setTo(user); + invite.setReason(reason); + mucUser.setInvite(invite); + // Add the MUCUser packet that includes the invitation to the message + message.addExtension(mucUser); + + connection.sendPacket(message); + } + + /** + * Informs the sender of an invitation that the invitee declines the invitation. The rejection + * will be sent to the room which in turn will forward the rejection to the inviter. + * + * @param conn the connection to use for sending the rejection. + * @param room the room that sent the original invitation. + * @param inviter the inviter of the declined invitation. + * @param reason the reason why the invitee is declining the invitation. + */ + public static void decline(Connection conn, String room, String inviter, String reason) { + Message message = new Message(room); + + // Create the MUCUser packet that will include the rejection + MUCUser mucUser = new MUCUser(); + MUCUser.Decline decline = new MUCUser.Decline(); + decline.setTo(inviter); + decline.setReason(reason); + mucUser.setDecline(decline); + // Add the MUCUser packet that includes the rejection + message.addExtension(mucUser); + + conn.sendPacket(message); + } + + /** + * Adds a listener to invitation notifications. The listener will be fired anytime + * an invitation is received. + * + * @param conn the connection where the listener will be applied. + * @param listener an invitation listener. + */ + public static void addInvitationListener(Connection conn, InvitationListener listener) { + InvitationsMonitor.getInvitationsMonitor(conn).addInvitationListener(listener); + } + + /** + * Removes a listener to invitation notifications. The listener will be fired anytime + * an invitation is received. + * + * @param conn the connection where the listener was applied. + * @param listener an invitation listener. + */ + public static void removeInvitationListener(Connection conn, InvitationListener listener) { + InvitationsMonitor.getInvitationsMonitor(conn).removeInvitationListener(listener); + } + + /** + * Adds a listener to invitation rejections notifications. The listener will be fired anytime + * an invitation is declined. + * + * @param listener an invitation rejection listener. + */ + public void addInvitationRejectionListener(InvitationRejectionListener listener) { + synchronized (invitationRejectionListeners) { + if (!invitationRejectionListeners.contains(listener)) { + invitationRejectionListeners.add(listener); + } + } + } + + /** + * Removes a listener from invitation rejections notifications. The listener will be fired + * anytime an invitation is declined. + * + * @param listener an invitation rejection listener. + */ + public void removeInvitationRejectionListener(InvitationRejectionListener listener) { + synchronized (invitationRejectionListeners) { + invitationRejectionListeners.remove(listener); + } + } + + /** + * Fires invitation rejection listeners. + * + * @param invitee the user being invited. + * @param reason the reason for the rejection + */ + private void fireInvitationRejectionListeners(String invitee, String reason) { + InvitationRejectionListener[] listeners; + synchronized (invitationRejectionListeners) { + listeners = new InvitationRejectionListener[invitationRejectionListeners.size()]; + invitationRejectionListeners.toArray(listeners); + } + for (InvitationRejectionListener listener : listeners) { + listener.invitationDeclined(invitee, reason); + } + } + + /** + * Adds a listener to subject change notifications. The listener will be fired anytime + * the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void addSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + if (!subjectUpdatedListeners.contains(listener)) { + subjectUpdatedListeners.add(listener); + } + } + } + + /** + * Removes a listener from subject change notifications. The listener will be fired + * anytime the room's subject changes. + * + * @param listener a subject updated listener. + */ + public void removeSubjectUpdatedListener(SubjectUpdatedListener listener) { + synchronized (subjectUpdatedListeners) { + subjectUpdatedListeners.remove(listener); + } + } + + /** + * Fires subject updated listeners. + */ + private void fireSubjectUpdatedListeners(String subject, String from) { + SubjectUpdatedListener[] listeners; + synchronized (subjectUpdatedListeners) { + listeners = new SubjectUpdatedListener[subjectUpdatedListeners.size()]; + subjectUpdatedListeners.toArray(listeners); + } + for (SubjectUpdatedListener listener : listeners) { + listener.subjectUpdated(subject, from); + } + } + + /** + * Adds a new {@link PacketInterceptor} that will be invoked every time a new presence + * is going to be sent by this MultiUserChat to the server. Packet interceptors may + * add new extensions to the presence that is going to be sent to the MUC service. + * + * @param presenceInterceptor the new packet interceptor that will intercept presence packets. + */ + public void addPresenceInterceptor(PacketInterceptor presenceInterceptor) { + presenceInterceptors.add(presenceInterceptor); + } + + /** + * Removes a {@link PacketInterceptor} that was being invoked every time a new presence + * was being sent by this MultiUserChat to the server. Packet interceptors may + * add new extensions to the presence that is going to be sent to the MUC service. + * + * @param presenceInterceptor the packet interceptor to remove. + */ + public void removePresenceInterceptor(PacketInterceptor presenceInterceptor) { + presenceInterceptors.remove(presenceInterceptor); + } + + /** + * Returns the last known room's subject or <tt>null</tt> if the user hasn't joined the room + * or the room does not have a subject yet. In case the room has a subject, as soon as the + * user joins the room a message with the current room's subject will be received.<p> + * + * To be notified every time the room's subject change you should add a listener + * to this room. {@link #addSubjectUpdatedListener(SubjectUpdatedListener)}<p> + * + * To change the room's subject use {@link #changeSubject(String)}. + * + * @return the room's subject or <tt>null</tt> if the user hasn't joined the room or the + * room does not have a subject yet. + */ + public String getSubject() { + return subject; + } + + /** + * Returns the reserved room nickname for the user in the room. A user may have a reserved + * nickname, for example through explicit room registration or database integration. In such + * cases it may be desirable for the user to discover the reserved nickname before attempting + * to enter the room. + * + * @return the reserved room nickname or <tt>null</tt> if none. + */ + public String getReservedNickname() { + try { + DiscoverInfo result = + ServiceDiscoveryManager.getInstanceFor(connection).discoverInfo( + room, + "x-roomuser-item"); + // Look for an Identity that holds the reserved nickname and return its name + for (Iterator<DiscoverInfo.Identity> identities = result.getIdentities(); + identities.hasNext();) { + DiscoverInfo.Identity identity = identities.next(); + return identity.getName(); + } + // If no Identity was found then the user does not have a reserved room nickname + return null; + } + catch (XMPPException e) { + e.printStackTrace(); + return null; + } + } + + /** + * Returns the nickname that was used to join the room, or <tt>null</tt> if not + * currently joined. + * + * @return the nickname currently being used. + */ + public String getNickname() { + return nickname; + } + + /** + * Changes the occupant's nickname to a new nickname within the room. Each room occupant + * will receive two presence packets. One of type "unavailable" for the old nickname and one + * indicating availability for the new nickname. The unavailable presence will contain the new + * nickname and an appropriate status code (namely 303) as extended presence information. The + * status code 303 indicates that the occupant is changing his/her nickname. + * + * @param nickname the new nickname within the room. + * @throws XMPPException if the new nickname is already in use by another occupant. + */ + public void changeNickname(String nickname) throws XMPPException { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // Check that we already have joined the room before attempting to change the + // nickname. + if (!joined) { + throw new IllegalStateException("Must be logged into the room to change nickname."); + } + // We change the nickname by sending a presence packet where the "to" + // field is in the form "roomName@service/nickname" + // We don't have to signal the MUC support again + Presence joinPresence = new Presence(Presence.Type.available); + joinPresence.setTo(room + "/" + nickname); + // Invoke presence interceptors so that extra information can be dynamically added + for (PacketInterceptor packetInterceptor : presenceInterceptors) { + packetInterceptor.interceptPacket(joinPresence); + } + + // Wait for a presence packet back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room + "/" + nickname), + new PacketTypeFilter(Presence.class)); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send join packet. + connection.sendPacket(joinPresence); + // Wait up to a certain number of seconds for a reply. + Presence presence = + (Presence) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (presence == null) { + throw new XMPPException("No response from server."); + } + else if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + this.nickname = nickname; + } + + /** + * Changes the occupant's availability status within the room. The presence type + * will remain available but with a new status that describes the presence update and + * a new presence mode (e.g. Extended away). + * + * @param status a text message describing the presence update. + * @param mode the mode type for the presence update. + */ + public void changeAvailabilityStatus(String status, Presence.Mode mode) { + if (nickname == null || nickname.equals("")) { + throw new IllegalArgumentException("Nickname must not be null or blank."); + } + // Check that we already have joined the room before attempting to change the + // availability status. + if (!joined) { + throw new IllegalStateException( + "Must be logged into the room to change the " + "availability status."); + } + // We change the availability status by sending a presence packet to the room with the + // new presence status and mode + Presence joinPresence = new Presence(Presence.Type.available); + joinPresence.setStatus(status); + joinPresence.setMode(mode); + joinPresence.setTo(room + "/" + nickname); + // Invoke presence interceptors so that extra information can be dynamically added + for (PacketInterceptor packetInterceptor : presenceInterceptors) { + packetInterceptor.interceptPacket(joinPresence); + } + + // Send join packet. + connection.sendPacket(joinPresence); + } + + /** + * Kicks a visitor or participant from the room. The kicked occupant will receive a presence + * of type "unavailable" including a status code 307 and optionally along with the reason + * (if provided) and the bare JID of the user who initiated the kick. After the occupant + * was kicked from the room, the rest of the occupants will receive a presence of type + * "unavailable". The presence will include a status code 307 which means that the occupant + * was kicked from the room. + * + * @param nickname the nickname of the participant or visitor to kick from the room + * (e.g. "john"). + * @param reason the reason why the participant or visitor is being kicked from the room. + * @throws XMPPException if an error occurs kicking the occupant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was intended to be kicked (i.e. Not Allowed error); or a + * 403 error can occur if the occupant that intended to kick another occupant does + * not have kicking privileges (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void kickParticipant(String nickname, String reason) throws XMPPException { + changeRole(nickname, "none", reason); + } + + /** + * Grants voice to visitors in the room. In a moderated room, a moderator may want to manage + * who does and does not have "voice" in the room. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nicknames the nicknames of the visitors to grant voice in the room (e.g. "john"). + * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a + * 403 error can occur if the occupant that intended to grant voice is not + * a moderator in this room (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void grantVoice(Collection<String> nicknames) throws XMPPException { + changeRole(nicknames, "participant"); + } + + /** + * Grants voice to a visitor in the room. In a moderated room, a moderator may want to manage + * who does and does not have "voice" in the room. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nickname the nickname of the visitor to grant voice in the room (e.g. "john"). + * @throws XMPPException if an error occurs granting voice to a visitor. In particular, a + * 403 error can occur if the occupant that intended to grant voice is not + * a moderator in this room (i.e. Forbidden error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void grantVoice(String nickname) throws XMPPException { + changeRole(nickname, "participant", null); + } + + /** + * Revokes voice from participants in the room. In a moderated room, a moderator may want to + * revoke an occupant's privileges to speak. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nicknames the nicknames of the participants to revoke voice (e.g. "john"). + * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to revoke his voice (i.e. Not Allowed error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void revokeVoice(Collection<String> nicknames) throws XMPPException { + changeRole(nicknames, "visitor"); + } + + /** + * Revokes voice from a participant in the room. In a moderated room, a moderator may want to + * revoke an occupant's privileges to speak. To have voice means that a room occupant + * is able to send messages to the room occupants. + * + * @param nickname the nickname of the participant to revoke voice (e.g. "john"). + * @throws XMPPException if an error occurs revoking voice from a participant. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to revoke his voice (i.e. Not Allowed error); or a + * 400 error can occur if the provided nickname is not present in the room. + */ + public void revokeVoice(String nickname) throws XMPPException { + changeRole(nickname, "visitor", null); + } + + /** + * Bans users from the room. An admin or owner of the room can ban users from a room. This + * means that the banned user will no longer be able to join the room unless the ban has been + * removed. If the banned user was present in the room then he/she will be removed from the + * room and notified that he/she was banned along with the reason (if provided) and the bare + * XMPP user ID of the user who initiated the ban. + * + * @param jids the bare XMPP user IDs of the users to ban. + * @throws XMPPException if an error occurs banning a user. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to be banned (i.e. Not Allowed error). + */ + public void banUsers(Collection<String> jids) throws XMPPException { + changeAffiliationByAdmin(jids, "outcast"); + } + + /** + * Bans a user from the room. An admin or owner of the room can ban users from a room. This + * means that the banned user will no longer be able to join the room unless the ban has been + * removed. If the banned user was present in the room then he/she will be removed from the + * room and notified that he/she was banned along with the reason (if provided) and the bare + * XMPP user ID of the user who initiated the ban. + * + * @param jid the bare XMPP user ID of the user to ban (e.g. "user@host.org"). + * @param reason the reason why the user was banned. + * @throws XMPPException if an error occurs banning a user. In particular, a + * 405 error can occur if a moderator or a user with an affiliation of "owner" or "admin" + * was tried to be banned (i.e. Not Allowed error). + */ + public void banUser(String jid, String reason) throws XMPPException { + changeAffiliationByAdmin(jid, "outcast", reason); + } + + /** + * Grants membership to other users. Only administrators are able to grant membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). + * + * @param jids the XMPP user IDs of the users to grant membership. + * @throws XMPPException if an error occurs granting membership to a user. + */ + public void grantMembership(Collection<String> jids) throws XMPPException { + changeAffiliationByAdmin(jids, "member"); + } + + /** + * Grants membership to a user. Only administrators are able to grant membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). + * + * @param jid the XMPP user ID of the user to grant membership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting membership to a user. + */ + public void grantMembership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "member", null); + } + + /** + * Revokes users' membership. Only administrators are able to revoke membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). If the user is in the room and + * the room is of type members-only then the user will be removed from the room. + * + * @param jids the bare XMPP user IDs of the users to revoke membership. + * @throws XMPPException if an error occurs revoking membership to a user. + */ + public void revokeMembership(Collection<String> jids) throws XMPPException { + changeAffiliationByAdmin(jids, "none"); + } + + /** + * Revokes a user's membership. Only administrators are able to revoke membership. A user + * that becomes a room member will be able to enter a room of type Members-Only (i.e. a room + * that a user cannot enter without being on the member list). If the user is in the room and + * the room is of type members-only then the user will be removed from the room. + * + * @param jid the bare XMPP user ID of the user to revoke membership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking membership to a user. + */ + public void revokeMembership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "none", null); + } + + /** + * Grants moderator privileges to participants or visitors. Room administrators may grant + * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite + * other users, modify room's subject plus all the partcipants privileges. + * + * @param nicknames the nicknames of the occupants to grant moderator privileges. + * @throws XMPPException if an error occurs granting moderator privileges to a user. + */ + public void grantModerator(Collection<String> nicknames) throws XMPPException { + changeRole(nicknames, "moderator"); + } + + /** + * Grants moderator privileges to a participant or visitor. Room administrators may grant + * moderator privileges. A moderator is allowed to kick users, grant and revoke voice, invite + * other users, modify room's subject plus all the partcipants privileges. + * + * @param nickname the nickname of the occupant to grant moderator privileges. + * @throws XMPPException if an error occurs granting moderator privileges to a user. + */ + public void grantModerator(String nickname) throws XMPPException { + changeRole(nickname, "moderator", null); + } + + /** + * Revokes moderator privileges from other users. The occupant that loses moderator + * privileges will become a participant. Room administrators may revoke moderator privileges + * only to occupants whose affiliation is member or none. This means that an administrator is + * not allowed to revoke moderator privileges from other room administrators or owners. + * + * @param nicknames the nicknames of the occupants to revoke moderator privileges. + * @throws XMPPException if an error occurs revoking moderator privileges from a user. + */ + public void revokeModerator(Collection<String> nicknames) throws XMPPException { + changeRole(nicknames, "participant"); + } + + /** + * Revokes moderator privileges from another user. The occupant that loses moderator + * privileges will become a participant. Room administrators may revoke moderator privileges + * only to occupants whose affiliation is member or none. This means that an administrator is + * not allowed to revoke moderator privileges from other room administrators or owners. + * + * @param nickname the nickname of the occupant to revoke moderator privileges. + * @throws XMPPException if an error occurs revoking moderator privileges from a user. + */ + public void revokeModerator(String nickname) throws XMPPException { + changeRole(nickname, "participant", null); + } + + /** + * Grants ownership privileges to other users. Room owners may grant ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * An owner is allowed to change defining room features as well as perform all administrative + * functions. + * + * @param jids the collection of bare XMPP user IDs of the users to grant ownership. + * @throws XMPPException if an error occurs granting ownership privileges to a user. + */ + public void grantOwnership(Collection<String> jids) throws XMPPException { + changeAffiliationByAdmin(jids, "owner"); + } + + /** + * Grants ownership privileges to another user. Room owners may grant ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * An owner is allowed to change defining room features as well as perform all administrative + * functions. + * + * @param jid the bare XMPP user ID of the user to grant ownership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting ownership privileges to a user. + */ + public void grantOwnership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "owner", null); + } + + /** + * Revokes ownership privileges from other users. The occupant that loses ownership + * privileges will become an administrator. Room owners may revoke ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * + * @param jids the bare XMPP user IDs of the users to revoke ownership. + * @throws XMPPException if an error occurs revoking ownership privileges from a user. + */ + public void revokeOwnership(Collection<String> jids) throws XMPPException { + changeAffiliationByAdmin(jids, "admin"); + } + + /** + * Revokes ownership privileges from another user. The occupant that loses ownership + * privileges will become an administrator. Room owners may revoke ownership privileges. + * Some room implementations will not allow to grant ownership privileges to other users. + * + * @param jid the bare XMPP user ID of the user to revoke ownership (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking ownership privileges from a user. + */ + public void revokeOwnership(String jid) throws XMPPException { + changeAffiliationByAdmin(jid, "admin", null); + } + + /** + * Grants administrator privileges to other users. Room owners may grant administrator + * privileges to a member or unaffiliated user. An administrator is allowed to perform + * administrative functions such as banning users and edit moderator list. + * + * @param jids the bare XMPP user IDs of the users to grant administrator privileges. + * @throws XMPPException if an error occurs granting administrator privileges to a user. + */ + public void grantAdmin(Collection<String> jids) throws XMPPException { + changeAffiliationByOwner(jids, "admin"); + } + + /** + * Grants administrator privileges to another user. Room owners may grant administrator + * privileges to a member or unaffiliated user. An administrator is allowed to perform + * administrative functions such as banning users and edit moderator list. + * + * @param jid the bare XMPP user ID of the user to grant administrator privileges + * (e.g. "user@host.org"). + * @throws XMPPException if an error occurs granting administrator privileges to a user. + */ + public void grantAdmin(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "admin"); + } + + /** + * Revokes administrator privileges from users. The occupant that loses administrator + * privileges will become a member. Room owners may revoke administrator privileges from + * a member or unaffiliated user. + * + * @param jids the bare XMPP user IDs of the user to revoke administrator privileges. + * @throws XMPPException if an error occurs revoking administrator privileges from a user. + */ + public void revokeAdmin(Collection<String> jids) throws XMPPException { + changeAffiliationByOwner(jids, "member"); + } + + /** + * Revokes administrator privileges from a user. The occupant that loses administrator + * privileges will become a member. Room owners may revoke administrator privileges from + * a member or unaffiliated user. + * + * @param jid the bare XMPP user ID of the user to revoke administrator privileges + * (e.g. "user@host.org"). + * @throws XMPPException if an error occurs revoking administrator privileges from a user. + */ + public void revokeAdmin(String jid) throws XMPPException { + changeAffiliationByOwner(jid, "member"); + } + + private void changeAffiliationByOwner(String jid, String affiliation) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new affiliation. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + item.setJid(jid); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByOwner(Collection<String> jids, String affiliation) + throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (String jid : jids) { + // Set the new affiliation. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + item.setJid(jid); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByAdmin(String jid, String affiliation, String reason) + throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new affiliation. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + item.setJid(jid); + if(reason != null) + item.setReason(reason); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeAffiliationByAdmin(Collection<String> jids, String affiliation) + throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (String jid : jids) { + // Set the new affiliation. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + item.setJid(jid); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeRole(String nickname, String role, String reason) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + // Set the new role. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + item.setNick(nickname); + item.setReason(reason); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + private void changeRole(Collection<String> nicknames, String role) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.SET); + for (String nickname : nicknames) { + // Set the new role. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + item.setNick(nickname); + iq.addItem(item); + } + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the change request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + IQ answer = (IQ) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Returns the number of occupants in the group chat.<p> + * + * Note: this value will only be accurate after joining the group chat, and + * may fluctuate over time. If you query this value directly after joining the + * group chat it may not be accurate, as it takes a certain amount of time for + * the server to send all presence packets to this client. + * + * @return the number of occupants in the group chat. + */ + public int getOccupantsCount() { + return occupantsMap.size(); + } + + /** + * Returns an Iterator (of Strings) for the list of fully qualified occupants + * in the group chat. For example, "conference@chat.jivesoftware.com/SomeUser". + * Typically, a client would only display the nickname of the occupant. To + * get the nickname from the fully qualified name, use the + * {@link org.jivesoftware.smack.util.StringUtils#parseResource(String)} method. + * Note: this value will only be accurate after joining the group chat, and may + * fluctuate over time. + * + * @return an Iterator for the occupants in the group chat. + */ + public Iterator<String> getOccupants() { + return Collections.unmodifiableList(new ArrayList<String>(occupantsMap.keySet())) + .iterator(); + } + + /** + * Returns the presence info for a particular user, or <tt>null</tt> if the user + * is not in the room.<p> + * + * @param user the room occupant to search for his presence. The format of user must + * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). + * @return the occupant's current presence, or <tt>null</tt> if the user is unavailable + * or if no presence information is available. + */ + public Presence getOccupantPresence(String user) { + return occupantsMap.get(user); + } + + /** + * Returns the Occupant information for a particular occupant, or <tt>null</tt> if the + * user is not in the room. The Occupant object may include information such as full + * JID of the user as well as the role and affiliation of the user in the room.<p> + * + * @param user the room occupant to search for his presence. The format of user must + * be: roomName@service/nickname (e.g. darkcave@macbeth.shakespeare.lit/thirdwitch). + * @return the Occupant or <tt>null</tt> if the user is unavailable (i.e. not in the room). + */ + public Occupant getOccupant(String user) { + Presence presence = occupantsMap.get(user); + if (presence != null) { + return new Occupant(presence); + } + return null; + } + + /** + * Adds a packet listener that will be notified of any new Presence packets + * sent to the group chat. Using a listener is a suitable way to know when the list + * of occupants should be re-loaded due to any changes. + * + * @param listener a packet listener that will be notified of any presence packets + * sent to the group chat. + */ + public void addParticipantListener(PacketListener listener) { + connection.addPacketListener(listener, presenceFilter); + connectionListeners.add(listener); + } + + /** + * Remoces a packet listener that was being notified of any new Presence packets + * sent to the group chat. + * + * @param listener a packet listener that was being notified of any presence packets + * sent to the group chat. + */ + public void removeParticipantListener(PacketListener listener) { + connection.removePacketListener(listener); + connectionListeners.remove(listener); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room owners. + * + * @return a collection of <code>Affiliate</code> with the room owners. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Affiliate> getOwners() throws XMPPException { + return getAffiliatesByAdmin("owner"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room administrators. + * + * @return a collection of <code>Affiliate</code> with the room administrators. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Affiliate> getAdmins() throws XMPPException { + return getAffiliatesByOwner("admin"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room members. + * + * @return a collection of <code>Affiliate</code> with the room members. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Affiliate> getMembers() throws XMPPException { + return getAffiliatesByAdmin("member"); + } + + /** + * Returns a collection of <code>Affiliate</code> with the room outcasts. + * + * @return a collection of <code>Affiliate</code> with the room outcasts. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Affiliate> getOutcasts() throws XMPPException { + return getAffiliatesByAdmin("outcast"); + } + + /** + * Returns a collection of <code>Affiliate</code> that have the specified room affiliation + * sending a request in the owner namespace. + * + * @param affiliation the affiliation of the users in the room. + * @return a collection of <code>Affiliate</code> that have the specified room affiliation. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection<Affiliate> getAffiliatesByOwner(String affiliation) throws XMPPException { + MUCOwner iq = new MUCOwner(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. + MUCOwner.Item item = new MUCOwner.Item(affiliation); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCOwner answer = (MUCOwner) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of affiliates from the server's answer + List<Affiliate> affiliates = new ArrayList<Affiliate>(); + for (Iterator it = answer.getItems(); it.hasNext();) { + affiliates.add(new Affiliate((MUCOwner.Item) it.next())); + } + return affiliates; + } + + /** + * Returns a collection of <code>Affiliate</code> that have the specified room affiliation + * sending a request in the admin namespace. + * + * @param affiliation the affiliation of the users in the room. + * @return a collection of <code>Affiliate</code> that have the specified room affiliation. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection<Affiliate> getAffiliatesByAdmin(String affiliation) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified affiliation. This may request the list of owners/admins/members/outcasts. + MUCAdmin.Item item = new MUCAdmin.Item(affiliation, null); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of affiliates from the server's answer + List<Affiliate> affiliates = new ArrayList<Affiliate>(); + for (Iterator it = answer.getItems(); it.hasNext();) { + affiliates.add(new Affiliate((MUCAdmin.Item) it.next())); + } + return affiliates; + } + + /** + * Returns a collection of <code>Occupant</code> with the room moderators. + * + * @return a collection of <code>Occupant</code> with the room moderators. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Occupant> getModerators() throws XMPPException { + return getOccupants("moderator"); + } + + /** + * Returns a collection of <code>Occupant</code> with the room participants. + * + * @return a collection of <code>Occupant</code> with the room participants. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + public Collection<Occupant> getParticipants() throws XMPPException { + return getOccupants("participant"); + } + + /** + * Returns a collection of <code>Occupant</code> that have the specified room role. + * + * @param role the role of the occupant in the room. + * @return a collection of <code>Occupant</code> that have the specified room role. + * @throws XMPPException if an error occured while performing the request to the server or you + * don't have enough privileges to get this information. + */ + private Collection<Occupant> getOccupants(String role) throws XMPPException { + MUCAdmin iq = new MUCAdmin(); + iq.setTo(room); + iq.setType(IQ.Type.GET); + // Set the specified role. This may request the list of moderators/participants. + MUCAdmin.Item item = new MUCAdmin.Item(null, role); + iq.addItem(item); + + // Wait for a response packet back from the server. + PacketFilter responseFilter = new PacketIDFilter(iq.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send the request to the server. + connection.sendPacket(iq); + // Wait up to a certain number of seconds for a reply. + MUCAdmin answer = (MUCAdmin) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + // Get the list of participants from the server's answer + List<Occupant> participants = new ArrayList<Occupant>(); + for (Iterator it = answer.getItems(); it.hasNext();) { + participants.add(new Occupant((MUCAdmin.Item) it.next())); + } + return participants; + } + + /** + * Sends a message to the chat room. + * + * @param text the text of the message to send. + * @throws XMPPException if sending the message fails. + */ + public void sendMessage(String text) throws XMPPException { + Message message = new Message(room, Message.Type.groupchat); + message.setBody(text); + connection.sendPacket(message); + } + + /** + * Returns a new Chat for sending private messages to a given room occupant. + * The Chat's occupant address is the room's JID (i.e. roomName@service/nick). The server + * service will change the 'from' address to the sender's room JID and delivering the message + * to the intended recipient's full JID. + * + * @param occupant occupant unique room JID (e.g. 'darkcave@macbeth.shakespeare.lit/Paul'). + * @param listener the listener is a message listener that will handle messages for the newly + * created chat. + * @return new Chat for sending private messages to a given room occupant. + */ + public Chat createPrivateChat(String occupant, MessageListener listener) { + return connection.getChatManager().createChat(occupant, listener); + } + + /** + * Creates a new Message to send to the chat room. + * + * @return a new Message addressed to the chat room. + */ + public Message createMessage() { + return new Message(room, Message.Type.groupchat); + } + + /** + * Sends a Message to the chat room. + * + * @param message the message. + * @throws XMPPException if sending the message fails. + */ + public void sendMessage(Message message) throws XMPPException { + connection.sendPacket(message); + } + + /** + * Polls for and returns the next message, or <tt>null</tt> if there isn't + * a message immediately available. This method provides significantly different + * functionalty than the {@link #nextMessage()} method since it's non-blocking. + * In other words, the method call will always return immediately, whereas the + * nextMessage method will return only when a message is available (or after + * a specific timeout). + * + * @return the next message if one is immediately available and + * <tt>null</tt> otherwise. + */ + public Message pollMessage() { + return (Message) messageCollector.pollResult(); + } + + /** + * Returns the next available message in the chat. The method call will block + * (not return) until a message is available. + * + * @return the next message. + */ + public Message nextMessage() { + return (Message) messageCollector.nextResult(); + } + + /** + * Returns the next available message in the chat. The method call will block + * (not return) until a packet is available or the <tt>timeout</tt> has elapased. + * If the timeout elapses without a result, <tt>null</tt> will be returned. + * + * @param timeout the maximum amount of time to wait for the next message. + * @return the next message, or <tt>null</tt> if the timeout elapses without a + * message becoming available. + */ + public Message nextMessage(long timeout) { + return (Message) messageCollector.nextResult(timeout); + } + + /** + * Adds a packet listener that will be notified of any new messages in the + * group chat. Only "group chat" messages addressed to this group chat will + * be delivered to the listener. If you wish to listen for other packets + * that may be associated with this group chat, you should register a + * PacketListener directly with the Connection with the appropriate + * PacketListener. + * + * @param listener a packet listener. + */ + public void addMessageListener(PacketListener listener) { + connection.addPacketListener(listener, messageFilter); + connectionListeners.add(listener); + } + + /** + * Removes a packet listener that was being notified of any new messages in the + * multi user chat. Only "group chat" messages addressed to this multi user chat were + * being delivered to the listener. + * + * @param listener a packet listener. + */ + public void removeMessageListener(PacketListener listener) { + connection.removePacketListener(listener); + connectionListeners.remove(listener); + } + + /** + * Changes the subject within the room. As a default, only users with a role of "moderator" + * are allowed to change the subject in a room. Although some rooms may be configured to + * allow a mere participant or even a visitor to change the subject. + * + * @param subject the new room's subject to set. + * @throws XMPPException if someone without appropriate privileges attempts to change the + * room subject will throw an error with code 403 (i.e. Forbidden) + */ + public void changeSubject(final String subject) throws XMPPException { + Message message = new Message(room, Message.Type.groupchat); + message.setSubject(subject); + // Wait for an error or confirmation message back from the server. + PacketFilter responseFilter = + new AndFilter( + new FromMatchesFilter(room), + new PacketTypeFilter(Message.class)); + responseFilter = new AndFilter(responseFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return subject.equals(msg.getSubject()); + } + }); + PacketCollector response = connection.createPacketCollector(responseFilter); + // Send change subject packet. + connection.sendPacket(message); + // Wait up to a certain number of seconds for a reply. + Message answer = + (Message) response.nextResult(SmackConfiguration.getPacketReplyTimeout()); + // Stop queuing results + response.cancel(); + + if (answer == null) { + throw new XMPPException("No response from server."); + } + else if (answer.getError() != null) { + throw new XMPPException(answer.getError()); + } + } + + /** + * Notification message that the user has joined the room. + */ + private synchronized void userHasJoined() { + // Update the list of joined rooms through this connection + List<String> rooms = joinedRooms.get(connection); + if (rooms == null) { + rooms = new ArrayList<String>(); + joinedRooms.put(connection, rooms); + } + rooms.add(room); + } + + /** + * Notification message that the user has left the room. + */ + private synchronized void userHasLeft() { + // Update the list of joined rooms through this connection + List<String> rooms = joinedRooms.get(connection); + if (rooms == null) { + return; + } + rooms.remove(room); + } + + /** + * Returns the MUCUser packet extension included in the packet or <tt>null</tt> if none. + * + * @param packet the packet that may include the MUCUser extension. + * @return the MUCUser found in the packet. + */ + private MUCUser getMUCUserExtension(Packet packet) { + if (packet != null) { + // Get the MUC User extension + return (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); + } + return null; + } + + /** + * Adds a listener that will be notified of changes in your status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a user status listener. + */ + public void addUserStatusListener(UserStatusListener listener) { + synchronized (userStatusListeners) { + if (!userStatusListeners.contains(listener)) { + userStatusListeners.add(listener); + } + } + } + + /** + * Removes a listener that was being notified of changes in your status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a user status listener. + */ + public void removeUserStatusListener(UserStatusListener listener) { + synchronized (userStatusListeners) { + userStatusListeners.remove(listener); + } + } + + private void fireUserStatusListeners(String methodName, Object[] params) { + UserStatusListener[] listeners; + synchronized (userStatusListeners) { + listeners = new UserStatusListener[userStatusListeners.size()]; + userStatusListeners.toArray(listeners); + } + // Get the classes of the method parameters + Class[] paramClasses = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + paramClasses[i] = params[i].getClass(); + } + try { + // Get the method to execute based on the requested methodName and parameters classes + Method method = UserStatusListener.class.getDeclaredMethod(methodName, paramClasses); + for (UserStatusListener listener : listeners) { + method.invoke(listener, params); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * Adds a listener that will be notified of changes in occupants status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a participant status listener. + */ + public void addParticipantStatusListener(ParticipantStatusListener listener) { + synchronized (participantStatusListeners) { + if (!participantStatusListeners.contains(listener)) { + participantStatusListeners.add(listener); + } + } + } + + /** + * Removes a listener that was being notified of changes in occupants status in the room + * such as the user being kicked, banned, or granted admin permissions. + * + * @param listener a participant status listener. + */ + public void removeParticipantStatusListener(ParticipantStatusListener listener) { + synchronized (participantStatusListeners) { + participantStatusListeners.remove(listener); + } + } + + private void fireParticipantStatusListeners(String methodName, List<String> params) { + ParticipantStatusListener[] listeners; + synchronized (participantStatusListeners) { + listeners = new ParticipantStatusListener[participantStatusListeners.size()]; + participantStatusListeners.toArray(listeners); + } + try { + // Get the method to execute based on the requested methodName and parameter + Class[] classes = new Class[params.size()]; + for (int i=0;i<params.size(); i++) { + classes[i] = String.class; + } + Method method = ParticipantStatusListener.class.getDeclaredMethod(methodName, classes); + for (ParticipantStatusListener listener : listeners) { + method.invoke(listener, params.toArray()); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private void init() { + // Create filters + messageFilter = + new AndFilter( + new FromMatchesFilter(room), + new MessageTypeFilter(Message.Type.groupchat)); + messageFilter = new AndFilter(messageFilter, new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getBody() != null; + } + }); + presenceFilter = + new AndFilter(new FromMatchesFilter(room), new PacketTypeFilter(Presence.class)); + + // Create a collector for incoming messages. + messageCollector = new ConnectionDetachedPacketCollector(); + + // Create a listener for subject updates. + PacketListener subjectListener = new PacketListener() { + public void processPacket(Packet packet) { + Message msg = (Message) packet; + // Update the room subject + subject = msg.getSubject(); + // Fire event for subject updated listeners + fireSubjectUpdatedListeners( + msg.getSubject(), + msg.getFrom()); + + } + }; + + // Create a listener for all presence updates. + PacketListener presenceListener = new PacketListener() { + public void processPacket(Packet packet) { + Presence presence = (Presence) packet; + String from = presence.getFrom(); + String myRoomJID = room + "/" + nickname; + boolean isUserStatusModification = presence.getFrom().equals(myRoomJID); + if (presence.getType() == Presence.Type.available) { + Presence oldPresence = occupantsMap.put(from, presence); + if (oldPresence != null) { + // Get the previous occupant's affiliation & role + MUCUser mucExtension = getMUCUserExtension(oldPresence); + String oldAffiliation = mucExtension.getItem().getAffiliation(); + String oldRole = mucExtension.getItem().getRole(); + // Get the new occupant's affiliation & role + mucExtension = getMUCUserExtension(presence); + String newAffiliation = mucExtension.getItem().getAffiliation(); + String newRole = mucExtension.getItem().getRole(); + // Fire role modification events + checkRoleModifications(oldRole, newRole, isUserStatusModification, from); + // Fire affiliation modification events + checkAffiliationModifications( + oldAffiliation, + newAffiliation, + isUserStatusModification, + from); + } + else { + // A new occupant has joined the room + if (!isUserStatusModification) { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("joined", params); + } + } + } + else if (presence.getType() == Presence.Type.unavailable) { + occupantsMap.remove(from); + MUCUser mucUser = getMUCUserExtension(presence); + if (mucUser != null && mucUser.getStatus() != null) { + // Fire events according to the received presence code + checkPresenceCode( + mucUser.getStatus().getCode(), + presence.getFrom().equals(myRoomJID), + mucUser, + from); + } else { + // An occupant has left the room + if (!isUserStatusModification) { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("left", params); + } + } + } + } + }; + + // Listens for all messages that include a MUCUser extension and fire the invitation + // rejection listeners if the message includes an invitation rejection. + PacketListener declinesListener = new PacketListener() { + public void processPacket(Packet packet) { + // Get the MUC User extension + MUCUser mucUser = getMUCUserExtension(packet); + // Check if the MUCUser informs that the invitee has declined the invitation + if (mucUser.getDecline() != null && + ((Message) packet).getType() != Message.Type.error) { + // Fire event for invitation rejection listeners + fireInvitationRejectionListeners( + mucUser.getDecline().getFrom(), + mucUser.getDecline().getReason()); + } + } + }; + + PacketMultiplexListener packetMultiplexor = new PacketMultiplexListener( + messageCollector, presenceListener, subjectListener, + declinesListener); + + roomListenerMultiplexor = RoomListenerMultiplexor.getRoomMultiplexor(connection); + + roomListenerMultiplexor.addRoom(room, packetMultiplexor); + } + + /** + * Fires notification events if the role of a room occupant has changed. If the occupant that + * changed his role is your occupant then the <code>UserStatusListeners</code> added to this + * <code>MultiUserChat</code> will be fired. On the other hand, if the occupant that changed + * his role is not yours then the <code>ParticipantStatusListeners</code> added to this + * <code>MultiUserChat</code> will be fired. The following table shows the events that will + * be fired depending on the previous and new role of the occupant. + * + * <pre> + * <table border="1"> + * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + * + * <tr><td>None</td><td>Visitor</td><td>--</td></tr> + * <tr><td>Visitor</td><td>Participant</td><td>voiceGranted</td></tr> + * <tr><td>Participant</td><td>Moderator</td><td>moderatorGranted</td></tr> + * + * <tr><td>None</td><td>Participant</td><td>voiceGranted</td></tr> + * <tr><td>None</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> + * <tr><td>Visitor</td><td>Moderator</td><td>voiceGranted + moderatorGranted</td></tr> + * + * <tr><td>Moderator</td><td>Participant</td><td>moderatorRevoked</td></tr> + * <tr><td>Participant</td><td>Visitor</td><td>voiceRevoked</td></tr> + * <tr><td>Visitor</td><td>None</td><td>kicked</td></tr> + * + * <tr><td>Moderator</td><td>Visitor</td><td>voiceRevoked + moderatorRevoked</td></tr> + * <tr><td>Moderator</td><td>None</td><td>kicked</td></tr> + * <tr><td>Participant</td><td>None</td><td>kicked</td></tr> + * </table> + * </pre> + * + * @param oldRole the previous role of the user in the room before receiving the new presence + * @param newRole the new role of the user in the room after receiving the new presence + * @param isUserModification whether the received presence is about your user in the room or not + * @param from the occupant whose role in the room has changed + * (e.g. room@conference.jabber.org/nick). + */ + private void checkRoleModifications( + String oldRole, + String newRole, + boolean isUserModification, + String from) { + // Voice was granted to a visitor + if (("visitor".equals(oldRole) || "none".equals(oldRole)) + && "participant".equals(newRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("voiceGranted", params); + } + } + // The participant's voice was revoked from the room + else if ( + "participant".equals(oldRole) + && ("visitor".equals(newRole) || "none".equals(newRole))) { + if (isUserModification) { + fireUserStatusListeners("voiceRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("voiceRevoked", params); + } + } + // Moderator privileges were granted to a participant + if (!"moderator".equals(oldRole) && "moderator".equals(newRole)) { + if ("visitor".equals(oldRole) || "none".equals(oldRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("voiceGranted", params); + } + } + if (isUserModification) { + fireUserStatusListeners("moderatorGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("moderatorGranted", params); + } + } + // Moderator privileges were revoked from a participant + else if ("moderator".equals(oldRole) && !"moderator".equals(newRole)) { + if ("visitor".equals(newRole) || "none".equals(newRole)) { + if (isUserModification) { + fireUserStatusListeners("voiceRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("voiceRevoked", params); + } + } + if (isUserModification) { + fireUserStatusListeners("moderatorRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("moderatorRevoked", params); + } + } + } + + /** + * Fires notification events if the affiliation of a room occupant has changed. If the + * occupant that changed his affiliation is your occupant then the + * <code>UserStatusListeners</code> added to this <code>MultiUserChat</code> will be fired. + * On the other hand, if the occupant that changed his affiliation is not yours then the + * <code>ParticipantStatusListeners</code> added to this <code>MultiUserChat</code> will be + * fired. The following table shows the events that will be fired depending on the previous + * and new affiliation of the occupant. + * + * <pre> + * <table border="1"> + * <tr><td><b>Old</b></td><td><b>New</b></td><td><b>Events</b></td></tr> + * + * <tr><td>None</td><td>Member</td><td>membershipGranted</td></tr> + * <tr><td>Member</td><td>Admin</td><td>membershipRevoked + adminGranted</td></tr> + * <tr><td>Admin</td><td>Owner</td><td>adminRevoked + ownershipGranted</td></tr> + * + * <tr><td>None</td><td>Admin</td><td>adminGranted</td></tr> + * <tr><td>None</td><td>Owner</td><td>ownershipGranted</td></tr> + * <tr><td>Member</td><td>Owner</td><td>membershipRevoked + ownershipGranted</td></tr> + * + * <tr><td>Owner</td><td>Admin</td><td>ownershipRevoked + adminGranted</td></tr> + * <tr><td>Admin</td><td>Member</td><td>adminRevoked + membershipGranted</td></tr> + * <tr><td>Member</td><td>None</td><td>membershipRevoked</td></tr> + * + * <tr><td>Owner</td><td>Member</td><td>ownershipRevoked + membershipGranted</td></tr> + * <tr><td>Owner</td><td>None</td><td>ownershipRevoked</td></tr> + * <tr><td>Admin</td><td>None</td><td>adminRevoked</td></tr> + * <tr><td><i>Anyone</i></td><td>Outcast</td><td>banned</td></tr> + * </table> + * </pre> + * + * @param oldAffiliation the previous affiliation of the user in the room before receiving the + * new presence + * @param newAffiliation the new affiliation of the user in the room after receiving the new + * presence + * @param isUserModification whether the received presence is about your user in the room or not + * @param from the occupant whose role in the room has changed + * (e.g. room@conference.jabber.org/nick). + */ + private void checkAffiliationModifications( + String oldAffiliation, + String newAffiliation, + boolean isUserModification, + String from) { + // First check for revoked affiliation and then for granted affiliations. The idea is to + // first fire the "revoke" events and then fire the "grant" events. + + // The user's ownership to the room was revoked + if ("owner".equals(oldAffiliation) && !"owner".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("ownershipRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("ownershipRevoked", params); + } + } + // The user's administrative privileges to the room were revoked + else if ("admin".equals(oldAffiliation) && !"admin".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("adminRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("adminRevoked", params); + } + } + // The user's membership to the room was revoked + else if ("member".equals(oldAffiliation) && !"member".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("membershipRevoked", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("membershipRevoked", params); + } + } + + // The user was granted ownership to the room + if (!"owner".equals(oldAffiliation) && "owner".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("ownershipGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("ownershipGranted", params); + } + } + // The user was granted administrative privileges to the room + else if (!"admin".equals(oldAffiliation) && "admin".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("adminGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("adminGranted", params); + } + } + // The user was granted membership to the room + else if (!"member".equals(oldAffiliation) && "member".equals(newAffiliation)) { + if (isUserModification) { + fireUserStatusListeners("membershipGranted", new Object[] {}); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + fireParticipantStatusListeners("membershipGranted", params); + } + } + } + + /** + * Fires events according to the received presence code. + * + * @param code + * @param isUserModification + * @param mucUser + * @param from + */ + private void checkPresenceCode( + String code, + boolean isUserModification, + MUCUser mucUser, + String from) { + // Check if an occupant was kicked from the room + if ("307".equals(code)) { + // Check if this occupant was kicked + if (isUserModification) { + joined = false; + + fireUserStatusListeners( + "kicked", + new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); + + // Reset occupant information. + occupantsMap.clear(); + nickname = null; + userHasLeft(); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + params.add(mucUser.getItem().getActor()); + params.add(mucUser.getItem().getReason()); + fireParticipantStatusListeners("kicked", params); + } + } + // A user was banned from the room + else if ("301".equals(code)) { + // Check if this occupant was banned + if (isUserModification) { + joined = false; + + fireUserStatusListeners( + "banned", + new Object[] { mucUser.getItem().getActor(), mucUser.getItem().getReason()}); + + // Reset occupant information. + occupantsMap.clear(); + nickname = null; + userHasLeft(); + } + else { + List<String> params = new ArrayList<String>(); + params.add(from); + params.add(mucUser.getItem().getActor()); + params.add(mucUser.getItem().getReason()); + fireParticipantStatusListeners("banned", params); + } + } + // A user's membership was revoked from the room + else if ("321".equals(code)) { + // Check if this occupant's membership was revoked + if (isUserModification) { + joined = false; + + fireUserStatusListeners("membershipRevoked", new Object[] {}); + + // Reset occupant information. + occupantsMap.clear(); + nickname = null; + userHasLeft(); + } + } + // A occupant has changed his nickname in the room + else if ("303".equals(code)) { + List<String> params = new ArrayList<String>(); + params.add(from); + params.add(mucUser.getItem().getNick()); + fireParticipantStatusListeners("nicknameChanged", params); + } + } + + protected void finalize() throws Throwable { + try { + if (connection != null) { + roomListenerMultiplexor.removeRoom(room); + // Remove all the PacketListeners added to the connection by this chat + for (PacketListener connectionListener : connectionListeners) { + connection.removePacketListener(connectionListener); + } + } + } + catch (Exception e) { + // Do nothing + } + super.finalize(); + } + + /** + * An InvitationsMonitor monitors a given connection to detect room invitations. Every + * time the InvitationsMonitor detects a new invitation it will fire the invitation listeners. + * + * @author Gaston Dombiak + */ + private static class InvitationsMonitor implements ConnectionListener { + // We use a WeakHashMap so that the GC can collect the monitor when the + // connection is no longer referenced by any object. + private final static Map<Connection, WeakReference<InvitationsMonitor>> monitors = + new WeakHashMap<Connection, WeakReference<InvitationsMonitor>>(); + + private final List<InvitationListener> invitationsListeners = + new ArrayList<InvitationListener>(); + private Connection connection; + private PacketFilter invitationFilter; + private PacketListener invitationPacketListener; + + /** + * Returns a new or existing InvitationsMonitor for a given connection. + * + * @param conn the connection to monitor for room invitations. + * @return a new or existing InvitationsMonitor for a given connection. + */ + public static InvitationsMonitor getInvitationsMonitor(Connection conn) { + synchronized (monitors) { + if (!monitors.containsKey(conn)) { + // We need to use a WeakReference because the monitor references the + // connection and this could prevent the GC from collecting the monitor + // when no other object references the monitor + monitors.put(conn, new WeakReference<InvitationsMonitor>(new InvitationsMonitor(conn))); + } + // Return the InvitationsMonitor that monitors the connection + return monitors.get(conn).get(); + } + } + + /** + * Creates a new InvitationsMonitor that will monitor invitations received + * on a given connection. + * + * @param connection the connection to monitor for possible room invitations + */ + private InvitationsMonitor(Connection connection) { + this.connection = connection; + } + + /** + * Adds a listener to invitation notifications. The listener will be fired anytime + * an invitation is received.<p> + * + * If this is the first monitor's listener then the monitor will be initialized in + * order to start listening to room invitations. + * + * @param listener an invitation listener. + */ + public void addInvitationListener(InvitationListener listener) { + synchronized (invitationsListeners) { + // If this is the first monitor's listener then initialize the listeners + // on the connection to detect room invitations + if (invitationsListeners.size() == 0) { + init(); + } + if (!invitationsListeners.contains(listener)) { + invitationsListeners.add(listener); + } + } + } + + /** + * Removes a listener to invitation notifications. The listener will be fired anytime + * an invitation is received.<p> + * + * If there are no more listeners to notifiy for room invitations then the monitor will + * be stopped. As soon as a new listener is added to the monitor, the monitor will resume + * monitoring the connection for new room invitations. + * + * @param listener an invitation listener. + */ + public void removeInvitationListener(InvitationListener listener) { + synchronized (invitationsListeners) { + if (invitationsListeners.contains(listener)) { + invitationsListeners.remove(listener); + } + // If there are no more listeners to notifiy for room invitations + // then proceed to cancel/release this monitor + if (invitationsListeners.size() == 0) { + cancel(); + } + } + } + + /** + * Fires invitation listeners. + */ + private void fireInvitationListeners(String room, String inviter, String reason, String password, + Message message) { + InvitationListener[] listeners; + synchronized (invitationsListeners) { + listeners = new InvitationListener[invitationsListeners.size()]; + invitationsListeners.toArray(listeners); + } + for (InvitationListener listener : listeners) { + listener.invitationReceived(connection, room, inviter, reason, password, message); + } + } + + public void connectionClosed() { + cancel(); + } + + public void connectionClosedOnError(Exception e) { + // ignore + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + /** + * Initializes the listeners to detect received room invitations and to detect when the + * connection gets closed. As soon as a room invitation is received the invitations + * listeners will be fired. When the connection gets closed the monitor will remove + * his listeners on the connection. + */ + private void init() { + // Listens for all messages that include a MUCUser extension and fire the invitation + // listeners if the message includes an invitation. + invitationFilter = + new PacketExtensionFilter("x", "http://jabber.org/protocol/muc#user"); + invitationPacketListener = new PacketListener() { + public void processPacket(Packet packet) { + // Get the MUCUser extension + MUCUser mucUser = + (MUCUser) packet.getExtension("x", "http://jabber.org/protocol/muc#user"); + // Check if the MUCUser extension includes an invitation + if (mucUser.getInvite() != null && + ((Message) packet).getType() != Message.Type.error) { + // Fire event for invitation listeners + fireInvitationListeners(packet.getFrom(), mucUser.getInvite().getFrom(), + mucUser.getInvite().getReason(), mucUser.getPassword(), (Message) packet); + } + } + }; + connection.addPacketListener(invitationPacketListener, invitationFilter); + // Add a listener to detect when the connection gets closed in order to + // cancel/release this monitor + connection.addConnectionListener(this); + } + + /** + * Cancels all the listeners that this InvitationsMonitor has added to the connection. + */ + private void cancel() { + connection.removePacketListener(invitationPacketListener); + connection.removeConnectionListener(this); + } + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java new file mode 100644 index 0000000..3b199a5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/Occupant.java @@ -0,0 +1,121 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.jivesoftware.smackx.packet.MUCUser; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + +/** + * Represents the information about an occupant in a given room. The information will always have + * the affiliation and role of the occupant in the room. The full JID and nickname are optional. + * + * @author Gaston Dombiak + */ +public class Occupant { + // Fields that must have a value + private String affiliation; + private String role; + // Fields that may have a value + private String jid; + private String nick; + + Occupant(MUCAdmin.Item item) { + super(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + this.nick = item.getNick(); + } + + Occupant(Presence presence) { + super(); + MUCUser mucUser = (MUCUser) presence.getExtension("x", + "http://jabber.org/protocol/muc#user"); + MUCUser.Item item = mucUser.getItem(); + this.jid = item.getJid(); + this.affiliation = item.getAffiliation(); + this.role = item.getRole(); + // Get the nickname from the FROM attribute of the presence + this.nick = StringUtils.parseResource(presence.getFrom()); + } + + /** + * Returns the full JID of the occupant. If this information was extracted from a presence and + * the room is semi or full-anonymous then the answer will be null. On the other hand, if this + * information was obtained while maintaining the voice list or the moderator list then we will + * always have a full JID. + * + * @return the full JID of the occupant. + */ + public String getJid() { + return jid; + } + + /** + * Returns the affiliation of the occupant. Possible affiliations are: "owner", "admin", + * "member", "outcast". This information will always be available. + * + * @return the affiliation of the occupant. + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the current role of the occupant in the room. This information will always be + * available. + * + * @return the current role of the occupant in the room. + */ + public String getRole() { + return role; + } + + /** + * Returns the current nickname of the occupant in the room. If this information was extracted + * from a presence then the answer will be null. + * + * @return the current nickname of the occupant in the room or null if this information was + * obtained from a presence. + */ + public String getNick() { + return nick; + } + + public boolean equals(Object obj) { + if(!(obj instanceof Occupant)) { + return false; + } + Occupant occupant = (Occupant)obj; + return jid.equals(occupant.jid); + } + + public int hashCode() { + int result; + result = affiliation.hashCode(); + result = 17 * result + role.hashCode(); + result = 17 * result + jid.hashCode(); + result = 17 * result + (nick != null ? nick.hashCode() : 0); + return result; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java new file mode 100644 index 0000000..c1863c2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/PacketMultiplexListener.java @@ -0,0 +1,96 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.PacketExtensionFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; + +/** + * The single <code>PacketListener</code> used by each {@link MultiUserChat} + * for all basic processing of presence, and message packets targeted to that chat. + * + * @author Larry Kirschner + */ +class PacketMultiplexListener implements PacketListener { + + private static final PacketFilter MESSAGE_FILTER = + new MessageTypeFilter(Message.Type.groupchat); + private static final PacketFilter PRESENCE_FILTER = new PacketTypeFilter(Presence.class); + private static final PacketFilter SUBJECT_FILTER = new PacketFilter() { + public boolean accept(Packet packet) { + Message msg = (Message) packet; + return msg.getSubject() != null; + } + }; + private static final PacketFilter DECLINES_FILTER = + new PacketExtensionFilter("x", + "http://jabber.org/protocol/muc#user"); + + private ConnectionDetachedPacketCollector messageCollector; + private PacketListener presenceListener; + private PacketListener subjectListener; + private PacketListener declinesListener; + + public PacketMultiplexListener( + ConnectionDetachedPacketCollector messageCollector, + PacketListener presenceListener, + PacketListener subjectListener, PacketListener declinesListener) { + if (messageCollector == null) { + throw new IllegalArgumentException("MessageCollector is null"); + } + if (presenceListener == null) { + throw new IllegalArgumentException("Presence listener is null"); + } + if (subjectListener == null) { + throw new IllegalArgumentException("Subject listener is null"); + } + if (declinesListener == null) { + throw new IllegalArgumentException("Declines listener is null"); + } + this.messageCollector = messageCollector; + this.presenceListener = presenceListener; + this.subjectListener = subjectListener; + this.declinesListener = declinesListener; + } + + public void processPacket(Packet p) { + if (PRESENCE_FILTER.accept(p)) { + presenceListener.processPacket(p); + } + else if (MESSAGE_FILTER.accept(p)) { + messageCollector.processPacket(p); + + if (SUBJECT_FILTER.accept(p)) { + subjectListener.processPacket(p); + } + } + else if (DECLINES_FILTER.accept(p)) { + declinesListener.processPacket(p); + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java new file mode 100644 index 0000000..c3e248d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/ParticipantStatusListener.java @@ -0,0 +1,179 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime a participant's status in a room is changed, such as the + * user being kicked, banned, or granted admin permissions. + * + * @author Gaston Dombiak + */ +public interface ParticipantStatusListener { + + /** + * Called when a new room occupant has joined the room. Note: Take in consideration that when + * you join a room you will receive the list of current occupants in the room. This message will + * be sent for each occupant. + * + * @param participant the participant that has just joined the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void joined(String participant); + + /** + * Called when a room occupant has left the room on its own. This means that the occupant was + * neither kicked nor banned from the room. + * + * @param participant the participant that has left the room on its own. + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void left(String participant); + + /** + * Called when a room participant has been kicked from the room. This means that the kicked + * participant is no longer participating in the room. + * + * @param participant the participant that was kicked from the room + * (e.g. room@conference.jabber.org/nick). + * @param actor the moderator that kicked the occupant from the room (e.g. user@host.org). + * @param reason the reason provided by the actor to kick the occupant from the room. + */ + public abstract void kicked(String participant, String actor, String reason); + + /** + * Called when a moderator grants voice to a visitor. This means that the visitor + * can now participate in the moderated room sending messages to all occupants. + * + * @param participant the participant that was granted voice in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void voiceGranted(String participant); + + /** + * Called when a moderator revokes voice from a participant. This means that the participant + * in the room was able to speak and now is a visitor that can't send messages to the room + * occupants. + * + * @param participant the participant that was revoked voice from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void voiceRevoked(String participant); + + /** + * Called when an administrator or owner banned a participant from the room. This means that + * banned participant will no longer be able to join the room unless the ban has been removed. + * + * @param participant the participant that was banned from the room + * (e.g. room@conference.jabber.org/nick). + * @param actor the administrator that banned the occupant (e.g. user@host.org). + * @param reason the reason provided by the administrator to ban the occupant. + */ + public abstract void banned(String participant, String actor, String reason); + + /** + * Called when an administrator grants a user membership to the room. This means that the user + * will be able to join the members-only room. + * + * @param participant the participant that was granted membership in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void membershipGranted(String participant); + + /** + * Called when an administrator revokes a user membership to the room. This means that the + * user will not be able to join the members-only room. + * + * @param participant the participant that was revoked membership from the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void membershipRevoked(String participant); + + /** + * Called when an administrator grants moderator privileges to a user. This means that the user + * will be able to kick users, grant and revoke voice, invite other users, modify room's + * subject plus all the partcipants privileges. + * + * @param participant the participant that was granted moderator privileges in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void moderatorGranted(String participant); + + /** + * Called when an administrator revokes moderator privileges from a user. This means that the + * user will no longer be able to kick users, grant and revoke voice, invite other users, + * modify room's subject plus all the partcipants privileges. + * + * @param participant the participant that was revoked moderator privileges in the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void moderatorRevoked(String participant); + + /** + * Called when an owner grants a user ownership on the room. This means that the user + * will be able to change defining room features as well as perform all administrative + * functions. + * + * @param participant the participant that was granted ownership on the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void ownershipGranted(String participant); + + /** + * Called when an owner revokes a user ownership on the room. This means that the user + * will no longer be able to change defining room features as well as perform all + * administrative functions. + * + * @param participant the participant that was revoked ownership on the room + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void ownershipRevoked(String participant); + + /** + * Called when an owner grants administrator privileges to a user. This means that the user + * will be able to perform administrative functions such as banning users and edit moderator + * list. + * + * @param participant the participant that was granted administrator privileges + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void adminGranted(String participant); + + /** + * Called when an owner revokes administrator privileges from a user. This means that the user + * will no longer be able to perform administrative functions such as banning users and edit + * moderator list. + * + * @param participant the participant that was revoked administrator privileges + * (e.g. room@conference.jabber.org/nick). + */ + public abstract void adminRevoked(String participant); + + /** + * Called when a participant changed his/her nickname in the room. The new participant's + * nickname will be informed with the next available presence. + * + * @param participant the participant that was revoked administrator privileges + * (e.g. room@conference.jabber.org/nick). + * @param newNickname the new nickname that the participant decided to use. + */ + public abstract void nicknameChanged(String participant, String newNickname); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java new file mode 100644 index 0000000..f97f544 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomInfo.java @@ -0,0 +1,190 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DiscoverInfo; + +import java.util.Iterator; + +/** + * Represents the room information that was discovered using Service Discovery. It's possible to + * obtain information about a room before joining the room but only for rooms that are public (i.e. + * rooms that may be discovered). + * + * @author Gaston Dombiak + */ +public class RoomInfo { + + /** + * JID of the room. The node of the JID is commonly used as the ID of the room or name. + */ + private String room; + /** + * Description of the room. + */ + private String description = ""; + /** + * Last known subject of the room. + */ + private String subject = ""; + /** + * Current number of occupants in the room. + */ + private int occupantsCount = -1; + /** + * A room is considered members-only if an invitation is required in order to enter the room. + * Any user that is not a member of the room won't be able to join the room unless the user + * decides to register with the room (thus becoming a member). + */ + private boolean membersOnly; + /** + * Moderated rooms enable only participants to speak. Users that join the room and aren't + * participants can't speak (they are just visitors). + */ + private boolean moderated; + /** + * Every presence packet can include the JID of every occupant unless the owner deactives this + * configuration. + */ + private boolean nonanonymous; + /** + * Indicates if users must supply a password to join the room. + */ + private boolean passwordProtected; + /** + * Persistent rooms are saved to the database to make sure that rooms configurations can be + * restored in case the server goes down. + */ + private boolean persistent; + + RoomInfo(DiscoverInfo info) { + super(); + this.room = info.getFrom(); + // Get the information based on the discovered features + this.membersOnly = info.containsFeature("muc_membersonly"); + this.moderated = info.containsFeature("muc_moderated"); + this.nonanonymous = info.containsFeature("muc_nonanonymous"); + this.passwordProtected = info.containsFeature("muc_passwordprotected"); + this.persistent = info.containsFeature("muc_persistent"); + // Get the information based on the discovered extended information + Form form = Form.getFormFrom(info); + if (form != null) { + FormField descField = form.getField("muc#roominfo_description"); + this.description = ( descField == null || !(descField.getValues().hasNext()) )? "" : descField.getValues().next(); + + FormField subjField = form.getField("muc#roominfo_subject"); + this.subject = ( subjField == null || !(subjField.getValues().hasNext()) ) ? "" : subjField.getValues().next(); + + FormField occCountField = form.getField("muc#roominfo_occupants"); + this.occupantsCount = occCountField == null ? -1 : Integer.parseInt(occCountField.getValues() + .next()); + } + } + + /** + * Returns the JID of the room whose information was discovered. + * + * @return the JID of the room whose information was discovered. + */ + public String getRoom() { + return room; + } + + /** + * Returns the discovered description of the room. + * + * @return the discovered description of the room. + */ + public String getDescription() { + return description; + } + + /** + * Returns the discovered subject of the room. The subject may be empty if the room does not + * have a subject. + * + * @return the discovered subject of the room. + */ + public String getSubject() { + return subject; + } + + /** + * Returns the discovered number of occupants that are currently in the room. If this + * information was not discovered (i.e. the server didn't send it) then a value of -1 will be + * returned. + * + * @return the number of occupants that are currently in the room or -1 if that information was + * not provided by the server. + */ + public int getOccupantsCount() { + return occupantsCount; + } + + /** + * Returns true if the room has restricted the access so that only members may enter the room. + * + * @return true if the room has restricted the access so that only members may enter the room. + */ + public boolean isMembersOnly() { + return membersOnly; + } + + /** + * Returns true if the room enabled only participants to speak. Occupants with a role of + * visitor won't be able to speak in the room. + * + * @return true if the room enabled only participants to speak. + */ + public boolean isModerated() { + return moderated; + } + + /** + * Returns true if presence packets will include the JID of every occupant. + * + * @return true if presence packets will include the JID of every occupant. + */ + public boolean isNonanonymous() { + return nonanonymous; + } + + /** + * Returns true if users musy provide a valid password in order to join the room. + * + * @return true if users musy provide a valid password in order to join the room. + */ + public boolean isPasswordProtected() { + return passwordProtected; + } + + /** + * Returns true if the room will persist after the last occupant have left the room. + * + * @return true if the room will persist after the last occupant have left the room. + */ + public boolean isPersistent() { + return persistent; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java new file mode 100644 index 0000000..6f8bf05 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/RoomListenerMultiplexor.java @@ -0,0 +1,227 @@ +/** + * $RCSfile$ + * $Revision: 2779 $ + * $Date: 2005-09-05 17:00:45 -0300 (Mon, 05 Sep 2005) $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +import org.jivesoftware.smack.ConnectionListener; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.StringUtils; + +import java.lang.ref.WeakReference; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A <code>RoomListenerMultiplexor</code> multiplexes incoming packets on + * a <code>Connection</code> using a single listener/filter pair. + * A single <code>RoomListenerMultiplexor</code> is created for each + * {@link org.jivesoftware.smack.Connection} that has joined MUC rooms + * within its session. + * + * @author Larry Kirschner + */ +class RoomListenerMultiplexor implements ConnectionListener { + + // We use a WeakHashMap so that the GC can collect the monitor when the + // connection is no longer referenced by any object. + private static final Map<Connection, WeakReference<RoomListenerMultiplexor>> monitors = + new WeakHashMap<Connection, WeakReference<RoomListenerMultiplexor>>(); + + private Connection connection; + private RoomMultiplexFilter filter; + private RoomMultiplexListener listener; + + /** + * Returns a new or existing RoomListenerMultiplexor for a given connection. + * + * @param conn the connection to monitor for room invitations. + * @return a new or existing RoomListenerMultiplexor for a given connection. + */ + public static RoomListenerMultiplexor getRoomMultiplexor(Connection conn) { + synchronized (monitors) { + if (!monitors.containsKey(conn) || monitors.get(conn).get() == null) { + RoomListenerMultiplexor rm = new RoomListenerMultiplexor(conn, new RoomMultiplexFilter(), + new RoomMultiplexListener()); + + rm.init(); + + // We need to use a WeakReference because the monitor references the + // connection and this could prevent the GC from collecting the monitor + // when no other object references the monitor + monitors.put(conn, new WeakReference<RoomListenerMultiplexor>(rm)); + } + // Return the InvitationsMonitor that monitors the connection + return monitors.get(conn).get(); + } + } + + /** + * All access should be through + * the static method {@link #getRoomMultiplexor(Connection)}. + */ + private RoomListenerMultiplexor(Connection connection, RoomMultiplexFilter filter, + RoomMultiplexListener listener) { + if (connection == null) { + throw new IllegalArgumentException("Connection is null"); + } + if (filter == null) { + throw new IllegalArgumentException("Filter is null"); + } + if (listener == null) { + throw new IllegalArgumentException("Listener is null"); + } + this.connection = connection; + this.filter = filter; + this.listener = listener; + } + + public void addRoom(String address, PacketMultiplexListener roomListener) { + filter.addRoom(address); + listener.addRoom(address, roomListener); + } + + public void connectionClosed() { + cancel(); + } + + public void connectionClosedOnError(Exception e) { + cancel(); + } + + public void reconnectingIn(int seconds) { + // ignore + } + + public void reconnectionSuccessful() { + // ignore + } + + public void reconnectionFailed(Exception e) { + // ignore + } + + /** + * Initializes the listeners to detect received room invitations and to detect when the + * connection gets closed. As soon as a room invitation is received the invitations + * listeners will be fired. When the connection gets closed the monitor will remove + * his listeners on the connection. + */ + public void init() { + connection.addConnectionListener(this); + connection.addPacketListener(listener, filter); + } + + public void removeRoom(String address) { + filter.removeRoom(address); + listener.removeRoom(address); + } + + /** + * Cancels all the listeners that this InvitationsMonitor has added to the connection. + */ + private void cancel() { + connection.removeConnectionListener(this); + connection.removePacketListener(listener); + } + + /** + * The single <code>Connection</code>-level <code>PacketFilter</code> used by a {@link RoomListenerMultiplexor} + * for all muc chat rooms on an <code>Connection</code>. + * Each time a muc chat room is added to/removed from an + * <code>Connection</code> the address for that chat room + * is added to/removed from that <code>Connection</code>'s + * <code>RoomMultiplexFilter</code>. + */ + private static class RoomMultiplexFilter implements PacketFilter { + + private Map<String, String> roomAddressTable = new ConcurrentHashMap<String, String>(); + + public boolean accept(Packet p) { + String from = p.getFrom(); + if (from == null) { + return false; + } + return roomAddressTable.containsKey(StringUtils.parseBareAddress(from).toLowerCase()); + } + + public void addRoom(String address) { + if (address == null) { + return; + } + roomAddressTable.put(address.toLowerCase(), address); + } + + public void removeRoom(String address) { + if (address == null) { + return; + } + roomAddressTable.remove(address.toLowerCase()); + } + } + + /** + * The single <code>Connection</code>-level <code>PacketListener</code> + * used by a {@link RoomListenerMultiplexor} + * for all muc chat rooms on an <code>Connection</code>. + * Each time a muc chat room is added to/removed from an + * <code>Connection</code> the address and listener for that chat room + * are added to/removed from that <code>Connection</code>'s + * <code>RoomMultiplexListener</code>. + * + * @author Larry Kirschner + */ + private static class RoomMultiplexListener implements PacketListener { + + private Map<String, PacketMultiplexListener> roomListenersByAddress = + new ConcurrentHashMap<String, PacketMultiplexListener>(); + + public void processPacket(Packet p) { + String from = p.getFrom(); + if (from == null) { + return; + } + + PacketMultiplexListener listener = + roomListenersByAddress.get(StringUtils.parseBareAddress(from).toLowerCase()); + + if (listener != null) { + listener.processPacket(p); + } + } + + public void addRoom(String address, PacketMultiplexListener listener) { + if (address == null) { + return; + } + roomListenersByAddress.put(address.toLowerCase(), listener); + } + + public void removeRoom(String address) { + if (address == null) { + return; + } + roomListenersByAddress.remove(address.toLowerCase()); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java new file mode 100644 index 0000000..3a2deab --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/SubjectUpdatedListener.java @@ -0,0 +1,38 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime a MUC room changes its subject. + * + * @author Gaston Dombiak + */ +public interface SubjectUpdatedListener { + + /** + * Called when a MUC room has changed its subject. + * + * @param subject the new room's subject. + * @param from the user that changed the room's subject (e.g. room@conference.jabber.org/nick). + */ + public abstract void subjectUpdated(String subject, String from); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java new file mode 100644 index 0000000..27f0f58 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/UserStatusListener.java @@ -0,0 +1,127 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.muc; + +/** + * A listener that is fired anytime your participant's status in a room is changed, such as the + * user being kicked, banned, or granted admin permissions. + * + * @author Gaston Dombiak + */ +public interface UserStatusListener { + + /** + * Called when a moderator kicked your user from the room. This means that you are no longer + * participanting in the room. + * + * @param actor the moderator that kicked your user from the room (e.g. user@host.org). + * @param reason the reason provided by the actor to kick you from the room. + */ + public abstract void kicked(String actor, String reason); + + /** + * Called when a moderator grants voice to your user. This means that you were a visitor in + * the moderated room before and now you can participate in the room by sending messages to + * all occupants. + * + */ + public abstract void voiceGranted(); + + /** + * Called when a moderator revokes voice from your user. This means that you were a + * participant in the room able to speak and now you are a visitor that can't send + * messages to the room occupants. + * + */ + public abstract void voiceRevoked(); + + /** + * Called when an administrator or owner banned your user from the room. This means that you + * will no longer be able to join the room unless the ban has been removed. + * + * @param actor the administrator that banned your user (e.g. user@host.org). + * @param reason the reason provided by the administrator to banned you. + */ + public abstract void banned(String actor, String reason); + + /** + * Called when an administrator grants your user membership to the room. This means that you + * will be able to join the members-only room. + * + */ + public abstract void membershipGranted(); + + /** + * Called when an administrator revokes your user membership to the room. This means that you + * will not be able to join the members-only room. + * + */ + public abstract void membershipRevoked(); + + /** + * Called when an administrator grants moderator privileges to your user. This means that you + * will be able to kick users, grant and revoke voice, invite other users, modify room's + * subject plus all the partcipants privileges. + * + */ + public abstract void moderatorGranted(); + + /** + * Called when an administrator revokes moderator privileges from your user. This means that + * you will no longer be able to kick users, grant and revoke voice, invite other users, + * modify room's subject plus all the partcipants privileges. + * + */ + public abstract void moderatorRevoked(); + + /** + * Called when an owner grants to your user ownership on the room. This means that you + * will be able to change defining room features as well as perform all administrative + * functions. + * + */ + public abstract void ownershipGranted(); + + /** + * Called when an owner revokes from your user ownership on the room. This means that you + * will no longer be able to change defining room features as well as perform all + * administrative functions. + * + */ + public abstract void ownershipRevoked(); + + /** + * Called when an owner grants administrator privileges to your user. This means that you + * will be able to perform administrative functions such as banning users and edit moderator + * list. + * + */ + public abstract void adminGranted(); + + /** + * Called when an owner revokes administrator privileges from your user. This means that you + * will no longer be able to perform administrative functions such as banning users and edit + * moderator list. + * + */ + public abstract void adminRevoked(); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html new file mode 100644 index 0000000..dcfaeaa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/muc/package.html @@ -0,0 +1 @@ +<body>Classes and Interfaces that implement Multi-User Chat (MUC).</body> \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html new file mode 100644 index 0000000..d574a2a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/package.html @@ -0,0 +1 @@ +<body>Smack extensions API.</body> \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java new file mode 100755 index 0000000..d0b49af --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AdHocCommandData.java @@ -0,0 +1,279 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2008 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.commands.AdHocCommand; +import org.jivesoftware.smackx.commands.AdHocCommand.Action; +import org.jivesoftware.smackx.commands.AdHocCommand.SpecificErrorCondition; +import org.jivesoftware.smackx.commands.AdHocCommandNote; + +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the state and the request of the execution of an adhoc command. + * + * @author Gabriel Guardincerri + */ +public class AdHocCommandData extends IQ { + + /* JID of the command host */ + private String id; + + /* Command name */ + private String name; + + /* Command identifier */ + private String node; + + /* Unique ID of the execution */ + private String sessionID; + + private List<AdHocCommandNote> notes = new ArrayList<AdHocCommandNote>(); + + private DataForm form; + + /* Action request to be executed */ + private AdHocCommand.Action action; + + /* Current execution status */ + private AdHocCommand.Status status; + + private ArrayList<AdHocCommand.Action> actions = new ArrayList<AdHocCommand.Action>(); + + private AdHocCommand.Action executeAction; + + private String lang; + + public AdHocCommandData() { + } + + @Override + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<command xmlns=\"http://jabber.org/protocol/commands\""); + buf.append(" node=\"").append(node).append("\""); + if (sessionID != null) { + if (!sessionID.equals("")) { + buf.append(" sessionid=\"").append(sessionID).append("\""); + } + } + if (status != null) { + buf.append(" status=\"").append(status).append("\""); + } + if (action != null) { + buf.append(" action=\"").append(action).append("\""); + } + + if (lang != null) { + if (!lang.equals("")) { + buf.append(" lang=\"").append(lang).append("\""); + } + } + buf.append(">"); + + if (getType() == Type.RESULT) { + buf.append("<actions"); + + if (executeAction != null) { + buf.append(" execute=\"").append(executeAction).append("\""); + } + if (actions.size() == 0) { + buf.append("/>"); + } else { + buf.append(">"); + + for (AdHocCommand.Action action : actions) { + buf.append("<").append(action).append("/>"); + } + buf.append("</actions>"); + } + } + + if (form != null) { + buf.append(form.toXML()); + } + + for (AdHocCommandNote note : notes) { + buf.append("<note type=\"").append(note.getType().toString()).append("\">"); + buf.append(note.getValue()); + buf.append("</note>"); + } + + // TODO ERRORS +// if (getError() != null) { +// buf.append(getError().toXML()); +// } + + buf.append("</command>"); + return buf.toString(); + } + + /** + * Returns the JID of the command host. + * + * @return the JID of the command host. + */ + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + /** + * Returns the human name of the command + * + * @return the name of the command. + */ + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + /** + * Returns the identifier of the command + * + * @return the node. + */ + public String getNode() { + return node; + } + + public void setNode(String node) { + this.node = node; + } + + /** + * Returns the list of notes that the command has. + * + * @return the notes. + */ + public List<AdHocCommandNote> getNotes() { + return notes; + } + + public void addNote(AdHocCommandNote note) { + this.notes.add(note); + } + + public void remveNote(AdHocCommandNote note) { + this.notes.remove(note); + } + + /** + * Returns the form of the command. + * + * @return the data form associated with the command. + */ + public DataForm getForm() { + return form; + } + + public void setForm(DataForm form) { + this.form = form; + } + + /** + * Returns the action to execute. The action is set only on a request. + * + * @return the action to execute. + */ + public AdHocCommand.Action getAction() { + return action; + } + + public void setAction(AdHocCommand.Action action) { + this.action = action; + } + + /** + * Returns the status of the execution. + * + * @return the status. + */ + public AdHocCommand.Status getStatus() { + return status; + } + + public void setStatus(AdHocCommand.Status status) { + this.status = status; + } + + public List<Action> getActions() { + return actions; + } + + public void addAction(Action action) { + actions.add(action); + } + + public void setExecuteAction(Action executeAction) { + this.executeAction = executeAction; + } + + public Action getExecuteAction() { + return executeAction; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + public String getSessionID() { + return sessionID; + } + + public static class SpecificError implements PacketExtension { + + public static final String namespace = "http://jabber.org/protocol/commands"; + + public SpecificErrorCondition condition; + + public SpecificError(SpecificErrorCondition condition) { + this.condition = condition; + } + + public String getElementName() { + return condition.toString(); + } + public String getNamespace() { + return namespace; + } + + public SpecificErrorCondition getCondition() { + return condition; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()); + buf.append(" xmlns=\"").append(getNamespace()).append("\"/>"); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java new file mode 100644 index 0000000..d86fa41 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/AttentionExtension.java @@ -0,0 +1,100 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2010 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A PacketExtension that implements XEP-0224: Attention + * + * This extension is expected to be added to message stanzas of type 'headline.' + * Please refer to the XEP for more implementation guidelines. + * + * @author Guus der Kinderen, guus.der.kinderen@gmail.com + * @see <a + * href="http://xmpp.org/extensions/xep-0224.html">XEP-0224: Attention</a> + */ +public class AttentionExtension implements PacketExtension { + + /** + * The XML element name of an 'attention' extension. + */ + public static final String ELEMENT_NAME = "attention"; + + /** + * The namespace that qualifies the XML element of an 'attention' extension. + */ + public static final String NAMESPACE = "urn:xmpp:attention:0"; + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#getElementName() + */ + public String getElementName() { + return ELEMENT_NAME; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace() + */ + public String getNamespace() { + return NAMESPACE; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#toXML() + */ + public String toXML() { + final StringBuilder sb = new StringBuilder(); + sb.append("<").append(getElementName()).append(" xmlns=\"").append( + getNamespace()).append("\"/>"); + return sb.toString(); + } + + /** + * A {@link PacketExtensionProvider} for the {@link AttentionExtension}. As + * Attention elements have no state/information other than the element name + * and namespace, this implementation simply returns new instances of + * {@link AttentionExtension}. + * + * @author Guus der Kinderen, guus.der.kinderen@gmail.com +s */ + public static class Provider implements PacketExtensionProvider { + + /* + * (non-Javadoc) + * + * @see + * org.jivesoftware.smack.provider.PacketExtensionProvider#parseExtension + * (org.xmlpull.v1.XmlPullParser) + */ + public PacketExtension parseExtension(XmlPullParser arg0) + throws Exception { + return new AttentionExtension(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java new file mode 100644 index 0000000..a7fc144 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/CapsExtension.java @@ -0,0 +1,83 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +public class CapsExtension implements PacketExtension { + + private String node, version, hash; + public static final String XMLNS = "http://jabber.org/protocol/caps"; + public static final String NODE_NAME = "c"; + + public CapsExtension() { + } + + public CapsExtension(String node, String version, String hash) { + this.node = node; + this.version = version; + this.hash = hash; + } + + public String getElementName() { + return NODE_NAME; + } + + public String getNamespace() { + return XMLNS; + } + + public String getNode() { + return node; + } + + public void setNode(String node) { + this.node = node; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + /* + * <c xmlns='http://jabber.org/protocol/caps' + * hash='sha-1' + * node='http://code.google.com/p/exodus' + * ver='QgayPKawpkPSDYmwT/WM94uAlu0='/> + * + */ + public String toXML() { + String xml = "<c xmlns='" + XMLNS + "' " + + "hash='" + hash + "' " + + "node='" + node + "' " + + "ver='" + version + "'/>"; + + return xml; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java new file mode 100644 index 0000000..ecc6acc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/ChatStateExtension.java @@ -0,0 +1,73 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smackx.ChatState; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * Represents a chat state which is an extension to message packets which is used to indicate + * the current status of a chat participant. + * + * @author Alexander Wenckus + * @see org.jivesoftware.smackx.ChatState + */ +public class ChatStateExtension implements PacketExtension { + + private ChatState state; + + /** + * Default constructor. The argument provided is the state that the extension will represent. + * + * @param state the state that the extension represents. + */ + public ChatStateExtension(ChatState state) { + this.state = state; + } + + public String getElementName() { + return state.name(); + } + + public String getNamespace() { + return "http://jabber.org/protocol/chatstates"; + } + + public String toXML() { + return "<" + getElementName() + " xmlns=\"" + getNamespace() + "\" />"; + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + ChatState state; + try { + state = ChatState.valueOf(parser.getName()); + } + catch (Exception ex) { + state = ChatState.active; + } + return new ChatStateExtension(state); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java new file mode 100644 index 0000000..c0c2630 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DataForm.java @@ -0,0 +1,296 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.FormField; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a form that could be use for gathering data as well as for reporting data + * returned from a search. + * + * @author Gaston Dombiak + */ +public class DataForm implements PacketExtension { + + private String type; + private String title; + private List<String> instructions = new ArrayList<String>(); + private ReportedData reportedData; + private final List<Item> items = new ArrayList<Item>(); + private final List<FormField> fields = new ArrayList<FormField>(); + + public DataForm(String type) { + this.type = type; + } + + /** + * Returns the meaning of the data within the context. The data could be part of a form + * to fill out, a form submission or data results.<p> + * + * Possible form types are: + * <ul> + * <li>form -> This packet contains a form to fill out. Display it to the user (if your + * program can).</li> + * <li>submit -> The form is filled out, and this is the data that is being returned from + * the form.</li> + * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> + * <li>result -> Data results being returned from a search, or some other query.</li> + * </ul> + * + * @return the form's type. + */ + public String getType() { + return type; + } + + /** + * Returns the description of the data. It is similar to the title on a web page or an X + * window. You can put a <title/> on either a form to fill out, or a set of data results. + * + * @return description of the data. + */ + public String getTitle() { + return title; + } + + /** + * Returns an Iterator for the list of instructions that explain how to fill out the form and + * what the form is about. The dataform could include multiple instructions since each + * instruction could not contain newlines characters. Join the instructions together in order + * to show them to the user. + * + * @return an Iterator for the list of instructions that explain how to fill out the form. + */ + public Iterator<String> getInstructions() { + synchronized (instructions) { + return Collections.unmodifiableList(new ArrayList<String>(instructions)).iterator(); + } + } + + /** + * Returns the fields that will be returned from a search. + * + * @return fields that will be returned from a search. + */ + public ReportedData getReportedData() { + return reportedData; + } + + /** + * Returns an Iterator for the items returned from a search. + * + * @return an Iterator for the items returned from a search. + */ + public Iterator<Item> getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList<Item>(items)).iterator(); + } + } + + /** + * Returns an Iterator for the fields that are part of the form. + * + * @return an Iterator for the fields that are part of the form. + */ + public Iterator<FormField> getFields() { + synchronized (fields) { + return Collections.unmodifiableList(new ArrayList<FormField>(fields)).iterator(); + } + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "jabber:x:data"; + } + + /** + * Sets the description of the data. It is similar to the title on a web page or an X window. + * You can put a <title/> on either a form to fill out, or a set of data results. + * + * @param title description of the data. + */ + public void setTitle(String title) { + this.title = title; + } + + /** + * Sets the list of instructions that explain how to fill out the form and what the form is + * about. The dataform could include multiple instructions since each instruction could not + * contain newlines characters. + * + * @param instructions list of instructions that explain how to fill out the form. + */ + public void setInstructions(List<String> instructions) { + this.instructions = instructions; + } + + /** + * Sets the fields that will be returned from a search. + * + * @param reportedData the fields that will be returned from a search. + */ + public void setReportedData(ReportedData reportedData) { + this.reportedData = reportedData; + } + + /** + * Adds a new field as part of the form. + * + * @param field the field to add to the form. + */ + public void addField(FormField field) { + synchronized (fields) { + fields.add(field); + } + } + + /** + * Adds a new instruction to the list of instructions that explain how to fill out the form + * and what the form is about. The dataform could include multiple instructions since each + * instruction could not contain newlines characters. + * + * @param instruction the new instruction that explain how to fill out the form. + */ + public void addInstruction(String instruction) { + synchronized (instructions) { + instructions.add(instruction); + } + } + + /** + * Adds a new item returned from a search. + * + * @param item the item returned from a search. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\" type=\"" + getType() +"\">"); + if (getTitle() != null) { + buf.append("<title>").append(getTitle()).append(""); + } + for (Iterator it=getInstructions(); it.hasNext();) { + buf.append("").append(it.next()).append(""); + } + // Append the list of fields returned from a search + if (getReportedData() != null) { + buf.append(getReportedData().toXML()); + } + // Loop through all the items returned from a search and append them to the string buffer + for (Iterator i = getItems(); i.hasNext();) { + Item item = (Item) i.next(); + buf.append(item.toXML()); + } + // Loop through all the form fields and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append(""); + return buf.toString(); + } + + /** + * + * Represents the fields that will be returned from a search. This information is useful when + * you try to use the jabber:iq:search namespace to return dynamic form information. + * + * @author Gaston Dombiak + */ + public static class ReportedData { + private List fields = new ArrayList(); + + public ReportedData(List fields) { + this.fields = fields; + } + + /** + * Returns the fields returned from a search. + * + * @return the fields returned from a search. + */ + public Iterator getFields() { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + // Loop through all the form items and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append(""); + return buf.toString(); + } + } + + /** + * + * Represents items of reported data. + * + * @author Gaston Dombiak + */ + public static class Item { + private List fields = new ArrayList(); + + public Item(List fields) { + this.fields = fields; + } + + /** + * Returns the fields that define the data that goes with the item. + * + * @return the fields that define the data that goes with the item. + */ + public Iterator getFields() { + return Collections.unmodifiableList(new ArrayList(fields)).iterator(); + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + // Loop through all the form items and append them to the string buffer + for (Iterator i = getFields(); i.hasNext();) { + FormField field = (FormField) i.next(); + buf.append(field.toXML()); + } + buf.append(""); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java new file mode 100644 index 0000000..f415f67 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DefaultPrivateData.java @@ -0,0 +1,137 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Default implementation of the PrivateData interface. Unless a PrivateDataProvider + * is registered with the PrivateDataManager class, instances of this class will be + * returned when getting private data.

      + * + * This class provides a very simple representation of an XML sub-document. Each element + * is a key in a Map with its CDATA being the value. For example, given the following + * XML sub-document: + * + *

      + * <foo xmlns="http://bar.com">
      + *     <color>blue</color>
      + *     <food>pizza</food>
      + * </foo>
      + * + * In this case, getValue("color") would return "blue", and getValue("food") would + * return "pizza". This parsing mechanism mechanism is very simplistic and will not work + * as desired in all cases (for example, if some of the elements have attributes. In those + * cases, a custom {@link org.jivesoftware.smackx.provider.PrivateDataProvider} should be used. + * + * @author Matt Tucker + */ +public class DefaultPrivateData implements PrivateData { + + private String elementName; + private String namespace; + private Map map; + + /** + * Creates a new generic private data object. + * + * @param elementName the name of the element of the XML sub-document. + * @param namespace the namespace of the element. + */ + public DefaultPrivateData(String elementName, String namespace) { + this.elementName = elementName; + this.namespace = namespace; + } + + /** + * Returns the XML element name of the private data sub-packet root element. + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return elementName; + } + + /** + * Returns the XML namespace of the private data sub-packet root element. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return namespace; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(elementName).append(" xmlns=\"").append(namespace).append("\">"); + for (Iterator i=getNames(); i.hasNext(); ) { + String name = (String)i.next(); + String value = getValue(name); + buf.append("<").append(name).append(">"); + buf.append(value); + buf.append(""); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns an Iterator for the names that can be used to get + * values of the private data. + * + * @return an Iterator for the names. + */ + public synchronized Iterator getNames() { + if (map == null) { + return Collections.EMPTY_LIST.iterator(); + } + return Collections.unmodifiableMap(new HashMap(map)).keySet().iterator(); + } + + /** + * Returns a value given a name. + * + * @param name the name. + * @return the value. + */ + public synchronized String getValue(String name) { + if (map == null) { + return null; + } + return (String)map.get(name); + } + + /** + * Sets a value given the name. + * + * @param name the name. + * @param value the value. + */ + public synchronized void setValue(String name, String value) { + if (map == null) { + map = new HashMap(); + } + map.put(name, value); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java new file mode 100644 index 0000000..f404971 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInfo.java @@ -0,0 +1,105 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import java.util.Date; + +import org.jivesoftware.smack.util.StringUtils; + +/** + * A decorator for the {@link DelayInformation} class to transparently support + * both the new Delay Delivery specification XEP-0203 and + * the old one XEP-0091. + * + * Existing code can be backward compatible. + * + * @author Robin Collier + */ +public class DelayInfo extends DelayInformation +{ + + DelayInformation wrappedInfo; + + /** + * Creates a new instance with given delay information. + * @param delay the delay information + */ + public DelayInfo(DelayInformation delay) + { + super(delay.getStamp()); + wrappedInfo = delay; + } + + @Override + public String getFrom() + { + return wrappedInfo.getFrom(); + } + + @Override + public String getReason() + { + return wrappedInfo.getReason(); + } + + @Override + public Date getStamp() + { + return wrappedInfo.getStamp(); + } + + @Override + public void setFrom(String from) + { + wrappedInfo.setFrom(from); + } + + @Override + public void setReason(String reason) + { + wrappedInfo.setReason(reason); + } + + @Override + public String getElementName() + { + return "delay"; + } + + @Override + public String getNamespace() + { + return "urn:xmpp:delay"; + } + + @Override + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\""); + buf.append(" stamp=\""); + buf.append(StringUtils.formatXEP0082Date(getStamp())); + buf.append("\""); + if (getFrom() != null && getFrom().length() > 0) { + buf.append(" from=\"").append(getFrom()).append("\""); + } + buf.append(">"); + if (getReason() != null && getReason().length() > 0) { + buf.append(getReason()); + } + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java new file mode 100644 index 0000000..b9ab485 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DelayInformation.java @@ -0,0 +1,149 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents timestamp information about data stored for later delivery. A DelayInformation will + * always includes the timestamp when the packet was originally sent and may include more + * information such as the JID of the entity that originally sent the packet as well as the reason + * for the delay.

      + * + * For more information see XEP-0091 + * and XEP-0203. + * + * @author Gaston Dombiak + */ +public class DelayInformation implements PacketExtension { + + /** + * Date format according to the obsolete XEP-0091 specification. + * XEP-0091 recommends to use this old format for date-time instead of + * the one specified in XEP-0082. + *

      + * Date formats are not synchronized. Since multiple threads access the format concurrently, + * it must be synchronized externally. + */ + public static final DateFormat XEP_0091_UTC_FORMAT = new SimpleDateFormat( + "yyyyMMdd'T'HH:mm:ss"); + static { + XEP_0091_UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private Date stamp; + private String from; + private String reason; + + /** + * Creates a new instance with the specified timestamp. + * @param stamp the timestamp + */ + public DelayInformation(Date stamp) { + super(); + this.stamp = stamp; + } + + /** + * Returns the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet or null if this information is not available. + * + * @return the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet. + */ + public String getFrom() { + return from; + } + + /** + * Sets the JID of the entity that originally sent the packet or that delayed the + * delivery of the packet or null if this information is not available. + * + * @param from the JID of the entity that originally sent the packet. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Returns the timestamp when the packet was originally sent. The returned Date is + * be understood as UTC. + * + * @return the timestamp when the packet was originally sent. + */ + public Date getStamp() { + return stamp; + } + + /** + * Returns a natural-language description of the reason for the delay or null if + * this information is not available. + * + * @return a natural-language description of the reason for the delay or null. + */ + public String getReason() { + return reason; + } + + /** + * Sets a natural-language description of the reason for the delay or null if + * this information is not available. + * + * @param reason a natural-language description of the reason for the delay or null. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "jabber:x:delay"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\""); + buf.append(" stamp=\""); + synchronized (XEP_0091_UTC_FORMAT) { + buf.append(XEP_0091_UTC_FORMAT.format(stamp)); + } + buf.append("\""); + if (from != null && from.length() > 0) { + buf.append(" from=\"").append(from).append("\""); + } + buf.append(">"); + if (reason != null && reason.length() > 0) { + buf.append(reason); + } + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java new file mode 100644 index 0000000..0291174 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceipt.java @@ -0,0 +1,54 @@ +/* + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents a message delivery receipt entry as specified by + * Message Delivery Receipts. + * + * @author Georg Lukas + */ +public class DeliveryReceipt implements PacketExtension +{ + public static final String NAMESPACE = "urn:xmpp:receipts"; + + private String id; /// original ID of the delivered message + + public DeliveryReceipt(String id) + { + this.id = id; + } + + public String getId() + { + return id; + } + + public String getElementName() + { + return "received"; + } + + public String getNamespace() + { + return NAMESPACE; + } + + public String toXML() + { + return ""; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java new file mode 100644 index 0000000..c58f108 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DeliveryReceiptRequest.java @@ -0,0 +1,40 @@ +/* + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents a message delivery receipt request entry as specified by + * Message Delivery Receipts. + * + * @author Georg Lukas + */ +public class DeliveryReceiptRequest implements PacketExtension +{ + public String getElementName() + { + return "request"; + } + + public String getNamespace() + { + return DeliveryReceipt.NAMESPACE; + } + + public String toXML() + { + return ""; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java new file mode 100644 index 0000000..2d8071e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverInfo.java @@ -0,0 +1,298 @@ +/** + * $RCSfile$ + * $Revision: 7071 $ + * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A DiscoverInfo IQ packet, which is used by XMPP clients to request and receive information + * to/from other XMPP entities.

      + * + * The received information may contain one or more identities of the requested XMPP entity, and + * a list of supported features by the requested XMPP entity. + * + * @author Gaston Dombiak + */ +public class DiscoverInfo extends IQ { + + private final List features = new CopyOnWriteArrayList(); + private final List identities = new CopyOnWriteArrayList(); + private String node; + + /** + * Adds a new feature to the discovered information. + * + * @param feature the discovered feature + */ + public void addFeature(String feature) { + addFeature(new Feature(feature)); + } + + private void addFeature(Feature feature) { + synchronized (features) { + features.add(feature); + } + } + + /** + * Returns the discovered features of an XMPP entity. + * + * @return an Iterator on the discovered features of an XMPP entity + */ + public Iterator getFeatures() { + synchronized (features) { + return Collections.unmodifiableList(features).iterator(); + } + } + + /** + * Adds a new identity of the requested entity to the discovered information. + * + * @param identity the discovered entity's identity + */ + public void addIdentity(Identity identity) { + synchronized (identities) { + identities.add(identity); + } + } + + /** + * Returns the discovered identities of an XMPP entity. + * + * @return an Iterator on the discoveted identities + */ + public Iterator getIdentities() { + synchronized (identities) { + return Collections.unmodifiableList(identities).iterator(); + } + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + /** + * Returns true if the specified feature is part of the discovered information. + * + * @param feature the feature to check + * @return true if the requestes feature has been discovered + */ + public boolean containsFeature(String feature) { + for (Iterator it = getFeatures(); it.hasNext();) { + if (feature.equals(it.next().getVar())) + return true; + } + return false; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (identities) { + for (Identity identity : identities) { + buf.append(identity.toXML()); + } + } + synchronized (features) { + for (Feature feature : features) { + buf.append(feature.toXML()); + } + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + public DiscoverInfo clone() { + DiscoverInfo d = new DiscoverInfo(); + + // Set node + d.setNode(getNode()); + + // Copy features + synchronized (features) { + for (Feature f : features) { + d.addFeature(f); + } + } + + // Copy identities + synchronized (identities) { + for (Identity i : identities) { + d.addIdentity(i); + } + } + + // Copy extensions + for (PacketExtension pe : getExtensions()) { + d.addExtension(pe); + } + + return d; + } + + /** + * Represents the identity of a given XMPP entity. An entity may have many identities but all + * the identities SHOULD have the same name.

      + * + * Refer to Jabber::Registrar + * in order to get the official registry of values for the category and type + * attributes. + * + */ + public static class Identity { + + private String category; + private String name; + private String type; + + /** + * Creates a new identity for an XMPP entity. + * + * @param category the entity's category. + * @param name the entity's name. + */ + public Identity(String category, String name) { + this.category = category; + this.name = name; + } + + /** + * Returns the entity's category. To get the official registry of values for the + * 'category' attribute refer to Jabber::Registrar + * + * @return the entity's category. + */ + public String getCategory() { + return category; + } + + /** + * Returns the identity's name. + * + * @return the identity's name. + */ + public String getName() { + return name; + } + + /** + * Returns the entity's type. To get the official registry of values for the + * 'type' attribute refer to Jabber::Registrar + * + * @return the entity's type. + */ + public String getType() { + return type; + } + + /** + * Sets the entity's type. To get the official registry of values for the + * 'type' attribute refer to Jabber::Registrar + * + * @param type the identity's type. + */ + public void setType(String type) { + this.type = type; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } + + /** + * Represents the features offered by the item. This information helps requestors determine + * what actions are possible with regard to this item (registration, search, join, etc.) + * as well as specific feature types of interest, if any (e.g., for the purpose of feature + * negotiation). + */ + public static class Feature { + + private String variable; + + /** + * Creates a new feature offered by an XMPP entity or item. + * + * @param variable the feature's variable. + */ + public Feature(String variable) { + this.variable = variable; + } + + /** + * Returns the feature's variable. + * + * @return the feature's variable. + */ + public String getVar() { + return variable; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java new file mode 100644 index 0000000..9120d7e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/DiscoverItems.java @@ -0,0 +1,237 @@ +/** + * $RCSfile$ + * $Revision: 7071 $ + * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * A DiscoverItems IQ packet, which is used by XMPP clients to request and receive items + * associated with XMPP entities.

      + * + * The items could also be queried in order to discover if they contain items inside. Some items + * may be addressable by its JID and others may require to be addressed by a JID and a node name. + * + * @author Gaston Dombiak + */ +public class DiscoverItems extends IQ { + + private final List items = new CopyOnWriteArrayList(); + private String node; + + /** + * Adds a new item to the discovered information. + * + * @param item the discovered entity's item + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + /** + * Returns the discovered items of the queried XMPP entity. + * + * @return an Iterator on the discovered entity's items + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(items).iterator(); + } + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (items) { + for (Item item : items) { + buf.append(item.toXML()); + } + } + buf.append(""); + return buf.toString(); + } + + /** + * An item is associated with an XMPP Entity, usually thought of a children of the parent + * entity and normally are addressable as a JID.

      + * + * An item associated with an entity may not be addressable as a JID. In order to handle + * such items, Service Discovery uses an optional 'node' attribute that supplements the + * 'jid' attribute. + */ + public static class Item { + + /** + * Request to create or update the item. + */ + public static final String UPDATE_ACTION = "update"; + + /** + * Request to remove the item. + */ + public static final String REMOVE_ACTION = "remove"; + + private String entityID; + private String name; + private String node; + private String action; + + /** + * Create a new Item associated with a given entity. + * + * @param entityID the id of the entity that contains the item + */ + public Item(String entityID) { + this.entityID = entityID; + } + + /** + * Returns the entity's ID. + * + * @return the entity's ID. + */ + public String getEntityID() { + return entityID; + } + + /** + * Returns the entity's name. + * + * @return the entity's name. + */ + public String getName() { + return name; + } + + /** + * Sets the entity's name. + * + * @param name the entity's name. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @return the node attribute that supplements the 'jid' attribute + */ + public String getNode() { + return node; + } + + /** + * Sets the node attribute that supplements the 'jid' attribute. A node is merely + * something that is associated with a JID and for which the JID can provide information.

      + * + * Node attributes SHOULD be used only when trying to provide or query information which + * is not directly addressable. + * + * @param node the node attribute that supplements the 'jid' attribute + */ + public void setNode(String node) { + this.node = node; + } + + /** + * Returns the action that specifies the action being taken for this item. Possible action + * values are: "update" and "remove". Update should either create a new entry if the node + * and jid combination does not already exist, or simply update an existing entry. If + * "remove" is used as the action, the item should be removed from persistent storage. + * + * @return the action being taken for this item + */ + public String getAction() { + return action; + } + + /** + * Sets the action that specifies the action being taken for this item. Possible action + * values are: "update" and "remove". Update should either create a new entry if the node + * and jid combination does not already exist, or simply update an existing entry. If + * "remove" is used as the action, the item should be removed from persistent storage. + * + * @param action the action being taken for this item + */ + public void setAction(String action) { + this.action = action; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java new file mode 100644 index 0000000..3fa8386 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Header.java @@ -0,0 +1,59 @@ +/* + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents a Header entry as specified by the Stanza Headers and Internet Metadata (SHIM) + + * @author Robin Collier + */ +public class Header implements PacketExtension +{ + private String name; + private String value; + + public Header(String name, String value) + { + this.name = name; + this.value = value; + } + + public String getName() + { + return name; + } + + public String getValue() + { + return value; + } + + public String getElementName() + { + return "header"; + } + + public String getNamespace() + { + return HeadersExtension.NAMESPACE; + } + + public String toXML() + { + return "

      " + value + "
      "; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java new file mode 100644 index 0000000..78564db --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/HeadersExtension.java @@ -0,0 +1,69 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import java.util.Collection; +import java.util.Collections; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Extension representing a list of headers as specified in Stanza Headers and Internet Metadata (SHIM) + * + * @see Header + * + * @author Robin Collier + */ +public class HeadersExtension implements PacketExtension +{ + public static final String NAMESPACE = "http://jabber.org/protocol/shim"; + + private Collection
      headers = Collections.EMPTY_LIST; + + public HeadersExtension(Collection
      headerList) + { + if (headerList != null) + headers = headerList; + } + + public Collection
      getHeaders() + { + return headers; + } + + public String getElementName() + { + return "headers"; + } + + public String getNamespace() + { + return NAMESPACE; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder("<" + getElementName() + " xmlns='" + getNamespace() + "'>"); + + for (Header header : headers) + { + builder.append(header.toXML()); + } + builder.append("'); + + return builder.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java new file mode 100644 index 0000000..30c616f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/LastActivity.java @@ -0,0 +1,157 @@ +/** + * $RCSfile$ + * $Revision: 2407 $ + * $Date: 2004-11-02 15:37:00 -0800 (Tue, 02 Nov 2004) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.xmlpull.v1.XmlPullParser; + +/** + * A last activity IQ for retrieving information about the last activity associated with a Jabber ID. + * LastActivity (JEP-012) allows for retrieval of how long a particular user has been idle and the + * message the specified when doing so. Use {@link org.jivesoftware.smackx.LastActivityManager} + * to get the last activity of a user. + * + * @author Derek DeMoro + */ +public class LastActivity extends IQ { + + public long lastActivity = -1; + public String message; + + public LastActivity() { + setType(IQ.Type.GET); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + + + public void setLastActivity(long lastActivity) { + this.lastActivity = lastActivity; + } + + + private void setMessage(String message) { + this.message = message; + } + + /** + * Returns number of seconds that have passed since the user last logged out. + * If the user is offline, 0 will be returned. + * + * @return the number of seconds that have passed since the user last logged out. + */ + public long getIdleTime() { + return lastActivity; + } + + + /** + * Returns the status message of the last unavailable presence received from the user. + * + * @return the status message of the last unavailable presence received from the user + */ + public String getStatusMessage() { + return message; + } + + + /** + * The IQ Provider for LastActivity. + * + * @author Derek DeMoro + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + LastActivity lastActivity = new LastActivity(); + try { + String seconds = parser.getAttributeValue("", "seconds"); + String message = parser.nextText(); + if (seconds != null) { + long xmlSeconds = new Double(seconds).longValue(); + lastActivity.setLastActivity((int)xmlSeconds); + } + + if (message != null) { + lastActivity.setMessage(message); + } + } + catch (Exception e) { + e.printStackTrace(); + } + return lastActivity; + } + } + + /** + * Retrieve the last activity of a particular jid. + * @param con the current Connection. + * @param jid the JID of the user. + * @return the LastActivity packet of the jid. + * @throws XMPPException thrown if a server error has occured. + * @deprecated This method only retreives the lapsed time since the last logout of a particular jid. + * Replaced by {@link org.jivesoftware.smackx.LastActivityManager#getLastActivity(Connection, String) getLastActivity} + */ + public static LastActivity getLastActivity(Connection con, String jid) throws XMPPException { + LastActivity activity = new LastActivity(); + jid = StringUtils.parseBareAddress(jid); + activity.setTo(jid); + + PacketCollector collector = con.createPacketCollector(new PacketIDFilter(activity.getPacketID())); + con.sendPacket(activity); + + LastActivity response = (LastActivity) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java new file mode 100644 index 0000000..5fd76a7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCAdmin.java @@ -0,0 +1,237 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; +import org.jivesoftware.smack.packet.IQ; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * IQ packet that serves for kicking users, granting and revoking voice, banning users, + * modifying the ban list, granting and revoking membership and granting and revoking + * moderator privileges. All these operations are scoped by the + * 'http://jabber.org/protocol/muc#admin' namespace. + * + * @author Gaston Dombiak + */ +public class MUCAdmin extends IQ { + + private List items = new ArrayList(); + + /** + * Returns an Iterator for item childs that holds information about roles, affiliation, + * jids and nicks. + * + * @return an Iterator for item childs that holds information about roles, affiliation, + * jids and nicks. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Adds an item child that holds information about roles, affiliation, jids and nicks. + * + * @param item the item child that holds information about roles, affiliation, jids and nicks. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + /** + * Item child that holds information about roles, affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + * @param role the privilege level of an occupant within a room. + */ + public Item(String affiliation, String role) { + this.affiliation = affiliation; + this.role = role; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + if (getActor() != null) { + buf.append(""); + } + buf.append(""); + } + return buf.toString(); + } + }; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java new file mode 100644 index 0000000..d3d2796 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCInitialPresence.java @@ -0,0 +1,223 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +/** + * Represents extended presence information whose sole purpose is to signal the ability of + * the occupant to speak the MUC protocol when joining a room. If the room requires a password + * then the MUCInitialPresence should include one.

      + * + * The amount of discussion history provided on entering a room (perhaps because the + * user is on a low-bandwidth connection or is using a small-footprint client) could be managed by + * setting a configured History instance to the MUCInitialPresence instance. + * @see MUCInitialPresence#setHistory(MUCInitialPresence.History). + * + * @author Gaston Dombiak + */ +public class MUCInitialPresence implements PacketExtension { + + private String password; + private History history; + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/muc"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getPassword() != null) { + buf.append("").append(getPassword()).append(""); + } + if (getHistory() != null) { + buf.append(getHistory().toXML()); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns the history that manages the amount of discussion history provided on + * entering a room. + * + * @return the history that manages the amount of discussion history provided on + * entering a room. + */ + public History getHistory() { + return history; + } + + /** + * Returns the password to use when the room requires a password. + * + * @return the password to use when the room requires a password. + */ + public String getPassword() { + return password; + } + + /** + * Sets the History that manages the amount of discussion history provided on + * entering a room. + * + * @param history that manages the amount of discussion history provided on + * entering a room. + */ + public void setHistory(History history) { + this.history = history; + } + + /** + * Sets the password to use when the room requires a password. + * + * @param password the password to use when the room requires a password. + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * The History class controls the number of characters or messages to receive + * when entering a room. + * + * @author Gaston Dombiak + */ + public static class History { + + private int maxChars = -1; + private int maxStanzas = -1; + private int seconds = -1; + private Date since; + + /** + * Returns the total number of characters to receive in the history. + * + * @return total number of characters to receive in the history. + */ + public int getMaxChars() { + return maxChars; + } + + /** + * Returns the total number of messages to receive in the history. + * + * @return the total number of messages to receive in the history. + */ + public int getMaxStanzas() { + return maxStanzas; + } + + /** + * Returns the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @return the number of seconds to use to filter the messages received during that time. + */ + public int getSeconds() { + return seconds; + } + + /** + * Returns the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @return the since date to use to filter the messages received during that time. + */ + public Date getSince() { + return since; + } + + /** + * Sets the total number of characters to receive in the history. + * + * @param maxChars the total number of characters to receive in the history. + */ + public void setMaxChars(int maxChars) { + this.maxChars = maxChars; + } + + /** + * Sets the total number of messages to receive in the history. + * + * @param maxStanzas the total number of messages to receive in the history. + */ + public void setMaxStanzas(int maxStanzas) { + this.maxStanzas = maxStanzas; + } + + /** + * Sets the number of seconds to use to filter the messages received during that time. + * In other words, only the messages received in the last "X" seconds will be included in + * the history. + * + * @param seconds the number of seconds to use to filter the messages received during + * that time. + */ + public void setSeconds(int seconds) { + this.seconds = seconds; + } + + /** + * Sets the since date to use to filter the messages received during that time. + * In other words, only the messages received since the datetime specified will be + * included in the history. + * + * @param since the since date to use to filter the messages received during that time. + */ + public void setSince(Date since) { + this.since = since; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java new file mode 100644 index 0000000..5fc3dd0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCOwner.java @@ -0,0 +1,342 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; +import org.jivesoftware.smack.packet.IQ; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * IQ packet that serves for granting and revoking ownership privileges, granting + * and revoking administrative privileges and destroying a room. All these operations + * are scoped by the 'http://jabber.org/protocol/muc#owner' namespace. + * + * @author Gaston Dombiak + */ +public class MUCOwner extends IQ { + + private List items = new ArrayList(); + private Destroy destroy; + + /** + * Returns an Iterator for item childs that holds information about affiliation, + * jids and nicks. + * + * @return an Iterator for item childs that holds information about affiliation, + * jids and nicks. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Returns a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @return a request to the server to destroy a room. + */ + public Destroy getDestroy() { + return destroy; + } + + /** + * Sets a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @param destroy the request to the server to destroy a room. + */ + public void setDestroy(Destroy destroy) { + this.destroy = destroy; + } + + /** + * Adds an item child that holds information about affiliation, jids and nicks. + * + * @param item the item child that holds information about affiliation, jids and nicks. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + if (getDestroy() != null) { + buf.append(getDestroy().toXML()); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + /** + * Item child that holds information about affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + */ + public Item(String affiliation) { + this.affiliation = affiliation; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + /** + * Sets the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @param role the new privilege level of an occupant within a room. + */ + public void setRole(String role) { + this.role = role; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + if (getActor() != null) { + buf.append(""); + } + buf.append(""); + } + return buf.toString(); + } + }; + + /** + * Represents a request to the server to destroy a room. The sender of the request + * should be the room's owner. If the sender of the destroy request is not the room's owner + * then the server will answer a "Forbidden" error. + * + * @author Gaston Dombiak + */ + public static class Destroy { + private String reason; + private String jid; + + + /** + * Returns the JID of an alternate location since the current room is being destroyed. + * + * @return the JID of an alternate location. + */ + public String getJid() { + return jid; + } + + /** + * Returns the reason for the room destruction. + * + * @return the reason for the room destruction. + */ + public String getReason() { + return reason; + } + + /** + * Sets the JID of an alternate location since the current room is being destroyed. + * + * @param jid the JID of an alternate location. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the reason for the room destruction. + * + * @param reason the reason for the room destruction. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + buf.append(""); + } + return buf.toString(); + } + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java new file mode 100644 index 0000000..bfcd67c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MUCUser.java @@ -0,0 +1,627 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents extended presence information about roles, affiliations, full JIDs, + * or status codes scoped by the 'http://jabber.org/protocol/muc#user' namespace. + * + * @author Gaston Dombiak + */ +public class MUCUser implements PacketExtension { + + private Invite invite; + private Decline decline; + private Item item; + private String password; + private Status status; + private Destroy destroy; + + public String getElementName() { + return "x"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/muc#user"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getInvite() != null) { + buf.append(getInvite().toXML()); + } + if (getDecline() != null) { + buf.append(getDecline().toXML()); + } + if (getItem() != null) { + buf.append(getItem().toXML()); + } + if (getPassword() != null) { + buf.append("").append(getPassword()).append(""); + } + if (getStatus() != null) { + buf.append(getStatus().toXML()); + } + if (getDestroy() != null) { + buf.append(getDestroy().toXML()); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns the invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @return an invitation for another user to a room. + */ + public Invite getInvite() { + return invite; + } + + /** + * Returns the rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @return a rejection to an invitation from another user to a room. + */ + public Decline getDecline() { + return decline; + } + + /** + * Returns the item child that holds information about roles, affiliation, jids and nicks. + * + * @return an item child that holds information about roles, affiliation, jids and nicks. + */ + public Item getItem() { + return item; + } + + /** + * Returns the password to use to enter Password-Protected Room. A Password-Protected Room is + * a room that a user cannot enter without first providing the correct password. + * + * @return the password to use to enter Password-Protected Room. + */ + public String getPassword() { + return password; + } + + /** + * Returns the status which holds a code that assists in presenting notification messages. + * + * @return the status which holds a code that assists in presenting notification messages. + */ + public Status getStatus() { + return status; + } + + /** + * Returns the notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @return a notification that the room has been destroyed. + */ + public Destroy getDestroy() { + return destroy; + } + + /** + * Sets the invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @param invite the invitation for another user to a room. + */ + public void setInvite(Invite invite) { + this.invite = invite; + } + + /** + * Sets the rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @param decline the rejection to an invitation from another user to a room. + */ + public void setDecline(Decline decline) { + this.decline = decline; + } + + /** + * Sets the item child that holds information about roles, affiliation, jids and nicks. + * + * @param item the item child that holds information about roles, affiliation, jids and nicks. + */ + public void setItem(Item item) { + this.item = item; + } + + /** + * Sets the password to use to enter Password-Protected Room. A Password-Protected Room is + * a room that a user cannot enter without first providing the correct password. + * + * @param string the password to use to enter Password-Protected Room. + */ + public void setPassword(String string) { + password = string; + } + + /** + * Sets the status which holds a code that assists in presenting notification messages. + * + * @param status the status which holds a code that assists in presenting notification + * messages. + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * Sets the notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @param destroy the notification that the room has been destroyed. + */ + public void setDestroy(Destroy destroy) { + this.destroy = destroy; + } + + /** + * Represents an invitation for another user to a room. The sender of the invitation + * must be an occupant of the room. The invitation will be sent to the room which in turn + * will forward the invitation to the invitee. + * + * @author Gaston Dombiak + */ + public static class Invite { + private String reason; + private String from; + private String to; + + /** + * Returns the bare JID of the inviter or, optionally, the room JID. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @return the room's occupant that sent the invitation. + */ + public String getFrom() { + return from; + } + + /** + * Returns the message explaining the invitation. + * + * @return the message explaining the invitation. + */ + public String getReason() { + return reason; + } + + /** + * Returns the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') + * + * @return the bare JID of the invitee. + */ + public String getTo() { + return to; + } + + /** + * Sets the bare JID of the inviter or, optionally, the room JID. (e.g. + * 'crone1@shakespeare.lit/desktop') + * + * @param from the bare JID of the inviter or, optionally, the room JID. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Sets the message explaining the invitation. + * + * @param reason the message explaining the invitation. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the bare JID of the invitee. (e.g. 'hecate@shakespeare.lit') + * + * @param to the bare JID of the invitee. + */ + public void setTo(String to) { + this.to = to; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + buf.append(""); + return buf.toString(); + } + } + + /** + * Represents a rejection to an invitation from another user to a room. The rejection will be + * sent to the room which in turn will forward the refusal to the inviter. + * + * @author Gaston Dombiak + */ + public static class Decline { + private String reason; + private String from; + private String to; + + /** + * Returns the bare JID of the invitee that rejected the invitation. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @return the bare JID of the invitee that rejected the invitation. + */ + public String getFrom() { + return from; + } + + /** + * Returns the message explaining why the invitation was rejected. + * + * @return the message explaining the reason for the rejection. + */ + public String getReason() { + return reason; + } + + /** + * Returns the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') + * + * @return the bare JID of the inviter. + */ + public String getTo() { + return to; + } + + /** + * Sets the bare JID of the invitee that rejected the invitation. (e.g. + * 'crone1@shakespeare.lit/desktop'). + * + * @param from the bare JID of the invitee that rejected the invitation. + */ + public void setFrom(String from) { + this.from = from; + } + + /** + * Sets the message explaining why the invitation was rejected. + * + * @param reason the message explaining the reason for the rejection. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the bare JID of the inviter. (e.g. 'hecate@shakespeare.lit') + * + * @param to the bare JID of the inviter. + */ + public void setTo(String to) { + this.to = to; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + buf.append(""); + return buf.toString(); + } + } + + /** + * Item child that holds information about roles, affiliation, jids and nicks. + * + * @author Gaston Dombiak + */ + public static class Item { + private String actor; + private String reason; + private String affiliation; + private String jid; + private String nick; + private String role; + + /** + * Creates a new item child. + * + * @param affiliation the actor's affiliation to the room + * @param role the privilege level of an occupant within a room. + */ + public Item(String affiliation, String role) { + this.affiliation = affiliation; + this.role = role; + } + + /** + * Returns the actor (JID of an occupant in the room) that was kicked or banned. + * + * @return the JID of an occupant in the room that was kicked or banned. + */ + public String getActor() { + return actor == null ? "" : actor; + } + + /** + * Returns the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @return the reason for the item child. + */ + public String getReason() { + return reason == null ? "" : reason; + } + + /** + * Returns the occupant's affiliation to the room. The affiliation is a semi-permanent + * association or connection with a room. The possible affiliations are "owner", "admin", + * "member", and "outcast" (naturally it is also possible to have no affiliation). An + * affiliation lasts across a user's visits to a room. + * + * @return the actor's affiliation to the room + */ + public String getAffiliation() { + return affiliation; + } + + /** + * Returns the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @return the room JID by which an occupant is identified within the room. + */ + public String getJid() { + return jid; + } + + /** + * Returns the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @return the new nickname of an occupant that is changing his/her nickname. + */ + public String getNick() { + return nick; + } + + /** + * Returns the temporary position or privilege level of an occupant within a room. The + * possible roles are "moderator", "participant", and "visitor" (it is also possible to + * have no defined role). A role lasts only for the duration of an occupant's visit to + * a room. + * + * @return the privilege level of an occupant within a room. + */ + public String getRole() { + return role; + } + + /** + * Sets the actor (JID of an occupant in the room) that was kicked or banned. + * + * @param actor the actor (JID of an occupant in the room) that was kicked or banned. + */ + public void setActor(String actor) { + this.actor = actor; + } + + /** + * Sets the reason for the item child. The reason is optional and could be used to + * explain the reason why a user (occupant) was kicked or banned. + * + * @param reason the reason why a user (occupant) was kicked or banned. + */ + public void setReason(String reason) { + this.reason = reason; + } + + /** + * Sets the by which an occupant is identified within the context + * of a room. If the room is non-anonymous, the JID will be included in the item. + * + * @param jid the JID by which an occupant is identified within a room. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the new nickname of an occupant that is changing his/her nickname. The new + * nickname is sent as part of the unavailable presence. + * + * @param nick the new nickname of an occupant that is changing his/her nickname. + */ + public void setNick(String nick) { + this.nick = nick; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + if (getActor() != null) { + buf.append(""); + } + buf.append(""); + } + return buf.toString(); + } + } + + /** + * Status code assists in presenting notification messages. The following link provides the + * list of existing error codes (@link http://www.jabber.org/jeps/jep-0045.html#errorstatus). + * + * @author Gaston Dombiak + */ + public static class Status { + private String code; + + /** + * Creates a new instance of Status with the specified code. + * + * @param code the code that uniquely identifies the reason of the error. + */ + public Status(String code) { + this.code = code; + } + + /** + * Returns the code that uniquely identifies the reason of the error. The code + * assists in presenting notification messages. + * + * @return the code that uniquely identifies the reason of the error. + */ + public String getCode() { + return code; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } + + /** + * Represents a notification that the room has been destroyed. After a room has been destroyed, + * the room occupants will receive a Presence packet of type 'unavailable' with the reason for + * the room destruction if provided by the room owner. + * + * @author Gaston Dombiak + */ + public static class Destroy { + private String reason; + private String jid; + + + /** + * Returns the JID of an alternate location since the current room is being destroyed. + * + * @return the JID of an alternate location. + */ + public String getJid() { + return jid; + } + + /** + * Returns the reason for the room destruction. + * + * @return the reason for the room destruction. + */ + public String getReason() { + return reason; + } + + /** + * Sets the JID of an alternate location since the current room is being destroyed. + * + * @param jid the JID of an alternate location. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Sets the reason for the room destruction. + * + * @param reason the reason for the room destruction. + */ + public void setReason(String reason) { + this.reason = reason; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + } + else { + buf.append(">"); + if (getReason() != null) { + buf.append("").append(getReason()).append(""); + } + buf.append(""); + } + return buf.toString(); + } + + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java new file mode 100644 index 0000000..91d959d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MessageEvent.java @@ -0,0 +1,335 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Represents message events relating to the delivery, display, composition and cancellation of + * messages.

      + * + * There are four message events currently defined in this namespace: + *

        + *
      1. Offline
        + * Indicates that the message has been stored offline by the intended recipient's server. This + * event is triggered only if the intended recipient's server supports offline storage, has that + * support enabled, and the recipient is offline when the server receives the message for delivery.
      2. + * + *
      3. Delivered
        + * Indicates that the message has been delivered to the recipient. This signifies that the message + * has reached the recipient's XMPP client, but does not necessarily mean that the message has + * been displayed. This event is to be raised by the XMPP client.
      4. + * + *
      5. Displayed
        + * Once the message has been received by the recipient's XMPP client, it may be displayed to the + * user. This event indicates that the message has been displayed, and is to be raised by the + * XMPP client. Even if a message is displayed multiple times, this event should be raised only + * once.
      6. + * + *
      7. Composing
        + * In threaded chat conversations, this indicates that the recipient is composing a reply to a + * message. The event is to be raised by the recipient's XMPP client. A XMPP client is allowed + * to raise this event multiple times in response to the same request, providing the original + * event is cancelled first.
      8. + *
      + * + * @author Gaston Dombiak + */ +public class MessageEvent implements PacketExtension { + + public static final String OFFLINE = "offline"; + public static final String COMPOSING = "composing"; + public static final String DISPLAYED = "displayed"; + public static final String DELIVERED = "delivered"; + public static final String CANCELLED = "cancelled"; + + private boolean offline = false; + private boolean delivered = false; + private boolean displayed = false; + private boolean composing = false; + private boolean cancelled = true; + + private String packetID = null; + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "x"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:event" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "jabber:x:event"; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the receiver is composing a reply. + * When the message is a notification returns if the receiver of the message is composing a + * reply. + * + * @return true if the sender is requesting to be notified when composing or when notifying + * that the receiver of the message is composing a reply + */ + public boolean isComposing() { + return composing; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the message is delivered. + * When the message is a notification returns if the message was delivered or not. + * + * @return true if the sender is requesting to be notified when delivered or when notifying + * that the message was delivered + */ + public boolean isDelivered() { + return delivered; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the message is displayed. + * When the message is a notification returns if the message was displayed or not. + * + * @return true if the sender is requesting to be notified when displayed or when notifying + * that the message was displayed + */ + public boolean isDisplayed() { + return displayed; + } + + /** + * When the message is a request returns if the sender of the message requests to be notified + * when the receiver of the message is offline. + * When the message is a notification returns if the receiver of the message was offline. + * + * @return true if the sender is requesting to be notified when offline or when notifying + * that the receiver of the message is offline + */ + public boolean isOffline() { + return offline; + } + + /** + * When the message is a notification returns if the receiver of the message cancelled + * composing a reply. + * + * @return true if the receiver of the message cancelled composing a reply + */ + public boolean isCancelled() { + return cancelled; + } + + /** + * Returns the unique ID of the message that requested to be notified of the event. + * The packet id is not used when the message is a request for notifications + * + * @return the message id that requested to be notified of the event. + */ + public String getPacketID() { + return packetID; + } + + /** + * Returns the types of events. The type of event could be: + * "offline", "composing","delivered","displayed", "offline" + * + * @return an iterator over all the types of events of the MessageEvent. + */ + public Iterator getEventTypes() { + ArrayList allEvents = new ArrayList(); + if (isDelivered()) { + allEvents.add(MessageEvent.DELIVERED); + } + if (!isMessageEventRequest() && isCancelled()) { + allEvents.add(MessageEvent.CANCELLED); + } + if (isComposing()) { + allEvents.add(MessageEvent.COMPOSING); + } + if (isDisplayed()) { + allEvents.add(MessageEvent.DISPLAYED); + } + if (isOffline()) { + allEvents.add(MessageEvent.OFFLINE); + } + return allEvents.iterator(); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the receiver is composing a reply. + * When the message is a notification sets if the receiver of the message is composing a + * reply. + * + * @param composing sets if the sender is requesting to be notified when composing or when + * notifying that the receiver of the message is composing a reply + */ + public void setComposing(boolean composing) { + this.composing = composing; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the message is delivered. + * When the message is a notification sets if the message was delivered or not. + * + * @param delivered sets if the sender is requesting to be notified when delivered or when + * notifying that the message was delivered + */ + public void setDelivered(boolean delivered) { + this.delivered = delivered; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the message is displayed. + * When the message is a notification sets if the message was displayed or not. + * + * @param displayed sets if the sender is requesting to be notified when displayed or when + * notifying that the message was displayed + */ + public void setDisplayed(boolean displayed) { + this.displayed = displayed; + setCancelled(false); + } + + /** + * When the message is a request sets if the sender of the message requests to be notified + * when the receiver of the message is offline. + * When the message is a notification sets if the receiver of the message was offline. + * + * @param offline sets if the sender is requesting to be notified when offline or when + * notifying that the receiver of the message is offline + */ + public void setOffline(boolean offline) { + this.offline = offline; + setCancelled(false); + } + + /** + * When the message is a notification sets if the receiver of the message cancelled + * composing a reply. + * The Cancelled event is never requested explicitly. It is requested implicitly when + * requesting to be notified of the Composing event. + * + * @param cancelled sets if the receiver of the message cancelled composing a reply + */ + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } + + /** + * Sets the unique ID of the message that requested to be notified of the event. + * The packet id is not used when the message is a request for notifications + * + * @param packetID the message id that requested to be notified of the event. + */ + public void setPacketID(String packetID) { + this.packetID = packetID; + } + + /** + * Returns true if this MessageEvent is a request for notifications. + * Returns false if this MessageEvent is a notification of an event. + * + * @return true if this message is a request for notifications. + */ + public boolean isMessageEventRequest() { + return this.packetID == null; + } + + /** + * Returns the XML representation of a Message Event according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following examples:

      + * + * Request to be notified when displayed: + *

      +     * <message
      +     *    to='romeo@montague.net/orchard'
      +     *    from='juliet@capulet.com/balcony'
      +     *    id='message22'>
      +     * <x xmlns='jabber:x:event'>
      +     *   <displayed/>
      +     * </x>
      +     * </message>
      +     * 
      + * + * Notification of displayed: + *
      +     * <message
      +     *    from='romeo@montague.net/orchard'
      +     *    to='juliet@capulet.com/balcony'>
      +     * <x xmlns='jabber:x:event'>
      +     *   <displayed/>
      +     *   <id>message22</id>
      +     * </x>
      +     * </message>
      +     * 
      + * + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Note: Cancellation events don't specify any tag. They just send the packetID + + // Add the offline tag if the sender requests to be notified of offline events or if + // the target is offline + if (isOffline()) + buf.append("<").append(MessageEvent.OFFLINE).append("/>"); + // Add the delivered tag if the sender requests to be notified when the message is + // delivered or if the target notifies that the message has been delivered + if (isDelivered()) + buf.append("<").append(MessageEvent.DELIVERED).append("/>"); + // Add the displayed tag if the sender requests to be notified when the message is + // displayed or if the target notifies that the message has been displayed + if (isDisplayed()) + buf.append("<").append(MessageEvent.DISPLAYED).append("/>"); + // Add the composing tag if the sender requests to be notified when the target is + // composing a reply or if the target notifies that he/she is composing a reply + if (isComposing()) + buf.append("<").append(MessageEvent.COMPOSING).append("/>"); + // Add the id tag only if the MessageEvent is a notification message (not a request) + if (getPacketID() != null) + buf.append("").append(getPacketID()).append(""); + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java new file mode 100644 index 0000000..3601a99 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/MultipleAddresses.java @@ -0,0 +1,205 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Packet extension that contains the list of addresses that a packet should be sent or was sent. + * + * @author Gaston Dombiak + */ +public class MultipleAddresses implements PacketExtension { + + public static final String BCC = "bcc"; + public static final String CC = "cc"; + public static final String NO_REPLY = "noreply"; + public static final String REPLY_ROOM = "replyroom"; + public static final String REPLY_TO = "replyto"; + public static final String TO = "to"; + + + private List addresses = new ArrayList(); + + /** + * Adds a new address to which the packet is going to be sent or was sent. + * + * @param type on of the static type (BCC, CC, NO_REPLY, REPLY_ROOM, etc.) + * @param jid the JID address of the recipient. + * @param node used to specify a sub-addressable unit at a particular JID, corresponding to + * a Service Discovery node. + * @param desc used to specify human-readable information for this address. + * @param delivered true when the packet was already delivered to this address. + * @param uri used to specify an external system address, such as a sip:, sips:, or im: URI. + */ + public void addAddress(String type, String jid, String node, String desc, boolean delivered, + String uri) { + // Create a new address with the specificed configuration + Address address = new Address(type); + address.setJid(jid); + address.setNode(node); + address.setDescription(desc); + address.setDelivered(delivered); + address.setUri(uri); + // Add the new address to the list of multiple recipients + addresses.add(address); + } + + /** + * Indicate that the packet being sent should not be replied. + */ + public void setNoReply() { + // Create a new address with the specificed configuration + Address address = new Address(NO_REPLY); + // Add the new address to the list of multiple recipients + addresses.add(address); + } + + /** + * Returns the list of addresses that matches the specified type. Examples of address + * type are: TO, CC, BCC, etc.. + * + * @param type Examples of address type are: TO, CC, BCC, etc. + * @return the list of addresses that matches the specified type. + */ + public List getAddressesOfType(String type) { + List answer = new ArrayList(addresses.size()); + for (Iterator it = addresses.iterator(); it.hasNext();) { + Address address = (Address) it.next(); + if (address.getType().equals(type)) { + answer.add(address); + } + } + + return answer; + } + + public String getElementName() { + return "addresses"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/address"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()); + buf.append(" xmlns=\"").append(getNamespace()).append("\">"); + // Loop through all the addresses and append them to the string buffer + for (Iterator i = addresses.iterator(); i.hasNext();) { + Address address = (Address) i.next(); + buf.append(address.toXML()); + } + buf.append(""); + return buf.toString(); + } + + public static class Address { + + private String type; + private String jid; + private String node; + private String description; + private boolean delivered; + private String uri; + + private Address(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public String getJid() { + return jid; + } + + private void setJid(String jid) { + this.jid = jid; + } + + public String getNode() { + return node; + } + + private void setNode(String node) { + this.node = node; + } + + public String getDescription() { + return description; + } + + private void setDescription(String description) { + this.description = description; + } + + public boolean isDelivered() { + return delivered; + } + + private void setDelivered(boolean delivered) { + this.delivered = delivered; + } + + public String getUri() { + return uri; + } + + private void setUri(String uri) { + this.uri = uri; + } + + private String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("
      0) { + buf.append(" desc=\""); + buf.append(description).append("\""); + } + if (delivered) { + buf.append(" delivered=\"true\""); + } + if (uri != null) { + buf.append(" uri=\""); + buf.append(uri).append("\""); + } + buf.append("/>"); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java new file mode 100644 index 0000000..9a64eaa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Nick.java @@ -0,0 +1,112 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A minimalistic implementation of a {@link PacketExtension} for nicknames. + * + * @author Guus der Kinderen, guus.der.kinderen@gmail.com + * @see XEP-0172: User Nickname + */ +public class Nick implements PacketExtension { + + public static final String NAMESPACE = "http://jabber.org/protocol/nick"; + + public static final String ELEMENT_NAME = "nick"; + + private String name = null; + + public Nick(String name) { + this.name = name; + } + + /** + * The value of this nickname + * + * @return the nickname + */ + public String getName() { + return name; + } + + /** + * Sets the value of this nickname + * + * @param name + * the name to set + */ + public void setName(String name) { + this.name = name; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#getElementName() + */ + public String getElementName() { + return ELEMENT_NAME; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#getNamespace() + */ + public String getNamespace() { + return NAMESPACE; + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.PacketExtension#toXML() + */ + public String toXML() { + final StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append( + NAMESPACE).append("\">"); + buf.append(getName()); + buf.append("'); + + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + + parser.next(); + final String name = parser.getText(); + + // Advance to end of extension. + while(parser.getEventType() != XmlPullParser.END_TAG) { + parser.next(); + } + + return new Nick(name); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java new file mode 100644 index 0000000..5f9954d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageInfo.java @@ -0,0 +1,128 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * OfflineMessageInfo is an extension included in the retrieved offline messages requested by + * the {@link org.jivesoftware.smackx.OfflineMessageManager}. This extension includes a stamp + * that uniquely identifies the offline message. This stamp may be used for deleting the offline + * message. The stamp may be of the form UTC timestamps but it is not required to have that format. + * + * @author Gaston Dombiak + */ +public class OfflineMessageInfo implements PacketExtension { + + private String node = null; + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "offline" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "offline"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/offline" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/offline"; + } + + /** + * Returns the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @return the stamp that uniquely identifies the offline message. + */ + public String getNode() { + return node; + } + + /** + * Sets the stamp that uniquely identifies the offline message. This stamp may + * be used for deleting the offline message. The stamp may be of the form UTC timestamps + * but it is not required to have that format. + * + * @param node the stamp that uniquely identifies the offline message. + */ + public void setNode(String node) { + this.node = node; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + if (getNode() != null) + buf.append(""); + buf.append(""); + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + /** + * Creates a new Provider. + * ProviderManager requires that every PacketExtensionProvider has a public, + * no-argument constructor + */ + public Provider() { + } + + /** + * Parses a OfflineMessageInfo packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + OfflineMessageInfo info = new OfflineMessageInfo(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) + info.setNode(parser.getAttributeValue("", "node")); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return info; + } + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java new file mode 100644 index 0000000..4357f40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/OfflineMessageRequest.java @@ -0,0 +1,237 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a request to get some or all the offline messages of a user. This class can also + * be used for deleting some or all the offline messages of a user. + * + * @author Gaston Dombiak + */ +public class OfflineMessageRequest extends IQ { + + private List items = new ArrayList(); + private boolean purge = false; + private boolean fetch = false; + + /** + * Returns an Iterator for item childs that holds information about offline messages to + * view or delete. + * + * @return an Iterator for item childs that holds information about offline messages to + * view or delete. + */ + public Iterator getItems() { + synchronized (items) { + return Collections.unmodifiableList(new ArrayList(items)).iterator(); + } + } + + /** + * Adds an item child that holds information about offline messages to view or delete. + * + * @param item the item child that holds information about offline messages to view or delete. + */ + public void addItem(Item item) { + synchronized (items) { + items.add(item); + } + } + + /** + * Returns true if all the offline messages of the user should be deleted. + * + * @return true if all the offline messages of the user should be deleted. + */ + public boolean isPurge() { + return purge; + } + + /** + * Sets if all the offline messages of the user should be deleted. + * + * @param purge true if all the offline messages of the user should be deleted. + */ + public void setPurge(boolean purge) { + this.purge = purge; + } + + /** + * Returns true if all the offline messages of the user should be retrieved. + * + * @return true if all the offline messages of the user should be retrieved. + */ + public boolean isFetch() { + return fetch; + } + + /** + * Sets if all the offline messages of the user should be retrieved. + * + * @param fetch true if all the offline messages of the user should be retrieved. + */ + public void setFetch(boolean fetch) { + this.fetch = fetch; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + synchronized (items) { + for (int i = 0; i < items.size(); i++) { + Item item = (Item) items.get(i); + buf.append(item.toXML()); + } + } + if (purge) { + buf.append(""); + } + if (fetch) { + buf.append(""); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + /** + * Item child that holds information about offline messages to view or delete. + * + * @author Gaston Dombiak + */ + public static class Item { + private String action; + private String jid; + private String node; + + /** + * Creates a new item child. + * + * @param node the actor's affiliation to the room + */ + public Item(String node) { + this.node = node; + } + + public String getNode() { + return node; + } + + /** + * Returns "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + * + * @return "view" or "remove" that indicate if the server should return the specified + * offline message or delete it. + */ + public String getAction() { + return action; + } + + /** + * Sets if the server should return the specified offline message or delete it. Possible + * values are "view" or "remove". + * + * @param action if the server should return the specified offline message or delete it. + */ + public void setAction(String action) { + this.action = action; + } + + public String getJid() { + return jid; + } + + public void setJid(String jid) { + this.jid = jid; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + return buf.toString(); + } + } + + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + OfflineMessageRequest request = new OfflineMessageRequest(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + request.addItem(parseItem(parser)); + } + else if (parser.getName().equals("purge")) { + request.setPurge(true); + } + else if (parser.getName().equals("fetch")) { + request.setFetch(true); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("offline")) { + done = true; + } + } + } + + return request; + } + + private Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + Item item = new Item(parser.getAttributeValue("", "node")); + item.setAction(parser.getAttributeValue("", "action")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java new file mode 100644 index 0000000..48f1de2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPEvent.java @@ -0,0 +1,105 @@ +/** + * $RCSfile: PEPEvent.java,v $ + * $Revision: 1.1 $ + * $Date: 2007/11/03 00:14:32 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents XMPP Personal Event Protocol packets.

      + * + * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client + * to subscribed clients (See XEP-163). + * + * @author Jeff Williams + */ +public class PEPEvent implements PacketExtension { + + PEPItem item; + + /** + * Creates a new empty roster exchange package. + * + */ + public PEPEvent() { + super(); + } + + /** + * Creates a new empty roster exchange package. + * + */ + public PEPEvent(PEPItem item) { + super(); + + this.item = item; + } + + public void addPEPItem(PEPItem item) { + this.item = item; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "event"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:roster" + * (which is not to be confused with the 'jabber:iq:roster' namespace + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/pubsub"; + } + + /** + * Returns the XML representation of a Personal Event Publish according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + *

      +     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      +     *     <subject>Any subject you want</subject>
      +     *     <body>This message contains roster items.</body>
      +     *     <x xmlns="jabber:x:roster">
      +     *         <item jid="gato1@gato.home"/>
      +     *         <item jid="gato2@gato.home"/>
      +     *     </x>
      +     * </message>
      +     * 
      + * + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(item.toXML()); + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java new file mode 100644 index 0000000..c3ff6f4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPItem.java @@ -0,0 +1,92 @@ +/** + * $RCSfile: PEPItem.java,v $ + * $Revision: 1.2 $ + * $Date: 2007/11/06 02:05:09 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents XMPP Personal Event Protocol packets.

      + * + * The 'http://jabber.org/protocol/pubsub#event' namespace is used to publish personal events items from one client + * to subscribed clients (See XEP-163). + * + * @author Jeff Williams + */ +public abstract class PEPItem implements PacketExtension { + + String id; + abstract String getNode(); + abstract String getItemDetailsXML(); + + /** + * Creates a new PEPItem. + * + */ + public PEPItem(String id) { + super(); + this.id = id; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "item"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/pubsub"; + } + + /** + * Returns the XML representation of a Personal Event Publish according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + *

      +     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      +     *     <subject>Any subject you want</subject>
      +     *     <body>This message contains roster items.</body>
      +     *     <x xmlns="jabber:x:roster">
      +     *         <item jid="gato1@gato.home"/>
      +     *         <item jid="gato2@gato.home"/>
      +     *     </x>
      +     * </message>
      +     * 
      + * + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" id=\"").append(id).append("\">"); + buf.append(getItemDetailsXML()); + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java new file mode 100644 index 0000000..420ce61 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PEPPubSub.java @@ -0,0 +1,95 @@ +/** + * $RCSfile: PEPPubSub.java,v $ + * $Revision: 1.2 $ + * $Date: 2007/11/03 04:46:52 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +/** + * Represents XMPP PEP/XEP-163 pubsub packets.

      + * + * The 'http://jabber.org/protocol/pubsub' namespace is used to publish personal events items from one client + * to subscribed clients (See XEP-163). + * + * @author Jeff Williams + */ +public class PEPPubSub extends IQ { + + PEPItem item; + + /** + * Creates a new PubSub. + * + */ + public PEPPubSub(PEPItem item) { + super(); + + this.item = item; + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "pubsub"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:roster" + * (which is not to be confused with the 'jabber:iq:roster' namespace + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/pubsub"; + } + + /** + * Returns the XML representation of a Personal Event Publish according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + *

      +     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      +     *     <subject>Any subject you want</subject>
      +     *     <body>This message contains roster items.</body>
      +     *     <x xmlns="jabber:x:roster">
      +     *         <item jid="gato1@gato.home"/>
      +     *         <item jid="gato2@gato.home"/>
      +     *     </x>
      +     * </message>
      +     * 
      + * + */ + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(""); + buf.append(item.toXML()); + buf.append(""); + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java new file mode 100644 index 0000000..3ddb7d5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/PrivateData.java @@ -0,0 +1,52 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +/** + * Interface to represent private data. Each private data chunk is an XML sub-document + * with a root element name and namespace. + * + * @see org.jivesoftware.smackx.PrivateDataManager + * @author Matt Tucker + */ +public interface PrivateData { + + /** + * Returns the root element name. + * + * @return the element name. + */ + public String getElementName(); + + /** + * Returns the root element XML namespace. + * + * @return the namespace. + */ + public String getNamespace(); + + /** + * Returns the XML reppresentation of the PrivateData. + * + * @return the private data as XML. + */ + public String toXML(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java new file mode 100644 index 0000000..3143991 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/RosterExchange.java @@ -0,0 +1,181 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.Roster; +import org.jivesoftware.smack.RosterEntry; +import org.jivesoftware.smack.RosterGroup; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.RemoteRosterEntry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents XMPP Roster Item Exchange packets.

      + * + * The 'jabber:x:roster' namespace (which is not to be confused with the 'jabber:iq:roster' + * namespace) is used to send roster items from one client to another. A roster item is sent by + * adding to the <message/> element an <x/> child scoped by the 'jabber:x:roster' namespace. This + * <x/> element may contain one or more <item/> children (one for each roster item to be sent).

      + * + * Each <item/> element may possess the following attributes:

      + * + * <jid/> -- The id of the contact being sent. This attribute is required.
      + * <name/> -- A natural-language nickname for the contact. This attribute is optional.

      + * + * Each <item/> element may also contain one or more <group/> children specifying the + * natural-language name of a user-specified group, for the purpose of categorizing this contact + * into one or more roster groups. + * + * @author Gaston Dombiak + */ +public class RosterExchange implements PacketExtension { + + private List remoteRosterEntries = new ArrayList(); + + /** + * Creates a new empty roster exchange package. + * + */ + public RosterExchange() { + super(); + } + + /** + * Creates a new roster exchange package with the entries specified in roster. + * + * @param roster the roster to send to other XMPP entity. + */ + public RosterExchange(Roster roster) { + // Add all the roster entries to the new RosterExchange + for (RosterEntry rosterEntry : roster.getEntries()) { + this.addRosterEntry(rosterEntry); + } + } + + /** + * Adds a roster entry to the packet. + * + * @param rosterEntry a roster entry to add. + */ + public void addRosterEntry(RosterEntry rosterEntry) { + // Obtain a String[] from the roster entry groups name + List groupNamesList = new ArrayList(); + String[] groupNames; + for (RosterGroup group : rosterEntry.getGroups()) { + groupNamesList.add(group.getName()); + } + groupNames = groupNamesList.toArray(new String[groupNamesList.size()]); + + // Create a new Entry based on the rosterEntry and add it to the packet + RemoteRosterEntry remoteRosterEntry = new RemoteRosterEntry(rosterEntry.getUser(), + rosterEntry.getName(), groupNames); + + addRosterEntry(remoteRosterEntry); + } + + /** + * Adds a remote roster entry to the packet. + * + * @param remoteRosterEntry a remote roster entry to add. + */ + public void addRosterEntry(RemoteRosterEntry remoteRosterEntry) { + synchronized (remoteRosterEntries) { + remoteRosterEntries.add(remoteRosterEntry); + } + } + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "x" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "x"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "jabber:x:roster" + * (which is not to be confused with the 'jabber:iq:roster' namespace + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "jabber:x:roster"; + } + + /** + * Returns an Iterator for the roster entries in the packet. + * + * @return an Iterator for the roster entries in the packet. + */ + public Iterator getRosterEntries() { + synchronized (remoteRosterEntries) { + List entries = Collections.unmodifiableList(new ArrayList(remoteRosterEntries)); + return entries.iterator(); + } + } + + /** + * Returns a count of the entries in the roster exchange. + * + * @return the number of entries in the roster exchange. + */ + public int getEntryCount() { + return remoteRosterEntries.size(); + } + + /** + * Returns the XML representation of a Roster Item Exchange according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + *

      +     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      +     *     <subject>Any subject you want</subject>
      +     *     <body>This message contains roster items.</body>
      +     *     <x xmlns="jabber:x:roster">
      +     *         <item jid="gato1@gato.home"/>
      +     *         <item jid="gato2@gato.home"/>
      +     *     </x>
      +     * </message>
      +     * 
      + * + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Loop through all roster entries and append them to the string buffer + for (Iterator i = getRosterEntries(); i.hasNext();) { + RemoteRosterEntry remoteRosterEntry = (RemoteRosterEntry) i.next(); + buf.append(remoteRosterEntry.toXML()); + } + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java new file mode 100644 index 0000000..ff0be41 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/SharedGroupsInfo.java @@ -0,0 +1,73 @@ +package org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * IQ packet used for discovering the user's shared groups and for getting the answer back + * from the server.

      + * + * Important note: This functionality is not part of the XMPP spec and it will only work + * with Wildfire. + * + * @author Gaston Dombiak + */ +public class SharedGroupsInfo extends IQ { + + private List groups = new ArrayList(); + + /** + * Returns a collection with the shared group names returned from the server. + * + * @return collection with the shared group names returned from the server. + */ + public List getGroups() { + return groups; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + for (Iterator it=groups.iterator(); it.hasNext();) { + buf.append("").append(it.next()).append(""); + } + buf.append(""); + return buf.toString(); + } + + /** + * Internal Search service Provider. + */ + public static class Provider implements IQProvider { + + /** + * Provider Constructor. + */ + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + SharedGroupsInfo groupsInfo = new SharedGroupsInfo(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("group")) { + groupsInfo.getGroups().add(parser.nextText()); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("sharedgroup")) { + done = true; + } + } + } + return groupsInfo; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java new file mode 100644 index 0000000..1659f19 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/StreamInitiation.java @@ -0,0 +1,419 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import java.util.Date; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.StringUtils; + +/** + * The process by which two entities initiate a stream. + * + * @author Alexander Wenckus + */ +public class StreamInitiation extends IQ { + + private String id; + + private String mimeType; + + private File file; + + private Feature featureNegotiation; + + /** + * The "id" attribute is an opaque identifier. This attribute MUST be + * present on type='set', and MUST be a valid string. This SHOULD NOT be + * sent back on type='result', since the "id" attribute provides the + * only context needed. This value is generated by the Sender, and the same + * value MUST be used throughout a session when talking to the Receiver. + * + * @param id The "id" attribute. + */ + public void setSesssionID(final String id) { + this.id = id; + } + + /** + * Uniquely identifies a stream initiation to the recipient. + * + * @return The "id" attribute. + * @see #setSesssionID(String) + */ + public String getSessionID() { + return id; + } + + /** + * The "mime-type" attribute identifies the MIME-type for the data across + * the stream. This attribute MUST be a valid MIME-type as registered with + * the Internet Assigned Numbers Authority (IANA) [3] (specifically, as + * listed at ). During + * negotiation, this attribute SHOULD be present, and is otherwise not + * required. If not included during negotiation, its value is assumed to be + * "binary/octect-stream". + * + * @param mimeType The valid mime-type. + */ + public void setMimeType(final String mimeType) { + this.mimeType = mimeType; + } + + /** + * Identifies the type of file that is desired to be transfered. + * + * @return The mime-type. + * @see #setMimeType(String) + */ + public String getMimeType() { + return mimeType; + } + + /** + * Sets the file which contains the information pertaining to the file to be + * transfered. + * + * @param file The file identified by the stream initiator to be sent. + */ + public void setFile(final File file) { + this.file = file; + } + + /** + * Returns the file containing the information about the request. + * + * @return Returns the file containing the information about the request. + */ + public File getFile() { + return file; + } + + /** + * Sets the data form which contains the valid methods of stream neotiation + * and transfer. + * + * @param form The dataform containing the methods. + */ + public void setFeatureNegotiationForm(final DataForm form) { + this.featureNegotiation = new Feature(form); + } + + /** + * Returns the data form which contains the valid methods of stream + * neotiation and transfer. + * + * @return Returns the data form which contains the valid methods of stream + * neotiation and transfer. + */ + public DataForm getFeatureNegotiationForm() { + return featureNegotiation.getData(); + } + + /* + * (non-Javadoc) + * + * @see org.jivesoftware.smack.packet.IQ#getChildElementXML() + */ + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + if (this.getType().equals(IQ.Type.SET)) { + buf.append(""); + + // Add the file section if there is one. + String fileXML = file.toXML(); + if (fileXML != null) { + buf.append(fileXML); + } + } + else if (this.getType().equals(IQ.Type.RESULT)) { + buf.append(""); + } + else { + throw new IllegalArgumentException("IQ Type not understood"); + } + if (featureNegotiation != null) { + buf.append(featureNegotiation.toXML()); + } + buf.append(""); + return buf.toString(); + } + + /** + *

        + *
      • size: The size, in bytes, of the data to be sent.
      • + *
      • name: The name of the file that the Sender wishes to send.
      • + *
      • date: The last modification time of the file. This is specified + * using the DateTime profile as described in Jabber Date and Time Profiles.
      • + *
      • hash: The MD5 sum of the file contents.
      • + *
      + *

      + *

      + * <desc> is used to provide a sender-generated description of the + * file so the receiver can better understand what is being sent. It MUST + * NOT be sent in the result. + *

      + *

      + * When <range> is sent in the offer, it should have no attributes. + * This signifies that the sender can do ranged transfers. When a Stream + * Initiation result is sent with the element, it uses these + * attributes: + *

      + *

        + *
      • offset: Specifies the position, in bytes, to start transferring the + * file data from. This defaults to zero (0) if not specified.
      • + *
      • length - Specifies the number of bytes to retrieve starting at + * offset. This defaults to the length of the file from offset to the end.
      • + *
      + *

      + *

      + * Both attributes are OPTIONAL on the <range> element. Sending no + * attributes is synonymous with not sending the <range> element. When + * no <range> element is sent in the Stream Initiation result, the + * Sender MUST send the complete file starting at offset 0. More generally, + * data is sent over the stream byte for byte starting at the offset + * position for the length specified. + * + * @author Alexander Wenckus + */ + public static class File implements PacketExtension { + + private final String name; + + private final long size; + + private String hash; + + private Date date; + + private String desc; + + private boolean isRanged; + + /** + * Constructor providing the name of the file and its size. + * + * @param name The name of the file. + * @param size The size of the file in bytes. + */ + public File(final String name, final long size) { + if (name == null) { + throw new NullPointerException("name cannot be null"); + } + + this.name = name; + this.size = size; + } + + /** + * Returns the file's name. + * + * @return Returns the file's name. + */ + public String getName() { + return name; + } + + /** + * Returns the file's size. + * + * @return Returns the file's size. + */ + public long getSize() { + return size; + } + + /** + * Sets the MD5 sum of the file's contents + * + * @param hash The MD5 sum of the file's contents. + */ + public void setHash(final String hash) { + this.hash = hash; + } + + /** + * Returns the MD5 sum of the file's contents + * + * @return Returns the MD5 sum of the file's contents + */ + public String getHash() { + return hash; + } + + /** + * Sets the date that the file was last modified. + * + * @param date The date that the file was last modified. + */ + public void setDate(Date date) { + this.date = date; + } + + /** + * Returns the date that the file was last modified. + * + * @return Returns the date that the file was last modified. + */ + public Date getDate() { + return date; + } + + /** + * Sets the description of the file. + * + * @param desc The description of the file so that the file reciever can + * know what file it is. + */ + public void setDesc(final String desc) { + this.desc = desc; + } + + /** + * Returns the description of the file. + * + * @return Returns the description of the file. + */ + public String getDesc() { + return desc; + } + + /** + * True if a range can be provided and false if it cannot. + * + * @param isRanged True if a range can be provided and false if it cannot. + */ + public void setRanged(final boolean isRanged) { + this.isRanged = isRanged; + } + + /** + * Returns whether or not the initiator can support a range for the file + * tranfer. + * + * @return Returns whether or not the initiator can support a range for + * the file tranfer. + */ + public boolean isRanged() { + return isRanged; + } + + public String getElementName() { + return "file"; + } + + public String getNamespace() { + return "http://jabber.org/protocol/si/profile/file-transfer"; + } + + public String toXML() { + StringBuilder buffer = new StringBuilder(); + + buffer.append("<").append(getElementName()).append(" xmlns=\"") + .append(getNamespace()).append("\" "); + + if (getName() != null) { + buffer.append("name=\"").append(StringUtils.escapeForXML(getName())).append("\" "); + } + + if (getSize() > 0) { + buffer.append("size=\"").append(getSize()).append("\" "); + } + + if (getDate() != null) { + buffer.append("date=\"").append(StringUtils.formatXEP0082Date(date)).append("\" "); + } + + if (getHash() != null) { + buffer.append("hash=\"").append(getHash()).append("\" "); + } + + if ((desc != null && desc.length() > 0) || isRanged) { + buffer.append(">"); + if (getDesc() != null && desc.length() > 0) { + buffer.append("").append(StringUtils.escapeForXML(getDesc())).append(""); + } + if (isRanged()) { + buffer.append(""); + } + buffer.append(""); + } + else { + buffer.append("/>"); + } + return buffer.toString(); + } + } + + /** + * The feature negotiation portion of the StreamInitiation packet. + * + * @author Alexander Wenckus + * + */ + public class Feature implements PacketExtension { + + private final DataForm data; + + /** + * The dataform can be provided as part of the constructor. + * + * @param data The dataform. + */ + public Feature(final DataForm data) { + this.data = data; + } + + /** + * Returns the dataform associated with the feature negotiation. + * + * @return Returns the dataform associated with the feature negotiation. + */ + public DataForm getData() { + return data; + } + + public String getNamespace() { + return "http://jabber.org/protocol/feature-neg"; + } + + public String getElementName() { + return "feature"; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf + .append(""); + buf.append(data.toXML()); + buf.append(""); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java new file mode 100644 index 0000000..5294e77 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Time.java @@ -0,0 +1,198 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * A Time IQ packet, which is used by XMPP clients to exchange their respective local + * times. Clients that wish to fully support the entitity time protocol should register + * a PacketListener for incoming time requests that then respond with the local time. + * This class can be used to request the time from other clients, such as in the + * following code snippet: + * + *

      + * // Request the time from a remote user.
      + * Time timeRequest = new Time();
      + * timeRequest.setType(IQ.Type.GET);
      + * timeRequest.setTo(someUser@example.com/resource);
      + *
      + * // Create a packet collector to listen for a response.
      + * PacketCollector collector = con.createPacketCollector(
      + *                new PacketIDFilter(timeRequest.getPacketID()));
      + *
      + * con.sendPacket(timeRequest);
      + *
      + * // Wait up to 5 seconds for a result.
      + * IQ result = (IQ)collector.nextResult(5000);
      + * if (result != null && result.getType() == IQ.Type.RESULT) {
      + *     Time timeResult = (Time)result;
      + *     // Do something with result...
      + * }

      + * + * Warning: this is an non-standard protocol documented by + * XEP-0090. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Matt Tucker + */ +public class Time extends IQ { + + private static SimpleDateFormat utcFormat = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + private static DateFormat displayFormat = DateFormat.getDateTimeInstance(); + + private String utc = null; + private String tz = null; + private String display = null; + + /** + * Creates a new Time instance with empty values for all fields. + */ + public Time() { + + } + + /** + * Creates a new Time instance using the specified calendar instance as + * the time value to send. + * + * @param cal the time value. + */ + public Time(Calendar cal) { + TimeZone timeZone = cal.getTimeZone(); + tz = cal.getTimeZone().getID(); + display = displayFormat.format(cal.getTime()); + // Convert local time to the UTC time. + utc = utcFormat.format(new Date( + cal.getTimeInMillis() - timeZone.getOffset(cal.getTimeInMillis()))); + } + + /** + * Returns the local time or null if the time hasn't been set. + * + * @return the lcocal time. + */ + public Date getTime() { + if (utc == null) { + return null; + } + Date date = null; + try { + Calendar cal = Calendar.getInstance(); + // Convert the UTC time to local time. + cal.setTime(new Date(utcFormat.parse(utc).getTime() + + cal.getTimeZone().getOffset(cal.getTimeInMillis()))); + date = cal.getTime(); + } + catch (Exception e) { + e.printStackTrace(); + } + return date; + } + + /** + * Sets the time using the local time. + * + * @param time the current local time. + */ + public void setTime(Date time) { + // Convert local time to UTC time. + utc = utcFormat.format(new Date( + time.getTime() - TimeZone.getDefault().getOffset(time.getTime()))); + } + + /** + * Returns the time as a UTC formatted String using the format CCYYMMDDThh:mm:ss. + * + * @return the time as a UTC formatted String. + */ + public String getUtc() { + return utc; + } + + /** + * Sets the time using UTC formatted String in the format CCYYMMDDThh:mm:ss. + * + * @param utc the time using a formatted String. + */ + public void setUtc(String utc) { + this.utc = utc; + + } + + /** + * Returns the time zone. + * + * @return the time zone. + */ + public String getTz() { + return tz; + } + + /** + * Sets the time zone. + * + * @param tz the time zone. + */ + public void setTz(String tz) { + this.tz = tz; + } + + /** + * Returns the local (non-utc) time in human-friendly format. + * + * @return the local time in human-friendly format. + */ + public String getDisplay() { + return display; + } + + /** + * Sets the local time in human-friendly format. + * + * @param display the local time in human-friendly format. + */ + public void setDisplay(String display) { + this.display = display; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (utc != null) { + buf.append("").append(utc).append(""); + } + if (tz != null) { + buf.append("").append(tz).append(""); + } + if (display != null) { + buf.append("").append(display).append(""); + } + buf.append(""); + return buf.toString(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java new file mode 100644 index 0000000..b4cd17c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/VCard.java @@ -0,0 +1,854 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.util.StringUtils; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A VCard class for use with the + * SMACK jabber library.

      + *

      + * You should refer to the + * JEP-54 documentation.

      + *

      + * Please note that this class is incomplete but it does provide the most commonly found + * information in vCards. Also remember that VCard transfer is not a standard, and the protocol + * may change or be replaced.

      + *

      + * Usage: + *

      + * 

      + * // To save VCard: + *

      + * VCard vCard = new VCard(); + * vCard.setFirstName("kir"); + * vCard.setLastName("max"); + * vCard.setEmailHome("foo@fee.bar"); + * vCard.setJabberId("jabber@id.org"); + * vCard.setOrganization("Jetbrains, s.r.o"); + * vCard.setNickName("KIR"); + *

      + * vCard.setField("TITLE", "Mr"); + * vCard.setAddressFieldHome("STREET", "Some street"); + * vCard.setAddressFieldWork("CTRY", "US"); + * vCard.setPhoneWork("FAX", "3443233"); + *

      + * vCard.save(connection); + *

      + * // To load VCard: + *

      + * VCard vCard = new VCard(); + * vCard.load(conn); // load own VCard + * vCard.load(conn, "joe@foo.bar"); // load someone's VCard + *

      + * + * @author Kirill Maximov (kir@maxkir.com) + */ +public class VCard extends IQ { + + /** + * Phone types: + * VOICE?, FAX?, PAGER?, MSG?, CELL?, VIDEO?, BBS?, MODEM?, ISDN?, PCS?, PREF? + */ + private Map homePhones = new HashMap(); + private Map workPhones = new HashMap(); + + + /** + * Address types: + * POSTAL?, PARCEL?, (DOM | INTL)?, PREF?, POBOX?, EXTADR?, STREET?, LOCALITY?, + * REGION?, PCODE?, CTRY? + */ + private Map homeAddr = new HashMap(); + private Map workAddr = new HashMap(); + + private String firstName; + private String lastName; + private String middleName; + + private String emailHome; + private String emailWork; + + private String organization; + private String organizationUnit; + + private String avatar; + + /** + * Such as DESC ROLE GEO etc.. see JEP-0054 + */ + private Map otherSimpleFields = new HashMap(); + + // fields that, as they are should not be escaped before forwarding to the server + private Map otherUnescapableFields = new HashMap(); + + public VCard() { + } + + /** + * Set generic VCard field. + * + * @param field value of field. Possible values: NICKNAME, PHOTO, BDAY, JABBERID, MAILER, TZ, + * GEO, TITLE, ROLE, LOGO, NOTE, PRODID, REV, SORT-STRING, SOUND, UID, URL, DESC. + */ + public String getField(String field) { + return otherSimpleFields.get(field); + } + + /** + * Set generic VCard field. + * + * @param value value of field + * @param field field to set. See {@link #getField(String)} + * @see #getField(String) + */ + public void setField(String field, String value) { + setField(field, value, false); + } + + /** + * Set generic, unescapable VCard field. If unescabale is set to true, XML maybe a part of the + * value. + * + * @param value value of field + * @param field field to set. See {@link #getField(String)} + * @param isUnescapable True if the value should not be escaped, and false if it should. + */ + public void setField(String field, String value, boolean isUnescapable) { + if (!isUnescapable) { + otherSimpleFields.put(field, value); + } + else { + otherUnescapableFields.put(field, value); + } + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + // Update FN field + updateFN(); + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + // Update FN field + updateFN(); + } + + public String getMiddleName() { + return middleName; + } + + public void setMiddleName(String middleName) { + this.middleName = middleName; + // Update FN field + updateFN(); + } + + public String getNickName() { + return otherSimpleFields.get("NICKNAME"); + } + + public void setNickName(String nickName) { + otherSimpleFields.put("NICKNAME", nickName); + } + + public String getEmailHome() { + return emailHome; + } + + public void setEmailHome(String email) { + this.emailHome = email; + } + + public String getEmailWork() { + return emailWork; + } + + public void setEmailWork(String emailWork) { + this.emailWork = emailWork; + } + + public String getJabberId() { + return otherSimpleFields.get("JABBERID"); + } + + public void setJabberId(String jabberId) { + otherSimpleFields.put("JABBERID", jabberId); + } + + public String getOrganization() { + return organization; + } + + public void setOrganization(String organization) { + this.organization = organization; + } + + public String getOrganizationUnit() { + return organizationUnit; + } + + public void setOrganizationUnit(String organizationUnit) { + this.organizationUnit = organizationUnit; + } + + /** + * Get home address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public String getAddressFieldHome(String addrField) { + return homeAddr.get(addrField); + } + + /** + * Set home address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public void setAddressFieldHome(String addrField, String value) { + homeAddr.put(addrField, value); + } + + /** + * Get work address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public String getAddressFieldWork(String addrField) { + return workAddr.get(addrField); + } + + /** + * Set work address field + * + * @param addrField one of POSTAL, PARCEL, (DOM | INTL), PREF, POBOX, EXTADR, STREET, + * LOCALITY, REGION, PCODE, CTRY + */ + public void setAddressFieldWork(String addrField, String value) { + workAddr.put(addrField, value); + } + + + /** + * Set home phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + * @param phoneNum phone number + */ + public void setPhoneHome(String phoneType, String phoneNum) { + homePhones.put(phoneType, phoneNum); + } + + /** + * Get home phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + */ + public String getPhoneHome(String phoneType) { + return homePhones.get(phoneType); + } + + /** + * Set work phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + * @param phoneNum phone number + */ + public void setPhoneWork(String phoneType, String phoneNum) { + workPhones.put(phoneType, phoneNum); + } + + /** + * Get work phone number + * + * @param phoneType one of VOICE, FAX, PAGER, MSG, CELL, VIDEO, BBS, MODEM, ISDN, PCS, PREF + */ + public String getPhoneWork(String phoneType) { + return workPhones.get(phoneType); + } + + /** + * Set the avatar for the VCard by specifying the url to the image. + * + * @param avatarURL the url to the image(png,jpeg,gif,bmp) + */ + public void setAvatar(URL avatarURL) { + byte[] bytes = new byte[0]; + try { + bytes = getBytes(avatarURL); + } + catch (IOException e) { + e.printStackTrace(); + } + + setAvatar(bytes); + } + + /** + * Specify the bytes for the avatar to use. + * + * @param bytes the bytes of the avatar. + */ + public void setAvatar(byte[] bytes) { + if (bytes == null) { + // Remove avatar (if any) from mappings + otherUnescapableFields.remove("PHOTO"); + return; + } + + // Otherwise, add to mappings. + String encodedImage = StringUtils.encodeBase64(bytes); + avatar = encodedImage; + + setField("PHOTO", "image/jpeg" + encodedImage + "", true); + } + + /** + * Specify the bytes for the avatar to use as well as the mime type. + * + * @param bytes the bytes of the avatar. + * @param mimeType the mime type of the avatar. + */ + public void setAvatar(byte[] bytes, String mimeType) { + if (bytes == null) { + // Remove avatar (if any) from mappings + otherUnescapableFields.remove("PHOTO"); + return; + } + + // Otherwise, add to mappings. + String encodedImage = StringUtils.encodeBase64(bytes); + avatar = encodedImage; + + setField("PHOTO", "" + mimeType + "" + encodedImage + "", true); + } + + /** + * Set the encoded avatar string. This is used by the provider. + * + * @param encodedAvatar the encoded avatar string. + */ + public void setEncodedImage(String encodedAvatar) { + //TODO Move VCard and VCardProvider into a vCard package. + this.avatar = encodedAvatar; + } + + /** + * Return the byte representation of the avatar(if one exists), otherwise returns null if + * no avatar could be found. + * Example 1 + *
      +     * // Load Avatar from VCard
      +     * byte[] avatarBytes = vCard.getAvatar();
      +     * 

      + * // To create an ImageIcon for Swing applications + * ImageIcon icon = new ImageIcon(avatar); + *

      + * // To create just an image object from the bytes + * ByteArrayInputStream bais = new ByteArrayInputStream(avatar); + * try { + * Image image = ImageIO.read(bais); + * } + * catch (IOException e) { + * e.printStackTrace(); + * } + *

      + * + * @return byte representation of avatar. + */ + public byte[] getAvatar() { + if (avatar == null) { + return null; + } + return StringUtils.decodeBase64(avatar); + } + + /** + * Common code for getting the bytes of a url. + * + * @param url the url to read. + */ + public static byte[] getBytes(URL url) throws IOException { + final String path = url.getPath(); + final File file = new File(path); + if (file.exists()) { + return getFileBytes(file); + } + + return null; + } + + private static byte[] getFileBytes(File file) throws IOException { + BufferedInputStream bis = null; + try { + bis = new BufferedInputStream(new FileInputStream(file)); + int bytes = (int) file.length(); + byte[] buffer = new byte[bytes]; + int readBytes = bis.read(buffer); + if (readBytes != buffer.length) { + throw new IOException("Entire file not read"); + } + return buffer; + } + finally { + if (bis != null) { + bis.close(); + } + } + } + + /** + * Returns the SHA-1 Hash of the Avatar image. + * + * @return the SHA-1 Hash of the Avatar image. + */ + public String getAvatarHash() { + byte[] bytes = getAvatar(); + if (bytes == null) { + return null; + } + + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-1"); + } + catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + + digest.update(bytes); + return StringUtils.encodeHex(digest.digest()); + } + + private void updateFN() { + StringBuilder sb = new StringBuilder(); + if (firstName != null) { + sb.append(StringUtils.escapeForXML(firstName)).append(' '); + } + if (middleName != null) { + sb.append(StringUtils.escapeForXML(middleName)).append(' '); + } + if (lastName != null) { + sb.append(StringUtils.escapeForXML(lastName)); + } + setField("FN", sb.toString()); + } + + /** + * Save this vCard for the user connected by 'connection'. Connection should be authenticated + * and not anonymous.

      + *

      + * NOTE: the method is asynchronous and does not wait for the returned value. + * + * @param connection the Connection to use. + * @throws XMPPException thrown if there was an issue setting the VCard in the server. + */ + public void save(Connection connection) throws XMPPException { + checkAuthenticated(connection, true); + + setType(IQ.Type.SET); + setFrom(connection.getUser()); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(getPacketID())); + connection.sendPacket(this); + + Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } + + /** + * Load VCard information for a connected user. Connection should be authenticated + * and not anonymous. + */ + public void load(Connection connection) throws XMPPException { + checkAuthenticated(connection, true); + + setFrom(connection.getUser()); + doLoad(connection, connection.getUser()); + } + + /** + * Load VCard information for a given user. Connection should be authenticated and not anonymous. + */ + public void load(Connection connection, String user) throws XMPPException { + checkAuthenticated(connection, false); + + setTo(user); + doLoad(connection, user); + } + + private void doLoad(Connection connection, String user) throws XMPPException { + setType(Type.GET); + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(getPacketID())); + connection.sendPacket(this); + + VCard result = null; + try { + result = (VCard) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + if (result == null) { + String errorMessage = "Timeout getting VCard information"; + throw new XMPPException(errorMessage, new XMPPError( + XMPPError.Condition.request_timeout, errorMessage)); + } + if (result.getError() != null) { + throw new XMPPException(result.getError()); + } + } + catch (ClassCastException e) { + System.out.println("No VCard for " + user); + } + + copyFieldsFrom(result); + } + + public String getChildElementXML() { + StringBuilder sb = new StringBuilder(); + new VCardWriter(sb).write(); + return sb.toString(); + } + + private void copyFieldsFrom(VCard result) { + if (result == null) result = new VCard(); + + Field[] fields = VCard.class.getDeclaredFields(); + for (Field field : fields) { + if (field.getDeclaringClass() == VCard.class && + !Modifier.isFinal(field.getModifiers())) { + try { + field.setAccessible(true); + field.set(this, field.get(result)); + } + catch (IllegalAccessException e) { + throw new RuntimeException("This cannot happen:" + field, e); + } + } + } + } + + private void checkAuthenticated(Connection connection, boolean checkForAnonymous) { + if (connection == null) { + throw new IllegalArgumentException("No connection was provided"); + } + if (!connection.isAuthenticated()) { + throw new IllegalArgumentException("Connection is not authenticated"); + } + if (checkForAnonymous && connection.isAnonymous()) { + throw new IllegalArgumentException("Connection cannot be anonymous"); + } + } + + private boolean hasContent() { + //noinspection OverlyComplexBooleanExpression + return hasNameField() + || hasOrganizationFields() + || emailHome != null + || emailWork != null + || otherSimpleFields.size() > 0 + || otherUnescapableFields.size() > 0 + || homeAddr.size() > 0 + || homePhones.size() > 0 + || workAddr.size() > 0 + || workPhones.size() > 0 + ; + } + + private boolean hasNameField() { + return firstName != null || lastName != null || middleName != null; + } + + private boolean hasOrganizationFields() { + return organization != null || organizationUnit != null; + } + + // Used in tests: + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final VCard vCard = (VCard) o; + + if (emailHome != null ? !emailHome.equals(vCard.emailHome) : vCard.emailHome != null) { + return false; + } + if (emailWork != null ? !emailWork.equals(vCard.emailWork) : vCard.emailWork != null) { + return false; + } + if (firstName != null ? !firstName.equals(vCard.firstName) : vCard.firstName != null) { + return false; + } + if (!homeAddr.equals(vCard.homeAddr)) { + return false; + } + if (!homePhones.equals(vCard.homePhones)) { + return false; + } + if (lastName != null ? !lastName.equals(vCard.lastName) : vCard.lastName != null) { + return false; + } + if (middleName != null ? !middleName.equals(vCard.middleName) : vCard.middleName != null) { + return false; + } + if (organization != null ? + !organization.equals(vCard.organization) : vCard.organization != null) { + return false; + } + if (organizationUnit != null ? + !organizationUnit.equals(vCard.organizationUnit) : vCard.organizationUnit != null) { + return false; + } + if (!otherSimpleFields.equals(vCard.otherSimpleFields)) { + return false; + } + if (!workAddr.equals(vCard.workAddr)) { + return false; + } + return workPhones.equals(vCard.workPhones); + + } + + public int hashCode() { + int result; + result = homePhones.hashCode(); + result = 29 * result + workPhones.hashCode(); + result = 29 * result + homeAddr.hashCode(); + result = 29 * result + workAddr.hashCode(); + result = 29 * result + (firstName != null ? firstName.hashCode() : 0); + result = 29 * result + (lastName != null ? lastName.hashCode() : 0); + result = 29 * result + (middleName != null ? middleName.hashCode() : 0); + result = 29 * result + (emailHome != null ? emailHome.hashCode() : 0); + result = 29 * result + (emailWork != null ? emailWork.hashCode() : 0); + result = 29 * result + (organization != null ? organization.hashCode() : 0); + result = 29 * result + (organizationUnit != null ? organizationUnit.hashCode() : 0); + result = 29 * result + otherSimpleFields.hashCode(); + return result; + } + + public String toString() { + return getChildElementXML(); + } + + //============================================================== + + private class VCardWriter { + + private final StringBuilder sb; + + VCardWriter(StringBuilder sb) { + this.sb = sb; + } + + public void write() { + appendTag("vCard", "xmlns", "vcard-temp", hasContent(), new ContentBuilder() { + public void addTagContent() { + buildActualContent(); + } + }); + } + + private void buildActualContent() { + if (hasNameField()) { + appendN(); + } + + appendOrganization(); + appendGenericFields(); + + appendEmail(emailWork, "WORK"); + appendEmail(emailHome, "HOME"); + + appendPhones(workPhones, "WORK"); + appendPhones(homePhones, "HOME"); + + appendAddress(workAddr, "WORK"); + appendAddress(homeAddr, "HOME"); + } + + private void appendEmail(final String email, final String type) { + if (email != null) { + appendTag("EMAIL", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(type); + appendEmptyTag("INTERNET"); + appendEmptyTag("PREF"); + appendTag("USERID", StringUtils.escapeForXML(email)); + } + }); + } + } + + private void appendPhones(Map phones, final String code) { + Iterator it = phones.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = (Map.Entry) it.next(); + appendTag("TEL", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(entry.getKey()); + appendEmptyTag(code); + appendTag("NUMBER", StringUtils.escapeForXML((String) entry.getValue())); + } + }); + } + } + + private void appendAddress(final Map addr, final String code) { + if (addr.size() > 0) { + appendTag("ADR", true, new ContentBuilder() { + public void addTagContent() { + appendEmptyTag(code); + + Iterator it = addr.entrySet().iterator(); + while (it.hasNext()) { + final Map.Entry entry = (Map.Entry) it.next(); + appendTag((String) entry.getKey(), StringUtils.escapeForXML((String) entry.getValue())); + } + } + }); + } + } + + private void appendEmptyTag(Object tag) { + sb.append('<').append(tag).append("/>"); + } + + private void appendGenericFields() { + Iterator it = otherSimpleFields.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + appendTag(entry.getKey().toString(), + StringUtils.escapeForXML((String) entry.getValue())); + } + + it = otherUnescapableFields.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = (Map.Entry) it.next(); + appendTag(entry.getKey().toString(), (String) entry.getValue()); + } + } + + private void appendOrganization() { + if (hasOrganizationFields()) { + appendTag("ORG", true, new ContentBuilder() { + public void addTagContent() { + appendTag("ORGNAME", StringUtils.escapeForXML(organization)); + appendTag("ORGUNIT", StringUtils.escapeForXML(organizationUnit)); + } + }); + } + } + + private void appendN() { + appendTag("N", true, new ContentBuilder() { + public void addTagContent() { + appendTag("FAMILY", StringUtils.escapeForXML(lastName)); + appendTag("GIVEN", StringUtils.escapeForXML(firstName)); + appendTag("MIDDLE", StringUtils.escapeForXML(middleName)); + } + }); + } + + private void appendTag(String tag, String attr, String attrValue, boolean hasContent, + ContentBuilder builder) { + sb.append('<').append(tag); + if (attr != null) { + sb.append(' ').append(attr).append('=').append('\'').append(attrValue).append('\''); + } + + if (hasContent) { + sb.append('>'); + builder.addTagContent(); + sb.append("\n"); + } + else { + sb.append("/>\n"); + } + } + + private void appendTag(String tag, boolean hasContent, ContentBuilder builder) { + appendTag(tag, null, null, hasContent, builder); + } + + private void appendTag(String tag, final String tagText) { + if (tagText == null) return; + final ContentBuilder contentBuilder = new ContentBuilder() { + public void addTagContent() { + sb.append(tagText.trim()); + } + }; + appendTag(tag, true, contentBuilder); + } + + } + + //============================================================== + + private interface ContentBuilder { + + void addTagContent(); + } + + //============================================================== +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java new file mode 100644 index 0000000..41ee419 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/Version.java @@ -0,0 +1,132 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.IQ; + +/** + * A Version IQ packet, which is used by XMPP clients to discover version information + * about the software running at another entity's JID.

      + * + * An example to discover the version of the server: + *

      + * // Request the version from the server.
      + * Version versionRequest = new Version();
      + * timeRequest.setType(IQ.Type.GET);
      + * timeRequest.setTo("example.com");
      + *
      + * // Create a packet collector to listen for a response.
      + * PacketCollector collector = con.createPacketCollector(
      + *                new PacketIDFilter(versionRequest.getPacketID()));
      + *
      + * con.sendPacket(versionRequest);
      + *
      + * // Wait up to 5 seconds for a result.
      + * IQ result = (IQ)collector.nextResult(5000);
      + * if (result != null && result.getType() == IQ.Type.RESULT) {
      + *     Version versionResult = (Version)result;
      + *     // Do something with result...
      + * }

      + * + * @author Gaston Dombiak + */ +public class Version extends IQ { + + private String name; + private String version; + private String os; + + /** + * Returns the natural-language name of the software. This property will always be + * present in a result. + * + * @return the natural-language name of the software. + */ + public String getName() { + return name; + } + + /** + * Sets the natural-language name of the software. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param name the natural-language name of the software. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Returns the specific version of the software. This property will always be + * present in a result. + * + * @return the specific version of the software. + */ + public String getVersion() { + return version; + } + + /** + * Sets the specific version of the software. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param version the specific version of the software. + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Returns the operating system of the queried entity. This property will always be + * present in a result. + * + * @return the operating system of the queried entity. + */ + public String getOs() { + return os; + } + + /** + * Sets the operating system of the queried entity. This message should only be + * invoked when parsing the XML and setting the property to a Version instance. + * + * @param os operating system of the queried entity. + */ + public void setOs(String os) { + this.os = os; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (name != null) { + buf.append("").append(name).append(""); + } + if (version != null) { + buf.append("").append(version).append(""); + } + if (os != null) { + buf.append("").append(os).append(""); + } + buf.append(""); + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java new file mode 100644 index 0000000..449f6fe --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/XHTMLExtension.java @@ -0,0 +1,126 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.packet; + +import org.jivesoftware.smack.packet.PacketExtension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * An XHTML sub-packet, which is used by XMPP clients to exchange formatted text. The XHTML + * extension is only a subset of XHTML 1.0.

      + * + * The following link summarizes the requirements of XHTML IM: + * Valid tags.

      + * + * Warning: this is an non-standard protocol documented by + * JEP-71. Because this is a + * non-standard protocol, it is subject to change. + * + * @author Gaston Dombiak + */ +public class XHTMLExtension implements PacketExtension { + + private List bodies = new ArrayList(); + + /** + * Returns the XML element name of the extension sub-packet root element. + * Always returns "html" + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "html"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is always "http://jabber.org/protocol/xhtml-im" + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() { + return "http://jabber.org/protocol/xhtml-im"; + } + + /** + * Returns the XML representation of a XHTML extension according the specification. + * + * Usually the XML representation will be inside of a Message XML representation like + * in the following example: + *

      +     * <message id="MlIpV-4" to="gato1@gato.home" from="gato3@gato.home/Smack">
      +     *     <subject>Any subject you want</subject>
      +     *     <body>This message contains something interesting.</body>
      +     *     <html xmlns="http://jabber.org/protocol/xhtml-im">
      +     *         <body><p style='font-size:large'>This message contains something <em>interesting</em>.</p></body>
      +     *     </html>
      +     * </message>
      +     * 
      + * + */ + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append( + "\">"); + // Loop through all the bodies and append them to the string buffer + for (Iterator i = getBodies(); i.hasNext();) { + buf.append((String) i.next()); + } + buf.append(""); + return buf.toString(); + } + + /** + * Returns an Iterator for the bodies in the packet. + * + * @return an Iterator for the bodies in the packet. + */ + public Iterator getBodies() { + synchronized (bodies) { + return Collections.unmodifiableList(new ArrayList(bodies)).iterator(); + } + } + + /** + * Adds a body to the packet. + * + * @param body the body to add. + */ + public void addBody(String body) { + synchronized (bodies) { + bodies.add(body); + } + } + + /** + * Returns a count of the bodies in the XHTML packet. + * + * @return the number of bodies in the XHTML packet. + */ + public int getBodiesCount() { + return bodies.size(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html new file mode 100644 index 0000000..490d1d7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/packet/package.html @@ -0,0 +1 @@ +XML packets that are part of the XMPP extension protocols. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java new file mode 100755 index 0000000..8d046ed --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/AdHocCommandDataProvider.java @@ -0,0 +1,155 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2005-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.XMPPError; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.commands.AdHocCommand; +import org.jivesoftware.smackx.commands.AdHocCommand.Action; +import org.jivesoftware.smackx.commands.AdHocCommandNote; +import org.jivesoftware.smackx.packet.AdHocCommandData; +import org.jivesoftware.smackx.packet.DataForm; +import org.xmlpull.v1.XmlPullParser; + +/** + * The AdHocCommandDataProvider parses AdHocCommandData packets. + * + * @author Gabriel Guardincerri + */ +public class AdHocCommandDataProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + boolean done = false; + AdHocCommandData adHocCommandData = new AdHocCommandData(); + DataFormProvider dataFormProvider = new DataFormProvider(); + + int eventType; + String elementName; + String namespace; + adHocCommandData.setSessionID(parser.getAttributeValue("", "sessionid")); + adHocCommandData.setNode(parser.getAttributeValue("", "node")); + + // Status + String status = parser.getAttributeValue("", "status"); + if (AdHocCommand.Status.executing.toString().equalsIgnoreCase(status)) { + adHocCommandData.setStatus(AdHocCommand.Status.executing); + } + else if (AdHocCommand.Status.completed.toString().equalsIgnoreCase(status)) { + adHocCommandData.setStatus(AdHocCommand.Status.completed); + } + else if (AdHocCommand.Status.canceled.toString().equalsIgnoreCase(status)) { + adHocCommandData.setStatus(AdHocCommand.Status.canceled); + } + + // Action + String action = parser.getAttributeValue("", "action"); + if (action != null) { + Action realAction = AdHocCommand.Action.valueOf(action); + if (realAction == null || realAction.equals(Action.unknown)) { + adHocCommandData.setAction(Action.unknown); + } + else { + adHocCommandData.setAction(realAction); + } + } + while (!done) { + eventType = parser.next(); + elementName = parser.getName(); + namespace = parser.getNamespace(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actions")) { + String execute = parser.getAttributeValue("", "execute"); + if (execute != null) { + adHocCommandData.setExecuteAction(AdHocCommand.Action.valueOf(execute)); + } + } + else if (parser.getName().equals("next")) { + adHocCommandData.addAction(AdHocCommand.Action.next); + } + else if (parser.getName().equals("complete")) { + adHocCommandData.addAction(AdHocCommand.Action.complete); + } + else if (parser.getName().equals("prev")) { + adHocCommandData.addAction(AdHocCommand.Action.prev); + } + else if (elementName.equals("x") && namespace.equals("jabber:x:data")) { + adHocCommandData.setForm((DataForm) dataFormProvider.parseExtension(parser)); + } + else if (parser.getName().equals("note")) { + AdHocCommandNote.Type type = AdHocCommandNote.Type.valueOf( + parser.getAttributeValue("", "type")); + String value = parser.nextText(); + adHocCommandData.addNote(new AdHocCommandNote(type, value)); + } + else if (parser.getName().equals("error")) { + XMPPError error = PacketParserUtils.parseError(parser); + adHocCommandData.setError(error); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("command")) { + done = true; + } + } + } + return adHocCommandData; + } + + public static class BadActionError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badAction); + } + } + + public static class MalformedActionError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.malformedAction); + } + } + + public static class BadLocaleError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badLocale); + } + } + + public static class BadPayloadError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badPayload); + } + } + + public static class BadSessionIDError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.badSessionid); + } + } + + public static class SessionExpiredError implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + return new AdHocCommandData.SpecificError(AdHocCommand.SpecificErrorCondition.sessionExpired); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java new file mode 100644 index 0000000..d8098a1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/CapsExtensionProvider.java @@ -0,0 +1,78 @@ +/* + * Copyright 2009 Jonas Ã…dahl. + * Copyright 2011 Florian Schmaus + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.io.IOException; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.CapsExtension; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +public class CapsExtensionProvider implements PacketExtensionProvider { + public PacketExtension parseExtension(XmlPullParser parser) throws XmlPullParserException, IOException { +// Original implementation by jonas +// boolean done = false; +// int startDepth = parser.getDepth(); +// +// String hash = parser.getAttributeValue(null, "hash"); +// String node = parser.getAttributeValue(null, "node"); +// String ver = parser.getAttributeValue(null, "ver"); +// +// // Make the parser +// while (true) { +// int eventType = parser.next(); +// +// if (eventType == XmlPullParser.END_TAG && +// parser.getDepth() == startDepth) +// break; +// } +// +// if (hash != null && node != null && ver != null) { +// return new CapsExtension(node, ver, hash); +// } +// else { +// //throw new XMPPException("Malformed caps element."); +// // Malformed, ignore it +// return null; +// } + + boolean done = false; + String hash = null; + String version = null; + String node = null; + while (!done) { + if (parser.getEventType() == XmlPullParser.START_TAG + && parser.getName().equalsIgnoreCase("c")) { + hash = parser.getAttributeValue(null, "hash"); + version = parser.getAttributeValue(null, "ver"); + node = parser.getAttributeValue(null, "node"); + } + + if (parser.getEventType() == XmlPullParser.END_TAG + && parser.getName().equalsIgnoreCase("c")) { + done = true; + } else { + parser.next(); + } + } + return new CapsExtension(node, version, hash); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java new file mode 100644 index 0000000..90794f4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DataFormProvider.java @@ -0,0 +1,160 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DataForm; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.List; + +/** + * The DataFormProvider parses DataForm packets. + * + * @author Gaston Dombiak + */ +public class DataFormProvider implements PacketExtensionProvider { + + /** + * Creates a new DataFormProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public DataFormProvider() { + } + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + boolean done = false; + StringBuilder buffer = null; + DataForm dataForm = new DataForm(parser.getAttributeValue("", "type")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("instructions")) { + dataForm.addInstruction(parser.nextText()); + } + else if (parser.getName().equals("title")) { + dataForm.setTitle(parser.nextText()); + } + else if (parser.getName().equals("field")) { + dataForm.addField(parseField(parser)); + } + else if (parser.getName().equals("item")) { + dataForm.addItem(parseItem(parser)); + } + else if (parser.getName().equals("reported")) { + dataForm.setReportedData(parseReported(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(dataForm.getElementName())) { + done = true; + } + } + } + return dataForm; + } + + private FormField parseField(XmlPullParser parser) throws Exception { + boolean done = false; + FormField formField = new FormField(parser.getAttributeValue("", "var")); + formField.setLabel(parser.getAttributeValue("", "label")); + formField.setType(parser.getAttributeValue("", "type")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("desc")) { + formField.setDescription(parser.nextText()); + } + else if (parser.getName().equals("value")) { + formField.addValue(parser.nextText()); + } + else if (parser.getName().equals("required")) { + formField.setRequired(true); + } + else if (parser.getName().equals("option")) { + formField.addOption(parseOption(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("field")) { + done = true; + } + } + } + return formField; + } + + private DataForm.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + List fields = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("field")) { + fields.add(parseField(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return new DataForm.Item(fields); + } + + private DataForm.ReportedData parseReported(XmlPullParser parser) throws Exception { + boolean done = false; + List fields = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("field")) { + fields.add(parseField(parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("reported")) { + done = true; + } + } + } + return new DataForm.ReportedData(fields); + } + + private FormField.Option parseOption(XmlPullParser parser) throws Exception { + boolean done = false; + FormField.Option option = null; + String label = parser.getAttributeValue("", "label"); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("value")) { + option = new FormField.Option(label, parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("option")) { + done = true; + } + } + } + return option; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java new file mode 100644 index 0000000..6fa52b7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInfoProvider.java @@ -0,0 +1,42 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.DelayInfo; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.xmlpull.v1.XmlPullParser; + +/** + * This provider simply creates a {@link DelayInfo} decorator for the {@link DelayInformation} that + * is returned by the superclass. This allows the new code using + * Delay Information XEP-0203 to be + * backward compatible with XEP-0091. + * + *

      This provider must be registered in the smack.properties file for the element + * delay with namespace urn:xmpp:delay

      + * + * @author Robin Collier + */ +public class DelayInfoProvider extends DelayInformationProvider +{ + + @Override + public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + return new DelayInfo((DelayInformation)super.parseExtension(parser)); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java new file mode 100644 index 0000000..5dc0ee7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DelayInformationProvider.java @@ -0,0 +1,205 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.xmlpull.v1.XmlPullParser; + +/** + * The DelayInformationProvider parses DelayInformation packets. + * + * @author Gaston Dombiak + * @author Henning Staib + */ +public class DelayInformationProvider implements PacketExtensionProvider { + + /* + * Date format used to parse dates in the XEP-0091 format but missing leading + * zeros for month and day. + */ + private static final SimpleDateFormat XEP_0091_UTC_FALLBACK_FORMAT = new SimpleDateFormat( + "yyyyMd'T'HH:mm:ss"); + static { + XEP_0091_UTC_FALLBACK_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /* + * Date format used to parse dates in the XEP-0082 format but missing milliseconds. + */ + private static final SimpleDateFormat XEP_0082_UTC_FORMAT_WITHOUT_MILLIS = new SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + static { + XEP_0082_UTC_FORMAT_WITHOUT_MILLIS.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /* + * Maps a regular expression for a date format to the date format parser. + */ + private static Map formats = new HashMap(); + static { + formats.put("^\\d+T\\d+:\\d+:\\d+$", DelayInformation.XEP_0091_UTC_FORMAT); + formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+\\.\\d+Z$", StringUtils.XEP_0082_UTC_FORMAT); + formats.put("^\\d+-\\d+-\\d+T\\d+:\\d+:\\d+Z$", XEP_0082_UTC_FORMAT_WITHOUT_MILLIS); + } + + /** + * Creates a new DeliveryInformationProvider. ProviderManager requires that + * every PacketExtensionProvider has a public, no-argument constructor + */ + public DelayInformationProvider() { + } + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + String stampString = (parser.getAttributeValue("", "stamp")); + Date stamp = null; + DateFormat format = null; + + for (String regexp : formats.keySet()) { + if (stampString.matches(regexp)) { + try { + format = formats.get(regexp); + synchronized (format) { + stamp = format.parse(stampString); + } + } + catch (ParseException e) { + // do nothing, format is still set + } + + // break because only one regexp can match + break; + } + } + + /* + * if date is in XEP-0091 format handle ambiguous dates missing the + * leading zero in month and day + */ + if (format == DelayInformation.XEP_0091_UTC_FORMAT + && stampString.split("T")[0].length() < 8) { + stamp = handleDateWithMissingLeadingZeros(stampString); + } + + /* + * if date could not be parsed but XML is valid, don't shutdown + * connection by throwing an exception instead set timestamp to current + * time + */ + if (stamp == null) { + stamp = new Date(); + } + + DelayInformation delayInformation = new DelayInformation(stamp); + delayInformation.setFrom(parser.getAttributeValue("", "from")); + String reason = parser.nextText(); + + /* + * parser.nextText() returns empty string if there is no reason. + * DelayInformation API specifies that null should be returned in that + * case. + */ + reason = "".equals(reason) ? null : reason; + delayInformation.setReason(reason); + + return delayInformation; + } + + /** + * Parses the given date string in different ways and returns the date that + * lies in the past and/or is nearest to the current date-time. + * + * @param stampString date in string representation + * @return the parsed date + */ + private Date handleDateWithMissingLeadingZeros(String stampString) { + Calendar now = new GregorianCalendar(); + Calendar xep91 = null; + Calendar xep91Fallback = null; + + xep91 = parseXEP91Date(stampString, DelayInformation.XEP_0091_UTC_FORMAT); + xep91Fallback = parseXEP91Date(stampString, XEP_0091_UTC_FALLBACK_FORMAT); + + List dates = filterDatesBefore(now, xep91, xep91Fallback); + + if (!dates.isEmpty()) { + return determineNearestDate(now, dates).getTime(); + } + return null; + } + + private Calendar parseXEP91Date(String stampString, DateFormat dateFormat) { + try { + synchronized (dateFormat) { + dateFormat.parse(stampString); + return dateFormat.getCalendar(); + } + } + catch (ParseException e) { + return null; + } + } + + private List filterDatesBefore(Calendar now, Calendar... dates) { + List result = new ArrayList(); + + for (Calendar calendar : dates) { + if (calendar != null && calendar.before(now)) { + result.add(calendar); + } + } + + return result; + } + + private Calendar determineNearestDate(final Calendar now, List dates) { + + Collections.sort(dates, new Comparator() { + + public int compare(Calendar o1, Calendar o2) { + Long diff1 = new Long(now.getTimeInMillis() - o1.getTimeInMillis()); + Long diff2 = new Long(now.getTimeInMillis() - o2.getTimeInMillis()); + return diff1.compareTo(diff2); + } + + }); + + return dates.get(0); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java new file mode 100644 index 0000000..36ce287 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DeliveryReceiptProvider.java @@ -0,0 +1,40 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.DeliveryReceipt; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * The DeliveryReceiptProvider parses DeliveryReceipt packets. + * + * @author Georg Lukas + */ +public class DeliveryReceiptProvider extends EmbeddedExtensionProvider +{ + + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, + Map attributeMap, List content) + { + return new DeliveryReceipt(attributeMap.get("id")); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java new file mode 100644 index 0000000..09defda --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverInfoProvider.java @@ -0,0 +1,83 @@ +/** + * $RCSfile$ + * $Revision: 7071 $ + * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.xmlpull.v1.XmlPullParser; + +/** +* The DiscoverInfoProvider parses Service Discovery information packets. +* +* @author Gaston Dombiak +*/ +public class DiscoverInfoProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + DiscoverInfo discoverInfo = new DiscoverInfo(); + boolean done = false; + DiscoverInfo.Feature feature = null; + DiscoverInfo.Identity identity = null; + String category = ""; + String name = ""; + String type = ""; + String variable = ""; + discoverInfo.setNode(parser.getAttributeValue("", "node")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("identity")) { + // Initialize the variables from the parsed XML + category = parser.getAttributeValue("", "category"); + name = parser.getAttributeValue("", "name"); + type = parser.getAttributeValue("", "type"); + } + else if (parser.getName().equals("feature")) { + // Initialize the variables from the parsed XML + variable = parser.getAttributeValue("", "var"); + } + // Otherwise, it must be a packet extension. + else { + discoverInfo.addExtension(PacketParserUtils.parsePacketExtension(parser + .getName(), parser.getNamespace(), parser)); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("identity")) { + // Create a new identity and add it to the discovered info. + identity = new DiscoverInfo.Identity(category, name); + identity.setType(type); + discoverInfo.addIdentity(identity); + } + if (parser.getName().equals("feature")) { + // Create a new feature and add it to the discovered info. + discoverInfo.addFeature(variable); + } + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return discoverInfo; + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java new file mode 100644 index 0000000..fcbe25f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/DiscoverItemsProvider.java @@ -0,0 +1,69 @@ +/** + * $RCSfile$ + * $Revision: 7071 $ + * $Date: 2007-02-12 08:59:05 +0800 (Mon, 12 Feb 2007) $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** +* The DiscoverInfoProvider parses Service Discovery items packets. +* +* @author Gaston Dombiak +*/ +public class DiscoverItemsProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + DiscoverItems discoverItems = new DiscoverItems(); + boolean done = false; + DiscoverItems.Item item; + String jid = ""; + String name = ""; + String action = ""; + String node = ""; + discoverItems.setNode(parser.getAttributeValue("", "node")); + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG && "item".equals(parser.getName())) { + // Initialize the variables from the parsed XML + jid = parser.getAttributeValue("", "jid"); + name = parser.getAttributeValue("", "name"); + node = parser.getAttributeValue("", "node"); + action = parser.getAttributeValue("", "action"); + } + else if (eventType == XmlPullParser.END_TAG && "item".equals(parser.getName())) { + // Create a new Item and add it to DiscoverItems. + item = new DiscoverItems.Item(jid); + item.setName(name); + item.setNode(node); + item.setAction(action); + discoverItems.addItem(item); + } + else if (eventType == XmlPullParser.END_TAG && "query".equals(parser.getName())) { + done = true; + } + } + + return discoverItems; + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java new file mode 100644 index 0000000..3d5ceb4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/EmbeddedExtensionProvider.java @@ -0,0 +1,111 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.pubsub.provider.ItemProvider; +import org.jivesoftware.smackx.pubsub.provider.ItemsProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * This class simplifies parsing of embedded elements by using the + * Template Method Pattern. + * After extracting the current element attributes and content of any child elements, the template method + * ({@link #createReturnExtension(String, String, Map, List)} is called. Subclasses + * then override this method to create the specific return type. + * + *

      To use this class, you simply register your subclasses as extension providers in the + * smack.properties file. Then they will be automatically picked up and used to parse + * any child elements. + * + *

      + * For example, given the following message
      + * 
      + * <message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo>
      + *    <event xmlns='http://jabber.org/protocol/pubsub#event>
      + *       <items node='princely_musings'>
      + *          <item id='asdjkwei3i34234n356'>
      + *             <entry xmlns='http://www.w3.org/2005/Atom'>
      + *                <title>Soliloquy</title>
      + *                <link rel='alternative' type='text/html'/>
      + *                <id>tag:denmark.lit,2003:entry-32397</id>
      + *             </entry>
      + *          </item>
      + *       </items>
      + *    </event>
      + * </message>
      + * 
      + * I would have a classes
      + * {@link ItemsProvider} extends {@link EmbeddedExtensionProvider}
      + * {@link ItemProvider} extends {@link EmbeddedExtensionProvider}
      + * and
      + * AtomProvider extends {@link PacketExtensionProvider}
      + * 
      + * These classes are then registered in the meta-inf/smack.providers file
      + * as follows.
      + * 
      + *   <extensionProvider>
      + *      <elementName>items</elementName>
      + *      <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      + *      <className>org.jivesoftware.smackx.provider.ItemsEventProvider</className>
      + *   </extensionProvider>
      + *   <extensionProvider>
      + *       <elementName>item</elementName>
      + *       <namespace>http://jabber.org/protocol/pubsub#event</namespace>
      + *       <className>org.jivesoftware.smackx.provider.ItemProvider</className>
      + *   </extensionProvider>
      + * 
      + * 
      + * + * @author Robin Collier + * + * @deprecated This has been moved to {@link org.jivesoftware.smack.provider.EmbeddedExtensionProvider} + */ +abstract public class EmbeddedExtensionProvider implements PacketExtensionProvider +{ + + final public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String namespace = parser.getNamespace(); + String name = parser.getName(); + Map attMap = new HashMap(); + + for(int i=0; i extensions = new ArrayList(); + + do + { + int tag = parser.next(); + + if (tag == XmlPullParser.START_TAG) + extensions.add(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); + } while (!name.equals(parser.getName())); + + return createReturnExtension(name, namespace, attMap, extensions); + } + + abstract protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java new file mode 100644 index 0000000..7344880 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeaderProvider.java @@ -0,0 +1,44 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.Header; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses the header element as defined in Stanza Headers and Internet Metadata (SHIM). + * + * @author Robin Collier + */ +public class HeaderProvider implements PacketExtensionProvider +{ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String name = parser.getAttributeValue(null, "name"); + String value = null; + + parser.next(); + + if (parser.getEventType() == XmlPullParser.TEXT) + value = parser.getText(); + + while(parser.getEventType() != XmlPullParser.END_TAG) + parser.next(); + + return new Header(name, value); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java new file mode 100644 index 0000000..056dd58 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/HeadersProvider.java @@ -0,0 +1,37 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.Header; +import org.jivesoftware.smackx.packet.HeadersExtension; + +/** + * Parses the headers element as defined in Stanza Headers and Internet Metadata (SHIM). + * + * @author Robin Collier + */ +public class HeadersProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new HeadersExtension((Collection
      )content); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java new file mode 100644 index 0000000..1072232 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCAdminProvider.java @@ -0,0 +1,81 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smackx.packet.MUCAdmin; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCAdminProvider parses MUCAdmin packets. (@see MUCAdmin) + * + * @author Gaston Dombiak + */ +public class MUCAdminProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + MUCAdmin mucAdmin = new MUCAdmin(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + mucAdmin.addItem(parseItem(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return mucAdmin; + } + + private MUCAdmin.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCAdmin.Item item = + new MUCAdmin.Item( + parser.getAttributeValue("", "affiliation"), + parser.getAttributeValue("", "role")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java new file mode 100644 index 0000000..ff3094e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCOwnerProvider.java @@ -0,0 +1,108 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.packet.MUCOwner; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCOwnerProvider parses MUCOwner packets. (@see MUCOwner) + * + * @author Gaston Dombiak + */ +public class MUCOwnerProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + MUCOwner mucOwner = new MUCOwner(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + mucOwner.addItem(parseItem(parser)); + } + else if (parser.getName().equals("destroy")) { + mucOwner.setDestroy(parseDestroy(parser)); + } + // Otherwise, it must be a packet extension. + else { + mucOwner.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + return mucOwner; + } + + private MUCOwner.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCOwner.Item item = new MUCOwner.Item(parser.getAttributeValue("", "affiliation")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setRole(parser.getAttributeValue("", "role")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + + private MUCOwner.Destroy parseDestroy(XmlPullParser parser) throws Exception { + boolean done = false; + MUCOwner.Destroy destroy = new MUCOwner.Destroy(); + destroy.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + destroy.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("destroy")) { + done = true; + } + } + } + return destroy; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java new file mode 100644 index 0000000..5a98af6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MUCUserProvider.java @@ -0,0 +1,174 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.provider.*; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MUCUserProvider parses packets with extended presence information about + * roles and affiliations. + * + * @author Gaston Dombiak + */ +public class MUCUserProvider implements PacketExtensionProvider { + + /** + * Creates a new MUCUserProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument + * constructor + */ + public MUCUserProvider() { + } + + /** + * Parses a MUCUser packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + MUCUser mucUser = new MUCUser(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("invite")) { + mucUser.setInvite(parseInvite(parser)); + } + if (parser.getName().equals("item")) { + mucUser.setItem(parseItem(parser)); + } + if (parser.getName().equals("password")) { + mucUser.setPassword(parser.nextText()); + } + if (parser.getName().equals("status")) { + mucUser.setStatus(new MUCUser.Status(parser.getAttributeValue("", "code"))); + } + if (parser.getName().equals("decline")) { + mucUser.setDecline(parseDecline(parser)); + } + if (parser.getName().equals("destroy")) { + mucUser.setDestroy(parseDestroy(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return mucUser; + } + + private MUCUser.Item parseItem(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Item item = + new MUCUser.Item( + parser.getAttributeValue("", "affiliation"), + parser.getAttributeValue("", "role")); + item.setNick(parser.getAttributeValue("", "nick")); + item.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("actor")) { + item.setActor(parser.getAttributeValue("", "jid")); + } + if (parser.getName().equals("reason")) { + item.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + done = true; + } + } + } + return item; + } + + private MUCUser.Invite parseInvite(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Invite invite = new MUCUser.Invite(); + invite.setFrom(parser.getAttributeValue("", "from")); + invite.setTo(parser.getAttributeValue("", "to")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + invite.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("invite")) { + done = true; + } + } + } + return invite; + } + + private MUCUser.Decline parseDecline(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Decline decline = new MUCUser.Decline(); + decline.setFrom(parser.getAttributeValue("", "from")); + decline.setTo(parser.getAttributeValue("", "to")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + decline.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("decline")) { + done = true; + } + } + } + return decline; + } + + private MUCUser.Destroy parseDestroy(XmlPullParser parser) throws Exception { + boolean done = false; + MUCUser.Destroy destroy = new MUCUser.Destroy(); + destroy.setJid(parser.getAttributeValue("", "jid")); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("reason")) { + destroy.setReason(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("destroy")) { + done = true; + } + } + } + return destroy; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java new file mode 100644 index 0000000..b631546 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MessageEventProvider.java @@ -0,0 +1,77 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.MessageEvent; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * The MessageEventProvider parses Message Event packets. +* + * @author Gaston Dombiak + */ +public class MessageEventProvider implements PacketExtensionProvider { + + /** + * Creates a new MessageEventProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public MessageEventProvider() { + } + + /** + * Parses a MessageEvent packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + MessageEvent messageEvent = new MessageEvent(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("id")) + messageEvent.setPacketID(parser.nextText()); + if (parser.getName().equals(MessageEvent.COMPOSING)) + messageEvent.setComposing(true); + if (parser.getName().equals(MessageEvent.DELIVERED)) + messageEvent.setDelivered(true); + if (parser.getName().equals(MessageEvent.DISPLAYED)) + messageEvent.setDisplayed(true); + if (parser.getName().equals(MessageEvent.OFFLINE)) + messageEvent.setOffline(true); + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return messageEvent; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java new file mode 100644 index 0000000..b51e5e0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/MultipleAddressesProvider.java @@ -0,0 +1,67 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.packet.MultipleAddresses; +import org.xmlpull.v1.XmlPullParser; + +/** + * The MultipleAddressesProvider parses {@link MultipleAddresses} packets. + * + * @author Gaston Dombiak + */ +public class MultipleAddressesProvider implements PacketExtensionProvider { + + /** + * Creates a new MultipleAddressesProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument + * constructor. + */ + public MultipleAddressesProvider() { + } + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + boolean done = false; + MultipleAddresses multipleAddresses = new MultipleAddresses(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("address")) { + String type = parser.getAttributeValue("", "type"); + String jid = parser.getAttributeValue("", "jid"); + String node = parser.getAttributeValue("", "node"); + String desc = parser.getAttributeValue("", "desc"); + boolean delivered = "true".equals(parser.getAttributeValue("", "delivered")); + String uri = parser.getAttributeValue("", "uri"); + // Add the parsed address + multipleAddresses.addAddress(type, jid, node, desc, delivered, uri); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(multipleAddresses.getElementName())) { + done = true; + } + } + } + return multipleAddresses; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java new file mode 100644 index 0000000..f33dcde --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PEPProvider.java @@ -0,0 +1,93 @@ +/** + * $RCSfile: PEPProvider.java,v $ + * $Revision: 1.2 $ + * $Date: 2007/11/06 02:05:09 $ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.util.HashMap; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * The PEPProvider parses incoming PEPEvent packets. + * (XEP-163 has a weird asymmetric deal: outbound PEP are + and inbound are + . + * The provider only deals with inbound, and so it only deals with . + * + * Anyhoo... + * + * The way this works is that PEPxxx classes are generic and providers, and anyone who + * wants to publish/receive PEPs, such as , , etc., simply need to extend PEPItem and register (here) + * a PacketExtensionProvider that knows how to parse that PEPItem extension. + * + * @author Jeff Williams + */ +public class PEPProvider implements PacketExtensionProvider { + + Map nodeParsers = new HashMap(); + PacketExtension pepItem; + + /** + * Creates a new PEPProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public PEPProvider() { + } + + public void registerPEPParserExtension(String node, PacketExtensionProvider pepItemParser) { + nodeParsers.put(node, pepItemParser); + } + + /** + * Parses a PEPEvent packet and extracts a PEPItem from it. + * (There is only one per .) + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("event")) { + } else if (parser.getName().equals("items")) { + // Figure out the node for this event. + String node = parser.getAttributeValue("", "node"); + // Get the parser for this kind of node, and if found then parse the node. + PacketExtensionProvider nodeParser = nodeParsers.get(node); + if (nodeParser != null) { + pepItem = nodeParser.parseExtension(parser); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("event")) { + done = true; + } + } + } + + return pepItem; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java new file mode 100644 index 0000000..b781a5a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/PrivateDataProvider.java @@ -0,0 +1,46 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.xmlpull.v1.XmlPullParser; +import org.jivesoftware.smackx.packet.PrivateData; + +/** + * An interface for parsing custom private data. Each PrivateDataProvider must + * be registered with the PrivateDataManager class for it to be used. Every implementation + * of this interface must have a public, no-argument constructor. + * + * @author Matt Tucker + */ +public interface PrivateDataProvider { + + /** + * Parse the private data sub-document and create a PrivateData instance. At the + * beginning of the method call, the xml parser will be positioned at the opening + * tag of the private data child element. At the end of the method call, the parser + * must be positioned on the closing tag of the child element. + * + * @param parser an XML parser. + * @return a new PrivateData instance. + * @throws Exception if an error occurs parsing the XML. + */ + public PrivateData parsePrivateData(XmlPullParser parser) throws Exception; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java new file mode 100644 index 0000000..ab8a02f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/RosterExchangeProvider.java @@ -0,0 +1,90 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.util.ArrayList; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.*; +import org.jivesoftware.smackx.packet.*; +import org.xmlpull.v1.XmlPullParser; + +/** + * + * The RosterExchangeProvider parses RosterExchange packets. + * + * @author Gaston Dombiak + */ +public class RosterExchangeProvider implements PacketExtensionProvider { + + /** + * Creates a new RosterExchangeProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public RosterExchangeProvider() { + } + + /** + * Parses a RosterExchange packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + + RosterExchange rosterExchange = new RosterExchange(); + boolean done = false; + RemoteRosterEntry remoteRosterEntry = null; + String jid = ""; + String name = ""; + ArrayList groupsName = new ArrayList(); + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("item")) { + // Reset this variable since they are optional for each item + groupsName = new ArrayList(); + // Initialize the variables from the parsed XML + jid = parser.getAttributeValue("", "jid"); + name = parser.getAttributeValue("", "name"); + } + if (parser.getName().equals("group")) { + groupsName.add(parser.nextText()); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("item")) { + // Create packet. + remoteRosterEntry = new RemoteRosterEntry(jid, name, (String[]) groupsName.toArray(new String[groupsName.size()])); + rosterExchange.addRosterEntry(remoteRosterEntry); + } + if (parser.getName().equals("x")) { + done = true; + } + } + } + + return rosterExchange; + + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java new file mode 100644 index 0000000..eb0c66c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/StreamInitiationProvider.java @@ -0,0 +1,125 @@ +/** + * $RCSfile$ + * $Revision: $ + * $Date: $ + * + * Copyright 2003-2006 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import java.text.ParseException; +import java.util.Date; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.StreamInitiation; +import org.jivesoftware.smackx.packet.StreamInitiation.File; +import org.xmlpull.v1.XmlPullParser; + +/** + * The StreamInitiationProvider parses StreamInitiation packets. + * + * @author Alexander Wenckus + * + */ +public class StreamInitiationProvider implements IQProvider { + + public IQ parseIQ(final XmlPullParser parser) throws Exception { + boolean done = false; + + // si + String id = parser.getAttributeValue("", "id"); + String mimeType = parser.getAttributeValue("", "mime-type"); + + StreamInitiation initiation = new StreamInitiation(); + + // file + String name = null; + String size = null; + String hash = null; + String date = null; + String desc = null; + boolean isRanged = false; + + // feature + DataForm form = null; + DataFormProvider dataFormProvider = new DataFormProvider(); + + int eventType; + String elementName; + String namespace; + while (!done) { + eventType = parser.next(); + elementName = parser.getName(); + namespace = parser.getNamespace(); + if (eventType == XmlPullParser.START_TAG) { + if (elementName.equals("file")) { + name = parser.getAttributeValue("", "name"); + size = parser.getAttributeValue("", "size"); + hash = parser.getAttributeValue("", "hash"); + date = parser.getAttributeValue("", "date"); + } else if (elementName.equals("desc")) { + desc = parser.nextText(); + } else if (elementName.equals("range")) { + isRanged = true; + } else if (elementName.equals("x") + && namespace.equals("jabber:x:data")) { + form = (DataForm) dataFormProvider.parseExtension(parser); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (elementName.equals("si")) { + done = true; + } else if (elementName.equals("file")) { + long fileSize = 0; + if(size != null && size.trim().length() !=0){ + try { + fileSize = Long.parseLong(size); + } + catch (NumberFormatException e) { + e.printStackTrace(); + } + } + + Date fileDate = new Date(); + if (date != null) { + try { + fileDate = StringUtils.parseXEP0082Date(date); + } catch (ParseException e) { + // couldn't parse date, use current date-time + } + } + + File file = new File(name, fileSize); + file.setHash(hash); + file.setDate(fileDate); + file.setDesc(desc); + file.setRanged(isRanged); + initiation.setFile(file); + } + } + } + + initiation.setSesssionID(id); + initiation.setMimeType(mimeType); + + initiation.setFeatureNegotiationForm(form); + + return initiation; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java new file mode 100644 index 0000000..b52c580 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/VCardProvider.java @@ -0,0 +1,258 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.VCard; +import org.w3c.dom.*; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * vCard provider. + * + * @author Gaston Dombiak + * @author Derek DeMoro + */ +public class VCardProvider implements IQProvider { + + private static final String PREFERRED_ENCODING = "UTF-8"; + + public IQ parseIQ(XmlPullParser parser) throws Exception { + final StringBuilder sb = new StringBuilder(); + try { + int event = parser.getEventType(); + // get the content + while (true) { + switch (event) { + case XmlPullParser.TEXT: + // We must re-escape the xml so that the DOM won't throw an exception + sb.append(StringUtils.escapeForXML(parser.getText())); + break; + case XmlPullParser.START_TAG: + sb.append('<').append(parser.getName()).append('>'); + break; + case XmlPullParser.END_TAG: + sb.append("'); + break; + default: + } + + if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break; + + event = parser.next(); + } + } + catch (XmlPullParserException e) { + e.printStackTrace(); + } + catch (IOException e) { + e.printStackTrace(); + } + + String xmlText = sb.toString(); + return createVCardFromXML(xmlText); + } + + /** + * Builds a users vCard from xml file. + * + * @param xml the xml representing a users vCard. + * @return the VCard. + * @throws Exception if an exception occurs. + */ + public static VCard createVCardFromXML(String xml) throws Exception { + VCard vCard = new VCard(); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document document = documentBuilder.parse( + new ByteArrayInputStream(xml.getBytes(PREFERRED_ENCODING))); + + new VCardReader(vCard, document).initializeFields(); + return vCard; + } + + private static class VCardReader { + + private final VCard vCard; + private final Document document; + + VCardReader(VCard vCard, Document document) { + this.vCard = vCard; + this.document = document; + } + + public void initializeFields() { + vCard.setFirstName(getTagContents("GIVEN")); + vCard.setLastName(getTagContents("FAMILY")); + vCard.setMiddleName(getTagContents("MIDDLE")); + vCard.setEncodedImage(getTagContents("BINVAL")); + + setupEmails(); + + vCard.setOrganization(getTagContents("ORGNAME")); + vCard.setOrganizationUnit(getTagContents("ORGUNIT")); + + setupSimpleFields(); + + setupPhones(); + setupAddresses(); + } + + private void setupEmails() { + NodeList nodes = document.getElementsByTagName("USERID"); + if (nodes == null) return; + for (int i = 0; i < nodes.getLength(); i++) { + Element element = (Element) nodes.item(i); + if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) { + vCard.setEmailWork(getTextContent(element)); + } + else { + vCard.setEmailHome(getTextContent(element)); + } + } + } + + private void setupPhones() { + NodeList allPhones = document.getElementsByTagName("TEL"); + if (allPhones == null) return; + for (int i = 0; i < allPhones.getLength(); i++) { + NodeList nodes = allPhones.item(i).getChildNodes(); + String type = null; + String code = null; + String value = null; + for (int j = 0; j < nodes.getLength(); j++) { + Node node = nodes.item(j); + if (node.getNodeType() != Node.ELEMENT_NODE) continue; + String nodeName = node.getNodeName(); + if ("NUMBER".equals(nodeName)) { + value = getTextContent(node); + } + else if (isWorkHome(nodeName)) { + type = nodeName; + } + else { + code = nodeName; + } + } + if (code == null || value == null) continue; + if ("HOME".equals(type)) { + vCard.setPhoneHome(code, value); + } + else { // By default, setup work phone + vCard.setPhoneWork(code, value); + } + } + } + + private boolean isWorkHome(String nodeName) { + return "HOME".equals(nodeName) || "WORK".equals(nodeName); + } + + private void setupAddresses() { + NodeList allAddresses = document.getElementsByTagName("ADR"); + if (allAddresses == null) return; + for (int i = 0; i < allAddresses.getLength(); i++) { + Element addressNode = (Element) allAddresses.item(i); + + String type = null; + List code = new ArrayList(); + List value = new ArrayList(); + NodeList childNodes = addressNode.getChildNodes(); + for (int j = 0; j < childNodes.getLength(); j++) { + Node node = childNodes.item(j); + if (node.getNodeType() != Node.ELEMENT_NODE) continue; + String nodeName = node.getNodeName(); + if (isWorkHome(nodeName)) { + type = nodeName; + } + else { + code.add(nodeName); + value.add(getTextContent(node)); + } + } + for (int j = 0; j < value.size(); j++) { + if ("HOME".equals(type)) { + vCard.setAddressFieldHome((String) code.get(j), (String) value.get(j)); + } + else { // By default, setup work address + vCard.setAddressFieldWork((String) code.get(j), (String) value.get(j)); + } + } + } + } + + private String getTagContents(String tag) { + NodeList nodes = document.getElementsByTagName(tag); + if (nodes != null && nodes.getLength() == 1) { + return getTextContent(nodes.item(0)); + } + return null; + } + + private void setupSimpleFields() { + NodeList childNodes = document.getDocumentElement().getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node node = childNodes.item(i); + if (node instanceof Element) { + Element element = (Element) node; + + String field = element.getNodeName(); + if (element.getChildNodes().getLength() == 0) { + vCard.setField(field, ""); + } + else if (element.getChildNodes().getLength() == 1 && + element.getChildNodes().item(0) instanceof Text) { + vCard.setField(field, getTextContent(element)); + } + } + } + } + + private String getTextContent(Node node) { + StringBuilder result = new StringBuilder(); + appendText(result, node); + return result.toString(); + } + + private void appendText(StringBuilder result, Node node) { + NodeList childNodes = node.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + Node nd = childNodes.item(i); + String nodeValue = nd.getNodeValue(); + if (nodeValue != null) { + result.append(nodeValue); + } + appendText(result, nd); + } + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java new file mode 100644 index 0000000..50f437f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/XHTMLExtensionProvider.java @@ -0,0 +1,94 @@ +/** + * $RCSfile$ + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.packet.XHTMLExtension; +import org.xmlpull.v1.XmlPullParser; + +/** + * The XHTMLExtensionProvider parses XHTML packets. + * + * @author Gaston Dombiak + */ +public class XHTMLExtensionProvider implements PacketExtensionProvider { + + /** + * Creates a new XHTMLExtensionProvider. + * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor + */ + public XHTMLExtensionProvider() { + } + + /** + * Parses a XHTMLExtension packet (extension sub-packet). + * + * @param parser the XML parser, positioned at the starting element of the extension. + * @return a PacketExtension. + * @throws Exception if a parsing error occurs. + */ + public PacketExtension parseExtension(XmlPullParser parser) + throws Exception { + XHTMLExtension xhtmlExtension = new XHTMLExtension(); + boolean done = false; + StringBuilder buffer = new StringBuilder(); + int startDepth = parser.getDepth(); + int depth = parser.getDepth(); + String lastTag = ""; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("body")) { + buffer = new StringBuilder(); + depth = parser.getDepth(); + } + lastTag = parser.getText(); + buffer.append(parser.getText()); + } else if (eventType == XmlPullParser.TEXT) { + if (buffer != null) { + // We need to return valid XML so any inner text needs to be re-escaped + buffer.append(StringUtils.escapeForXML(parser.getText())); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("body") && parser.getDepth() <= depth) { + buffer.append(parser.getText()); + xhtmlExtension.addBody(buffer.toString()); + } + else if (parser.getName().equals(xhtmlExtension.getElementName()) + && parser.getDepth() <= startDepth) { + done = true; + } + else { + // This is a check for tags that are both a start and end tag like
      + // So that they aren't doubled + if(lastTag == null || !lastTag.equals(parser.getText())) { + buffer.append(parser.getText()); + } + } + } + } + + return xhtmlExtension; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html new file mode 100644 index 0000000..962ba63 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/provider/package.html @@ -0,0 +1 @@ +Provides pluggable parsing logic for Smack extensions. \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java new file mode 100644 index 0000000..c1fa546 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AccessModel.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * This enumeration represents the access models for the pubsub node + * as defined in the pubsub specification section 16.4.3 + * + * @author Robin Collier + */ +public enum AccessModel +{ + /** Anyone may subscribe and retrieve items */ + open, + + /** Subscription request must be approved and only subscribers may retrieve items */ + authorize, + + /** Anyone with a presence subscription of both or from may subscribe and retrieve items */ + presence, + + /** Anyone in the specified roster group(s) may subscribe and retrieve items */ + roster, + + /** Only those on a whitelist may subscribe and retrieve items */ + whitelist; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java new file mode 100644 index 0000000..d55534d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Affiliation.java @@ -0,0 +1,111 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents a affiliation between a user and a node, where the {@link #type} defines + * the type of affiliation. + * + * Affiliations are retrieved from the {@link PubSubManager#getAffiliations()} method, which + * gets affiliations for the calling user, based on the identity that is associated with + * the {@link Connection}. + * + * @author Robin Collier + */ +public class Affiliation implements PacketExtension +{ + protected String jid; + protected String node; + protected Type type; + + public enum Type + { + member, none, outcast, owner, publisher + } + + /** + * Constructs an affiliation. + * + * @param jid The JID with affiliation. + * @param affiliation The type of affiliation. + */ + public Affiliation(String jid, Type affiliation) + { + this(jid, null, affiliation); + } + + /** + * Constructs an affiliation. + * + * @param jid The JID with affiliation. + * @param node The node with affiliation. + * @param affiliation The type of affiliation. + */ + public Affiliation(String jid, String node, Type affiliation) + { + this.jid = jid; + this.node = node; + type = affiliation; + } + + public String getJid() + { + return jid; + } + + public String getNode() + { + return node; + } + + public Type getType() + { + return type; + } + + public String getElementName() + { + return "affiliation"; + } + + public String getNamespace() + { + return null; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + if (node != null) + appendAttribute(builder, "node", node); + appendAttribute(builder, "jid", jid); + appendAttribute(builder, "affiliation", type.toString()); + + builder.append("/>"); + return builder.toString(); + } + + private void appendAttribute(StringBuilder builder, String att, String value) + { + builder.append(" "); + builder.append(att); + builder.append("='"); + builder.append(value); + builder.append("'"); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java new file mode 100644 index 0000000..563147e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/AffiliationsExtension.java @@ -0,0 +1,91 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Collections; +import java.util.List; + +/** + * Represents the affiliations element of the reply to a request for affiliations. + * It is defined in the specification in section 5.7 Retrieve Affiliations. + * + * @author Robin Collier + */ +public class AffiliationsExtension extends NodeExtension +{ + protected List items = Collections.EMPTY_LIST; + + public AffiliationsExtension() + { + super(PubSubElementType.AFFILIATIONS); + } + + public AffiliationsExtension(List affiliationList) + { + super(PubSubElementType.AFFILIATIONS); + + if (affiliationList != null) + items = affiliationList; + } + + /** + * Affiliations for the specified node. + * + * @param nodeId + * @param subList + */ + public AffiliationsExtension(String nodeId, List affiliationList) + { + super(PubSubElementType.AFFILIATIONS, nodeId); + + if (affiliationList != null) + items = affiliationList; + } + + public List getAffiliations() + { + return items; + } + + @Override + public String toXML() + { + if ((items == null) || (items.size() == 0)) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + if (getNode() != null) + { + builder.append(" node='"); + builder.append(getNode()); + builder.append("'"); + } + builder.append(">"); + + for (Affiliation item : items) + { + builder.append(item.toXML()); + } + + builder.append(""); + return builder.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java new file mode 100644 index 0000000..933a39e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ChildrenAssociationPolicy.java @@ -0,0 +1,32 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * This enumeration represents the children association policy for associating leaf nodes + * with collection nodes as defined in the pubsub specification section 16.4.3 + * + * @author Robin Collier + */ +public enum ChildrenAssociationPolicy +{ + /** Anyone may associate leaf nodes with the collection */ + all, + + /** Only collection node owners may associate leaf nodes with the collection. */ + owners, + + /** Only those on a whitelist may associate leaf nodes with the collection. */ + whitelist; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java new file mode 100644 index 0000000..0300610 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/CollectionNode.java @@ -0,0 +1,15 @@ +/* + * Created on 2009-07-13 + */ +package org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.Connection; + +public class CollectionNode extends Node +{ + CollectionNode(Connection connection, String nodeId) + { + super(connection, nodeId); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java new file mode 100644 index 0000000..67b8304 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigurationEvent.java @@ -0,0 +1,56 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * Represents the configuration element of a pubsub message event which + * associates a configuration form to the node which was configured. The form + * contains the current node configuration. + * + * @author Robin Collier + */ +public class ConfigurationEvent extends NodeExtension implements EmbeddedPacketExtension +{ + private ConfigureForm form; + + public ConfigurationEvent(String nodeId) + { + super(PubSubElementType.CONFIGURATION, nodeId); + } + + public ConfigurationEvent(String nodeId, ConfigureForm configForm) + { + super(PubSubElementType.CONFIGURATION, nodeId); + form = configForm; + } + + public ConfigureForm getConfiguration() + { + return form; + } + + public List getExtensions() + { + if (getConfiguration() == null) + return Collections.EMPTY_LIST; + else + return Arrays.asList(((PacketExtension)getConfiguration().getDataFormToSend())); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java new file mode 100644 index 0000000..7f1005e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureForm.java @@ -0,0 +1,709 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DataForm; + +/** + * A decorator for a {@link Form} to easily enable reading and updating + * of node configuration. All operations read or update the underlying {@link DataForm}. + * + *

      Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all ConfigureForm.setXXX methods will create the field in the wrapped form + * if it does not already exist. + * + * @author Robin Collier + */ +public class ConfigureForm extends Form +{ + /** + * Create a decorator from an existing {@link DataForm} that has been + * retrieved from parsing a node configuration request. + * + * @param configDataForm + */ + public ConfigureForm(DataForm configDataForm) + { + super(configDataForm); + } + + /** + * Create a decorator from an existing {@link Form} for node configuration. + * Typically, this can be used to create a decorator for an answer form + * by using the result of {@link #createAnswerForm()} as the input parameter. + * + * @param nodeConfigForm + */ + public ConfigureForm(Form nodeConfigForm) + { + super(nodeConfigForm.getDataFormToSend()); + } + + /** + * Create a new form for configuring a node. This would typically only be used + * when creating and configuring a node at the same time via {@link PubSubManager#createNode(String, Form)}, since + * configuration of an existing node is typically accomplished by calling {@link LeafNode#getNodeConfiguration()} and + * using the resulting form to create a answer form. See {@link #ConfigureForm(Form)}. + * @param formType + */ + public ConfigureForm(FormType formType) + { + super(formType.toString()); + } + + /** + * Get the currently configured {@link AccessModel}, null if it is not set. + * + * @return The current {@link AccessModel} + */ + public AccessModel getAccessModel() + { + String value = getFieldValue(ConfigureNodeFields.access_model); + + if (value == null) + return null; + else + return AccessModel.valueOf(value); + } + + /** + * Sets the value of access model. + * + * @param accessModel + */ + public void setAccessModel(AccessModel accessModel) + { + addField(ConfigureNodeFields.access_model, FormField.TYPE_LIST_SINGLE); + setAnswer(ConfigureNodeFields.access_model.getFieldName(), getListSingle(accessModel.toString())); + } + + /** + * Returns the URL of an XSL transformation which can be applied to payloads in order to + * generate an appropriate message body element. + * + * @return URL to an XSL + */ + public String getBodyXSLT() + { + return getFieldValue(ConfigureNodeFields.body_xslt); + } + + /** + * Set the URL of an XSL transformation which can be applied to payloads in order to + * generate an appropriate message body element. + * + * @param bodyXslt The URL of an XSL + */ + public void setBodyXSLT(String bodyXslt) + { + addField(ConfigureNodeFields.body_xslt, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.body_xslt.getFieldName(), bodyXslt); + } + + /** + * The id's of the child nodes associated with a collection node (both leaf and collection). + * + * @return Iterator over the list of child nodes. + */ + public Iterator getChildren() + { + return getFieldValues(ConfigureNodeFields.children); + } + + /** + * Set the list of child node ids that are associated with a collection node. + * + * @param children + */ + public void setChildren(List children) + { + addField(ConfigureNodeFields.children, FormField.TYPE_TEXT_MULTI); + setAnswer(ConfigureNodeFields.children.getFieldName(), children); + } + + /** + * Returns the policy that determines who may associate children with the node. + * + * @return The current policy + */ + public ChildrenAssociationPolicy getChildrenAssociationPolicy() + { + String value = getFieldValue(ConfigureNodeFields.children_association_policy); + + if (value == null) + return null; + else + return ChildrenAssociationPolicy.valueOf(value); + } + + /** + * Sets the policy that determines who may associate children with the node. + * + * @param policy The policy being set + */ + public void setChildrenAssociationPolicy(ChildrenAssociationPolicy policy) + { + addField(ConfigureNodeFields.children_association_policy, FormField.TYPE_LIST_SINGLE); + List values = new ArrayList(1); + values.add(policy.toString()); + setAnswer(ConfigureNodeFields.children_association_policy.getFieldName(), values); + } + + /** + * Iterator of JID's that are on the whitelist that determines who can associate child nodes + * with the collection node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to + * {@link ChildrenAssociationPolicy#whitelist}. + * + * @return Iterator over whitelist + */ + public Iterator getChildrenAssociationWhitelist() + { + return getFieldValues(ConfigureNodeFields.children_association_whitelist); + } + + /** + * Set the JID's in the whitelist of users that can associate child nodes with the collection + * node. This is only relevant if {@link #getChildrenAssociationPolicy()} is set to + * {@link ChildrenAssociationPolicy#whitelist}. + * + * @param whitelist The list of JID's + */ + public void setChildrenAssociationWhitelist(List whitelist) + { + addField(ConfigureNodeFields.children_association_whitelist, FormField.TYPE_JID_MULTI); + setAnswer(ConfigureNodeFields.children_association_whitelist.getFieldName(), whitelist); + } + + /** + * Gets the maximum number of child nodes that can be associated with the collection node. + * + * @return The maximum number of child nodes + */ + public int getChildrenMax() + { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.children_max)); + } + + /** + * Set the maximum number of child nodes that can be associated with a collection node. + * + * @param max The maximum number of child nodes. + */ + public void setChildrenMax(int max) + { + addField(ConfigureNodeFields.children_max, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.children_max.getFieldName(), max); + } + + /** + * Gets the collection node which the node is affiliated with. + * + * @return The collection node id + */ + public String getCollection() + { + return getFieldValue(ConfigureNodeFields.collection); + } + + /** + * Sets the collection node which the node is affiliated with. + * + * @param collection The node id of the collection node + */ + public void setCollection(String collection) + { + addField(ConfigureNodeFields.collection, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.collection.getFieldName(), collection); + } + + /** + * Gets the URL of an XSL transformation which can be applied to the payload + * format in order to generate a valid Data Forms result that the client could + * display using a generic Data Forms rendering engine. + * + * @return The URL of an XSL transformation + */ + public String getDataformXSLT() + { + return getFieldValue(ConfigureNodeFields.dataform_xslt); + } + + /** + * Sets the URL of an XSL transformation which can be applied to the payload + * format in order to generate a valid Data Forms result that the client could + * display using a generic Data Forms rendering engine. + * + * @param url The URL of an XSL transformation + */ + public void setDataformXSLT(String url) + { + addField(ConfigureNodeFields.dataform_xslt, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.dataform_xslt.getFieldName(), url); + } + + /** + * Does the node deliver payloads with event notifications. + * + * @return true if it does, false otherwise + */ + public boolean isDeliverPayloads() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.deliver_payloads)); + } + + /** + * Sets whether the node will deliver payloads with event notifications. + * + * @param deliver true if the payload will be delivered, false otherwise + */ + public void setDeliverPayloads(boolean deliver) + { + addField(ConfigureNodeFields.deliver_payloads, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.deliver_payloads.getFieldName(), deliver); + } + + /** + * Determines who should get replies to items + * + * @return Who should get the reply + */ + public ItemReply getItemReply() + { + String value = getFieldValue(ConfigureNodeFields.itemreply); + + if (value == null) + return null; + else + return ItemReply.valueOf(value); + } + + /** + * Sets who should get the replies to items + * + * @param reply Defines who should get the reply + */ + public void setItemReply(ItemReply reply) + { + addField(ConfigureNodeFields.itemreply, FormField.TYPE_LIST_SINGLE); + setAnswer(ConfigureNodeFields.itemreply.getFieldName(), getListSingle(reply.toString())); + } + + /** + * Gets the maximum number of items to persisted to this node if {@link #isPersistItems()} is + * true. + * + * @return The maximum number of items to persist + */ + public int getMaxItems() + { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_items)); + } + + /** + * Set the maximum number of items to persisted to this node if {@link #isPersistItems()} is + * true. + * + * @param max The maximum number of items to persist + */ + public void setMaxItems(int max) + { + addField(ConfigureNodeFields.max_items, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.max_items.getFieldName(), max); + } + + /** + * Gets the maximum payload size in bytes. + * + * @return The maximum payload size + */ + public int getMaxPayloadSize() + { + return Integer.parseInt(getFieldValue(ConfigureNodeFields.max_payload_size)); + } + + /** + * Sets the maximum payload size in bytes + * + * @param max The maximum payload size + */ + public void setMaxPayloadSize(int max) + { + addField(ConfigureNodeFields.max_payload_size, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.max_payload_size.getFieldName(), max); + } + + /** + * Gets the node type + * + * @return The node type + */ + public NodeType getNodeType() + { + String value = getFieldValue(ConfigureNodeFields.node_type); + + if (value == null) + return null; + else + return NodeType.valueOf(value); + } + + /** + * Sets the node type + * + * @param type The node type + */ + public void setNodeType(NodeType type) + { + addField(ConfigureNodeFields.node_type, FormField.TYPE_LIST_SINGLE); + setAnswer(ConfigureNodeFields.node_type.getFieldName(), getListSingle(type.toString())); + } + + /** + * Determines if subscribers should be notified when the configuration changes. + * + * @return true if they should be notified, false otherwise + */ + public boolean isNotifyConfig() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.notify_config)); + } + + /** + * Sets whether subscribers should be notified when the configuration changes. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyConfig(boolean notify) + { + addField(ConfigureNodeFields.notify_config, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.notify_config.getFieldName(), notify); + } + + /** + * Determines whether subscribers should be notified when the node is deleted. + * + * @return true if subscribers should be notified, false otherwise + */ + public boolean isNotifyDelete() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.notify_delete)); + } + + /** + * Sets whether subscribers should be notified when the node is deleted. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyDelete(boolean notify) + { + addField(ConfigureNodeFields.notify_delete, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.notify_delete.getFieldName(), notify); + } + + /** + * Determines whether subscribers should be notified when items are deleted + * from the node. + * + * @return true if subscribers should be notified, false otherwise + */ + public boolean isNotifyRetract() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.notify_retract)); + } + + /** + * Sets whether subscribers should be notified when items are deleted + * from the node. + * + * @param notify true if subscribers should be notified, false otherwise + */ + public void setNotifyRetract(boolean notify) + { + addField(ConfigureNodeFields.notify_retract, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.notify_retract.getFieldName(), notify); + } + + /** + * Determines whether items should be persisted in the node. + * + * @return true if items are persisted + */ + public boolean isPersistItems() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.persist_items)); + } + + /** + * Sets whether items should be persisted in the node. + * + * @param persist true if items should be persisted, false otherwise + */ + public void setPersistentItems(boolean persist) + { + addField(ConfigureNodeFields.persist_items, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.persist_items.getFieldName(), persist); + } + + /** + * Determines whether to deliver notifications to available users only. + * + * @return true if users must be available + */ + public boolean isPresenceBasedDelivery() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.presence_based_delivery)); + } + + /** + * Sets whether to deliver notifications to available users only. + * + * @param presenceBased true if user must be available, false otherwise + */ + public void setPresenceBasedDelivery(boolean presenceBased) + { + addField(ConfigureNodeFields.presence_based_delivery, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.presence_based_delivery.getFieldName(), presenceBased); + } + + /** + * Gets the publishing model for the node, which determines who may publish to it. + * + * @return The publishing model + */ + public PublishModel getPublishModel() + { + String value = getFieldValue(ConfigureNodeFields.publish_model); + + if (value == null) + return null; + else + return PublishModel.valueOf(value); + } + + /** + * Sets the publishing model for the node, which determines who may publish to it. + * + * @param publish The enum representing the possible options for the publishing model + */ + public void setPublishModel(PublishModel publish) + { + addField(ConfigureNodeFields.publish_model, FormField.TYPE_LIST_SINGLE); + setAnswer(ConfigureNodeFields.publish_model.getFieldName(), getListSingle(publish.toString())); + } + + /** + * Iterator over the multi user chat rooms that are specified as reply rooms. + * + * @return The reply room JID's + */ + public Iterator getReplyRoom() + { + return getFieldValues(ConfigureNodeFields.replyroom); + } + + /** + * Sets the multi user chat rooms that are specified as reply rooms. + * + * @param replyRooms The multi user chat room to use as reply rooms + */ + public void setReplyRoom(List replyRooms) + { + addField(ConfigureNodeFields.replyroom, FormField.TYPE_LIST_MULTI); + setAnswer(ConfigureNodeFields.replyroom.getFieldName(), replyRooms); + } + + /** + * Gets the specific JID's for reply to. + * + * @return The JID's + */ + public Iterator getReplyTo() + { + return getFieldValues(ConfigureNodeFields.replyto); + } + + /** + * Sets the specific JID's for reply to. + * + * @param replyTos The JID's to reply to + */ + public void setReplyTo(List replyTos) + { + addField(ConfigureNodeFields.replyto, FormField.TYPE_LIST_MULTI); + setAnswer(ConfigureNodeFields.replyto.getFieldName(), replyTos); + } + + /** + * Gets the roster groups that are allowed to subscribe and retrieve items. + * + * @return The roster groups + */ + public Iterator getRosterGroupsAllowed() + { + return getFieldValues(ConfigureNodeFields.roster_groups_allowed); + } + + /** + * Sets the roster groups that are allowed to subscribe and retrieve items. + * + * @param groups The roster groups + */ + public void setRosterGroupsAllowed(List groups) + { + addField(ConfigureNodeFields.roster_groups_allowed, FormField.TYPE_LIST_MULTI); + setAnswer(ConfigureNodeFields.roster_groups_allowed.getFieldName(), groups); + } + + /** + * Determines if subscriptions are allowed. + * + * @return true if subscriptions are allowed, false otherwise + */ + public boolean isSubscibe() + { + return parseBoolean(getFieldValue(ConfigureNodeFields.subscribe)); + } + + /** + * Sets whether subscriptions are allowed. + * + * @param subscribe true if they are, false otherwise + */ + public void setSubscribe(boolean subscribe) + { + addField(ConfigureNodeFields.subscribe, FormField.TYPE_BOOLEAN); + setAnswer(ConfigureNodeFields.subscribe.getFieldName(), subscribe); + } + + /** + * Gets the human readable node title. + * + * @return The node title + */ + public String getTitle() + { + return getFieldValue(ConfigureNodeFields.title); + } + + /** + * Sets a human readable title for the node. + * + * @param title The node title + */ + public void setTitle(String title) + { + addField(ConfigureNodeFields.title, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.title.getFieldName(), title); + } + + /** + * The type of node data, usually specified by the namespace of the payload (if any). + * + * @return The type of node data + */ + public String getDataType() + { + return getFieldValue(ConfigureNodeFields.type); + } + + /** + * Sets the type of node data, usually specified by the namespace of the payload (if any). + * + * @param type The type of node data + */ + public void setDataType(String type) + { + addField(ConfigureNodeFields.type, FormField.TYPE_TEXT_SINGLE); + setAnswer(ConfigureNodeFields.type.getFieldName(), type); + } + + @Override + public String toString() + { + StringBuilder result = new StringBuilder(getClass().getName() + " Content ["); + + Iterator fields = getFields(); + + while (fields.hasNext()) + { + FormField formField = fields.next(); + result.append('('); + result.append(formField.getVariable()); + result.append(':'); + + Iterator values = formField.getValues(); + StringBuilder valuesBuilder = new StringBuilder(); + + while (values.hasNext()) + { + if (valuesBuilder.length() > 0) + result.append(','); + String value = (String)values.next(); + valuesBuilder.append(value); + } + + if (valuesBuilder.length() == 0) + valuesBuilder.append("NOT SET"); + result.append(valuesBuilder); + result.append(')'); + } + result.append(']'); + return result.toString(); + } + + static private boolean parseBoolean(String fieldValue) + { + return ("1".equals(fieldValue) || "true".equals(fieldValue)); + } + + private String getFieldValue(ConfigureNodeFields field) + { + FormField formField = getField(field.getFieldName()); + + return formField.getValues().next(); + } + + private Iterator getFieldValues(ConfigureNodeFields field) + { + FormField formField = getField(field.getFieldName()); + + return formField.getValues(); + } + + private void addField(ConfigureNodeFields nodeField, String type) + { + String fieldName = nodeField.getFieldName(); + + if (getField(fieldName) == null) + { + FormField field = new FormField(fieldName); + field.setType(type); + addField(field); + } + } + + private List getListSingle(String value) + { + List list = new ArrayList(1); + list.add(value); + return list; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java new file mode 100644 index 0000000..3912483 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ConfigureNodeFields.java @@ -0,0 +1,218 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.net.URL; + +import org.jivesoftware.smackx.Form; + +/** + * This enumeration represents all the fields of a node configuration form. This enumeration + * is not required when using the {@link ConfigureForm} to configure nodes, but may be helpful + * for generic UI's using only a {@link Form} for configuration. + * + * @author Robin Collier + */ +public enum ConfigureNodeFields +{ + /** + * Determines who may subscribe and retrieve items + * + *

      Value: {@link AccessModel}

      + */ + access_model, + + /** + * The URL of an XSL transformation which can be applied to + * payloads in order to generate an appropriate message + * body element + * + *

      Value: {@link URL}

      + */ + body_xslt, + + /** + * The collection with which a node is affiliated + * + *

      Value: String

      + */ + collection, + + /** + * The URL of an XSL transformation which can be applied to + * payload format in order to generate a valid Data Forms result + * that the client could display using a generic Data Forms + * rendering engine body element. + * + *

      Value: {@link URL}

      + */ + dataform_xslt, + + /** + * Whether to deliver payloads with event notifications + * + *

      Value: boolean

      + */ + deliver_payloads, + + /** + * Whether owners or publisher should receive replies to items + * + *

      Value: {@link ItemReply}

      + */ + itemreply, + + /** + * Who may associate leaf nodes with a collection + * + *

      Value: {@link ChildrenAssociationPolicy}

      + */ + children_association_policy, + + /** + * The list of JIDs that may associate leaf nodes with a + * collection + * + *

      Value: List of JIDs as Strings

      + */ + children_association_whitelist, + + /** + * The child nodes (leaf or collection) associated with a collection + * + *

      Value: List of Strings

      + */ + children, + + /** + * The maximum number of child nodes that can be associated with a + * collection + * + *

      Value: int

      + */ + children_max, + + /** + * The maximum number of items to persist + * + *

      Value: int

      + */ + max_items, + + /** + * The maximum payload size in bytes + * + *

      Value: int

      + */ + max_payload_size, + + /** + * Whether the node is a leaf (default) or collection + * + *

      Value: {@link NodeType}

      + */ + node_type, + + /** + * Whether to notify subscribers when the node configuration changes + * + *

      Value: boolean

      + */ + notify_config, + + /** + * Whether to notify subscribers when the node is deleted + * + *

      Value: boolean

      + */ + notify_delete, + + /** + * Whether to notify subscribers when items are removed from the node + * + *

      Value: boolean

      + */ + notify_retract, + + /** + * Whether to persist items to storage. This is required to have multiple + * items in the node. + * + *

      Value: boolean

      + */ + persist_items, + + /** + * Whether to deliver notifications to available users only + * + *

      Value: boolean

      + */ + presence_based_delivery, + + /** + * Defines who can publish to the node + * + *

      Value: {@link PublishModel}

      + */ + publish_model, + + /** + * The specific multi-user chat rooms to specify for replyroom + * + *

      Value: List of JIDs as Strings

      + */ + replyroom, + + /** + * The specific JID(s) to specify for replyto + * + *

      Value: List of JIDs as Strings

      + */ + replyto, + + /** + * The roster group(s) allowed to subscribe and retrieve items + * + *

      Value: List of strings

      + */ + roster_groups_allowed, + + /** + * Whether to allow subscriptions + * + *

      Value: boolean

      + */ + subscribe, + + /** + * A friendly name for the node + * + *

      Value: String

      + */ + title, + + /** + * The type of node data, ussually specified by the namespace + * of the payload(if any);MAY be a list-single rather than a + * text single + * + *

      Value: String

      + */ + type; + + public String getFieldName() + { + return "pubsub#" + toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java new file mode 100644 index 0000000..b17a66a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EmbeddedPacketExtension.java @@ -0,0 +1,45 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.List; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.util.PacketParserUtils; + +/** + * This interface defines {@link PacketExtension} implementations that contain other + * extensions. This effectively extends the idea of an extension within one of the + * top level {@link Packet} types to consider any embedded element to be an extension + * of its parent. This more easily enables the usage of some of Smacks parsing + * utilities such as {@link PacketParserUtils#parsePacketExtension(String, String, org.xmlpull.v1.XmlPullParser)} to be used + * to parse any element of the XML being parsed. + * + *

      Top level extensions have only one element, but they can have multiple children, or + * their children can have multiple children. This interface is a way of allowing extensions + * to be embedded within one another as a partial or complete one to one mapping of extension + * to element. + * + * @author Robin Collier + */ +public interface EmbeddedPacketExtension extends PacketExtension +{ + /** + * Get the list of embedded {@link PacketExtension} objects. + * + * @return List of embedded {@link PacketExtension} + */ + List getExtensions(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java new file mode 100644 index 0000000..165970f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElement.java @@ -0,0 +1,74 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Arrays; +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; + +/** + * Represents the top level element of a pubsub event extension. All types of pubsub events are + * represented by this class. The specific type can be found by {@link #getEventType()}. The + * embedded event information, which is specific to the event type, can be retrieved by the {@link #getEvent()} + * method. + * + * @author Robin Collier + */ +public class EventElement implements EmbeddedPacketExtension +{ + private EventElementType type; + private NodeExtension ext; + + public EventElement(EventElementType eventType, NodeExtension eventExt) + { + type = eventType; + ext = eventExt; + } + + public EventElementType getEventType() + { + return type; + } + + public List getExtensions() + { + return Arrays.asList(new PacketExtension[]{getEvent()}); + } + + public NodeExtension getEvent() + { + return ext; + } + + public String getElementName() + { + return "event"; + } + + public String getNamespace() + { + return PubSubNamespace.EVENT.getXmlns(); + } + + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + + builder.append(ext.toXML()); + builder.append(""); + return builder.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java new file mode 100644 index 0000000..343edbe --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/EventElementType.java @@ -0,0 +1,41 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * This enumeration defines the possible event types that are supported within pubsub + * event messages. + * + * @author Robin Collier + */ +public enum EventElementType +{ + /** A node has been associated or dissassociated with a collection node */ + collection, + + /** A node has had its configuration changed */ + configuration, + + /** A node has been deleted */ + delete, + + /** Items have been published to a node */ + items, + + /** All items have been purged from a node */ + purge, + + /** A node has been subscribed to */ + subscription +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java new file mode 100644 index 0000000..e08bed2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNode.java @@ -0,0 +1,99 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.Form; + +/** + * Generic packet extension which represents any pubsub form that is + * parsed from the incoming stream or being sent out to the server. + * + * Form types are defined in {@link FormNodeType}. + * + * @author Robin Collier + */ +public class FormNode extends NodeExtension +{ + private Form configForm; + + /** + * Create a {@link FormNode} which contains the specified form. + * + * @param formType The type of form being sent + * @param submitForm The form + */ + public FormNode(FormNodeType formType, Form submitForm) + { + super(formType.getNodeElement()); + + if (submitForm == null) + throw new IllegalArgumentException("Submit form cannot be null"); + configForm = submitForm; + } + + /** + * Create a {@link FormNode} which contains the specified form, which is + * associated with the specified node. + * + * @param formType The type of form being sent + * @param nodeId The node the form is associated with + * @param submitForm The form + */ + public FormNode(FormNodeType formType, String nodeId, Form submitForm) + { + super(formType.getNodeElement(), nodeId); + + if (submitForm == null) + throw new IllegalArgumentException("Submit form cannot be null"); + configForm = submitForm; + } + + /** + * Get the Form that is to be sent, or was retrieved from the server. + * + * @return The form + */ + public Form getForm() + { + return configForm; + } + + @Override + public String toXML() + { + if (configForm == null) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + + if (getNode() != null) + { + builder.append(" node='"); + builder.append(getNode()); + builder.append("'>"); + } + else + builder.append('>'); + builder.append(configForm.getDataFormToSend().toXML()); + builder.append("'); + return builder.toString(); + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java new file mode 100644 index 0000000..6a163ee --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormNodeType.java @@ -0,0 +1,50 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; + +/** + * The types of forms supported by the pubsub specification. + * + * @author Robin Collier + */ +public enum FormNodeType +{ + /** Form for configuring an existing node */ + CONFIGURE_OWNER, + + /** Form for configuring a node during creation */ + CONFIGURE, + + /** Form for configuring subscription options */ + OPTIONS, + + /** Form which represents the default node configuration options */ + DEFAULT; + + public PubSubElementType getNodeElement() + { + return PubSubElementType.valueOf(toString()); + } + + public static FormNodeType valueOfFromElementName(String elem, String configNamespace) + { + if ("configure".equals(elem) && PubSubNamespace.OWNER.getXmlns().equals(configNamespace)) + { + return CONFIGURE_OWNER; + } + return valueOf(elem.toUpperCase()); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java new file mode 100644 index 0000000..e0fff51 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/FormType.java @@ -0,0 +1,26 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.Form; + +/** + * Defines the allowable types for a {@link Form} + * + * @author Robin Collier + */ +public enum FormType +{ + form, submit, cancel, result; +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java new file mode 100644 index 0000000..341b7b5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/GetItemsRequest.java @@ -0,0 +1,85 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Represents a request to subscribe to a node. + * + * @author Robin Collier + */ +public class GetItemsRequest extends NodeExtension +{ + protected String subId; + protected int maxItems; + + public GetItemsRequest(String nodeId) + { + super(PubSubElementType.ITEMS, nodeId); + } + + public GetItemsRequest(String nodeId, String subscriptionId) + { + super(PubSubElementType.ITEMS, nodeId); + subId = subscriptionId; + } + + public GetItemsRequest(String nodeId, int maxItemsToReturn) + { + super(PubSubElementType.ITEMS, nodeId); + maxItems = maxItemsToReturn; + } + + public GetItemsRequest(String nodeId, String subscriptionId, int maxItemsToReturn) + { + this(nodeId, maxItemsToReturn); + subId = subscriptionId; + } + + public String getSubscriptionId() + { + return subId; + } + + public int getMaxItems() + { + return maxItems; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + + builder.append(" node='"); + builder.append(getNode()); + builder.append("'"); + + if (getSubscriptionId() != null) + { + builder.append(" subid='"); + builder.append(getSubscriptionId()); + builder.append("'"); + } + + if (getMaxItems() > 0) + { + builder.append(" max_items='"); + builder.append(getMaxItems()); + builder.append("'"); + } + builder.append("/>"); + return builder.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java new file mode 100644 index 0000000..2ce0baa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Item.java @@ -0,0 +1,132 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smackx.pubsub.provider.ItemProvider; + +/** + * This class represents an item that has been, or will be published to a + * pubsub node. An Item has several properties that are dependent + * on the configuration of the node to which it has been or will be published. + * + *

      An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} + *
    • Will always have an id (either user or server generated) unless node configuration has both + * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false. + *
    • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true, otherwise it will be null. + * + *

      An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} + *
    • The id is optional, since the server will generate one if necessary, but should be used if it is + * meaningful in the context of the node. This value must be unique within the node that it is sent to, since + * resending an item with the same id will overwrite the one that already exists if the items are persisted. + *
    • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true. + * + *

      To customise the payload object being returned from the {@link #getPayload()} method, you can + * add a custom parser as explained in {@link ItemProvider}. + * + * @author Robin Collier + */ +public class Item extends NodeExtension +{ + private String id; + + /** + * Create an empty Item with no id. This is a valid item for nodes which are configured + * so that {@link ConfigureForm#isDeliverPayloads()} is false. In most cases an id will be generated by the server. + * For nodes configured with {@link ConfigureForm#isDeliverPayloads()} and {@link ConfigureForm#isPersistItems()} + * set to false, no Item is sent to the node, you have to use {@link LeafNode#send()} or {@link LeafNode#publish()} + * methods in this case. + */ + public Item() + { + super(PubSubElementType.ITEM); + } + + /** + * Create an Item with an id but no payload. This is a valid item for nodes which are configured + * so that {@link ConfigureForm#isDeliverPayloads()} is false. + * + * @param itemId The id if the item. It must be unique within the node unless overwriting and existing item. + * Passing null is the equivalent of calling {@link #Item()}. + */ + public Item(String itemId) + { + // The element type is actually irrelevant since we override getNamespace() to return null + super(PubSubElementType.ITEM); + id = itemId; + } + + /** + * Create an Item with an id and a node id. + *

      + * Note: This is not valid for publishing an item to a node, only receiving from + * one as part of {@link Message}. If used to create an Item to publish + * (via {@link LeafNode#publish(Item)}, the server may return an + * error for an invalid packet. + * + * @param itemId The id of the item. + * @param nodeId The id of the node which the item was published to. + */ + public Item(String itemId, String nodeId) + { + super(PubSubElementType.ITEM_EVENT, nodeId); + id = itemId; + } + + /** + * Get the item id. Unique to the node it is associated with. + * + * @return The id + */ + public String getId() + { + return id; + } + + @Override + public String getNamespace() + { + return null; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + + return builder.toString(); + } + + @Override + public String toString() + { + return getClass().getName() + " | Content [" + toXML() + "]"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java new file mode 100644 index 0000000..82ab7df --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemDeleteEvent.java @@ -0,0 +1,62 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Collections; +import java.util.List; + +/** + * Represents an event in which items have been deleted from the node. + * + * @author Robin Collier + */ +public class ItemDeleteEvent extends SubscriptionEvent +{ + private List itemIds = Collections.EMPTY_LIST; + + /** + * Constructs an ItemDeleteEvent that indicates the the supplied + * items (by id) have been deleted, and that the event matches the listed + * subscriptions. The subscriptions would have been created by calling + * {@link LeafNode#subscribe(String)}. + * + * @param nodeId The id of the node the event came from + * @param deletedItemIds The item ids of the items that were deleted. + * @param subscriptionIds The subscriptions that match the event. + */ + public ItemDeleteEvent(String nodeId, List deletedItemIds, List subscriptionIds) + { + super(nodeId, subscriptionIds); + + if (deletedItemIds == null) + throw new IllegalArgumentException("deletedItemIds cannot be null"); + itemIds = deletedItemIds; + } + + /** + * Get the item id's of the items that have been deleted. + * + * @return List of item id's + */ + public List getItemIds() + { + return Collections.unmodifiableList(itemIds); + } + + @Override + public String toString() + { + return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Deleted Items: " + itemIds + ']'; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java new file mode 100644 index 0000000..1ef1f67 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemPublishEvent.java @@ -0,0 +1,123 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +/** + * Represents an event generated by an item(s) being published to a node. + * + * @author Robin Collier + */ +public class ItemPublishEvent extends SubscriptionEvent +{ + private List items; + private Date originalDate; + + /** + * Constructs an ItemPublishEvent with the provided list + * of {@link Item} that were published. + * + * @param nodeId The id of the node the event came from + * @param eventItems The list of {@link Item} that were published + */ + public ItemPublishEvent(String nodeId, List eventItems) + { + super(nodeId); + items = eventItems; + } + + /** + * Constructs an ItemPublishEvent with the provided list + * of {@link Item} that were published. The list of subscription ids + * represents the subscriptions that matched the event, in the case + * of the user having multiple subscriptions. + * + * @param nodeId The id of the node the event came from + * @param eventItems The list of {@link Item} that were published + * @param subscriptionIds The list of subscriptionIds + */ + public ItemPublishEvent(String nodeId, List eventItems, List subscriptionIds) + { + super(nodeId, subscriptionIds); + items = eventItems; + } + + /** + * Constructs an ItemPublishEvent with the provided list + * of {@link Item} that were published in the past. The published + * date signifies that this is delayed event. The list of subscription ids + * represents the subscriptions that matched the event, in the case + * of the user having multiple subscriptions. + * + * @param nodeId The id of the node the event came from + * @param eventItems The list of {@link Item} that were published + * @param subscriptionIds The list of subscriptionIds + * @param publishedDate The original publishing date of the events + */ + public ItemPublishEvent(String nodeId, List eventItems, List subscriptionIds, Date publishedDate) + { + super(nodeId, subscriptionIds); + items = eventItems; + + if (publishedDate != null) + originalDate = publishedDate; + } + + /** + * Get the list of {@link Item} that were published. + * + * @return The list of published {@link Item} + */ + public List getItems() + { + return Collections.unmodifiableList(items); + } + + /** + * Indicates whether this event was delayed. That is, the items + * were published to the node at some time in the past. This will + * typically happen if there is an item already published to the node + * before a user subscribes to it. In this case, when the user + * subscribes, the server may send the last item published to the node + * with a delay date showing its time of original publication. + * + * @return true if the items are delayed, false otherwise. + */ + public boolean isDelayed() + { + return (originalDate != null); + } + + /** + * Gets the original date the items were published. This is only + * valid if {@link #isDelayed()} is true. + * + * @return Date items were published if {@link #isDelayed()} is true, null otherwise. + */ + public Date getPublishedDate() + { + return originalDate; + } + + @Override + public String toString() + { + return getClass().getName() + " [subscriptions: " + getSubscriptions() + "], [Delayed: " + + (isDelayed() ? originalDate.toString() : "false") + ']'; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java new file mode 100644 index 0000000..3e090d9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemReply.java @@ -0,0 +1,29 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * These are the options for the node configuration setting {@link ConfigureForm#setItemReply(ItemReply)}, + * which defines who should receive replies to items. + * + * @author Robin Collier + */ +public enum ItemReply +{ + /** The node owner */ + owner, + + /** The item publisher */ + publisher; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java new file mode 100644 index 0000000..c98d93a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/ItemsExtension.java @@ -0,0 +1,196 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.List; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * This class is used to for multiple purposes. + *

    • It can represent an event containing a list of items that have been published + *
    • It can represent an event containing a list of retracted (deleted) items. + *
    • It can represent a request to delete a list of items. + *
    • It can represent a request to get existing items. + * + *

      Please note, this class is used for internal purposes, and is not required for usage of + * pubsub functionality. + * + * @author Robin Collier + */ +public class ItemsExtension extends NodeExtension implements EmbeddedPacketExtension +{ + protected ItemsElementType type; + protected Boolean notify; + protected List items; + + public enum ItemsElementType + { + /** An items element, which has an optional max_items attribute when requesting items */ + items(PubSubElementType.ITEMS, "max_items"), + + /** A retract element, which has an optional notify attribute when publishing deletions */ + retract(PubSubElementType.RETRACT, "notify"); + + private PubSubElementType elem; + private String att; + + private ItemsElementType(PubSubElementType nodeElement, String attribute) + { + elem = nodeElement; + att = attribute; + } + + public PubSubElementType getNodeElement() + { + return elem; + } + + public String getElementAttribute() + { + return att; + } + } + + /** + * Construct an instance with a list representing items that have been published or deleted. + * + *

      Valid scenarios are: + *

    • Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an + * optional value for the max_items attribute. + *
    • Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing + * only id's and an optional value for the notify attribute. + *
    • Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and + * attributeValue = null + *
    • Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and + * attributeValue = null + * + * @param itemsType Type of representation + * @param nodeId The node to which the items are being sent or deleted + * @param items The list of {@link Item} or {@link RetractItem} + * @param attributeValue The value of the max_items + */ + public ItemsExtension(ItemsElementType itemsType, String nodeId, List items) + { + super(itemsType.getNodeElement(), nodeId); + type = itemsType; + this.items = items; + } + + /** + * Construct an instance with a list representing items that have been published or deleted. + * + *

      Valid scenarios are: + *

    • Request items from node - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and an + * optional value for the max_items attribute. + *
    • Request to delete items - itemsType = {@link ItemsElementType#retract}, items = list of {@link Item} containing + * only id's and an optional value for the notify attribute. + *
    • Items published event - itemsType = {@link ItemsElementType#items}, items = list of {@link Item} and + * attributeValue = null + *
    • Items deleted event - itemsType = {@link ItemsElementType#items}, items = list of {@link RetractItem} and + * attributeValue = null + * + * @param itemsType Type of representation + * @param nodeId The node to which the items are being sent or deleted + * @param items The list of {@link Item} or {@link RetractItem} + * @param attributeValue The value of the max_items + */ + public ItemsExtension(String nodeId, List items, boolean notify) + { + super(ItemsElementType.retract.getNodeElement(), nodeId); + type = ItemsElementType.retract; + this.items = items; + this.notify = notify; + } + + /** + * Get the type of element + * + * @return The element type + */ + public ItemsElementType getItemsElementType() + { + return type; + } + + public List getExtensions() + { + return (List)getItems(); + } + + /** + * Gets the items related to the type of request or event. + * + * return List of {@link Item}, {@link RetractItem}, or null + */ + public List getItems() + { + return items; + } + + /** + * Gets the value of the optional attribute related to the {@link ItemsElementType}. + * + * @return The attribute value + */ + public boolean getNotify() + { + return notify; + } + + @Override + public String toXML() + { + if ((items == null) || (items.size() == 0)) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + builder.append(" node='"); + builder.append(getNode()); + + if (notify != null) + { + builder.append("' "); + builder.append(type.getElementAttribute()); + builder.append("='"); + builder.append(notify.equals(Boolean.TRUE) ? 1 : 0); + builder.append("'>"); + } + else + { + builder.append("'>"); + for (PacketExtension item : items) + { + builder.append(item.toXML()); + } + } + + builder.append(""); + return builder.toString(); + } + } + + @Override + public String toString() + { + return getClass().getName() + "Content [" + toXML() + "]"; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java new file mode 100644 index 0000000..eee6293 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/LeafNode.java @@ -0,0 +1,352 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.pubsub.packet.PubSub; +import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; + +/** + * The main class for the majority of pubsub functionality. In general + * almost all pubsub capabilities are related to the concept of a node. + * All items are published to a node, and typically subscribed to by other + * users. These users then retrieve events based on this subscription. + * + * @author Robin Collier + */ +public class LeafNode extends Node +{ + LeafNode(Connection connection, String nodeName) + { + super(connection, nodeName); + } + + /** + * Get information on the items in the node in standard + * {@link DiscoverItems} format. + * + * @return The item details in {@link DiscoverItems} format + * + * @throws XMPPException + */ + public DiscoverItems discoverItems() + throws XMPPException + { + DiscoverItems items = new DiscoverItems(); + items.setTo(to); + items.setNode(getId()); + return (DiscoverItems)SyncPacketSend.getReply(con, items); + } + + /** + * Get the current items stored in the node. + * + * @return List of {@link Item} in the node + * + * @throws XMPPException + */ + public List getItems() + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId())); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)itemsElem.getItems(); + } + + /** + * Get the current items stored in the node based + * on the subscription associated with the provided + * subscription id. + * + * @param subscriptionId - The subscription id for the + * associated subscription. + * @return List of {@link Item} in the node + * + * @throws XMPPException + */ + public List getItems(String subscriptionId) + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId)); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)itemsElem.getItems(); + } + + /** + * Get the items specified from the node. This would typically be + * used when the server does not return the payload due to size + * constraints. The user would be required to retrieve the payload + * after the items have been retrieved via {@link #getItems()} or an + * event, that did not include the payload. + * + * @param ids Item ids of the items to retrieve + * + * @return The list of {@link Item} with payload + * + * @throws XMPPException + */ + public List getItems(Collection ids) + throws XMPPException + { + List itemList = new ArrayList(ids.size()); + + for (String id : ids) + { + itemList.add(new Item(id)); + } + PubSub request = createPubsubPacket(Type.GET, new ItemsExtension(ItemsExtension.ItemsElementType.items, getId(), itemList)); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)itemsElem.getItems(); + } + + /** + * Get items persisted on the node, limited to the specified number. + * + * @param maxItems Maximum number of items to return + * + * @return List of {@link Item} + * + * @throws XMPPException + */ + public List getItems(int maxItems) + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), maxItems)); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)itemsElem.getItems(); + } + + /** + * Get items persisted on the node, limited to the specified number + * based on the subscription associated with the provided subscriptionId. + * + * @param maxItems Maximum number of items to return + * @param subscriptionId The subscription which the retrieval is based + * on. + * + * @return List of {@link Item} + * + * @throws XMPPException + */ + public List getItems(int maxItems, String subscriptionId) + throws XMPPException + { + PubSub request = createPubsubPacket(Type.GET, new GetItemsRequest(getId(), subscriptionId, maxItems)); + + PubSub result = (PubSub)SyncPacketSend.getReply(con, request); + ItemsExtension itemsElem = (ItemsExtension)result.getExtension(PubSubElementType.ITEMS); + return (List)itemsElem.getItems(); + } + + /** + * Publishes an event to the node. This is an empty event + * with no item. + * + * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false + * and {@link ConfigureForm#isDeliverPayloads()}=false. + * + * This is an asynchronous call which returns as soon as the + * packet has been sent. + * + * For synchronous calls use {@link #send() send()}. + */ + public void publish() + { + PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId())); + + con.sendPacket(packet); + } + + /** + * Publishes an event to the node. This is a simple item + * with no payload. + * + * If the id is null, an empty item (one without an id) will be sent. + * Please note that this is not the same as {@link #send()}, which + * publishes an event with NO item. + * + * This is an asynchronous call which returns as soon as the + * packet has been sent. + * + * For synchronous calls use {@link #send(Item) send(Item))}. + * + * @param item - The item being sent + */ + public void publish(T item) + { + Collection items = new ArrayList(1); + items.add((T)(item == null ? new Item() : item)); + publish(items); + } + + /** + * Publishes multiple events to the node. Same rules apply as in {@link #publish(Item)}. + * + * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input + * list will get stored on the node, assuming it stores the last sent item. + * + * This is an asynchronous call which returns as soon as the + * packet has been sent. + * + * For synchronous calls use {@link #send(Collection) send(Collection))}. + * + * @param items - The collection of items being sent + */ + public void publish(Collection items) + { + PubSub packet = createPubsubPacket(Type.SET, new PublishItem(getId(), items)); + + con.sendPacket(packet); + } + + /** + * Publishes an event to the node. This is an empty event + * with no item. + * + * This is only acceptable for nodes with {@link ConfigureForm#isPersistItems()}=false + * and {@link ConfigureForm#isDeliverPayloads()}=false. + * + * This is a synchronous call which will throw an exception + * on failure. + * + * For asynchronous calls, use {@link #publish() publish()}. + * + * @throws XMPPException + */ + public void send() + throws XMPPException + { + PubSub packet = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PUBLISH, getId())); + + SyncPacketSend.getReply(con, packet); + } + + /** + * Publishes an event to the node. This can be either a simple item + * with no payload, or one with it. This is determined by the Node + * configuration. + * + * If the node has deliver_payload=false, the Item must not + * have a payload. + * + * If the id is null, an empty item (one without an id) will be sent. + * Please note that this is not the same as {@link #send()}, which + * publishes an event with NO item. + * + * This is a synchronous call which will throw an exception + * on failure. + * + * For asynchronous calls, use {@link #publish(Item) publish(Item)}. + * + * @param item - The item being sent + * + * @throws XMPPException + */ + public void send(T item) + throws XMPPException + { + Collection items = new ArrayList(1); + items.add((item == null ? (T)new Item() : item)); + send(items); + } + + /** + * Publishes multiple events to the node. Same rules apply as in {@link #send(Item)}. + * + * In addition, if {@link ConfigureForm#isPersistItems()}=false, only the last item in the input + * list will get stored on the node, assuming it stores the last sent item. + * + * This is a synchronous call which will throw an exception + * on failure. + * + * For asynchronous calls, use {@link #publish(Collection) publish(Collection))}. + * + * @param items - The collection of {@link Item} objects being sent + * + * @throws XMPPException + */ + public void send(Collection items) + throws XMPPException + { + PubSub packet = createPubsubPacket(Type.SET, new PublishItem(getId(), items)); + + SyncPacketSend.getReply(con, packet); + } + + /** + * Purges the node of all items. + * + *

      Note: Some implementations may keep the last item + * sent. + * + * @throws XMPPException + */ + public void deleteAllItems() + throws XMPPException + { + PubSub request = createPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.PURGE_OWNER, getId()), PubSubElementType.PURGE_OWNER.getNamespace()); + + SyncPacketSend.getReply(con, request); + } + + /** + * Delete the item with the specified id from the node. + * + * @param itemId The id of the item + * + * @throws XMPPException + */ + public void deleteItem(String itemId) + throws XMPPException + { + Collection items = new ArrayList(1); + items.add(itemId); + deleteItem(items); + } + + /** + * Delete the items with the specified id's from the node. + * + * @param itemIds The list of id's of items to delete + * + * @throws XMPPException + */ + public void deleteItem(Collection itemIds) + throws XMPPException + { + List items = new ArrayList(itemIds.size()); + + for (String id : itemIds) + { + items.add(new Item(id)); + } + PubSub request = createPubsubPacket(Type.SET, new ItemsExtension(ItemsExtension.ItemsElementType.retract, getId(), items)); + SyncPacketSend.getReply(con, request); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java new file mode 100644 index 0000000..c900493 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Node.java @@ -0,0 +1,525 @@ +/* + * Created on 2009-07-09 + */ +package org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.OrFilter; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.packet.DelayInformation; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.Header; +import org.jivesoftware.smackx.packet.HeadersExtension; +import org.jivesoftware.smackx.pubsub.listener.ItemDeleteListener; +import org.jivesoftware.smackx.pubsub.listener.ItemEventListener; +import org.jivesoftware.smackx.pubsub.listener.NodeConfigListener; +import org.jivesoftware.smackx.pubsub.packet.PubSub; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; +import org.jivesoftware.smackx.pubsub.util.NodeUtils; + +abstract public class Node +{ + protected Connection con; + protected String id; + protected String to; + + protected ConcurrentHashMap itemEventToListenerMap = new ConcurrentHashMap(); + protected ConcurrentHashMap itemDeleteToListenerMap = new ConcurrentHashMap(); + protected ConcurrentHashMap configEventToListenerMap = new ConcurrentHashMap(); + + /** + * Construct a node associated to the supplied connection with the specified + * node id. + * + * @param connection The connection the node is associated with + * @param nodeName The node id + */ + Node(Connection connection, String nodeName) + { + con = connection; + id = nodeName; + } + + /** + * Some XMPP servers may require a specific service to be addressed on the + * server. + * + * For example, OpenFire requires the server to be prefixed by pubsub + */ + void setTo(String toAddress) + { + to = toAddress; + } + + /** + * Get the NodeId + * + * @return the node id + */ + public String getId() + { + return id; + } + /** + * Returns a configuration form, from which you can create an answer form to be submitted + * via the {@link #sendConfigurationForm(Form)}. + * + * @return the configuration form + */ + public ConfigureForm getNodeConfiguration() + throws XMPPException + { + Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.CONFIGURE_OWNER, getId()), PubSubNamespace.OWNER); + return NodeUtils.getFormFromPacket(reply, PubSubElementType.CONFIGURE_OWNER); + } + + /** + * Update the configuration with the contents of the new {@link Form} + * + * @param submitForm + */ + public void sendConfigurationForm(Form submitForm) + throws XMPPException + { + PubSub packet = createPubsubPacket(Type.SET, new FormNode(FormNodeType.CONFIGURE_OWNER, getId(), submitForm), PubSubNamespace.OWNER); + SyncPacketSend.getReply(con, packet); + } + + /** + * Discover node information in standard {@link DiscoverInfo} format. + * + * @return The discovery information about the node. + * + * @throws XMPPException + */ + public DiscoverInfo discoverInfo() + throws XMPPException + { + DiscoverInfo info = new DiscoverInfo(); + info.setTo(to); + info.setNode(getId()); + return (DiscoverInfo)SyncPacketSend.getReply(con, info); + } + + /** + * Get the subscriptions currently associated with this node. + * + * @return List of {@link Subscription} + * + * @throws XMPPException + */ + public List getSubscriptions() + throws XMPPException + { + PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS, getId())); + SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS); + return subElem.getSubscriptions(); + } + + /** + * The user subscribes to the node using the supplied jid. The + * bare jid portion of this one must match the jid for the connection. + * + * Please note that the {@link Subscription.State} should be checked + * on return since more actions may be required by the caller. + * {@link Subscription.State#pending} - The owner must approve the subscription + * request before messages will be received. + * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, + * the caller must configure the subscription before messages will be received. If it is false + * the caller can configure it but is not required to do so. + * @param jid The jid to subscribe as. + * @return The subscription + * @exception XMPPException + */ + public Subscription subscribe(String jid) + throws XMPPException + { + PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new SubscribeExtension(jid, getId())); + return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION); + } + + /** + * The user subscribes to the node using the supplied jid and subscription + * options. The bare jid portion of this one must match the jid for the + * connection. + * + * Please note that the {@link Subscription.State} should be checked + * on return since more actions may be required by the caller. + * {@link Subscription.State#pending} - The owner must approve the subscription + * request before messages will be received. + * {@link Subscription.State#unconfigured} - If the {@link Subscription#isConfigRequired()} is true, + * the caller must configure the subscription before messages will be received. If it is false + * the caller can configure it but is not required to do so. + * @param jid The jid to subscribe as. + * @return The subscription + * @exception XMPPException + */ + public Subscription subscribe(String jid, SubscribeForm subForm) + throws XMPPException + { + PubSub request = createPubsubPacket(Type.SET, new SubscribeExtension(jid, getId())); + request.addExtension(new FormNode(FormNodeType.OPTIONS, subForm)); + PubSub reply = (PubSub)PubSubManager.sendPubsubPacket(con, jid, Type.SET, request); + return (Subscription)reply.getExtension(PubSubElementType.SUBSCRIPTION); + } + + /** + * Remove the subscription related to the specified JID. This will only + * work if there is only 1 subscription. If there are multiple subscriptions, + * use {@link #unsubscribe(String, String)}. + * + * @param jid The JID used to subscribe to the node + * + * @throws XMPPException + */ + public void unsubscribe(String jid) + throws XMPPException + { + unsubscribe(jid, null); + } + + /** + * Remove the specific subscription related to the specified JID. + * + * @param jid The JID used to subscribe to the node + * @param subscriptionId The id of the subscription being removed + * + * @throws XMPPException + */ + public void unsubscribe(String jid, String subscriptionId) + throws XMPPException + { + sendPubsubPacket(Type.SET, new UnsubscribeExtension(jid, getId(), subscriptionId)); + } + + /** + * Returns a SubscribeForm for subscriptions, from which you can create an answer form to be submitted + * via the {@link #sendConfigurationForm(Form)}. + * + * @return A subscription options form + * + * @throws XMPPException + */ + public SubscribeForm getSubscriptionOptions(String jid) + throws XMPPException + { + return getSubscriptionOptions(jid, null); + } + + + /** + * Get the options for configuring the specified subscription. + * + * @param jid JID the subscription is registered under + * @param subscriptionId The subscription id + * + * @return The subscription option form + * + * @throws XMPPException + */ + public SubscribeForm getSubscriptionOptions(String jid, String subscriptionId) + throws XMPPException + { + PubSub packet = (PubSub)sendPubsubPacket(Type.GET, new OptionsExtension(jid, getId(), subscriptionId)); + FormNode ext = (FormNode)packet.getExtension(PubSubElementType.OPTIONS); + return new SubscribeForm(ext.getForm()); + } + + /** + * Register a listener for item publication events. This + * listener will get called whenever an item is published to + * this node. + * + * @param listener The handler for the event + */ + public void addItemEventListener(ItemEventListener listener) + { + PacketListener conListener = new ItemEventTranslator(listener); + itemEventToListenerMap.put(listener, conListener); + con.addPacketListener(conListener, new EventContentFilter(EventElementType.items.toString(), "item")); + } + + /** + * Unregister a listener for publication events. + * + * @param listener The handler to unregister + */ + public void removeItemEventListener(ItemEventListener listener) + { + PacketListener conListener = itemEventToListenerMap.remove(listener); + + if (conListener != null) + con.removePacketListener(conListener); + } + + /** + * Register a listener for configuration events. This listener + * will get called whenever the node's configuration changes. + * + * @param listener The handler for the event + */ + public void addConfigurationListener(NodeConfigListener listener) + { + PacketListener conListener = new NodeConfigTranslator(listener); + configEventToListenerMap.put(listener, conListener); + con.addPacketListener(conListener, new EventContentFilter(EventElementType.configuration.toString())); + } + + /** + * Unregister a listener for configuration events. + * + * @param listener The handler to unregister + */ + public void removeConfigurationListener(NodeConfigListener listener) + { + PacketListener conListener = configEventToListenerMap .remove(listener); + + if (conListener != null) + con.removePacketListener(conListener); + } + + /** + * Register an listener for item delete events. This listener + * gets called whenever an item is deleted from the node. + * + * @param listener The handler for the event + */ + public void addItemDeleteListener(ItemDeleteListener listener) + { + PacketListener delListener = new ItemDeleteTranslator(listener); + itemDeleteToListenerMap.put(listener, delListener); + EventContentFilter deleteItem = new EventContentFilter(EventElementType.items.toString(), "retract"); + EventContentFilter purge = new EventContentFilter(EventElementType.purge.toString()); + + con.addPacketListener(delListener, new OrFilter(deleteItem, purge)); + } + + /** + * Unregister a listener for item delete events. + * + * @param listener The handler to unregister + */ + public void removeItemDeleteListener(ItemDeleteListener listener) + { + PacketListener conListener = itemDeleteToListenerMap .remove(listener); + + if (conListener != null) + con.removePacketListener(conListener); + } + + @Override + public String toString() + { + return super.toString() + " " + getClass().getName() + " id: " + id; + } + + protected PubSub createPubsubPacket(Type type, PacketExtension ext) + { + return createPubsubPacket(type, ext, null); + } + + protected PubSub createPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) + { + return PubSubManager.createPubsubPacket(to, type, ext, ns); + } + + protected Packet sendPubsubPacket(Type type, NodeExtension ext) + throws XMPPException + { + return PubSubManager.sendPubsubPacket(con, to, type, ext); + } + + protected Packet sendPubsubPacket(Type type, NodeExtension ext, PubSubNamespace ns) + throws XMPPException + { + return PubSubManager.sendPubsubPacket(con, to, type, ext, ns); + } + + + private static List getSubscriptionIds(Packet packet) + { + HeadersExtension headers = (HeadersExtension)packet.getExtension("headers", "http://jabber.org/protocol/shim"); + List values = null; + + if (headers != null) + { + values = new ArrayList(headers.getHeaders().size()); + + for (Header header : headers.getHeaders()) + { + values.add(header.getValue()); + } + } + return values; + } + + /** + * This class translates low level item publication events into api level objects for + * user consumption. + * + * @author Robin Collier + */ + public class ItemEventTranslator implements PacketListener + { + private ItemEventListener listener; + + public ItemEventTranslator(ItemEventListener eventListener) + { + listener = eventListener; + } + + public void processPacket(Packet packet) + { + EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); + ItemsExtension itemsElem = (ItemsExtension)event.getEvent(); + DelayInformation delay = (DelayInformation)packet.getExtension("delay", "urn:xmpp:delay"); + + // If there was no delay based on XEP-0203, then try XEP-0091 for backward compatibility + if (delay == null) + { + delay = (DelayInformation)packet.getExtension("x", "jabber:x:delay"); + } + ItemPublishEvent eventItems = new ItemPublishEvent(itemsElem.getNode(), (List)itemsElem.getItems(), getSubscriptionIds(packet), (delay == null ? null : delay.getStamp())); + listener.handlePublishedItems(eventItems); + } + } + + /** + * This class translates low level item deletion events into api level objects for + * user consumption. + * + * @author Robin Collier + */ + public class ItemDeleteTranslator implements PacketListener + { + private ItemDeleteListener listener; + + public ItemDeleteTranslator(ItemDeleteListener eventListener) + { + listener = eventListener; + } + + public void processPacket(Packet packet) + { + EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); + + List extList = event.getExtensions(); + + if (extList.get(0).getElementName().equals(PubSubElementType.PURGE_EVENT.getElementName())) + { + listener.handlePurge(); + } + else + { + ItemsExtension itemsElem = (ItemsExtension)event.getEvent(); + Collection pubItems = itemsElem.getItems(); + Iterator it = (Iterator)pubItems.iterator(); + List items = new ArrayList(pubItems.size()); + + while (it.hasNext()) + { + RetractItem item = it.next(); + items.add(item.getId()); + } + + ItemDeleteEvent eventItems = new ItemDeleteEvent(itemsElem.getNode(), items, getSubscriptionIds(packet)); + listener.handleDeletedItems(eventItems); + } + } + } + + /** + * This class translates low level node configuration events into api level objects for + * user consumption. + * + * @author Robin Collier + */ + public class NodeConfigTranslator implements PacketListener + { + private NodeConfigListener listener; + + public NodeConfigTranslator(NodeConfigListener eventListener) + { + listener = eventListener; + } + + public void processPacket(Packet packet) + { + EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); + ConfigurationEvent config = (ConfigurationEvent)event.getEvent(); + + listener.handleNodeConfiguration(config); + } + } + + /** + * Filter for {@link PacketListener} to filter out events not specific to the + * event type expected for this node. + * + * @author Robin Collier + */ + class EventContentFilter implements PacketFilter + { + private String firstElement; + private String secondElement; + + EventContentFilter(String elementName) + { + firstElement = elementName; + } + + EventContentFilter(String firstLevelEelement, String secondLevelElement) + { + firstElement = firstLevelEelement; + secondElement = secondLevelElement; + } + + public boolean accept(Packet packet) + { + if (!(packet instanceof Message)) + return false; + + EventElement event = (EventElement)packet.getExtension("event", PubSubNamespace.EVENT.getXmlns()); + + if (event == null) + return false; + + NodeExtension embedEvent = event.getEvent(); + + if (embedEvent == null) + return false; + + if (embedEvent.getElementName().equals(firstElement)) + { + if (!embedEvent.getNode().equals(getId())) + return false; + + if (secondElement == null) + return true; + + if (embedEvent instanceof EmbeddedPacketExtension) + { + List secondLevelList = ((EmbeddedPacketExtension)embedEvent).getExtensions(); + + if (secondLevelList.size() > 0 && secondLevelList.get(0).getElementName().equals(secondElement)) + return true; + } + } + return false; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java new file mode 100644 index 0000000..c83204f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeEvent.java @@ -0,0 +1,19 @@ +/* + * Created on 2009-05-12 + */ +package org.jivesoftware.smackx.pubsub; + +abstract public class NodeEvent +{ + private String nodeId; + + protected NodeEvent(String id) + { + nodeId = id; + } + + public String getNodeId() + { + return nodeId; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java new file mode 100644 index 0000000..7e4cdec --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeExtension.java @@ -0,0 +1,85 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * A class which represents a common element within the pubsub defined + * schemas. One which has a node as an attribute. This class is + * used on its own as well as a base class for many others, since the + * node is a central concept to most pubsub functionality. + * + * @author Robin Collier + */ +public class NodeExtension implements PacketExtension +{ + private PubSubElementType element; + private String node; + + /** + * Constructs a NodeExtension with an element name specified + * by {@link PubSubElementType} and the specified node id. + * + * @param elem Defines the element name and namespace + * @param nodeId Specifies the id of the node + */ + public NodeExtension(PubSubElementType elem, String nodeId) + { + element = elem; + this.node = nodeId; + } + + /** + * Constructs a NodeExtension with an element name specified + * by {@link PubSubElementType}. + * + * @param elem Defines the element name and namespace + */ + public NodeExtension(PubSubElementType elem) + { + this(elem, null); + } + + /** + * Gets the node id + * + * @return The node id + */ + public String getNode() + { + return node; + } + + public String getElementName() + { + return element.getElementName(); + } + + public String getNamespace() + { + return element.getNamespace().getXmlns(); + } + + public String toXML() + { + return '<' + getElementName() + (node == null ? "" : " node='" + node + '\'') + "/>"; + } + + @Override + public String toString() + { + return getClass().getName() + " - content [" + toXML() + "]"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java new file mode 100644 index 0000000..5ee5a05 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/NodeType.java @@ -0,0 +1,25 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Defines the available types of nodes + * + * @author Robin Collier + */ +public enum NodeType +{ + leaf, + collection; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java new file mode 100644 index 0000000..32c0331 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/OptionsExtension.java @@ -0,0 +1,72 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.pubsub.util.XmlUtils; + +/** + * A packet extension representing the options element. + * + * @author Robin Collier + */ +public class OptionsExtension extends NodeExtension +{ + protected String jid; + protected String id; + + public OptionsExtension(String subscriptionJid) + { + this(subscriptionJid, null, null); + } + + public OptionsExtension(String subscriptionJid, String nodeId) + { + this(subscriptionJid, nodeId, null); + } + + public OptionsExtension(String jid, String nodeId, String subscriptionId) + { + super(PubSubElementType.OPTIONS, nodeId); + this.jid = jid; + id = subscriptionId; + } + + public String getJid() + { + return jid; + } + + public String getId() + { + return id; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + XmlUtils.appendAttribute(builder, "jid", jid); + + if (getNode() != null) + XmlUtils.appendAttribute(builder, "node", getNode()); + + if (id != null) + XmlUtils.appendAttribute(builder, "subid", id); + + builder.append("/>"); + return builder.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java new file mode 100644 index 0000000..488fd97 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PayloadItem.java @@ -0,0 +1,138 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.pubsub.provider.ItemProvider; + +/** + * This class represents an item that has been, or will be published to a + * pubsub node. An Item has several properties that are dependent + * on the configuration of the node to which it has been or will be published. + * + *

      An Item received from a node (via {@link LeafNode#getItems()} or {@link LeafNode#addItemEventListener(org.jivesoftware.smackx.pubsub.listener.ItemEventListener)} + *
    • Will always have an id (either user or server generated) unless node configuration has both + * {@link ConfigureForm#isPersistItems()} and {@link ConfigureForm#isDeliverPayloads()}set to false. + *
    • Will have a payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true, otherwise it will be null. + * + *

      An Item created to send to a node (via {@link LeafNode#send()} or {@link LeafNode#publish()} + *
    • The id is optional, since the server will generate one if necessary, but should be used if it is + * meaningful in the context of the node. This value must be unique within the node that it is sent to, since + * resending an item with the same id will overwrite the one that already exists if the items are persisted. + *
    • Will require payload if the node configuration has {@link ConfigureForm#isDeliverPayloads()} set + * to true. + * + *

      To customise the payload object being returned from the {@link #getPayload()} method, you can + * add a custom parser as explained in {@link ItemProvider}. + * + * @author Robin Collier + */ +public class PayloadItem extends Item +{ + private E payload; + + /** + * Create an Item with no id and a payload The id will be set by the server. + * + * @param payloadExt A {@link PacketExtension} which represents the payload data. + */ + public PayloadItem(E payloadExt) + { + super(); + + if (payloadExt == null) + throw new IllegalArgumentException("payload cannot be 'null'"); + payload = payloadExt; + } + + /** + * Create an Item with an id and payload. + * + * @param itemId The id of this item. It can be null if we want the server to set the id. + * @param payloadExt A {@link PacketExtension} which represents the payload data. + */ + public PayloadItem(String itemId, E payloadExt) + { + super(itemId); + + if (payloadExt == null) + throw new IllegalArgumentException("payload cannot be 'null'"); + payload = payloadExt; + } + + /** + * Create an Item with an id, node id and payload. + * + *

      + * Note: This is not valid for publishing an item to a node, only receiving from + * one as part of {@link Message}. If used to create an Item to publish + * (via {@link LeafNode#publish(Item)}, the server may return an + * error for an invalid packet. + * + * @param itemId The id of this item. + * @param nodeId The id of the node the item was published to. + * @param payloadExt A {@link PacketExtension} which represents the payload data. + */ + public PayloadItem(String itemId, String nodeId, E payloadExt) + { + super(itemId, nodeId); + + if (payloadExt == null) + throw new IllegalArgumentException("payload cannot be 'null'"); + payload = payloadExt; + } + + /** + * Get the payload associated with this Item. Customising the payload + * parsing from the server can be accomplished as described in {@link ItemProvider}. + * + * @return The payload as a {@link PacketExtension}. + */ + public E getPayload() + { + return payload; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + builder.append(payload.toXML()); + builder.append(""); + + return builder.toString(); + } + + @Override + public String toString() + { + return getClass().getName() + " | Content [" + toXML() + "]"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java new file mode 100644 index 0000000..0612fc2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PresenceState.java @@ -0,0 +1,25 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Defines the possible valid presence states for node subscription via + * {@link SubscribeForm#getShowValues()}. + * + * @author Robin Collier + */ +public enum PresenceState +{ + chat, online, away, xa, dnd +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java new file mode 100644 index 0000000..a887ca2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubElementType.java @@ -0,0 +1,80 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; + +/** + * Defines all the possible element types as defined for all the pubsub + * schemas in all 3 namespaces. + * + * @author Robin Collier + */ +public enum PubSubElementType +{ + CREATE("create", PubSubNamespace.BASIC), + DELETE("delete", PubSubNamespace.OWNER), + DELETE_EVENT("delete", PubSubNamespace.EVENT), + CONFIGURE("configure", PubSubNamespace.BASIC), + CONFIGURE_OWNER("configure", PubSubNamespace.OWNER), + CONFIGURATION("configuration", PubSubNamespace.EVENT), + OPTIONS("options", PubSubNamespace.BASIC), + DEFAULT("default", PubSubNamespace.OWNER), + ITEMS("items", PubSubNamespace.BASIC), + ITEMS_EVENT("items", PubSubNamespace.EVENT), + ITEM("item", PubSubNamespace.BASIC), + ITEM_EVENT("item", PubSubNamespace.EVENT), + PUBLISH("publish", PubSubNamespace.BASIC), + PUBLISH_OPTIONS("publish-options", PubSubNamespace.BASIC), + PURGE_OWNER("purge", PubSubNamespace.OWNER), + PURGE_EVENT("purge", PubSubNamespace.EVENT), + RETRACT("retract", PubSubNamespace.BASIC), + AFFILIATIONS("affiliations", PubSubNamespace.BASIC), + SUBSCRIBE("subscribe", PubSubNamespace.BASIC), + SUBSCRIPTION("subscription", PubSubNamespace.BASIC), + SUBSCRIPTIONS("subscriptions", PubSubNamespace.BASIC), + UNSUBSCRIBE("unsubscribe", PubSubNamespace.BASIC); + + private String eName; + private PubSubNamespace nSpace; + + private PubSubElementType(String elemName, PubSubNamespace ns) + { + eName = elemName; + nSpace = ns; + } + + public PubSubNamespace getNamespace() + { + return nSpace; + } + + public String getElementName() + { + return eName; + } + + public static PubSubElementType valueOfFromElemName(String elemName, String namespace) + { + int index = namespace.lastIndexOf('#'); + String fragment = (index == -1 ? null : namespace.substring(index+1)); + + if (fragment != null) + { + return valueOf((elemName + '_' + fragment).toUpperCase()); + } + return valueOf(elemName.toUpperCase().replace('-', '_')); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java new file mode 100644 index 0000000..e3eeae4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PubSubManager.java @@ -0,0 +1,329 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.packet.IQ.Type; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; +import org.jivesoftware.smackx.pubsub.packet.PubSub; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.jivesoftware.smackx.pubsub.packet.SyncPacketSend; +import org.jivesoftware.smackx.pubsub.util.NodeUtils; + +/** + * This is the starting point for access to the pubsub service. It + * will provide access to general information about the service, as + * well as create or retrieve pubsub {@link LeafNode} instances. These + * instances provide the bulk of the functionality as defined in the + * pubsub specification XEP-0060. + * + * @author Robin Collier + */ +final public class PubSubManager +{ + private Connection con; + private String to; + private Map nodeMap = new ConcurrentHashMap(); + + /** + * Create a pubsub manager associated to the specified connection. Defaults the service + * name to pubsub + * + * @param connection The XMPP connection + */ + public PubSubManager(Connection connection) + { + con = connection; + to = "pubsub." + connection.getServiceName(); + } + + /** + * Create a pubsub manager associated to the specified connection where + * the pubsub requests require a specific to address for packets. + * + * @param connection The XMPP connection + * @param toAddress The pubsub specific to address (required for some servers) + */ + public PubSubManager(Connection connection, String toAddress) + { + con = connection; + to = toAddress; + } + + /** + * Creates an instant node, if supported. + * + * @return The node that was created + * @exception XMPPException + */ + public LeafNode createNode() + throws XMPPException + { + PubSub reply = (PubSub)sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.CREATE)); + NodeExtension elem = (NodeExtension)reply.getExtension("create", PubSubNamespace.BASIC.getXmlns()); + + LeafNode newNode = new LeafNode(con, elem.getNode()); + newNode.setTo(to); + nodeMap.put(newNode.getId(), newNode); + + return newNode; + } + + /** + * Creates a node with default configuration. + * + * @param id The id of the node, which must be unique within the + * pubsub service + * @return The node that was created + * @exception XMPPException + */ + public LeafNode createNode(String id) + throws XMPPException + { + return (LeafNode)createNode(id, null); + } + + /** + * Creates a node with specified configuration. + * + * Note: This is the only way to create a collection node. + * + * @param name The name of the node, which must be unique within the + * pubsub service + * @param config The configuration for the node + * @return The node that was created + * @exception XMPPException + */ + public Node createNode(String name, Form config) + throws XMPPException + { + PubSub request = createPubsubPacket(to, Type.SET, new NodeExtension(PubSubElementType.CREATE, name)); + boolean isLeafNode = true; + + if (config != null) + { + request.addExtension(new FormNode(FormNodeType.CONFIGURE, config)); + FormField nodeTypeField = config.getField(ConfigureNodeFields.node_type.getFieldName()); + + if (nodeTypeField != null) + isLeafNode = nodeTypeField.getValues().next().equals(NodeType.leaf.toString()); + } + + // Errors will cause exceptions in getReply, so it only returns + // on success. + sendPubsubPacket(con, to, Type.SET, request); + Node newNode = isLeafNode ? new LeafNode(con, name) : new CollectionNode(con, name); + newNode.setTo(to); + nodeMap.put(newNode.getId(), newNode); + + return newNode; + } + + /** + * Retrieves the requested node, if it exists. It will throw an + * exception if it does not. + * + * @param id - The unique id of the node + * @return the node + * @throws XMPPException The node does not exist + */ + public Node getNode(String id) + throws XMPPException + { + Node node = nodeMap.get(id); + + if (node == null) + { + DiscoverInfo info = new DiscoverInfo(); + info.setTo(to); + info.setNode(id); + + DiscoverInfo infoReply = (DiscoverInfo)SyncPacketSend.getReply(con, info); + + if (infoReply.getIdentities().next().getType().equals(NodeType.leaf.toString())) + node = new LeafNode(con, id); + else + node = new CollectionNode(con, id); + node.setTo(to); + nodeMap.put(id, node); + } + return node; + } + + /** + * Get all the nodes that currently exist as a child of the specified + * collection node. If the service does not support collection nodes + * then all nodes will be returned. + * + * To retrieve contents of the root collection node (if it exists), + * or there is no root collection node, pass null as the nodeId. + * + * @param nodeId - The id of the collection node for which the child + * nodes will be returned. + * @return {@link DiscoverItems} representing the existing nodes + * + * @throws XMPPException + */ + public DiscoverItems discoverNodes(String nodeId) + throws XMPPException + { + DiscoverItems items = new DiscoverItems(); + + if (nodeId != null) + items.setNode(nodeId); + items.setTo(to); + DiscoverItems nodeItems = (DiscoverItems)SyncPacketSend.getReply(con, items); + return nodeItems; + } + + /** + * Gets the subscriptions on the root node. + * + * @return List of exceptions + * + * @throws XMPPException + */ + public List getSubscriptions() + throws XMPPException + { + Packet reply = sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.SUBSCRIPTIONS)); + SubscriptionsExtension subElem = (SubscriptionsExtension)reply.getExtension(PubSubElementType.SUBSCRIPTIONS.getElementName(), PubSubElementType.SUBSCRIPTIONS.getNamespace().getXmlns()); + return subElem.getSubscriptions(); + } + + /** + * Gets the affiliations on the root node. + * + * @return List of affiliations + * + * @throws XMPPException + */ + public List getAffiliations() + throws XMPPException + { + PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.AFFILIATIONS)); + AffiliationsExtension listElem = (AffiliationsExtension)reply.getExtension(PubSubElementType.AFFILIATIONS); + return listElem.getAffiliations(); + } + + /** + * Delete the specified node + * + * @param nodeId + * @throws XMPPException + */ + public void deleteNode(String nodeId) + throws XMPPException + { + sendPubsubPacket(Type.SET, new NodeExtension(PubSubElementType.DELETE, nodeId), PubSubElementType.DELETE.getNamespace()); + nodeMap.remove(nodeId); + } + + /** + * Returns the default settings for Node configuration. + * + * @return configuration form containing the default settings. + */ + public ConfigureForm getDefaultConfiguration() + throws XMPPException + { + // Errors will cause exceptions in getReply, so it only returns + // on success. + PubSub reply = (PubSub)sendPubsubPacket(Type.GET, new NodeExtension(PubSubElementType.DEFAULT), PubSubElementType.DEFAULT.getNamespace()); + return NodeUtils.getFormFromPacket(reply, PubSubElementType.DEFAULT); + } + + /** + * Gets the supported features of the servers pubsub implementation + * as a standard {@link DiscoverInfo} instance. + * + * @return The supported features + * + * @throws XMPPException + */ + public DiscoverInfo getSupportedFeatures() + throws XMPPException + { + ServiceDiscoveryManager mgr = ServiceDiscoveryManager.getInstanceFor(con); + return mgr.discoverInfo(to); + } + + private Packet sendPubsubPacket(Type type, PacketExtension ext, PubSubNamespace ns) + throws XMPPException + { + return sendPubsubPacket(con, to, type, ext, ns); + } + + private Packet sendPubsubPacket(Type type, PacketExtension ext) + throws XMPPException + { + return sendPubsubPacket(type, ext, null); + } + + static PubSub createPubsubPacket(String to, Type type, PacketExtension ext) + { + return createPubsubPacket(to, type, ext, null); + } + + static PubSub createPubsubPacket(String to, Type type, PacketExtension ext, PubSubNamespace ns) + { + PubSub request = new PubSub(); + request.setTo(to); + request.setType(type); + + if (ns != null) + { + request.setPubSubNamespace(ns); + } + request.addExtension(ext); + + return request; + } + + static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext) + throws XMPPException + { + return sendPubsubPacket(con, to, type, ext, null); + } + + static Packet sendPubsubPacket(Connection con, String to, Type type, PacketExtension ext, PubSubNamespace ns) + throws XMPPException + { + return SyncPacketSend.getReply(con, createPubsubPacket(to, type, ext, ns)); + } + + static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet) + throws XMPPException + { + return sendPubsubPacket(con, to, type, packet, null); + } + + static Packet sendPubsubPacket(Connection con, String to, Type type, PubSub packet, PubSubNamespace ns) + throws XMPPException + { + return SyncPacketSend.getReply(con, packet); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java new file mode 100644 index 0000000..ffbd705 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishItem.java @@ -0,0 +1,70 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Represents a request to publish an item(s) to a specific node. + * + * @author Robin Collier + */ +public class PublishItem extends NodeExtension +{ + protected Collection items; + + /** + * Construct a request to publish an item to a node. + * + * @param nodeId The node to publish to + * @param toPublish The {@link Item} to publish + */ + public PublishItem(String nodeId, T toPublish) + { + super(PubSubElementType.PUBLISH, nodeId); + items = new ArrayList(1); + items.add(toPublish); + } + + /** + * Construct a request to publish multiple items to a node. + * + * @param nodeId The node to publish to + * @param toPublish The list of {@link Item} to publish + */ + public PublishItem(String nodeId, Collection toPublish) + { + super(PubSubElementType.PUBLISH, nodeId); + items = toPublish; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + builder.append(" node='"); + builder.append(getNode()); + builder.append("'>"); + + for (Item item : items) + { + builder.append(item.toXML()); + } + builder.append(""); + + return builder.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java new file mode 100644 index 0000000..4b5a851 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/PublishModel.java @@ -0,0 +1,32 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Determines who may publish to a node. Denotes possible values + * for {@link ConfigureForm#setPublishModel(PublishModel)}. + * + * @author Robin Collier + */ +public enum PublishModel +{ + /** Only publishers may publish */ + publishers, + + /** Only subscribers may publish */ + subscribers, + + /** Anyone may publish */ + open; +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java new file mode 100644 index 0000000..97db5cc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/RetractItem.java @@ -0,0 +1,59 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; + +/** + * Represents and item that has been deleted from a node. + * + * @author Robin Collier + */ +public class RetractItem implements PacketExtension +{ + private String id; + + /** + * Construct a RetractItem with the specified id. + * + * @param itemId The id if the item deleted + */ + public RetractItem(String itemId) + { + if (itemId == null) + throw new IllegalArgumentException("itemId must not be 'null'"); + id = itemId; + } + + public String getId() + { + return id; + } + + public String getElementName() + { + return "retract"; + } + + public String getNamespace() + { + return PubSubNamespace.EVENT.getXmlns(); + } + + public String toXML() + { + return ""; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java new file mode 100644 index 0000000..9d114b0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SimplePayload.java @@ -0,0 +1,65 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * The default payload representation for {@link Item#getPayload()}. It simply + * stores the XML payload as a string. + * + * @author Robin Collier + */ +public class SimplePayload implements PacketExtension +{ + private String elemName; + private String ns; + private String payload; + + /** + * Construct a SimplePayload object with the specified element name, + * namespace and content. The content must be well formed XML. + * + * @param elementName The root element name (of the payload) + * @param namespace The namespace of the payload, null if there is none + * @param xmlPayload The payload data + */ + public SimplePayload(String elementName, String namespace, String xmlPayload) + { + elemName = elementName; + payload = xmlPayload; + ns = namespace; + } + + public String getElementName() + { + return elemName; + } + + public String getNamespace() + { + return ns; + } + + public String toXML() + { + return payload; + } + + @Override + public String toString() + { + return getClass().getName() + "payload [" + toXML() + "]"; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java new file mode 100644 index 0000000..daf8db7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeExtension.java @@ -0,0 +1,60 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Represents a request to subscribe to a node. + * + * @author Robin Collier + */ +public class SubscribeExtension extends NodeExtension +{ + protected String jid; + + public SubscribeExtension(String subscribeJid) + { + super(PubSubElementType.SUBSCRIBE); + jid = subscribeJid; + } + + public SubscribeExtension(String subscribeJid, String nodeId) + { + super(PubSubElementType.SUBSCRIBE, nodeId); + jid = subscribeJid; + } + + public String getJid() + { + return jid; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + + if (getNode() != null) + { + builder.append(" node='"); + builder.append(getNode()); + builder.append("'"); + } + builder.append(" jid='"); + builder.append(getJid()); + builder.append("'/>"); + + return builder.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java new file mode 100644 index 0000000..5ed5311 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeForm.java @@ -0,0 +1,242 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.Iterator; +import java.util.UnknownFormatConversionException; + +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.packet.DataForm; + +/** + * A decorator for a {@link Form} to easily enable reading and updating + * of subscription options. All operations read or update the underlying {@link DataForm}. + * + *

      Unlike the {@link Form}.setAnswer(XXX)} methods, which throw an exception if the field does not + * exist, all SubscribeForm.setXXX methods will create the field in the wrapped form + * if it does not already exist. + * + * @author Robin Collier + */ +public class SubscribeForm extends Form +{ + private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + public SubscribeForm(DataForm configDataForm) + { + super(configDataForm); + } + + public SubscribeForm(Form subscribeOptionsForm) + { + super(subscribeOptionsForm.getDataFormToSend()); + } + + public SubscribeForm(FormType formType) + { + super(formType.toString()); + } + + /** + * Determines if an entity wants to receive notifications. + * + * @return true if want to receive, false otherwise + */ + public boolean isDeliverOn() + { + return parseBoolean(getFieldValue(SubscribeOptionFields.deliver)); + } + + /** + * Sets whether an entity wants to receive notifications. + * + * @param deliverNotifications + */ + public void setDeliverOn(boolean deliverNotifications) + { + addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN); + setAnswer(SubscribeOptionFields.deliver.getFieldName(), deliverNotifications); + } + + /** + * Determines if notifications should be delivered as aggregations or not. + * + * @return true to aggregate, false otherwise + */ + public boolean isDigestOn() + { + return parseBoolean(getFieldValue(SubscribeOptionFields.digest)); + } + + /** + * Sets whether notifications should be delivered as aggregations or not. + * + * @param digestOn true to aggregate, false otherwise + */ + public void setDigestOn(boolean digestOn) + { + addField(SubscribeOptionFields.deliver, FormField.TYPE_BOOLEAN); + setAnswer(SubscribeOptionFields.deliver.getFieldName(), digestOn); + } + + /** + * Gets the minimum number of milliseconds between sending notification digests + * + * @return The frequency in milliseconds + */ + public int getDigestFrequency() + { + return Integer.parseInt(getFieldValue(SubscribeOptionFields.digest_frequency)); + } + + /** + * Sets the minimum number of milliseconds between sending notification digests + * + * @param frequency The frequency in milliseconds + */ + public void setDigestFrequency(int frequency) + { + addField(SubscribeOptionFields.digest_frequency, FormField.TYPE_TEXT_SINGLE); + setAnswer(SubscribeOptionFields.digest_frequency.getFieldName(), frequency); + } + + /** + * Get the time at which the leased subscription will expire, or has expired. + * + * @return The expiry date + */ + public Date getExpiry() + { + String dateTime = getFieldValue(SubscribeOptionFields.expire); + try + { + return format.parse(dateTime); + } + catch (ParseException e) + { + UnknownFormatConversionException exc = new UnknownFormatConversionException(dateTime); + exc.initCause(e); + throw exc; + } + } + + /** + * Sets the time at which the leased subscription will expire, or has expired. + * + * @param expire The expiry date + */ + public void setExpiry(Date expire) + { + addField(SubscribeOptionFields.expire, FormField.TYPE_TEXT_SINGLE); + setAnswer(SubscribeOptionFields.expire.getFieldName(), format.format(expire)); + } + + /** + * Determines whether the entity wants to receive an XMPP message body in + * addition to the payload format. + * + * @return true to receive the message body, false otherwise + */ + public boolean isIncludeBody() + { + return parseBoolean(getFieldValue(SubscribeOptionFields.include_body)); + } + + /** + * Sets whether the entity wants to receive an XMPP message body in + * addition to the payload format. + * + * @param include true to receive the message body, false otherwise + */ + public void setIncludeBody(boolean include) + { + addField(SubscribeOptionFields.include_body, FormField.TYPE_BOOLEAN); + setAnswer(SubscribeOptionFields.include_body.getFieldName(), include); + } + + /** + * Gets the {@link PresenceState} for which an entity wants to receive + * notifications. + * + * @return iterator over the list of states + */ + public Iterator getShowValues() + { + ArrayList result = new ArrayList(5); + Iterator it = getFieldValues(SubscribeOptionFields.show_values); + + while (it.hasNext()) + { + String state = it.next(); + result.add(PresenceState.valueOf(state)); + } + return result.iterator(); + } + + /** + * Sets the list of {@link PresenceState} for which an entity wants + * to receive notifications. + * + * @param stateValues The list of states + */ + public void setShowValues(Collection stateValues) + { + ArrayList values = new ArrayList(stateValues.size()); + + for (PresenceState state : stateValues) + { + values.add(state.toString()); + } + addField(SubscribeOptionFields.show_values, FormField.TYPE_LIST_MULTI); + setAnswer(SubscribeOptionFields.show_values.getFieldName(), values); + } + + + static private boolean parseBoolean(String fieldValue) + { + return ("1".equals(fieldValue) || "true".equals(fieldValue)); + } + + private String getFieldValue(SubscribeOptionFields field) + { + FormField formField = getField(field.getFieldName()); + + return formField.getValues().next(); + } + + private Iterator getFieldValues(SubscribeOptionFields field) + { + FormField formField = getField(field.getFieldName()); + + return formField.getValues(); + } + + private void addField(SubscribeOptionFields nodeField, String type) + { + String fieldName = nodeField.getFieldName(); + + if (getField(fieldName) == null) + { + FormField field = new FormField(fieldName); + field.setType(type); + addField(field); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java new file mode 100644 index 0000000..dfca601 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscribeOptionFields.java @@ -0,0 +1,99 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Calendar; + +/** + * Defines the possible field options for a subscribe options form as defined + * by Section 16.4.2. + * + * @author Robin Collier + */ +public enum SubscribeOptionFields +{ + /** + * Whether an entity wants to receive or disable notifications + * + *

      Value: boolean

      + */ + deliver, + + /** + * Whether an entity wants to receive digests (aggregations) of + * notifications or all notifications individually. + * + *

      Value: boolean

      + */ + digest, + + /** + * The minimum number of seconds between sending any two notifications digests + * + *

      Value: int

      + */ + digest_frequency, + + /** + * The DateTime at which a leased subsscription will end ro has ended. + * + *

      Value: {@link Calendar}

      + */ + expire, + + /** + * Whether an entity wants to receive an XMPP message body in addition to + * the payload format. + * + *

      Value: boolean

      + */ + include_body, + + /** + * The presence states for which an entity wants to receive notifications. + * + *

      Value: {@link PresenceState}

      + */ + show_values, + + /** + * + * + *

      Value:

      + */ + subscription_type, + + /** + * + *

      Value:

      + */ + subscription_depth; + + public String getFieldName() + { + if (this == show_values) + return "pubsub#" + toString().replace('_', '-'); + return "pubsub#" + toString(); + } + + static public SubscribeOptionFields valueOfFromElement(String elementName) + { + String portion = elementName.substring(elementName.lastIndexOf('#' + 1)); + + if ("show-values".equals(portion)) + return show_values; + else + return valueOf(portion); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java new file mode 100644 index 0000000..19ad8a8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/Subscription.java @@ -0,0 +1,160 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +/** + * Represents a subscription to node for both requests and replies. + * + * @author Robin Collier + */ +public class Subscription extends NodeExtension +{ + protected String jid; + protected String id; + protected State state; + protected boolean configRequired = false; + + public enum State + { + subscribed, unconfigured, pending, none + } + + /** + * Used to constructs a subscription request to the root node with the specified + * JID. + * + * @param subscriptionJid The subscriber JID + */ + public Subscription(String subscriptionJid) + { + this(subscriptionJid, null, null, null); + } + + /** + * Used to constructs a subscription request to the specified node with the specified + * JID. + * + * @param subscriptionJid The subscriber JID + * @param nodeId The node id + */ + public Subscription(String subscriptionJid, String nodeId) + { + this(subscriptionJid, nodeId, null, null); + } + + /** + * Constructs a representation of a subscription reply to the specified node + * and JID. The server will have supplied the subscription id and current state. + * + * @param jid The JID the request was made under + * @param nodeId The node subscribed to + * @param subscriptionId The id of this subscription + * @param state The current state of the subscription + */ + public Subscription(String jid, String nodeId, String subscriptionId, State state) + { + super(PubSubElementType.SUBSCRIPTION, nodeId); + this.jid = jid; + id = subscriptionId; + this.state = state; + } + + /** + * Constructs a representation of a subscription reply to the specified node + * and JID. The server will have supplied the subscription id and current state + * and whether the subscription need to be configured. + * + * @param jid The JID the request was made under + * @param nodeId The node subscribed to + * @param subscriptionId The id of this subscription + * @param state The current state of the subscription + * @param configRequired Is configuration required to complete the subscription + */ + public Subscription(String jid, String nodeId, String subscriptionId, State state, boolean configRequired) + { + super(PubSubElementType.SUBSCRIPTION, nodeId); + this.jid = jid; + id = subscriptionId; + this.state = state; + this.configRequired = configRequired; + } + + /** + * Gets the JID the subscription is created for + * + * @return The JID + */ + public String getJid() + { + return jid; + } + + /** + * Gets the subscription id + * + * @return The subscription id + */ + public String getId() + { + return id; + } + + /** + * Gets the current subscription state. + * + * @return Current subscription state + */ + public State getState() + { + return state; + } + + /** + * This value is only relevant when the {@link #getState()} is {@link State#unconfigured} + * + * @return true if configuration is required, false otherwise + */ + public boolean isConfigRequired() + { + return configRequired; + } + + public String toXML() + { + StringBuilder builder = new StringBuilder(""); + return builder.toString(); + } + + private void appendAttribute(StringBuilder builder, String att, String value) + { + builder.append(" "); + builder.append(att); + builder.append("='"); + builder.append(value); + builder.append("'"); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java new file mode 100644 index 0000000..99f18d5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionEvent.java @@ -0,0 +1,75 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Collections; +import java.util.List; + +/** + * Base class to represents events that are associated to subscriptions. + * + * @author Robin Collier + */ +abstract public class SubscriptionEvent extends NodeEvent +{ + private List subIds = Collections.EMPTY_LIST; + + /** + * Construct an event with no subscription id's. This can + * occur when there is only one subscription to a node. The + * event may or may not report the subscription id along + * with the event. + * + * @param nodeId The id of the node the event came from + */ + protected SubscriptionEvent(String nodeId) + { + super(nodeId); + } + + /** + * Construct an event with multiple subscriptions. + * + * @param nodeId The id of the node the event came from + * @param subscriptionIds The list of subscription id's + */ + protected SubscriptionEvent(String nodeId, List subscriptionIds) + { + super(nodeId); + + if (subscriptionIds != null) + subIds = subscriptionIds; + } + + /** + * Get the subscriptions this event is associated with. + * + * @return List of subscription id's + */ + public List getSubscriptions() + { + return Collections.unmodifiableList(subIds); + } + + /** + * Set the list of subscription id's for this event. + * + * @param subscriptionIds The list of subscription id's + */ + protected void setSubscriptions(List subscriptionIds) + { + if (subscriptionIds != null) + subIds = subscriptionIds; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java new file mode 100644 index 0000000..a28cbe2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/SubscriptionsExtension.java @@ -0,0 +1,96 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import java.util.Collections; +import java.util.List; + +/** + * Represents the element holding the list of subscription elements. + * + * @author Robin Collier + */ +public class SubscriptionsExtension extends NodeExtension +{ + protected List items = Collections.EMPTY_LIST; + + /** + * Subscriptions to the root node + * + * @param subList The list of subscriptions + */ + public SubscriptionsExtension(List subList) + { + super(PubSubElementType.SUBSCRIPTIONS); + + if (subList != null) + items = subList; + } + + /** + * Subscriptions to the specified node. + * + * @param nodeId The node subscribed to + * @param subList The list of subscriptions + */ + public SubscriptionsExtension(String nodeId, List subList) + { + super(PubSubElementType.SUBSCRIPTIONS, nodeId); + + if (subList != null) + items = subList; + } + + /** + * Gets the list of subscriptions. + * + * @return List of subscriptions + */ + public List getSubscriptions() + { + return items; + } + + @Override + public String toXML() + { + if ((items == null) || (items.size() == 0)) + { + return super.toXML(); + } + else + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + + if (getNode() != null) + { + builder.append(" node='"); + builder.append(getNode()); + builder.append("'"); + } + builder.append(">"); + + for (Subscription item : items) + { + builder.append(item.toXML()); + } + + builder.append(""); + return builder.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java new file mode 100644 index 0000000..ac14c60 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/UnsubscribeExtension.java @@ -0,0 +1,73 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub; + +import org.jivesoftware.smackx.pubsub.util.XmlUtils; + + +/** + * Represents an unsubscribe element. + * + * @author Robin Collier + */ +public class UnsubscribeExtension extends NodeExtension +{ + protected String jid; + protected String id; + + public UnsubscribeExtension(String subscriptionJid) + { + this(subscriptionJid, null, null); + } + + public UnsubscribeExtension(String subscriptionJid, String nodeId) + { + this(subscriptionJid, nodeId, null); + } + + public UnsubscribeExtension(String jid, String nodeId, String subscriptionId) + { + super(PubSubElementType.UNSUBSCRIBE, nodeId); + this.jid = jid; + id = subscriptionId; + } + + public String getJid() + { + return jid; + } + + public String getId() + { + return id; + } + + @Override + public String toXML() + { + StringBuilder builder = new StringBuilder("<"); + builder.append(getElementName()); + XmlUtils.appendAttribute(builder, "jid", jid); + + if (getNode() != null) + XmlUtils.appendAttribute(builder, "node", getNode()); + + if (id != null) + XmlUtils.appendAttribute(builder, "subid", id); + + builder.append("/>"); + return builder.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java new file mode 100644 index 0000000..d228e8f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemDeleteListener.java @@ -0,0 +1,41 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.listener; + +import org.jivesoftware.smackx.pubsub.ItemDeleteEvent; +import org.jivesoftware.smackx.pubsub.LeafNode; + +/** + * Defines the listener for item deletion events from a node. + * + * @see LeafNode#addItemDeleteListener(ItemDeleteListener) + * + * @author Robin Collier + */ +public interface ItemDeleteListener +{ + /** + * Called when items are deleted from a node the listener is + * registered with. + * + * @param items The event with item deletion details + */ + void handleDeletedItems(ItemDeleteEvent items); + + /** + * Called when all items are deleted from a node the listener is + * registered with. + */ + void handlePurge(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java new file mode 100644 index 0000000..714b2c0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/ItemEventListener.java @@ -0,0 +1,36 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.listener; + +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.ItemPublishEvent; +import org.jivesoftware.smackx.pubsub.LeafNode; + +/** + * Defines the listener for items being published to a node. + * + * @see LeafNode#addItemEventListener(ItemEventListener) + * + * @author Robin Collier + */ +public interface ItemEventListener +{ + /** + * Called whenever an item is published to the node the listener + * is registered with. + * + * @param items The publishing details. + */ + void handlePublishedItems(ItemPublishEvent items); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java new file mode 100644 index 0000000..39db5a5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/listener/NodeConfigListener.java @@ -0,0 +1,35 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.listener; + +import org.jivesoftware.smackx.pubsub.ConfigurationEvent; +import org.jivesoftware.smackx.pubsub.LeafNode; + +/** + * Defines the listener for a node being configured. + * + * @see LeafNode#addConfigurationListener(NodeConfigListener) + * + * @author Robin Collier + */ +public interface NodeConfigListener +{ + /** + * Called whenever the node the listener + * is registered with is configured. + * + * @param config The configuration details. + */ + void handleNodeConfiguration(ConfigurationEvent config); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java new file mode 100644 index 0000000..5aa4865 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSub.java @@ -0,0 +1,106 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.pubsub.PubSubElementType; + +/** + * The standard PubSub extension of an {@link IQ} packet. This is the topmost + * element of all pubsub requests and replies as defined in the Publish-Subscribe + * specification. + * + * @author Robin Collier + */ +public class PubSub extends IQ +{ + private PubSubNamespace ns = PubSubNamespace.BASIC; + + /** + * Returns the XML element name of the extension sub-packet root element. + * + * @return the XML element name of the packet extension. + */ + public String getElementName() { + return "pubsub"; + } + + /** + * Returns the XML namespace of the extension sub-packet root element. + * According the specification the namespace is + * http://jabber.org/protocol/pubsub with a specific fragment depending + * on the request. The namespace is defined at XMPP Registrar at + * + * The default value has no fragment. + * + * @return the XML namespace of the packet extension. + */ + public String getNamespace() + { + return ns.getXmlns(); + } + + /** + * Set the namespace for the packet if it something other than the default + * case of {@link PubSubNamespace#BASIC}. The {@link #getNamespace()} method will return + * the result of calling {@link PubSubNamespace#getXmlns()} on the specified enum. + * + * @param ns - The new value for the namespace. + */ + public void setPubSubNamespace(PubSubNamespace ns) + { + this.ns = ns; + } + + public PacketExtension getExtension(PubSubElementType elem) + { + return getExtension(elem.getElementName(), elem.getNamespace().getXmlns()); + } + + /** + * Returns the current value of the namespace. The {@link #getNamespace()} method will return + * the result of calling {@link PubSubNamespace#getXmlns()} this value. + * + * @return The current value of the namespace. + */ + public PubSubNamespace getPubSubNamespace() + { + return ns; + } + /** + * Returns the XML representation of a pubsub element according the specification. + * + * The XML representation will be inside of an iq packet like + * in the following example: + *
      +     * <iq type='set' id="MlIpV-4" to="pubsub.gato.home" from="gato3@gato.home/Smack">
      +     *     <pubsub xmlns="http://jabber.org/protocol/pubsub">
      +     *                      :
      +     *         Specific request extension
      +     *                      :
      +     *     </pubsub>
      +     * </iq>
      +     * 
      + * + */ + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(getElementName()).append(" xmlns=\"").append(getNamespace()).append("\">"); + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java new file mode 100644 index 0000000..eecf959 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/PubSubNamespace.java @@ -0,0 +1,63 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.packet; + +/** + * Defines all the valid namespaces that are used with the {@link PubSub} packet + * as defined by the specification. + * + * @author Robin Collier + */ +public enum PubSubNamespace +{ + BASIC(null), + ERROR("errors"), + EVENT("event"), + OWNER("owner"); + + private String fragment; + + private PubSubNamespace(String fragment) + { + this.fragment = fragment; + } + + public String getXmlns() + { + String ns = "http://jabber.org/protocol/pubsub"; + + if (fragment != null) + ns += '#' + fragment; + + return ns; + } + + public String getFragment() + { + return fragment; + } + + public static PubSubNamespace valueOfFromXmlns(String ns) + { + int index = ns.lastIndexOf('#'); + + if (index != -1) + { + String suffix = ns.substring(ns.lastIndexOf('#')+1); + return valueOf(suffix.toUpperCase()); + } + else + return BASIC; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java new file mode 100644 index 0000000..080129b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/packet/SyncPacketSend.java @@ -0,0 +1,63 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.packet; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.Packet; + +/** + * Utility class for doing synchronous calls to the server. Provides several + * methods for sending a packet to the server and waiting for the reply. + * + * @author Robin Collier + */ +final public class SyncPacketSend +{ + private SyncPacketSend() + { } + + static public Packet getReply(Connection connection, Packet packet, long timeout) + throws XMPPException + { + PacketFilter responseFilter = new PacketIDFilter(packet.getPacketID()); + PacketCollector response = connection.createPacketCollector(responseFilter); + + connection.sendPacket(packet); + + // Wait up to a certain number of seconds for a reply. + Packet result = response.nextResult(timeout); + + // Stop queuing results + response.cancel(); + + if (result == null) { + throw new XMPPException("No response from server."); + } + else if (result.getError() != null) { + throw new XMPPException(result.getError()); + } + return result; + } + + static public Packet getReply(Connection connection, Packet packet) + throws XMPPException + { + return getReply(connection, packet, SmackConfiguration.getPacketReplyTimeout()); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java new file mode 100644 index 0000000..892eec6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationProvider.java @@ -0,0 +1,37 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.Affiliation; + +/** + * Parses the affiliation element out of the reply stanza from the server + * as specified in the affiliation schema. + * + * @author Robin Collier + */ +public class AffiliationProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new Affiliation(attributeMap.get("jid"), attributeMap.get("node"), Affiliation.Type.valueOf(attributeMap.get("affiliation"))); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java new file mode 100644 index 0000000..ee7af05 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/AffiliationsProvider.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.Affiliation; +import org.jivesoftware.smackx.pubsub.AffiliationsExtension; + +/** + * Parses the affiliations element out of the reply stanza from the server + * as specified in the affiliation schema. + * + * @author Robin Collier + */public class AffiliationsProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new AffiliationsExtension(attributeMap.get("node"), (List)content); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java new file mode 100644 index 0000000..30e3017 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ConfigEventProvider.java @@ -0,0 +1,42 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.ConfigurationEvent; +import org.jivesoftware.smackx.pubsub.ConfigureForm; + +/** + * Parses the node configuration element out of the message event stanza from + * the server as specified in the configuration schema. + * + * @author Robin Collier + */ +public class ConfigEventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List content) + { + if (content.size() == 0) + return new ConfigurationEvent(attMap.get("node")); + else + return new ConfigurationEvent(attMap.get("node"), new ConfigureForm((DataForm)content.iterator().next())); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java new file mode 100644 index 0000000..ef5671e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/EventProvider.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.EventElement; +import org.jivesoftware.smackx.pubsub.EventElementType; +import org.jivesoftware.smackx.pubsub.NodeExtension; + +/** + * Parses the event element out of the message stanza from + * the server as specified in the event schema. + * + * @author Robin Collier + */ +public class EventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attMap, List content) + { + return new EventElement(EventElementType.valueOf(content.get(0).getElementName()), (NodeExtension)content.get(0)); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java new file mode 100644 index 0000000..da75b24 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/FormNodeProvider.java @@ -0,0 +1,39 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.FormNode; +import org.jivesoftware.smackx.pubsub.FormNodeType; + +/** + * Parses one of several elements used in pubsub that contain a form of some kind as a child element. The + * elements and namespaces supported is defined in {@link FormNodeType}. + * + * @author Robin Collier + */ +public class FormNodeProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new FormNode(FormNodeType.valueOfFromElementName(currentElement, currentNamespace), attributeMap.get("node"), new Form((DataForm)content.iterator().next())); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java new file mode 100644 index 0000000..aabd4cb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemProvider.java @@ -0,0 +1,76 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smack.provider.ProviderManager; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.PayloadItem; +import org.jivesoftware.smackx.pubsub.SimplePayload; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses an item element as is defined in both the {@link PubSubNamespace#BASIC} and {@link PubSubNamespace#EVENT} + * namespaces. To parse the item contents, it will use whatever {@link PacketExtensionProvider} is registered in + * smack.providers for its element name and namespace. If no provider is registered, it will return a {@link SimplePayload}. + * + * @author Robin Collier + */ +public class ItemProvider implements PacketExtensionProvider +{ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String id = parser.getAttributeValue(null, "id"); + String node = parser.getAttributeValue(null, "node"); + String elem = parser.getName(); + + int tag = parser.next(); + + if (tag == XmlPullParser.END_TAG) + { + return new Item(id, node); + } + else + { + String payloadElemName = parser.getName(); + String payloadNS = parser.getNamespace(); + + if (ProviderManager.getInstance().getExtensionProvider(payloadElemName, payloadNS) == null) + { + boolean done = false; + StringBuilder payloadText = new StringBuilder(); + + while (!done) + { + if (tag == XmlPullParser.END_TAG && parser.getName().equals(elem)) + done = true; + else if (!((tag == XmlPullParser.START_TAG) && parser.isEmptyElementTag())) + payloadText.append(parser.getText()); + + if (!done) + tag = parser.next(); + } + return new PayloadItem(id, node, new SimplePayload(payloadElemName, payloadNS, payloadText.toString())); + } + else + { + return new PayloadItem(id, node, PacketParserUtils.parsePacketExtension(payloadElemName, payloadNS, parser)); + } + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java new file mode 100644 index 0000000..01cb9d4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/ItemsProvider.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.ItemsExtension; + +/** + * Parses the items element out of the message event stanza from + * the server as specified in the items schema. + * + * @author Robin Collier + */ +public class ItemsProvider extends EmbeddedExtensionProvider +{ + + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new ItemsExtension(ItemsExtension.ItemsElementType.items, attributeMap.get("node"), content); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java new file mode 100644 index 0000000..742f219 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/PubSubProvider.java @@ -0,0 +1,62 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.pubsub.packet.PubSub; +import org.jivesoftware.smackx.pubsub.packet.PubSubNamespace; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses the root pubsub packet extensions of the {@link IQ} packet and returns + * a {@link PubSub} instance. + * + * @author Robin Collier + */ +public class PubSubProvider implements IQProvider +{ + public IQ parseIQ(XmlPullParser parser) throws Exception + { + PubSub pubsub = new PubSub(); + String namespace = parser.getNamespace(); + pubsub.setPubSubNamespace(PubSubNamespace.valueOfFromXmlns(namespace)); + boolean done = false; + + while (!done) + { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) + { + PacketExtension ext = PacketParserUtils.parsePacketExtension(parser.getName(), namespace, parser); + + if (ext != null) + { + pubsub.addExtension(ext); + } + } + else if (eventType == XmlPullParser.END_TAG) + { + if (parser.getName().equals("pubsub")) + { + done = true; + } + } + } + return pubsub; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java new file mode 100644 index 0000000..8fa3337 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/RetractEventProvider.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.RetractItem; + +/** + * Parses the retract element out of the message event stanza from + * the server as specified in the retract schema. + * This element is a child of the items element. + * + * @author Robin Collier + */ +public class RetractEventProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new RetractItem(attributeMap.get("id")); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java new file mode 100644 index 0000000..d2b7d30 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SimpleNodeProvider.java @@ -0,0 +1,37 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.NodeExtension; +import org.jivesoftware.smackx.pubsub.PubSubElementType; + +/** + * Parses simple elements that only contain a node attribute. This is common amongst many of the + * elements defined in the pubsub specification. For this common case a {@link NodeExtension} is returned. + * + * @author Robin Collier + */ +public class SimpleNodeProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new NodeExtension(PubSubElementType.valueOfFromElemName(currentElement, currentNamespace), attributeMap.get("node")); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java new file mode 100644 index 0000000..eccbe08 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionProvider.java @@ -0,0 +1,52 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.jivesoftware.smackx.pubsub.Subscription; +import org.xmlpull.v1.XmlPullParser; + +/** + * Parses the subscription element out of the pubsub IQ message from + * the server as specified in the subscription schema. + * + * @author Robin Collier + */ +public class SubscriptionProvider implements PacketExtensionProvider +{ + public PacketExtension parseExtension(XmlPullParser parser) throws Exception + { + String jid = parser.getAttributeValue(null, "jid"); + String nodeId = parser.getAttributeValue(null, "node"); + String subId = parser.getAttributeValue(null, "subid"); + String state = parser.getAttributeValue(null, "subscription"); + boolean isRequired = false; + + int tag = parser.next(); + + if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("subscribe-options")) + { + tag = parser.next(); + + if ((tag == XmlPullParser.START_TAG) && parser.getName().equals("required")) + isRequired = true; + + while (parser.next() != XmlPullParser.END_TAG && parser.getName() != "subscribe-options"); + } + while (parser.getEventType() != XmlPullParser.END_TAG) parser.next(); + return new Subscription(jid, nodeId, subId, (state == null ? null : Subscription.State.valueOf(state)), isRequired); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java new file mode 100644 index 0000000..94dc61d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/provider/SubscriptionsProvider.java @@ -0,0 +1,38 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.provider; + +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.provider.EmbeddedExtensionProvider; +import org.jivesoftware.smackx.pubsub.Subscription; +import org.jivesoftware.smackx.pubsub.SubscriptionsExtension; + +/** + * Parses the subscriptions element out of the pubsub IQ message from + * the server as specified in the subscriptions schema. + * + * @author Robin Collier + */ +public class SubscriptionsProvider extends EmbeddedExtensionProvider +{ + @Override + protected PacketExtension createReturnExtension(String currentElement, String currentNamespace, Map attributeMap, List content) + { + return new SubscriptionsExtension(attributeMap.get("node"), (List)content); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java new file mode 100644 index 0000000..414601f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/NodeUtils.java @@ -0,0 +1,43 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.util; + +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.pubsub.ConfigureForm; +import org.jivesoftware.smackx.pubsub.FormNode; +import org.jivesoftware.smackx.pubsub.PubSubElementType; + +/** + * Utility for extracting information from packets. + * + * @author Robin Collier + */ +public class NodeUtils +{ + /** + * Get a {@link ConfigureForm} from a packet. + * + * @param packet + * @param elem + * @return The configuration form + */ + public static ConfigureForm getFormFromPacket(Packet packet, PubSubElementType elem) + { + FormNode config = (FormNode)packet.getExtension(elem.getElementName(), elem.getNamespace().getXmlns()); + Form formReply = config.getForm(); + return new ConfigureForm(formReply); + + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java new file mode 100644 index 0000000..95ffca5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/pubsub/util/XmlUtils.java @@ -0,0 +1,41 @@ +/** + * All rights reserved. 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 org.jivesoftware.smackx.pubsub.util; + +import java.io.StringReader; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * Simple utility for pretty printing xml. + * + * @author Robin Collier + */ +public class XmlUtils +{ + + static public void appendAttribute(StringBuilder builder, String att, String value) + { + builder.append(" "); + builder.append(att); + builder.append("='"); + builder.append(value); + builder.append("'"); + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java new file mode 100644 index 0000000..3d5f646 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/SimpleUserSearch.java @@ -0,0 +1,151 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.search; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ReportedData; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * SimpleUserSearch is used to support the non-dataform type of JEP 55. This provides + * the mechanism for allowing always type ReportedData to be returned by any search result, + * regardless of the form of the data returned from the server. + * + * @author Derek DeMoro + */ +class SimpleUserSearch extends IQ { + + private Form form; + private ReportedData data; + + public void setForm(Form form) { + this.form = form; + } + + public ReportedData getReportedData() { + return data; + } + + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(getItemsToSearch()); + buf.append(""); + return buf.toString(); + } + + private String getItemsToSearch() { + StringBuilder buf = new StringBuilder(); + + if (form == null) { + form = Form.getFormFrom(this); + } + + if (form == null) { + return ""; + } + + Iterator fields = form.getFields(); + while (fields.hasNext()) { + FormField field = fields.next(); + String name = field.getVariable(); + String value = getSingleValue(field); + if (value.trim().length() > 0) { + buf.append("<").append(name).append(">").append(value).append(""); + } + } + + return buf.toString(); + } + + private static String getSingleValue(FormField formField) { + Iterator values = formField.getValues(); + while (values.hasNext()) { + return values.next(); + } + return ""; + } + + protected void parseItems(XmlPullParser parser) throws Exception { + ReportedData data = new ReportedData(); + data.addColumn(new ReportedData.Column("JID", "jid", "text-single")); + + boolean done = false; + + List fields = new ArrayList(); + while (!done) { + if (parser.getAttributeCount() > 0) { + String jid = parser.getAttributeValue("", "jid"); + List valueList = new ArrayList(); + valueList.add(jid); + ReportedData.Field field = new ReportedData.Field("jid", valueList); + fields.add(field); + } + + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("item")) { + fields = new ArrayList(); + } + else if (eventType == XmlPullParser.END_TAG && parser.getName().equals("item")) { + ReportedData.Row row = new ReportedData.Row(fields); + data.addRow(row); + } + else if (eventType == XmlPullParser.START_TAG) { + String name = parser.getName(); + String value = parser.nextText(); + + List valueList = new ArrayList(); + valueList.add(value); + ReportedData.Field field = new ReportedData.Field(name, valueList); + fields.add(field); + + boolean exists = false; + Iterator cols = data.getColumns(); + while (cols.hasNext()) { + ReportedData.Column column = (ReportedData.Column) cols.next(); + if (column.getVariable().equals(name)) { + exists = true; + } + } + + // Column name should be the same + if (!exists) { + ReportedData.Column column = new ReportedData.Column(name, name, "text-single"); + data.addColumn(column); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + + } + + this.data = data; + } + + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java new file mode 100644 index 0000000..781dd9a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearch.java @@ -0,0 +1,255 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.search; + +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ReportedData; +import org.jivesoftware.smackx.packet.DataForm; +import org.xmlpull.v1.XmlPullParser; + +/** + * Implements the protocol currently used to search information repositories on the Jabber network. To date, the jabber:iq:search protocol + * has been used mainly to search for people who have registered with user directories (e.g., the "Jabber User Directory" hosted at users.jabber.org). + * However, the jabber:iq:search protocol is not limited to user directories, and could be used to search other Jabber information repositories + * (such as chatroom directories) or even to provide a Jabber interface to conventional search engines. + *

      + * The basic functionality is to query an information repository regarding the possible search fields, to send a search query, and to receive search results. + * + * @author Derek DeMoro + */ +public class UserSearch extends IQ { + + /** + * Creates a new instance of UserSearch. + */ + public UserSearch() { + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(getExtensionsXML()); + buf.append(""); + return buf.toString(); + } + + /** + * Returns the form for all search fields supported by the search service. + * + * @param con the current Connection. + * @param searchService the search service to use. (ex. search.jivesoftware.com) + * @return the search form received by the server. + * @throws org.jivesoftware.smack.XMPPException + * thrown if a server error has occurred. + */ + public Form getSearchForm(Connection con, String searchService) throws XMPPException { + UserSearch search = new UserSearch(); + search.setType(IQ.Type.GET); + search.setTo(searchService); + + PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); + con.sendPacket(search); + + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return Form.getFormFrom(response); + } + + /** + * Sends the filled out answer form to be sent and queried by the search service. + * + * @param con the current Connection. + * @param searchForm the Form to send for querying. + * @param searchService the search service to use. (ex. search.jivesoftware.com) + * @return ReportedData the data found from the query. + * @throws org.jivesoftware.smack.XMPPException + * thrown if a server error has occurred. + */ + public ReportedData sendSearchForm(Connection con, Form searchForm, String searchService) throws XMPPException { + UserSearch search = new UserSearch(); + search.setType(IQ.Type.SET); + search.setTo(searchService); + search.addExtension(searchForm.getDataFormToSend()); + + PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); + + con.sendPacket(search); + + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + return sendSimpleSearchForm(con, searchForm, searchService); + } + + + return ReportedData.getReportedDataFrom(response); + } + + /** + * Sends the filled out answer form to be sent and queried by the search service. + * + * @param con the current Connection. + * @param searchForm the Form to send for querying. + * @param searchService the search service to use. (ex. search.jivesoftware.com) + * @return ReportedData the data found from the query. + * @throws org.jivesoftware.smack.XMPPException + * thrown if a server error has occurred. + */ + public ReportedData sendSimpleSearchForm(Connection con, Form searchForm, String searchService) throws XMPPException { + SimpleUserSearch search = new SimpleUserSearch(); + search.setForm(searchForm); + search.setType(IQ.Type.SET); + search.setTo(searchService); + + PacketCollector collector = con.createPacketCollector(new PacketIDFilter(search.getPacketID())); + + con.sendPacket(search); + + IQ response = (IQ) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + + if (response instanceof SimpleUserSearch) { + return ((SimpleUserSearch) response).getReportedData(); + } + return null; + } + + /** + * Internal Search service Provider. + */ + public static class Provider implements IQProvider { + + /** + * Provider Constructor. + */ + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + UserSearch search = null; + SimpleUserSearch simpleUserSearch = new SimpleUserSearch(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG && parser.getName().equals("instructions")) { + buildDataForm(simpleUserSearch, parser.nextText(), parser); + return simpleUserSearch; + } + else if (eventType == XmlPullParser.START_TAG && parser.getName().equals("item")) { + simpleUserSearch.parseItems(parser); + return simpleUserSearch; + } + else if (eventType == XmlPullParser.START_TAG && parser.getNamespace().equals("jabber:x:data")) { + // Otherwise, it must be a packet extension. + search = new UserSearch(); + search.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser)); + + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + } + + if (search != null) { + return search; + } + return simpleUserSearch; + } + } + + private static void buildDataForm(SimpleUserSearch search, String instructions, XmlPullParser parser) throws Exception { + DataForm dataForm = new DataForm(Form.TYPE_FORM); + boolean done = false; + dataForm.setTitle("User Search"); + dataForm.addInstruction(instructions); + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG && !parser.getNamespace().equals("jabber:x:data")) { + String name = parser.getName(); + FormField field = new FormField(name); + + // Handle hard coded values. + if(name.equals("first")){ + field.setLabel("First Name"); + } + else if(name.equals("last")){ + field.setLabel("Last Name"); + } + else if(name.equals("email")){ + field.setLabel("Email Address"); + } + else if(name.equals("nick")){ + field.setLabel("Nickname"); + } + + field.setType(FormField.TYPE_TEXT_SINGLE); + dataForm.addField(field); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("query")) { + done = true; + } + } + else if (eventType == XmlPullParser.START_TAG && parser.getNamespace().equals("jabber:x:data")) { + search.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser)); + done = true; + } + } + if (search.getExtension("x", "jabber:x:data") == null) { + search.addExtension(dataForm); + } + } + + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java new file mode 100644 index 0000000..83c19e3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/search/UserSearchManager.java @@ -0,0 +1,124 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.search; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.ReportedData; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.DiscoverItems; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * The UserSearchManager is a facade built upon Jabber Search Services (JEP-055) to allow for searching + * repositories on a Jabber Server. This implementation allows for transparency of implementation of + * searching (DataForms or No DataForms), but allows the user to simply use the DataForm model for both + * types of support. + *

      + * Connection con = new XMPPConnection("jabber.org");
      + * con.login("john", "doe");
      + * UserSearchManager search = new UserSearchManager(con, "users.jabber.org");
      + * Form searchForm = search.getSearchForm();
      + * Form answerForm = searchForm.createAnswerForm();
      + * answerForm.setAnswer("last", "DeMoro");
      + * ReportedData data = search.getSearchResults(answerForm);
      + * // Use Returned Data
      + * 
      + * + * @author Derek DeMoro + */ +public class UserSearchManager { + + private Connection con; + private UserSearch userSearch; + + /** + * Creates a new UserSearchManager. + * + * @param con the Connection to use. + */ + public UserSearchManager(Connection con) { + this.con = con; + userSearch = new UserSearch(); + } + + /** + * Returns the form to fill out to perform a search. + * + * @param searchService the search service to query. + * @return the form to fill out to perform a search. + * @throws XMPPException thrown if a server error has occurred. + */ + public Form getSearchForm(String searchService) throws XMPPException { + return userSearch.getSearchForm(con, searchService); + } + + /** + * Submits a search form to the server and returns the resulting information + * in the form of ReportedData + * + * @param searchForm the Form to submit for searching. + * @param searchService the name of the search service to use. + * @return the ReportedData returned by the server. + * @throws XMPPException thrown if a server error has occurred. + */ + public ReportedData getSearchResults(Form searchForm, String searchService) throws XMPPException { + return userSearch.sendSearchForm(con, searchForm, searchService); + } + + + /** + * Returns a collection of search services found on the server. + * + * @return a Collection of search services found on the server. + * @throws XMPPException thrown if a server error has occurred. + */ + public Collection getSearchServices() throws XMPPException { + final List searchServices = new ArrayList(); + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(con); + DiscoverItems items = discoManager.discoverItems(con.getServiceName()); + Iterator iter = items.getItems(); + while (iter.hasNext()) { + DiscoverItems.Item item = iter.next(); + try { + DiscoverInfo info; + try { + info = discoManager.discoverInfo(item.getEntityID()); + } + catch (XMPPException e) { + // Ignore Case + continue; + } + + if (info.containsFeature("jabber:iq:search")) { + searchServices.add(item.getEntityID()); + } + } + catch (Exception e) { + // No info found. + break; + } + } + return searchServices; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java new file mode 100644 index 0000000..7673835 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/MetaData.java @@ -0,0 +1,67 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup; + +import java.util.Map; + +import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; + +import org.jivesoftware.smack.packet.PacketExtension; + +/** + * MetaData packet extension. + */ +public class MetaData implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "metadata"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + private Map metaData; + + public MetaData(Map metaData) { + this.metaData = metaData; + } + + /** + * @return the Map of metadata contained by this instance + */ + public Map getMetaData() { + return metaData; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + return MetaDataUtils.serializeMetaData(this.getMetaData()); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java new file mode 100644 index 0000000..89a1899 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/QueueUser.java @@ -0,0 +1,85 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup; + +import java.util.Date; + +/** + * An immutable class which wraps up customer-in-queue data return from the server; depending on + * the type of information dispatched from the server, not all information will be available in + * any given instance. + * + * @author loki der quaeler + */ +public class QueueUser { + + private String userID; + + private int queuePosition; + private int estimatedTime; + private Date joinDate; + + /** + * @param uid the user jid of the customer in the queue + * @param position the position customer sits in the queue + * @param time the estimate of how much longer the customer will be in the queue in seconds + * @param joinedAt the timestamp of when the customer entered the queue + */ + public QueueUser (String uid, int position, int time, Date joinedAt) { + super(); + + this.userID = uid; + this.queuePosition = position; + this.estimatedTime = time; + this.joinDate = joinedAt; + } + + /** + * @return the user jid of the customer in the queue + */ + public String getUserID () { + return this.userID; + } + + /** + * @return the position in the queue at which the customer sits, or -1 if the update which + * this instance embodies is only a time update instead + */ + public int getQueuePosition () { + return this.queuePosition; + } + + /** + * @return the estimated time remaining of the customer in the queue in seconds, or -1 if + * if the update which this instance embodies is only a position update instead + */ + public int getEstimatedRemainingTime () { + return this.estimatedTime; + } + + /** + * @return the timestamp of when this customer entered the queue, or null if the server did not + * provide this information + */ + public Date getQueueJoinTimestamp () { + return this.joinDate; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java new file mode 100644 index 0000000..8409966 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitation.java @@ -0,0 +1,133 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup; + +import java.util.Map; + +/** + * An immutable class wrapping up the basic information which comprises a group chat invitation. + * + * @author loki der quaeler + */ +public class WorkgroupInvitation { + + protected String uniqueID; + + protected String sessionID; + + protected String groupChatName; + protected String issuingWorkgroupName; + protected String messageBody; + protected String invitationSender; + protected Map metaData; + + /** + * This calls the 5-argument constructor with a null MetaData argument value + * + * @param jid the jid string with which the issuing AgentSession or Workgroup instance + * was created + * @param group the jid of the room to which the person is invited + * @param workgroup the jid of the workgroup issuing the invitation + * @param sessID the session id associated with the pending chat + * @param msgBody the body of the message which contained the invitation + * @param from the user jid who issued the invitation, if known, null otherwise + */ + public WorkgroupInvitation (String jid, String group, String workgroup, + String sessID, String msgBody, String from) { + this(jid, group, workgroup, sessID, msgBody, from, null); + } + + /** + * @param jid the jid string with which the issuing AgentSession or Workgroup instance + * was created + * @param group the jid of the room to which the person is invited + * @param workgroup the jid of the workgroup issuing the invitation + * @param sessID the session id associated with the pending chat + * @param msgBody the body of the message which contained the invitation + * @param from the user jid who issued the invitation, if known, null otherwise + * @param metaData the metadata sent with the invitation + */ + public WorkgroupInvitation (String jid, String group, String workgroup, String sessID, String msgBody, + String from, Map metaData) { + super(); + + this.uniqueID = jid; + this.sessionID = sessID; + this.groupChatName = group; + this.issuingWorkgroupName = workgroup; + this.messageBody = msgBody; + this.invitationSender = from; + this.metaData = metaData; + } + + /** + * @return the jid string with which the issuing AgentSession or Workgroup instance + * was created. + */ + public String getUniqueID () { + return this.uniqueID; + } + + /** + * @return the session id associated with the pending chat; working backwards temporally, + * this session id should match the session id to the corresponding offer request + * which resulted in this invitation. + */ + public String getSessionID () { + return this.sessionID; + } + + /** + * @return the jid of the room to which the person is invited. + */ + public String getGroupChatName () { + return this.groupChatName; + } + + /** + * @return the name of the workgroup from which the invitation was issued. + */ + public String getWorkgroupName () { + return this.issuingWorkgroupName; + } + + /** + * @return the contents of the body-block of the message that housed this invitation. + */ + public String getMessageBody () { + return this.messageBody; + } + + /** + * @return the user who issued the invitation, or null if it wasn't known. + */ + public String getInvitationSender () { + return this.invitationSender; + } + + /** + * @return the meta data associated with the invitation, or null if this instance was + * constructed with none + */ + public Map getMetaData () { + return this.metaData; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java new file mode 100644 index 0000000..bc73242 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/WorkgroupInvitationListener.java @@ -0,0 +1,39 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup; + +/** + * An interface which all classes interested in hearing about group chat invitations should + * implement. + * + * @author loki der quaeler + */ +public interface WorkgroupInvitationListener { + + /** + * The implementing class instance will be notified via this method when an invitation + * to join a group chat has been received from the server. + * + * @param invitation an Invitation instance embodying the information pertaining to the + * invitation + */ + public void invitationReceived(WorkgroupInvitation invitation); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java new file mode 100644 index 0000000..bebac37 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Agent.java @@ -0,0 +1,138 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smackx.workgroup.packet.AgentInfo; +import org.jivesoftware.smackx.workgroup.packet.AgentWorkgroups; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; + +import java.util.Collection; + +/** + * The Agent class is used to represent one agent in a Workgroup Queue. + * + * @author Derek DeMoro + */ +public class Agent { + private Connection connection; + private String workgroupJID; + + public static Collection getWorkgroups(String serviceJID, String agentJID, Connection connection) throws XMPPException { + AgentWorkgroups request = new AgentWorkgroups(agentJID); + request.setTo(serviceJID); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + // Send the request + connection.sendPacket(request); + + AgentWorkgroups response = (AgentWorkgroups)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response.getWorkgroups(); + } + + /** + * Constructs an Agent. + */ + Agent(Connection connection, String workgroupJID) { + this.connection = connection; + this.workgroupJID = workgroupJID; + } + + /** + * Return the agents JID + * + * @return - the agents JID. + */ + public String getUser() { + return connection.getUser(); + } + + /** + * Return the agents name. + * + * @return - the agents name. + */ + public String getName() throws XMPPException { + AgentInfo agentInfo = new AgentInfo(); + agentInfo.setType(IQ.Type.GET); + agentInfo.setTo(workgroupJID); + agentInfo.setFrom(getUser()); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID())); + // Send the request + connection.sendPacket(agentInfo); + + AgentInfo response = (AgentInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response.getName(); + } + + /** + * Changes the name of the agent in the server. The server may have this functionality + * disabled for all the agents or for this agent in particular. If the agent is not + * allowed to change his name then an exception will be thrown with a service_unavailable + * error code. + * + * @param newName the new name of the agent. + * @throws XMPPException if the agent is not allowed to change his name or no response was + * obtained from the server. + */ + public void setName(String newName) throws XMPPException { + AgentInfo agentInfo = new AgentInfo(); + agentInfo.setType(IQ.Type.SET); + agentInfo.setTo(workgroupJID); + agentInfo.setFrom(getUser()); + agentInfo.setName(newName); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(agentInfo.getPacketID())); + // Send the request + connection.sendPacket(agentInfo); + + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java new file mode 100644 index 0000000..155a1c3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRoster.java @@ -0,0 +1,386 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smackx.workgroup.packet.AgentStatus; +import org.jivesoftware.smackx.workgroup.packet.AgentStatusRequest; +import org.jivesoftware.smack.PacketListener; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.filter.PacketFilter; +import org.jivesoftware.smack.filter.PacketTypeFilter; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.packet.Presence; +import org.jivesoftware.smack.util.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Manges information about the agents in a workgroup and their presence. + * + * @author Matt Tucker + * @see AgentSession#getAgentRoster() + */ +public class AgentRoster { + + private static final int EVENT_AGENT_ADDED = 0; + private static final int EVENT_AGENT_REMOVED = 1; + private static final int EVENT_PRESENCE_CHANGED = 2; + + private Connection connection; + private String workgroupJID; + private List entries; + private List listeners; + private Map> presenceMap; + // The roster is marked as initialized when at least a single roster packet + // has been recieved and processed. + boolean rosterInitialized = false; + + /** + * Constructs a new AgentRoster. + * + * @param connection an XMPP connection. + */ + AgentRoster(Connection connection, String workgroupJID) { + this.connection = connection; + this.workgroupJID = workgroupJID; + entries = new ArrayList(); + listeners = new ArrayList(); + presenceMap = new HashMap>(); + // Listen for any roster packets. + PacketFilter rosterFilter = new PacketTypeFilter(AgentStatusRequest.class); + connection.addPacketListener(new AgentStatusListener(), rosterFilter); + // Listen for any presence packets. + connection.addPacketListener(new PresencePacketListener(), + new PacketTypeFilter(Presence.class)); + + // Send request for roster. + AgentStatusRequest request = new AgentStatusRequest(); + request.setTo(workgroupJID); + connection.sendPacket(request); + } + + /** + * Reloads the entire roster from the server. This is an asynchronous operation, + * which means the method will return immediately, and the roster will be + * reloaded at a later point when the server responds to the reload request. + */ + public void reload() { + AgentStatusRequest request = new AgentStatusRequest(); + request.setTo(workgroupJID); + connection.sendPacket(request); + } + + /** + * Adds a listener to this roster. The listener will be fired anytime one or more + * changes to the roster are pushed from the server. + * + * @param listener an agent roster listener. + */ + public void addListener(AgentRosterListener listener) { + synchronized (listeners) { + if (!listeners.contains(listener)) { + listeners.add(listener); + + // Fire events for the existing entries and presences in the roster + for (Iterator it = getAgents().iterator(); it.hasNext();) { + String jid = it.next(); + // Check again in case the agent is no longer in the roster (highly unlikely + // but possible) + if (entries.contains(jid)) { + // Fire the agent added event + listener.agentAdded(jid); + Map userPresences = presenceMap.get(jid); + if (userPresences != null) { + Iterator presences = userPresences.values().iterator(); + while (presences.hasNext()) { + // Fire the presence changed event + listener.presenceChanged(presences.next()); + } + } + } + } + } + } + } + + /** + * Removes a listener from this roster. The listener will be fired anytime one or more + * changes to the roster are pushed from the server. + * + * @param listener a roster listener. + */ + public void removeListener(AgentRosterListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + /** + * Returns a count of all agents in the workgroup. + * + * @return the number of agents in the workgroup. + */ + public int getAgentCount() { + return entries.size(); + } + + /** + * Returns all agents (String JID values) in the workgroup. + * + * @return all entries in the roster. + */ + public Set getAgents() { + Set agents = new HashSet(); + synchronized (entries) { + for (Iterator i = entries.iterator(); i.hasNext();) { + agents.add(i.next()); + } + } + return Collections.unmodifiableSet(agents); + } + + /** + * Returns true if the specified XMPP address is an agent in the workgroup. + * + * @param jid the XMPP address of the agent (eg "jsmith@example.com"). The + * address can be in any valid format (e.g. "domain/resource", "user@domain" + * or "user@domain/resource"). + * @return true if the XMPP address is an agent in the workgroup. + */ + public boolean contains(String jid) { + if (jid == null) { + return false; + } + synchronized (entries) { + for (Iterator i = entries.iterator(); i.hasNext();) { + String entry = i.next(); + if (entry.toLowerCase().equals(jid.toLowerCase())) { + return true; + } + } + } + return false; + } + + /** + * Returns the presence info for a particular agent, or null if the agent + * is unavailable (offline) or if no presence information is available.

      + * + * @param user a fully qualified xmpp JID. The address could be in any valid format (e.g. + * "domain/resource", "user@domain" or "user@domain/resource"). + * @return the agent's current presence, or null if the agent is unavailable + * or if no presence information is available.. + */ + public Presence getPresence(String user) { + String key = getPresenceMapKey(user); + Map userPresences = presenceMap.get(key); + if (userPresences == null) { + Presence presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return presence; + } + else { + // Find the resource with the highest priority + // Might be changed to use the resource with the highest availability instead. + Iterator it = userPresences.keySet().iterator(); + Presence p; + Presence presence = null; + + while (it.hasNext()) { + p = (Presence)userPresences.get(it.next()); + if (presence == null){ + presence = p; + } + else { + if (p.getPriority() > presence.getPriority()) { + presence = p; + } + } + } + if (presence == null) { + presence = new Presence(Presence.Type.unavailable); + presence.setFrom(user); + return presence; + } + else { + return presence; + } + } + } + + /** + * Returns the key to use in the presenceMap for a fully qualified xmpp ID. The roster + * can contain any valid address format such us "domain/resource", "user@domain" or + * "user@domain/resource". If the roster contains an entry associated with the fully qualified + * xmpp ID then use the fully qualified xmpp ID as the key in presenceMap, otherwise use the + * bare address. Note: When the key in presenceMap is a fully qualified xmpp ID, the + * userPresences is useless since it will always contain one entry for the user. + * + * @param user the fully qualified xmpp ID, e.g. jdoe@example.com/Work. + * @return the key to use in the presenceMap for the fully qualified xmpp ID. + */ + private String getPresenceMapKey(String user) { + String key = user; + if (!contains(user)) { + key = StringUtils.parseBareAddress(user).toLowerCase(); + } + return key; + } + + /** + * Fires event to listeners. + */ + private void fireEvent(int eventType, Object eventObject) { + AgentRosterListener[] listeners = null; + synchronized (this.listeners) { + listeners = new AgentRosterListener[this.listeners.size()]; + this.listeners.toArray(listeners); + } + for (int i = 0; i < listeners.length; i++) { + switch (eventType) { + case EVENT_AGENT_ADDED: + listeners[i].agentAdded((String)eventObject); + break; + case EVENT_AGENT_REMOVED: + listeners[i].agentRemoved((String)eventObject); + break; + case EVENT_PRESENCE_CHANGED: + listeners[i].presenceChanged((Presence)eventObject); + break; + } + } + } + + /** + * Listens for all presence packets and processes them. + */ + private class PresencePacketListener implements PacketListener { + public void processPacket(Packet packet) { + Presence presence = (Presence)packet; + String from = presence.getFrom(); + if (from == null) { + // TODO Check if we need to ignore these presences or this is a server bug? + System.out.println("Presence with no FROM: " + presence.toXML()); + return; + } + String key = getPresenceMapKey(from); + + // If an "available" packet, add it to the presence map. Each presence map will hold + // for a particular user a map with the presence packets saved for each resource. + if (presence.getType() == Presence.Type.available) { + // Ignore the presence packet unless it has an agent status extension. + AgentStatus agentStatus = (AgentStatus)presence.getExtension( + AgentStatus.ELEMENT_NAME, AgentStatus.NAMESPACE); + if (agentStatus == null) { + return; + } + // Ensure that this presence is coming from an Agent of the same workgroup + // of this Agent + else if (!workgroupJID.equals(agentStatus.getWorkgroupJID())) { + return; + } + Map userPresences; + // Get the user presence map + if (presenceMap.get(key) == null) { + userPresences = new HashMap(); + presenceMap.put(key, userPresences); + } + else { + userPresences = presenceMap.get(key); + } + // Add the new presence, using the resources as a key. + synchronized (userPresences) { + userPresences.put(StringUtils.parseResource(from), presence); + } + // Fire an event. + synchronized (entries) { + for (Iterator i = entries.iterator(); i.hasNext();) { + String entry = (String)i.next(); + if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) { + fireEvent(EVENT_PRESENCE_CHANGED, packet); + } + } + } + } + // If an "unavailable" packet, remove any entries in the presence map. + else if (presence.getType() == Presence.Type.unavailable) { + if (presenceMap.get(key) != null) { + Map userPresences = presenceMap.get(key); + synchronized (userPresences) { + userPresences.remove(StringUtils.parseResource(from)); + } + if (userPresences.isEmpty()) { + presenceMap.remove(key); + } + } + // Fire an event. + synchronized (entries) { + for (Iterator i = entries.iterator(); i.hasNext();) { + String entry = (String)i.next(); + if (entry.toLowerCase().equals(StringUtils.parseBareAddress(key).toLowerCase())) { + fireEvent(EVENT_PRESENCE_CHANGED, packet); + } + } + } + } + } + } + + /** + * Listens for all roster packets and processes them. + */ + private class AgentStatusListener implements PacketListener { + + public void processPacket(Packet packet) { + if (packet instanceof AgentStatusRequest) { + AgentStatusRequest statusRequest = (AgentStatusRequest)packet; + for (Iterator i = statusRequest.getAgents().iterator(); i.hasNext();) { + AgentStatusRequest.Item item = (AgentStatusRequest.Item)i.next(); + String agentJID = item.getJID(); + if ("remove".equals(item.getType())) { + + // Removing the user from the roster, so remove any presence information + // about them. + String key = StringUtils.parseName(StringUtils.parseName(agentJID) + "@" + + StringUtils.parseServer(agentJID)); + presenceMap.remove(key); + // Fire event for roster listeners. + fireEvent(EVENT_AGENT_REMOVED, agentJID); + } + else { + entries.add(agentJID); + // Fire event for roster listeners. + fireEvent(EVENT_AGENT_ADDED, agentJID); + } + } + + // Mark the roster as initialized. + rosterInitialized = true; + } + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java new file mode 100644 index 0000000..4db9203 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentRosterListener.java @@ -0,0 +1,35 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smack.packet.Presence; + +/** + * + * @author Matt Tucker + */ +public interface AgentRosterListener { + + public void agentAdded(String jid); + + public void agentRemoved(String jid); + + public void presenceChanged(Presence presence); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java new file mode 100644 index 0000000..340de4f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/AgentSession.java @@ -0,0 +1,1184 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smackx.workgroup.MetaData; +import org.jivesoftware.smackx.workgroup.WorkgroupInvitation; +import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener; +import org.jivesoftware.smackx.workgroup.ext.history.AgentChatHistory; +import org.jivesoftware.smackx.workgroup.ext.history.ChatMetadata; +import org.jivesoftware.smackx.workgroup.ext.macros.MacroGroup; +import org.jivesoftware.smackx.workgroup.ext.macros.Macros; +import org.jivesoftware.smackx.workgroup.ext.notes.ChatNotes; +import org.jivesoftware.smackx.workgroup.packet.*; +import org.jivesoftware.smackx.workgroup.settings.GenericSettings; +import org.jivesoftware.smackx.workgroup.settings.SearchSettings; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.ReportedData; +import org.jivesoftware.smackx.packet.MUCUser; + +import java.util.*; + +/** + * This class embodies the agent's active presence within a given workgroup. The application + * should have N instances of this class, where N is the number of workgroups to which the + * owning agent of the application belongs. This class provides all functionality that a + * session within a given workgroup is expected to have from an agent's perspective -- setting + * the status, tracking the status of queues to which the agent belongs within the workgroup, and + * dequeuing customers. + * + * @author Matt Tucker + * @author Derek DeMoro + */ +public class AgentSession { + + private Connection connection; + + private String workgroupJID; + + private boolean online = false; + private Presence.Mode presenceMode; + private int maxChats; + private final Map metaData; + + private Map queues; + + private final List offerListeners; + private final List invitationListeners; + private final List queueUsersListeners; + + private AgentRoster agentRoster = null; + private TranscriptManager transcriptManager; + private TranscriptSearchManager transcriptSearchManager; + private Agent agent; + private PacketListener packetListener; + + /** + * Constructs a new agent session instance. Note, the {@link #setOnline(boolean)} + * method must be called with an argument of true to mark the agent + * as available to accept chat requests. + * + * @param connection a connection instance which must have already gone through + * authentication. + * @param workgroupJID the fully qualified JID of the workgroup. + */ + public AgentSession(String workgroupJID, Connection connection) { + // Login must have been done before passing in connection. + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must login to server before creating workgroup."); + } + + this.workgroupJID = workgroupJID; + this.connection = connection; + this.transcriptManager = new TranscriptManager(connection); + this.transcriptSearchManager = new TranscriptSearchManager(connection); + + this.maxChats = -1; + + this.metaData = new HashMap(); + + this.queues = new HashMap(); + + offerListeners = new ArrayList(); + invitationListeners = new ArrayList(); + queueUsersListeners = new ArrayList(); + + // Create a filter to listen for packets we're interested in. + OrFilter filter = new OrFilter(); + filter.addFilter(new PacketTypeFilter(OfferRequestProvider.OfferRequestPacket.class)); + filter.addFilter(new PacketTypeFilter(OfferRevokeProvider.OfferRevokePacket.class)); + filter.addFilter(new PacketTypeFilter(Presence.class)); + filter.addFilter(new PacketTypeFilter(Message.class)); + + packetListener = new PacketListener() { + public void processPacket(Packet packet) { + try { + handlePacket(packet); + } + catch (Exception e) { + e.printStackTrace(); + } + } + }; + connection.addPacketListener(packetListener, filter); + // Create the agent associated to this session + agent = new Agent(connection, workgroupJID); + } + + /** + * Close the agent session. The underlying connection will remain opened but the + * packet listeners that were added by this agent session will be removed. + */ + public void close() { + connection.removePacketListener(packetListener); + } + + /** + * Returns the agent roster for the workgroup, which contains + * + * @return the AgentRoster + */ + public AgentRoster getAgentRoster() { + if (agentRoster == null) { + agentRoster = new AgentRoster(connection, workgroupJID); + } + + // This might be the first time the user has asked for the roster. If so, we + // want to wait up to 2 seconds for the server to send back the list of agents. + // This behavior shields API users from having to worry about the fact that the + // operation is asynchronous, although they'll still have to listen for changes + // to the roster. + int elapsed = 0; + while (!agentRoster.rosterInitialized && elapsed <= 2000) { + try { + Thread.sleep(500); + } + catch (Exception e) { + // Ignore + } + elapsed += 500; + } + return agentRoster; + } + + /** + * Returns the agent's current presence mode. + * + * @return the agent's current presence mode. + */ + public Presence.Mode getPresenceMode() { + return presenceMode; + } + + /** + * Returns the maximum number of chats the agent can participate in. + * + * @return the maximum number of chats the agent can participate in. + */ + public int getMaxChats() { + return maxChats; + } + + /** + * Returns true if the agent is online with the workgroup. + * + * @return true if the agent is online with the workgroup. + */ + public boolean isOnline() { + return online; + } + + /** + * Allows the addition of a new key-value pair to the agent's meta data, if the value is + * new data, the revised meta data will be rebroadcast in an agent's presence broadcast. + * + * @param key the meta data key + * @param val the non-null meta data value + * @throws XMPPException if an exception occurs. + */ + public void setMetaData(String key, String val) throws XMPPException { + synchronized (this.metaData) { + String oldVal = (String)this.metaData.get(key); + + if ((oldVal == null) || (!oldVal.equals(val))) { + metaData.put(key, val); + + setStatus(presenceMode, maxChats); + } + } + } + + /** + * Allows the removal of data from the agent's meta data, if the key represents existing data, + * the revised meta data will be rebroadcast in an agent's presence broadcast. + * + * @param key the meta data key. + * @throws XMPPException if an exception occurs. + */ + public void removeMetaData(String key) throws XMPPException { + synchronized (this.metaData) { + String oldVal = (String)metaData.remove(key); + + if (oldVal != null) { + setStatus(presenceMode, maxChats); + } + } + } + + /** + * Allows the retrieval of meta data for a specified key. + * + * @param key the meta data key + * @return the meta data value associated with the key or null if the meta-data + * doesn't exist.. + */ + public String getMetaData(String key) { + return (String)metaData.get(key); + } + + /** + * Sets whether the agent is online with the workgroup. If the user tries to go online with + * the workgroup but is not allowed to be an agent, an XMPPError with error code 401 will + * be thrown. + * + * @param online true to set the agent as online with the workgroup. + * @throws XMPPException if an error occurs setting the online status. + */ + public void setOnline(boolean online) throws XMPPException { + // If the online status hasn't changed, do nothing. + if (this.online == online) { + return; + } + + Presence presence; + + // If the user is going online... + if (online) { + presence = new Presence(Presence.Type.available); + presence.setTo(workgroupJID); + presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, + AgentStatus.NAMESPACE)); + + PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID))); + + connection.sendPacket(presence); + + presence = (Presence)collector.nextResult(5000); + collector.cancel(); + if (!presence.isAvailable()) { + throw new XMPPException("No response from server on status set."); + } + + if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + + // We can safely update this iv since we didn't get any error + this.online = online; + } + // Otherwise the user is going offline... + else { + // Update this iv now since we don't care at this point of any error + this.online = online; + + presence = new Presence(Presence.Type.unavailable); + presence.setTo(workgroupJID); + presence.addExtension(new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, + AgentStatus.NAMESPACE)); + connection.sendPacket(presence); + } + } + + /** + * Sets the agent's current status with the workgroup. The presence mode affects + * how offers are routed to the agent. The possible presence modes with their + * meanings are as follows:

        + *

        + *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats + * (equivalent to Presence.Mode.CHAT). + *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. + * However, special case, or extreme urgency chats may still be offered to the agent. + *
      • Presence.Mode.AWAY -- the agent is not available and should not + * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      + *

      + * The max chats value is the maximum number of chats the agent is willing to have + * routed to them at once. Some servers may be configured to only accept max chat + * values in a certain range; for example, between two and five. In that case, the + * maxChats value the agent sends may be adjusted by the server to a value within that + * range. + * + * @param presenceMode the presence mode of the agent. + * @param maxChats the maximum number of chats the agent is willing to accept. + * @throws XMPPException if an error occurs setting the agent status. + * @throws IllegalStateException if the agent is not online with the workgroup. + */ + public void setStatus(Presence.Mode presenceMode, int maxChats) throws XMPPException { + setStatus(presenceMode, maxChats, null); + } + + /** + * Sets the agent's current status with the workgroup. The presence mode affects how offers + * are routed to the agent. The possible presence modes with their meanings are as follows:

        + *

        + *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats + * (equivalent to Presence.Mode.CHAT). + *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. + * However, special case, or extreme urgency chats may still be offered to the agent. + *
      • Presence.Mode.AWAY -- the agent is not available and should not + * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      + *

      + * The max chats value is the maximum number of chats the agent is willing to have routed to + * them at once. Some servers may be configured to only accept max chat values in a certain + * range; for example, between two and five. In that case, the maxChats value the agent sends + * may be adjusted by the server to a value within that range. + * + * @param presenceMode the presence mode of the agent. + * @param maxChats the maximum number of chats the agent is willing to accept. + * @param status sets the status message of the presence update. + * @throws XMPPException if an error occurs setting the agent status. + * @throws IllegalStateException if the agent is not online with the workgroup. + */ + public void setStatus(Presence.Mode presenceMode, int maxChats, String status) + throws XMPPException { + if (!online) { + throw new IllegalStateException("Cannot set status when the agent is not online."); + } + + if (presenceMode == null) { + presenceMode = Presence.Mode.available; + } + this.presenceMode = presenceMode; + this.maxChats = maxChats; + + Presence presence = new Presence(Presence.Type.available); + presence.setMode(presenceMode); + presence.setTo(this.getWorkgroupJID()); + + if (status != null) { + presence.setStatus(status); + } + // Send information about max chats and current chats as a packet extension. + DefaultPacketExtension agentStatus = new DefaultPacketExtension(AgentStatus.ELEMENT_NAME, + AgentStatus.NAMESPACE); + agentStatus.setValue("max-chats", "" + maxChats); + presence.addExtension(agentStatus); + presence.addExtension(new MetaData(this.metaData)); + + PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), new FromContainsFilter(workgroupJID))); + + this.connection.sendPacket(presence); + + presence = (Presence)collector.nextResult(5000); + collector.cancel(); + if (!presence.isAvailable()) { + throw new XMPPException("No response from server on status set."); + } + + if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + } + + /** + * Sets the agent's current status with the workgroup. The presence mode affects how offers + * are routed to the agent. The possible presence modes with their meanings are as follows:

        + *

        + *

      • Presence.Mode.AVAILABLE -- (Default) the agent is available for more chats + * (equivalent to Presence.Mode.CHAT). + *
      • Presence.Mode.DO_NOT_DISTURB -- the agent is busy and should not be disturbed. + * However, special case, or extreme urgency chats may still be offered to the agent. + *
      • Presence.Mode.AWAY -- the agent is not available and should not + * have a chat routed to them (equivalent to Presence.Mode.EXTENDED_AWAY).
      + * + * @param presenceMode the presence mode of the agent. + * @param status sets the status message of the presence update. + * @throws XMPPException if an error occurs setting the agent status. + * @throws IllegalStateException if the agent is not online with the workgroup. + */ + public void setStatus(Presence.Mode presenceMode, String status) throws XMPPException { + if (!online) { + throw new IllegalStateException("Cannot set status when the agent is not online."); + } + + if (presenceMode == null) { + presenceMode = Presence.Mode.available; + } + this.presenceMode = presenceMode; + + Presence presence = new Presence(Presence.Type.available); + presence.setMode(presenceMode); + presence.setTo(this.getWorkgroupJID()); + + if (status != null) { + presence.setStatus(status); + } + presence.addExtension(new MetaData(this.metaData)); + + PacketCollector collector = this.connection.createPacketCollector(new AndFilter(new PacketTypeFilter(Presence.class), + new FromContainsFilter(workgroupJID))); + + this.connection.sendPacket(presence); + + presence = (Presence)collector.nextResult(5000); + collector.cancel(); + if (!presence.isAvailable()) { + throw new XMPPException("No response from server on status set."); + } + + if (presence.getError() != null) { + throw new XMPPException(presence.getError()); + } + } + + /** + * Removes a user from the workgroup queue. This is an administrative action that the + *

      + * The agent is not guaranteed of having privileges to perform this action; an exception + * denying the request may be thrown. + * + * @param userID the ID of the user to remove. + * @throws XMPPException if an exception occurs. + */ + public void dequeueUser(String userID) throws XMPPException { + // todo: this method simply won't work right now. + DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID); + + // PENDING + this.connection.sendPacket(departPacket); + } + + /** + * Returns the transcripts of a given user. The answer will contain the complete history of + * conversations that a user had. + * + * @param userID the id of the user to get his conversations. + * @return the transcripts of a given user. + * @throws XMPPException if an error occurs while getting the information. + */ + public Transcripts getTranscripts(String userID) throws XMPPException { + return transcriptManager.getTranscripts(workgroupJID, userID); + } + + /** + * Returns the full conversation transcript of a given session. + * + * @param sessionID the id of the session to get the full transcript. + * @return the full conversation transcript of a given session. + * @throws XMPPException if an error occurs while getting the information. + */ + public Transcript getTranscript(String sessionID) throws XMPPException { + return transcriptManager.getTranscript(workgroupJID, sessionID); + } + + /** + * Returns the Form to use for searching transcripts. It is unlikely that the server + * will change the form (without a restart) so it is safe to keep the returned form + * for future submissions. + * + * @return the Form to use for searching transcripts. + * @throws XMPPException if an error occurs while sending the request to the server. + */ + public Form getTranscriptSearchForm() throws XMPPException { + return transcriptSearchManager.getSearchForm(StringUtils.parseServer(workgroupJID)); + } + + /** + * Submits the completed form and returns the result of the transcript search. The result + * will include all the data returned from the server so be careful with the amount of + * data that the search may return. + * + * @param completedForm the filled out search form. + * @return the result of the transcript search. + * @throws XMPPException if an error occurs while submiting the search to the server. + */ + public ReportedData searchTranscripts(Form completedForm) throws XMPPException { + return transcriptSearchManager.submitSearch(StringUtils.parseServer(workgroupJID), + completedForm); + } + + /** + * Asks the workgroup for information about the occupants of the specified room. The returned + * information will include the real JID of the occupants, the nickname of the user in the + * room as well as the date when the user joined the room. + * + * @param roomID the room to get information about its occupants. + * @return information about the occupants of the specified room. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public OccupantsInfo getOccupantsInfo(String roomID) throws XMPPException { + OccupantsInfo request = new OccupantsInfo(roomID); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + OccupantsInfo response = (OccupantsInfo)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * @return the fully-qualified name of the workgroup for which this session exists + */ + public String getWorkgroupJID() { + return workgroupJID; + } + + /** + * Returns the Agent associated to this session. + * + * @return the Agent associated to this session. + */ + public Agent getAgent() { + return agent; + } + + /** + * @param queueName the name of the queue + * @return an instance of WorkgroupQueue for the argument queue name, or null if none exists + */ + public WorkgroupQueue getQueue(String queueName) { + return queues.get(queueName); + } + + public Iterator getQueues() { + return Collections.unmodifiableMap((new HashMap(queues))).values().iterator(); + } + + public void addQueueUsersListener(QueueUsersListener listener) { + synchronized (queueUsersListeners) { + if (!queueUsersListeners.contains(listener)) { + queueUsersListeners.add(listener); + } + } + } + + public void removeQueueUsersListener(QueueUsersListener listener) { + synchronized (queueUsersListeners) { + queueUsersListeners.remove(listener); + } + } + + /** + * Adds an offer listener. + * + * @param offerListener the offer listener. + */ + public void addOfferListener(OfferListener offerListener) { + synchronized (offerListeners) { + if (!offerListeners.contains(offerListener)) { + offerListeners.add(offerListener); + } + } + } + + /** + * Removes an offer listener. + * + * @param offerListener the offer listener. + */ + public void removeOfferListener(OfferListener offerListener) { + synchronized (offerListeners) { + offerListeners.remove(offerListener); + } + } + + /** + * Adds an invitation listener. + * + * @param invitationListener the invitation listener. + */ + public void addInvitationListener(WorkgroupInvitationListener invitationListener) { + synchronized (invitationListeners) { + if (!invitationListeners.contains(invitationListener)) { + invitationListeners.add(invitationListener); + } + } + } + + /** + * Removes an invitation listener. + * + * @param invitationListener the invitation listener. + */ + public void removeInvitationListener(WorkgroupInvitationListener invitationListener) { + synchronized (invitationListeners) { + invitationListeners.remove(invitationListener); + } + } + + private void fireOfferRequestEvent(OfferRequestProvider.OfferRequestPacket requestPacket) { + Offer offer = new Offer(this.connection, this, requestPacket.getUserID(), + requestPacket.getUserJID(), this.getWorkgroupJID(), + new Date((new Date()).getTime() + (requestPacket.getTimeout() * 1000)), + requestPacket.getSessionID(), requestPacket.getMetaData(), requestPacket.getContent()); + + synchronized (offerListeners) { + for (OfferListener listener : offerListeners) { + listener.offerReceived(offer); + } + } + } + + private void fireOfferRevokeEvent(OfferRevokeProvider.OfferRevokePacket orp) { + RevokedOffer revokedOffer = new RevokedOffer(orp.getUserJID(), orp.getUserID(), + this.getWorkgroupJID(), orp.getSessionID(), orp.getReason(), new Date()); + + synchronized (offerListeners) { + for (OfferListener listener : offerListeners) { + listener.offerRevoked(revokedOffer); + } + } + } + + private void fireInvitationEvent(String groupChatJID, String sessionID, String body, + String from, Map metaData) { + WorkgroupInvitation invitation = new WorkgroupInvitation(connection.getUser(), groupChatJID, + workgroupJID, sessionID, body, from, metaData); + + synchronized (invitationListeners) { + for (WorkgroupInvitationListener listener : invitationListeners) { + listener.invitationReceived(invitation); + } + } + } + + private void fireQueueUsersEvent(WorkgroupQueue queue, WorkgroupQueue.Status status, + int averageWaitTime, Date oldestEntry, Set users) { + synchronized (queueUsersListeners) { + for (QueueUsersListener listener : queueUsersListeners) { + if (status != null) { + listener.statusUpdated(queue, status); + } + if (averageWaitTime != -1) { + listener.averageWaitTimeUpdated(queue, averageWaitTime); + } + if (oldestEntry != null) { + listener.oldestEntryUpdated(queue, oldestEntry); + } + if (users != null) { + listener.usersUpdated(queue, users); + } + } + } + } + + // PacketListener Implementation. + + private void handlePacket(Packet packet) { + if (packet instanceof OfferRequestProvider.OfferRequestPacket) { + // Acknowledge the IQ set. + IQ reply = new IQ() { + public String getChildElementXML() { + return null; + } + }; + reply.setPacketID(packet.getPacketID()); + reply.setTo(packet.getFrom()); + reply.setType(IQ.Type.RESULT); + connection.sendPacket(reply); + + fireOfferRequestEvent((OfferRequestProvider.OfferRequestPacket)packet); + } + else if (packet instanceof Presence) { + Presence presence = (Presence)packet; + + // The workgroup can send us a number of different presence packets. We + // check for different packet extensions to see what type of presence + // packet it is. + + String queueName = StringUtils.parseResource(presence.getFrom()); + WorkgroupQueue queue = queues.get(queueName); + // If there isn't already an entry for the queue, create a new one. + if (queue == null) { + queue = new WorkgroupQueue(queueName); + queues.put(queueName, queue); + } + + // QueueOverview packet extensions contain basic information about a queue. + QueueOverview queueOverview = (QueueOverview)presence.getExtension(QueueOverview.ELEMENT_NAME, QueueOverview.NAMESPACE); + if (queueOverview != null) { + if (queueOverview.getStatus() == null) { + queue.setStatus(WorkgroupQueue.Status.CLOSED); + } + else { + queue.setStatus(queueOverview.getStatus()); + } + queue.setAverageWaitTime(queueOverview.getAverageWaitTime()); + queue.setOldestEntry(queueOverview.getOldestEntry()); + // Fire event. + fireQueueUsersEvent(queue, queueOverview.getStatus(), + queueOverview.getAverageWaitTime(), queueOverview.getOldestEntry(), + null); + return; + } + + // QueueDetails packet extensions contain information about the users in + // a queue. + QueueDetails queueDetails = (QueueDetails)packet.getExtension(QueueDetails.ELEMENT_NAME, QueueDetails.NAMESPACE); + if (queueDetails != null) { + queue.setUsers(queueDetails.getUsers()); + // Fire event. + fireQueueUsersEvent(queue, null, -1, null, queueDetails.getUsers()); + return; + } + + // Notify agent packets gives an overview of agent activity in a queue. + DefaultPacketExtension notifyAgents = (DefaultPacketExtension)presence.getExtension("notify-agents", "http://jabber.org/protocol/workgroup"); + if (notifyAgents != null) { + int currentChats = Integer.parseInt(notifyAgents.getValue("current-chats")); + int maxChats = Integer.parseInt(notifyAgents.getValue("max-chats")); + queue.setCurrentChats(currentChats); + queue.setMaxChats(maxChats); + // Fire event. + // TODO: might need another event for current chats and max chats of queue + return; + } + } + else if (packet instanceof Message) { + Message message = (Message)packet; + + // Check if a room invitation was sent and if the sender is the workgroup + MUCUser mucUser = (MUCUser)message.getExtension("x", + "http://jabber.org/protocol/muc#user"); + MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null; + if (invite != null && workgroupJID.equals(invite.getFrom())) { + String sessionID = null; + Map metaData = null; + + SessionID sessionIDExt = (SessionID)message.getExtension(SessionID.ELEMENT_NAME, + SessionID.NAMESPACE); + if (sessionIDExt != null) { + sessionID = sessionIDExt.getSessionID(); + } + + MetaData metaDataExt = (MetaData)message.getExtension(MetaData.ELEMENT_NAME, + MetaData.NAMESPACE); + if (metaDataExt != null) { + metaData = metaDataExt.getMetaData(); + } + + this.fireInvitationEvent(message.getFrom(), sessionID, message.getBody(), + message.getFrom(), metaData); + } + } + else if (packet instanceof OfferRevokeProvider.OfferRevokePacket) { + // Acknowledge the IQ set. + IQ reply = new IQ() { + public String getChildElementXML() { + return null; + } + }; + reply.setPacketID(packet.getPacketID()); + reply.setType(IQ.Type.RESULT); + connection.sendPacket(reply); + + fireOfferRevokeEvent((OfferRevokeProvider.OfferRevokePacket)packet); + } + } + + /** + * Creates a ChatNote that will be mapped to the given chat session. + * + * @param sessionID the session id of a Chat Session. + * @param note the chat note to add. + * @throws XMPPException is thrown if an error occurs while adding the note. + */ + public void setNote(String sessionID, String note) throws XMPPException { + note = ChatNotes.replace(note, "\n", "\\n"); + note = StringUtils.escapeForXML(note); + + ChatNotes notes = new ChatNotes(); + notes.setType(IQ.Type.SET); + notes.setTo(workgroupJID); + notes.setSessionID(sessionID); + notes.setNotes(note); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(notes.getPacketID())); + // Send the request + connection.sendPacket(notes); + + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } + + /** + * Retrieves the ChatNote associated with a given chat session. + * + * @param sessionID the sessionID of the chat session. + * @return the ChatNote associated with a given chat session. + * @throws XMPPException if an error occurs while retrieving the ChatNote. + */ + public ChatNotes getNote(String sessionID) throws XMPPException { + ChatNotes request = new ChatNotes(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + request.setSessionID(sessionID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + ChatNotes response = (ChatNotes)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + + } + + /** + * Retrieves the AgentChatHistory associated with a particular agent jid. + * + * @param jid the jid of the agent. + * @param maxSessions the max number of sessions to retrieve. + * @param startDate the starting date of sessions to retrieve. + * @return the chat history associated with a given jid. + * @throws XMPPException if an error occurs while retrieving the AgentChatHistory. + */ + public AgentChatHistory getAgentHistory(String jid, int maxSessions, Date startDate) throws XMPPException { + AgentChatHistory request; + if (startDate != null) { + request = new AgentChatHistory(jid, maxSessions, startDate); + } + else { + request = new AgentChatHistory(jid, maxSessions); + } + + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + AgentChatHistory response = (AgentChatHistory)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Asks the workgroup for it's Search Settings. + * + * @return SearchSettings the search settings for this workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public SearchSettings getSearchSettings() throws XMPPException { + SearchSettings request = new SearchSettings(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + SearchSettings response = (SearchSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Asks the workgroup for it's Global Macros. + * + * @param global true to retrieve global macros, otherwise false for personal macros. + * @return MacroGroup the root macro group. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public MacroGroup getMacros(boolean global) throws XMPPException { + Macros request = new Macros(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + request.setPersonal(!global); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + Macros response = (Macros)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response.getRootGroup(); + } + + /** + * Persists the Personal Macro for an agent. + * + * @param group the macro group to save. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public void saveMacros(MacroGroup group) throws XMPPException { + Macros request = new Macros(); + request.setType(IQ.Type.SET); + request.setTo(workgroupJID); + request.setPersonal(true); + request.setPersonalMacroGroup(group); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } + + /** + * Query for metadata associated with a session id. + * + * @param sessionID the sessionID to query for. + * @return Map a map of all metadata associated with the sessionID. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public Map getChatMetadata(String sessionID) throws XMPPException { + ChatMetadata request = new ChatMetadata(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + request.setSessionID(sessionID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + ChatMetadata response = (ChatMetadata)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response.getMetadata(); + } + + /** + * Invites a user or agent to an existing session support. The provided invitee's JID can be of + * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service + * will decide the best agent to receive the invitation.

      + * + * This method will return either when the service returned an ACK of the request or if an error occured + * while requesting the invitation. After sending the ACK the service will send the invitation to the target + * entity. When dealing with agents the common sequence of offer-response will be followed. However, when + * sending an invitation to a user a standard MUC invitation will be sent.

      + * + * The agent or user that accepted the offer MUST join the room. Failing to do so will make + * the invitation to fail. The inviter will eventually receive a message error indicating that the invitee + * accepted the offer but failed to join the room. + * + * Different situations may lead to a failed invitation. Possible cases are: 1) all agents rejected the + * offer and ther are no agents available, 2) the agent that accepted the offer failed to join the room or + * 2) the user that received the MUC invitation never replied or joined the room. In any of these cases + * (or other failing cases) the inviter will get an error message with the failed notification. + * + * @param type type of entity that will get the invitation. + * @param invitee JID of entity that will get the invitation. + * @param sessionID ID of the support session that the invitee is being invited. + * @param reason the reason of the invitation. + * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process + * the request. + */ + public void sendRoomInvitation(RoomInvitation.Type type, String invitee, String sessionID, String reason) + throws XMPPException { + final RoomInvitation invitation = new RoomInvitation(type, invitee, sessionID, reason); + IQ iq = new IQ() { + + public String getChildElementXML() { + return invitation.toXML(); + } + }; + iq.setType(IQ.Type.SET); + iq.setTo(workgroupJID); + iq.setFrom(connection.getUser()); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID())); + connection.sendPacket(iq); + + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } + + /** + * Transfer an existing session support to another user or agent. The provided invitee's JID can be of + * a user, an agent, a queue or a workgroup. In the case of a queue or a workgroup the workgroup service + * will decide the best agent to receive the invitation.

      + * + * This method will return either when the service returned an ACK of the request or if an error occured + * while requesting the transfer. After sending the ACK the service will send the invitation to the target + * entity. When dealing with agents the common sequence of offer-response will be followed. However, when + * sending an invitation to a user a standard MUC invitation will be sent.

      + * + * Once the invitee joins the support room the workgroup service will kick the inviter from the room.

      + * + * Different situations may lead to a failed transfers. Possible cases are: 1) all agents rejected the + * offer and there are no agents available, 2) the agent that accepted the offer failed to join the room + * or 2) the user that received the MUC invitation never replied or joined the room. In any of these cases + * (or other failing cases) the inviter will get an error message with the failed notification. + * + * @param type type of entity that will get the invitation. + * @param invitee JID of entity that will get the invitation. + * @param sessionID ID of the support session that the invitee is being invited. + * @param reason the reason of the invitation. + * @throws XMPPException if the sender of the invitation is not an agent or the service failed to process + * the request. + */ + public void sendRoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason) + throws XMPPException { + final RoomTransfer transfer = new RoomTransfer(type, invitee, sessionID, reason); + IQ iq = new IQ() { + + public String getChildElementXML() { + return transfer.toXML(); + } + }; + iq.setType(IQ.Type.SET); + iq.setTo(workgroupJID); + iq.setFrom(connection.getUser()); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(iq.getPacketID())); + connection.sendPacket(iq); + + IQ response = (IQ)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } + + /** + * Returns the generic metadata of the workgroup the agent belongs to. + * + * @param con the Connection to use. + * @param query an optional query object used to tell the server what metadata to retrieve. This can be null. + * @throws XMPPException if an error occurs while sending the request to the server. + * @return the settings for the workgroup. + */ + public GenericSettings getGenericSettings(Connection con, String query) throws XMPPException { + GenericSettings setting = new GenericSettings(); + setting.setType(IQ.Type.GET); + setting.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(setting.getPacketID())); + connection.sendPacket(setting); + + GenericSettings response = (GenericSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + public boolean hasMonitorPrivileges(Connection con) throws XMPPException { + MonitorPacket request = new MonitorPacket(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + MonitorPacket response = (MonitorPacket)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response.isMonitor(); + + } + + public void makeRoomOwner(Connection con, String sessionID) throws XMPPException { + MonitorPacket request = new MonitorPacket(); + request.setType(IQ.Type.SET); + request.setTo(workgroupJID); + request.setSessionID(sessionID); + + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + Packet response = collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java new file mode 100644 index 0000000..16b324a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/InvitationRequest.java @@ -0,0 +1,62 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +/** + * Request sent by an agent to invite another agent or user. + * + * @author Gaston Dombiak + */ +public class InvitationRequest extends OfferContent { + + private String inviter; + private String room; + private String reason; + + public InvitationRequest(String inviter, String room, String reason) { + this.inviter = inviter; + this.room = room; + this.reason = reason; + } + + public String getInviter() { + return inviter; + } + + public String getRoom() { + return room; + } + + public String getReason() { + return reason; + } + + boolean isUserRequest() { + return false; + } + + boolean isInvitation() { + return true; + } + + boolean isTransfer() { + return false; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java new file mode 100644 index 0000000..cb8dcfb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/Offer.java @@ -0,0 +1,222 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; + +import java.util.Date; +import java.util.Map; + +/** + * A class embodying the semantic agent chat offer; specific instances allow the acceptance or + * rejecting of the offer.
      + * + * @author Matt Tucker + * @author loki der quaeler + * @author Derek DeMoro + */ +public class Offer { + + private Connection connection; + private AgentSession session; + + private String sessionID; + private String userJID; + private String userID; + private String workgroupName; + private Date expiresDate; + private Map metaData; + private OfferContent content; + + private boolean accepted = false; + private boolean rejected = false; + + /** + * Creates a new offer. + * + * @param conn the XMPP connection with which the issuing session was created. + * @param agentSession the agent session instance through which this offer was issued. + * @param userID the userID of the user from which the offer originates. + * @param userJID the XMPP address of the user from which the offer originates. + * @param workgroupName the fully qualified name of the workgroup. + * @param expiresDate the date at which this offer expires. + * @param sessionID the session id associated with the offer. + * @param metaData the metadata associated with the offer. + * @param content content of the offer. The content explains the reason for the offer + * (e.g. user request, transfer) + */ + Offer(Connection conn, AgentSession agentSession, String userID, + String userJID, String workgroupName, Date expiresDate, + String sessionID, Map metaData, OfferContent content) + { + this.connection = conn; + this.session = agentSession; + this.userID = userID; + this.userJID = userJID; + this.workgroupName = workgroupName; + this.expiresDate = expiresDate; + this.sessionID = sessionID; + this.metaData = metaData; + this.content = content; + } + + /** + * Accepts the offer. + */ + public void accept() { + Packet acceptPacket = new AcceptPacket(this.session.getWorkgroupJID()); + connection.sendPacket(acceptPacket); + // TODO: listen for a reply. + accepted = true; + } + + /** + * Rejects the offer. + */ + public void reject() { + RejectPacket rejectPacket = new RejectPacket(this.session.getWorkgroupJID()); + connection.sendPacket(rejectPacket); + // TODO: listen for a reply. + rejected = true; + } + + /** + * Returns the userID that the offer originates from. In most cases, the + * userID will simply be the JID of the requesting user. However, users can + * also manually specify a userID for their request. In that case, that value will + * be returned. + * + * @return the userID of the user from which the offer originates. + */ + public String getUserID() { + return userID; + } + + /** + * Returns the JID of the user that made the offer request. + * + * @return the user's JID. + */ + public String getUserJID() { + return userJID; + } + + /** + * The fully qualified name of the workgroup (eg support@example.com). + * + * @return the name of the workgroup. + */ + public String getWorkgroupName() { + return this.workgroupName; + } + + /** + * The date when the offer will expire. The agent must {@link #accept()} + * the offer before the expiration date or the offer will lapse and be + * routed to another agent. Alternatively, the agent can {@link #reject()} + * the offer at any time if they don't wish to accept it.. + * + * @return the date at which this offer expires. + */ + public Date getExpiresDate() { + return this.expiresDate; + } + + /** + * The session ID associated with the offer. + * + * @return the session id associated with the offer. + */ + public String getSessionID() { + return this.sessionID; + } + + /** + * The meta-data associated with the offer. + * + * @return the offer meta-data. + */ + public Map getMetaData() { + return this.metaData; + } + + /** + * Returns the content of the offer. The content explains the reason for the offer + * (e.g. user request, transfer) + * + * @return the content of the offer. + */ + public OfferContent getContent() { + return content; + } + + /** + * Returns true if the agent accepted this offer. + * + * @return true if the agent accepted this offer. + */ + public boolean isAccepted() { + return accepted; + } + + /** + * Return true if the agent rejected this offer. + * + * @return true if the agent rejected this offer. + */ + public boolean isRejected() { + return rejected; + } + + /** + * Packet for rejecting offers. + */ + private class RejectPacket extends IQ { + + RejectPacket(String workgroup) { + this.setTo(workgroup); + this.setType(IQ.Type.SET); + } + + public String getChildElementXML() { + return ""; + } + } + + /** + * Packet for accepting an offer. + */ + private class AcceptPacket extends IQ { + + AcceptPacket(String workgroup) { + this.setTo(workgroup); + this.setType(IQ.Type.SET); + } + + public String getChildElementXML() { + return ""; + } + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java new file mode 100644 index 0000000..f55d588 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmation.java @@ -0,0 +1,114 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + + +public class OfferConfirmation extends IQ { + private String userJID; + private long sessionID; + + public String getUserJID() { + return userJID; + } + + public void setUserJID(String userJID) { + this.userJID = userJID; + } + + public long getSessionID() { + return sessionID; + } + + public void setSessionID(long sessionID) { + this.sessionID = sessionID; + } + + + public void notifyService(Connection con, String workgroup, String createdRoomName) { + NotifyServicePacket packet = new NotifyServicePacket(workgroup, createdRoomName); + con.sendPacket(packet); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(""); + return buf.toString(); + } + + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + final OfferConfirmation confirmation = new OfferConfirmation(); + + boolean done = false; + while (!done) { + parser.next(); + String elementName = parser.getName(); + if (parser.getEventType() == XmlPullParser.START_TAG && "user-jid".equals(elementName)) { + try { + confirmation.setUserJID(parser.nextText()); + } + catch (NumberFormatException nfe) { + } + } + else if (parser.getEventType() == XmlPullParser.START_TAG && "session-id".equals(elementName)) { + try { + confirmation.setSessionID(Long.valueOf(parser.nextText())); + } + catch (NumberFormatException nfe) { + } + } + else if (parser.getEventType() == XmlPullParser.END_TAG && "offer-confirmation".equals(elementName)) { + done = true; + } + } + + + return confirmation; + } + } + + + /** + * Packet for notifying server of RoomName + */ + private class NotifyServicePacket extends IQ { + String roomName; + + NotifyServicePacket(String workgroup, String roomName) { + this.setTo(workgroup); + this.setType(IQ.Type.RESULT); + + this.roomName = roomName; + } + + public String getChildElementXML() { + return ""; + } + } + + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java new file mode 100644 index 0000000..fb10550 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferConfirmationListener.java @@ -0,0 +1,32 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +public interface OfferConfirmationListener { + + + /** + * The implementing class instance will be notified via this when the AgentSession has confirmed + * the acceptance of the Offer. The instance will then have the ability to create the room and + * send the service the room name created for tracking. + * + * @param confirmedOffer the ConfirmedOffer + */ + void offerConfirmed(OfferConfirmation confirmedOffer); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java new file mode 100644 index 0000000..a11ddc3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferContent.java @@ -0,0 +1,55 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +/** + * Type of content being included in the offer. The content actually explains the reason + * the agent is getting an offer. + * + * @author Gaston Dombiak + */ +public abstract class OfferContent { + + /** + * Returns true if the content of the offer is related to a user request. This is the + * most common type of offers an agent should receive. + * + * @return true if the content of the offer is related to a user request. + */ + abstract boolean isUserRequest(); + + /** + * Returns true if the content of the offer is related to a room invitation made by another + * agent. This type of offer include the room to join, metadata sent by the user while joining + * the queue and the reason why the agent is being invited. + * + * @return true if the content of the offer is related to a room invitation made by another agent. + */ + abstract boolean isInvitation(); + + /** + * Returns true if the content of the offer is related to a service transfer made by another + * agent. This type of offers include the room to join, metadata sent by the user while joining the + * queue and the reason why the agent is receiving the transfer offer. + * + * @return true if the content of the offer is related to a service transfer made by another agent. + */ + abstract boolean isTransfer(); +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java new file mode 100644 index 0000000..5efde99 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/OfferListener.java @@ -0,0 +1,49 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +/** + * An interface which all classes interested in hearing about chat offers associated to a particular + * AgentSession instance should implement.
      + * + * @author Matt Tucker + * @author loki der quaeler + * @see org.jivesoftware.smackx.workgroup.agent.AgentSession + */ +public interface OfferListener { + + /** + * The implementing class instance will be notified via this when the AgentSession has received + * an offer for a chat. The instance will then have the ability to accept, reject, or ignore + * the request (resulting in a revocation-by-timeout). + * + * @param request the Offer instance embodying the details of the offer + */ + public void offerReceived (Offer request); + + /** + * The implementing class instance will be notified via this when the AgentSessino has received + * a revocation of a previously extended offer. + * + * @param revokedOffer the RevokedOffer instance embodying the details of the revoked offer + */ + public void offerRevoked (RevokedOffer revokedOffer); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java new file mode 100644 index 0000000..620a58c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/QueueUsersListener.java @@ -0,0 +1,58 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import java.util.Date; +import java.util.Set; + +public interface QueueUsersListener { + + /** + * The status of the queue was updated. + * + * @param queue the workgroup queue. + * @param status the status of queue. + */ + public void statusUpdated(WorkgroupQueue queue, WorkgroupQueue.Status status); + + /** + * The average wait time of the queue was updated. + * + * @param queue the workgroup queue. + * @param averageWaitTime the average wait time of the queue. + */ + public void averageWaitTimeUpdated(WorkgroupQueue queue, int averageWaitTime); + + /** + * The date of oldest entry waiting in the queue was updated. + * + * @param queue the workgroup queue. + * @param oldestEntry the date of the oldest entry waiting in the queue. + */ + public void oldestEntryUpdated(WorkgroupQueue queue, Date oldestEntry); + + /** + * The list of users waiting in the queue was updated. + * + * @param queue the workgroup queue. + * @param users the list of users waiting in the queue. + */ + public void usersUpdated(WorkgroupQueue queue, Set users); +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java new file mode 100644 index 0000000..dab4d91 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/RevokedOffer.java @@ -0,0 +1,98 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import java.util.Date; + +/** + * An immutable simple class to embody the information concerning a revoked offer, this is namely + * the reason, the workgroup, the userJID, and the timestamp which the message was received.
      + * + * @author loki der quaeler + */ +public class RevokedOffer { + + private String userJID; + private String userID; + private String workgroupName; + private String sessionID; + private String reason; + private Date timestamp; + + /** + * + * @param userJID the JID of the user for which this revocation was issued. + * @param userID the user ID of the user for which this revocation was issued. + * @param workgroupName the fully qualified name of the workgroup + * @param sessionID the session id attributed to this chain of packets + * @param reason the server issued message as to why this revocation was issued. + * @param timestamp the timestamp at which the revocation was issued + */ + RevokedOffer(String userJID, String userID, String workgroupName, String sessionID, + String reason, Date timestamp) { + super(); + + this.userJID = userJID; + this.userID = userID; + this.workgroupName = workgroupName; + this.sessionID = sessionID; + this.reason = reason; + this.timestamp = timestamp; + } + + public String getUserJID() { + return userJID; + } + + /** + * @return the jid of the user for which this revocation was issued + */ + public String getUserID() { + return this.userID; + } + + /** + * @return the fully qualified name of the workgroup + */ + public String getWorkgroupName() { + return this.workgroupName; + } + + /** + * @return the session id which will associate all packets for the pending chat + */ + public String getSessionID() { + return this.sessionID; + } + + /** + * @return the server issued message as to why this revocation was issued + */ + public String getReason() { + return this.reason; + } + + /** + * @return the timestamp at which the revocation was issued + */ + public Date getTimestamp() { + return this.timestamp; + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java new file mode 100644 index 0000000..8a3801f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptManager.java @@ -0,0 +1,100 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smackx.workgroup.packet.Transcript; +import org.jivesoftware.smackx.workgroup.packet.Transcripts; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; + +/** + * A TranscriptManager helps to retrieve the full conversation transcript of a given session + * {@link #getTranscript(String, String)} or to retrieve a list with the summary of all the + * conversations that a user had {@link #getTranscripts(String, String)}. + * + * @author Gaston Dombiak + */ +public class TranscriptManager { + private Connection connection; + + public TranscriptManager(Connection connection) { + this.connection = connection; + } + + /** + * Returns the full conversation transcript of a given session. + * + * @param sessionID the id of the session to get the full transcript. + * @param workgroupJID the JID of the workgroup that will process the request. + * @return the full conversation transcript of a given session. + * @throws XMPPException if an error occurs while getting the information. + */ + public Transcript getTranscript(String workgroupJID, String sessionID) throws XMPPException { + Transcript request = new Transcript(sessionID); + request.setTo(workgroupJID); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + // Send the request + connection.sendPacket(request); + + Transcript response = (Transcript) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Returns the transcripts of a given user. The answer will contain the complete history of + * conversations that a user had. + * + * @param userID the id of the user to get his conversations. + * @param workgroupJID the JID of the workgroup that will process the request. + * @return the transcripts of a given user. + * @throws XMPPException if an error occurs while getting the information. + */ + public Transcripts getTranscripts(String workgroupJID, String userID) throws XMPPException { + Transcripts request = new Transcripts(userID); + request.setTo(workgroupJID); + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + // Send the request + connection.sendPacket(request); + + Transcripts response = (Transcripts) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java new file mode 100644 index 0000000..8260cd6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TranscriptSearchManager.java @@ -0,0 +1,111 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import org.jivesoftware.smackx.workgroup.packet.TranscriptSearch; +import org.jivesoftware.smack.PacketCollector; +import org.jivesoftware.smack.SmackConfiguration; +import org.jivesoftware.smack.Connection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smack.filter.PacketIDFilter; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.ReportedData; + +/** + * A TranscriptSearchManager helps to retrieve the form to use for searching transcripts + * {@link #getSearchForm(String)} or to submit a search form and return the results of + * the search {@link #submitSearch(String, Form)}. + * + * @author Gaston Dombiak + */ +public class TranscriptSearchManager { + private Connection connection; + + public TranscriptSearchManager(Connection connection) { + this.connection = connection; + } + + /** + * Returns the Form to use for searching transcripts. It is unlikely that the server + * will change the form (without a restart) so it is safe to keep the returned form + * for future submissions. + * + * @param serviceJID the address of the workgroup service. + * @return the Form to use for searching transcripts. + * @throws XMPPException if an error occurs while sending the request to the server. + */ + public Form getSearchForm(String serviceJID) throws XMPPException { + TranscriptSearch search = new TranscriptSearch(); + search.setType(IQ.Type.GET); + search.setTo(serviceJID); + + PacketCollector collector = connection.createPacketCollector( + new PacketIDFilter(search.getPacketID())); + connection.sendPacket(search); + + TranscriptSearch response = (TranscriptSearch) collector.nextResult( + SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return Form.getFormFrom(response); + } + + /** + * Submits the completed form and returns the result of the transcript search. The result + * will include all the data returned from the server so be careful with the amount of + * data that the search may return. + * + * @param serviceJID the address of the workgroup service. + * @param completedForm the filled out search form. + * @return the result of the transcript search. + * @throws XMPPException if an error occurs while submiting the search to the server. + */ + public ReportedData submitSearch(String serviceJID, Form completedForm) throws XMPPException { + TranscriptSearch search = new TranscriptSearch(); + search.setType(IQ.Type.GET); + search.setTo(serviceJID); + search.addExtension(completedForm.getDataFormToSend()); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(search.getPacketID())); + connection.sendPacket(search); + + TranscriptSearch response = (TranscriptSearch) collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return ReportedData.getReportedDataFrom(response); + } +} + + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java new file mode 100644 index 0000000..a3abbaa --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/TransferRequest.java @@ -0,0 +1,62 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +/** + * Request sent by an agent to transfer a support session to another agent or user. + * + * @author Gaston Dombiak + */ +public class TransferRequest extends OfferContent { + + private String inviter; + private String room; + private String reason; + + public TransferRequest(String inviter, String room, String reason) { + this.inviter = inviter; + this.room = room; + this.reason = reason; + } + + public String getInviter() { + return inviter; + } + + public String getRoom() { + return room; + } + + public String getReason() { + return reason; + } + + boolean isUserRequest() { + return false; + } + + boolean isInvitation() { + return false; + } + + boolean isTransfer() { + return true; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java new file mode 100644 index 0000000..ccaaaf3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/UserRequest.java @@ -0,0 +1,47 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +/** + * Requests made by users to get support by some agent. + * + * @author Gaston Dombiak + */ +public class UserRequest extends OfferContent { + // TODO Do we want to use a singleton? Should we store the userID here? + private static UserRequest instance = new UserRequest(); + + public static OfferContent getInstance() { + return instance; + } + + boolean isUserRequest() { + return true; + } + + boolean isInvitation() { + return false; + } + + boolean isTransfer() { + return false; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java new file mode 100644 index 0000000..696d892 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/agent/WorkgroupQueue.java @@ -0,0 +1,222 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.agent; + +import java.util.*; + +/** + * A queue in a workgroup, which is a pool of agents that are routed a specific type of + * chat request. + */ +public class WorkgroupQueue { + + private String name; + private Status status = Status.CLOSED; + + private int averageWaitTime = -1; + private Date oldestEntry = null; + private Set users = Collections.EMPTY_SET; + + private int maxChats = 0; + private int currentChats = 0; + + /** + * Creates a new workgroup queue instance. + * + * @param name the name of the queue. + */ + WorkgroupQueue(String name) { + this.name = name; + } + + /** + * Returns the name of the queue. + * + * @return the name of the queue. + */ + public String getName() { + return name; + } + + /** + * Returns the status of the queue. + * + * @return the status of the queue. + */ + public Status getStatus() { + return status; + } + + void setStatus(Status status) { + this.status = status; + } + + /** + * Returns the number of users waiting in the queue waiting to be routed to + * an agent. + * + * @return the number of users waiting in the queue. + */ + public int getUserCount() { + if (users == null) { + return 0; + } + return users.size(); + } + + /** + * Returns an Iterator for the users in the queue waiting to be routed to + * an agent (QueueUser instances). + * + * @return an Iterator for the users waiting in the queue. + */ + public Iterator getUsers() { + if (users == null) { + return Collections.EMPTY_SET.iterator(); + } + return Collections.unmodifiableSet(users).iterator(); + } + + void setUsers(Set users) { + this.users = users; + } + + /** + * Returns the average amount of time users wait in the queue before being + * routed to an agent. If average wait time info isn't available, -1 will + * be returned. + * + * @return the average wait time + */ + public int getAverageWaitTime() { + return averageWaitTime; + } + + void setAverageWaitTime(int averageTime) { + this.averageWaitTime = averageTime; + } + + /** + * Returns the date of the oldest request waiting in the queue. If there + * are no requests waiting to be routed, this method will return null. + * + * @return the date of the oldest request in the queue. + */ + public Date getOldestEntry() { + return oldestEntry; + } + + void setOldestEntry(Date oldestEntry) { + this.oldestEntry = oldestEntry; + } + + /** + * Returns the maximum number of simultaneous chats the queue can handle. + * + * @return the max number of chats the queue can handle. + */ + public int getMaxChats() { + return maxChats; + } + + void setMaxChats(int maxChats) { + this.maxChats = maxChats; + } + + /** + * Returns the current number of active chat sessions in the queue. + * + * @return the current number of active chat sessions in the queue. + */ + public int getCurrentChats() { + return currentChats; + } + + void setCurrentChats(int currentChats) { + this.currentChats = currentChats; + } + + /** + * A class to represent the status of the workgroup. The possible values are: + * + *

        + *
      • WorkgroupQueue.Status.OPEN -- the queue is active and accepting new chat requests. + *
      • WorkgroupQueue.Status.ACTIVE -- the queue is active but NOT accepting new chat + * requests. + *
      • WorkgroupQueue.Status.CLOSED -- the queue is NOT active and NOT accepting new + * chat requests. + *
      + */ + public static class Status { + + /** + * The queue is active and accepting new chat requests. + */ + public static final Status OPEN = new Status("open"); + + /** + * The queue is active but NOT accepting new chat requests. This state might + * occur when the workgroup has closed because regular support hours have closed, + * but there are still several requests left in the queue. + */ + public static final Status ACTIVE = new Status("active"); + + /** + * The queue is NOT active and NOT accepting new chat requests. + */ + public static final Status CLOSED = new Status("closed"); + + /** + * Converts a String into the corresponding status. Valid String values + * that can be converted to a status are: "open", "active", and "closed". + * + * @param type the String value to covert. + * @return the corresponding Type. + */ + public static Status fromString(String type) { + if (type == null) { + return null; + } + type = type.toLowerCase(); + if (OPEN.toString().equals(type)) { + return OPEN; + } + else if (ACTIVE.toString().equals(type)) { + return ACTIVE; + } + else if (CLOSED.toString().equals(type)) { + return CLOSED; + } + else { + return null; + } + } + + private String value; + + private Status(String value) { + this.value = value; + } + + public String toString() { + return value; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java new file mode 100644 index 0000000..f2dc08e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/forms/WorkgroupForm.java @@ -0,0 +1,82 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.forms; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParser; + +public class WorkgroupForm extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "workgroup-form"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for WebForm packets. + * + * @author Derek DeMoro + */ + public static class InternalProvider implements IQProvider { + + public InternalProvider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + WorkgroupForm answer = new WorkgroupForm(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + // Parse the packet extension + answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), + parser.getNamespace(), parser)); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return answer; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java new file mode 100644 index 0000000..027c269 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatHistory.java @@ -0,0 +1,155 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.history; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +/** + * IQ provider used to retrieve individual agent information. Each chat session can be mapped + * to one or more jids and therefore retrievable. + */ +public class AgentChatHistory extends IQ { + private String agentJID; + private int maxSessions; + private long startDate; + + private List agentChatSessions = new ArrayList(); + + public AgentChatHistory(String agentJID, int maxSessions, Date startDate) { + this.agentJID = agentJID; + this.maxSessions = maxSessions; + this.startDate = startDate.getTime(); + } + + public AgentChatHistory(String agentJID, int maxSessions) { + this.agentJID = agentJID; + this.maxSessions = maxSessions; + this.startDate = 0; + } + + public AgentChatHistory() { + } + + public void addChatSession(AgentChatSession chatSession) { + agentChatSessions.add(chatSession); + } + + public Collection getAgentChatSessions() { + return agentChatSessions; + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "chat-sessions"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append(" agentJID=\"" + agentJID + "\""); + buf.append(" maxSessions=\"" + maxSessions + "\""); + buf.append(" startDate=\"" + startDate + "\""); + + buf.append("> "); + return buf.toString(); + } + + /** + * Packet extension provider for AgentHistory packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + AgentChatHistory agentChatHistory = new AgentChatHistory(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("chat-session".equals(parser.getName()))) { + agentChatHistory.addChatSession(parseChatSetting(parser)); + + } + else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { + done = true; + } + } + return agentChatHistory; + } + + private AgentChatSession parseChatSetting(XmlPullParser parser) throws Exception { + + boolean done = false; + Date date = null; + long duration = 0; + String visitorsName = null; + String visitorsEmail = null; + String sessionID = null; + String question = null; + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("date".equals(parser.getName()))) { + String dateStr = parser.nextText(); + long l = Long.valueOf(dateStr).longValue(); + date = new Date(l); + } + else if ((eventType == XmlPullParser.START_TAG) && ("duration".equals(parser.getName()))) { + duration = Long.valueOf(parser.nextText()).longValue(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("visitorsName".equals(parser.getName()))) { + visitorsName = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("visitorsEmail".equals(parser.getName()))) { + visitorsEmail = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("sessionID".equals(parser.getName()))) { + sessionID = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("question".equals(parser.getName()))) { + question = parser.nextText(); + } + else if (eventType == XmlPullParser.END_TAG && "chat-session".equals(parser.getName())) { + done = true; + } + } + return new AgentChatSession(date, duration, visitorsName, visitorsEmail, sessionID, question); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java new file mode 100644 index 0000000..5113cda --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/AgentChatSession.java @@ -0,0 +1,93 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.history; + +import java.util.Date; + +/** + * Represents one chat session for an agent. + */ +public class AgentChatSession { + public Date startDate; + public long duration; + public String visitorsName; + public String visitorsEmail; + public String sessionID; + public String question; + + public AgentChatSession(Date date, long duration, String visitorsName, String visitorsEmail, String sessionID, String question) { + this.startDate = date; + this.duration = duration; + this.visitorsName = visitorsName; + this.visitorsEmail = visitorsEmail; + this.sessionID = sessionID; + this.question = question; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public String getVisitorsName() { + return visitorsName; + } + + public void setVisitorsName(String visitorsName) { + this.visitorsName = visitorsName; + } + + public String getVisitorsEmail() { + return visitorsEmail; + } + + public void setVisitorsEmail(String visitorsEmail) { + this.visitorsEmail = visitorsEmail; + } + + public String getSessionID() { + return sessionID; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + public void setQuestion(String question){ + this.question = question; + } + + public String getQuestion(){ + return question; + } + + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java new file mode 100644 index 0000000..cb36fc7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/history/ChatMetadata.java @@ -0,0 +1,115 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.history; + +import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Map; + +public class ChatMetadata extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "chat-metadata"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + + private String sessionID; + + public String getSessionID() { + return sessionID; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + + private Map map = new HashMap(); + + public void setMetadata(Map metadata){ + this.map = metadata; + } + + public Map getMetadata(){ + return map; + } + + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + buf.append("").append(getSessionID()).append(""); + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for Metadata packets. + * + * @author Derek DeMoro + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + final ChatMetadata chatM = new ChatMetadata(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("sessionID")) { + chatM.setSessionID(parser.nextText()); + } + else if (parser.getName().equals("metadata")) { + Map map = MetaDataUtils.parseMetaData(parser); + chatM.setMetadata(map); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return chatM; + } + } +} + + + + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java new file mode 100644 index 0000000..acf6196 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macro.java @@ -0,0 +1,68 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.macros; + +/** + * Macro datamodel. + */ +public class Macro { + public static final int TEXT = 0; + public static final int URL = 1; + public static final int IMAGE = 2; + + + private String title; + private String description; + private String response; + private int type; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getResponse() { + return response; + } + + public void setResponse(String response) { + this.response = response; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java new file mode 100644 index 0000000..0742b3d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/MacroGroup.java @@ -0,0 +1,143 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.macros; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * MacroGroup datamodel. + */ +public class MacroGroup { + private List macros; + private List macroGroups; + + + // Define MacroGroup + private String title; + + public MacroGroup() { + macros = new ArrayList(); + macroGroups = new ArrayList(); + } + + public void addMacro(Macro macro) { + macros.add(macro); + } + + public void removeMacro(Macro macro) { + macros.remove(macro); + } + + public Macro getMacroByTitle(String title) { + Collection col = Collections.unmodifiableList(macros); + Iterator iter = col.iterator(); + while (iter.hasNext()) { + Macro macro = (Macro)iter.next(); + if (macro.getTitle().equalsIgnoreCase(title)) { + return macro; + } + } + return null; + } + + public void addMacroGroup(MacroGroup group) { + macroGroups.add(group); + } + + public void removeMacroGroup(MacroGroup group) { + macroGroups.remove(group); + } + + public Macro getMacro(int location) { + return (Macro)macros.get(location); + } + + public MacroGroup getMacroGroupByTitle(String title) { + Collection col = Collections.unmodifiableList(macroGroups); + Iterator iter = col.iterator(); + while (iter.hasNext()) { + MacroGroup group = (MacroGroup)iter.next(); + if (group.getTitle().equalsIgnoreCase(title)) { + return group; + } + } + return null; + } + + public MacroGroup getMacroGroup(int location) { + return (MacroGroup)macroGroups.get(location); + } + + + public List getMacros() { + return macros; + } + + public void setMacros(List macros) { + this.macros = macros; + } + + public List getMacroGroups() { + return macroGroups; + } + + public void setMacroGroups(List macroGroups) { + this.macroGroups = macroGroups; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append("" + getTitle() + ""); + buf.append(""); + for (Macro macro : getMacros()) + { + buf.append(""); + buf.append("" + macro.getTitle() + ""); + buf.append("" + macro.getType() + ""); + buf.append("" + macro.getDescription() + ""); + buf.append("" + macro.getResponse() + ""); + buf.append(""); + } + buf.append(""); + + if (getMacroGroups().size() > 0) { + buf.append(""); + for (MacroGroup groups : getMacroGroups()) { + buf.append(groups.toXML()); + } + buf.append(""); + } + buf.append(""); + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java new file mode 100644 index 0000000..869ec57 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/macros/Macros.java @@ -0,0 +1,198 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.macros; + +import java.io.StringReader; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.xmlpull.v1.XmlPullParserFactory; +import org.xmlpull.v1.XmlPullParser; + +/** + * Macros iq is responsible for handling global and personal macros in the a Live Assistant + * Workgroup. + */ +public class Macros extends IQ { + + private MacroGroup rootGroup; + private boolean personal; + private MacroGroup personalMacroGroup; + + public MacroGroup getRootGroup() { + return rootGroup; + } + + public void setRootGroup(MacroGroup rootGroup) { + this.rootGroup = rootGroup; + } + + public boolean isPersonal() { + return personal; + } + + public void setPersonal(boolean personal) { + this.personal = personal; + } + + public MacroGroup getPersonalMacroGroup() { + return personalMacroGroup; + } + + public void setPersonalMacroGroup(MacroGroup personalMacroGroup) { + this.personalMacroGroup = personalMacroGroup; + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "macros"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + if (isPersonal()) { + buf.append("true"); + } + if (getPersonalMacroGroup() != null) { + buf.append(""); + buf.append(StringUtils.escapeForXML(getPersonalMacroGroup().toXML())); + buf.append(""); + } + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for Macro packets. + * + * @author Derek DeMoro + */ + public static class InternalProvider implements IQProvider { + + public InternalProvider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + Macros macroGroup = new Macros(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("model")) { + String macros = parser.nextText(); + MacroGroup group = parseMacroGroups(macros); + macroGroup.setRootGroup(group); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return macroGroup; + } + + public Macro parseMacro(XmlPullParser parser) throws Exception { + Macro macro = new Macro(); + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("title")) { + parser.next(); + macro.setTitle(parser.getText()); + } + else if (parser.getName().equals("description")) { + macro.setDescription(parser.nextText()); + } + else if (parser.getName().equals("response")) { + macro.setResponse(parser.nextText()); + } + else if (parser.getName().equals("type")) { + macro.setType(Integer.valueOf(parser.nextText()).intValue()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("macro")) { + done = true; + } + } + } + return macro; + } + + public MacroGroup parseMacroGroup(XmlPullParser parser) throws Exception { + MacroGroup group = new MacroGroup(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("macrogroup")) { + group.addMacroGroup(parseMacroGroup(parser)); + } + if (parser.getName().equals("title")) { + group.setTitle(parser.nextText()); + } + if (parser.getName().equals("macro")) { + group.addMacro(parseMacro(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("macrogroup")) { + done = true; + } + } + } + return group; + } + + public MacroGroup parseMacroGroups(String macros) throws Exception { + + MacroGroup group = null; + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setInput(new StringReader(macros)); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("macrogroup")) { + group = parseMacroGroup(parser); + } + } + } + return group; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java new file mode 100644 index 0000000..eff3c6c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/ext/notes/ChatNotes.java @@ -0,0 +1,155 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.ext.notes; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * IQ packet for retrieving and adding Chat Notes. + */ +public class ChatNotes extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "chat-notes"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + + private String sessionID; + private String notes; + + public String getSessionID() { + return sessionID; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + public String getNotes() { + return notes; + } + + public void setNotes(String notes) { + this.notes = notes; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + buf.append("").append(getSessionID()).append(""); + + if (getNotes() != null) { + buf.append("").append(getNotes()).append(""); + } + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for ChatNotes packets. + * + * @author Derek DeMoro + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + ChatNotes chatNotes = new ChatNotes(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("sessionID")) { + chatNotes.setSessionID(parser.nextText()); + } + else if (parser.getName().equals("text")) { + String note = parser.nextText(); + note = note.replaceAll("\\\\n", "\n"); + chatNotes.setNotes(note); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return chatNotes; + } + } + + /** + * Replaces all instances of oldString with newString in string. + * + * @param string the String to search to perform replacements on + * @param oldString the String that should be replaced by newString + * @param newString the String that will replace all instances of oldString + * @return a String will all instances of oldString replaced by newString + */ + public static final String replace(String string, String oldString, String newString) { + if (string == null) { + return null; + } + // If the newString is null or zero length, just return the string since there's nothing + // to replace. + if (newString == null) { + return string; + } + int i = 0; + // Make sure that oldString appears at least once before doing any processing. + if ((i = string.indexOf(oldString, i)) >= 0) { + // Use char []'s, as they are more efficient to deal with. + char[] string2 = string.toCharArray(); + char[] newString2 = newString.toCharArray(); + int oLength = oldString.length(); + StringBuilder buf = new StringBuilder(string2.length); + buf.append(string2, 0, i).append(newString2); + i += oLength; + int j = i; + // Replace all remaining instances of oldString with newString. + while ((i = string.indexOf(oldString, i)) > 0) { + buf.append(string2, j, i - j).append(newString2); + i += oLength; + j = i; + } + buf.append(string2, j, string2.length - j); + return buf.toString(); + } + return string; + } +} + + + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java new file mode 100644 index 0000000..8b9d230 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentInfo.java @@ -0,0 +1,132 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * IQ packet for retrieving and changing the Agent personal information. + */ +public class AgentInfo extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "agent-info"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + private String jid; + private String name; + + /** + * Returns the Agent's jid. + * + * @return the Agent's jid. + */ + public String getJid() { + return jid; + } + + /** + * Sets the Agent's jid. + * + * @param jid the jid of the agent. + */ + public void setJid(String jid) { + this.jid = jid; + } + + /** + * Returns the Agent's name. The name of the agent may be different than the user's name. + * This property may be shown in the webchat client. + * + * @return the Agent's name. + */ + public String getName() { + return name; + } + + /** + * Sets the Agent's name. The name of the agent may be different than the user's name. + * This property may be shown in the webchat client. + * + * @param name the new name of the agent. + */ + public void setName(String name) { + this.name = name; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + if (jid != null) { + buf.append("").append(getJid()).append(""); + } + if (name != null) { + buf.append("").append(getName()).append(""); + } + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for AgentInfo packets. + * + * @author Gaston Dombiak + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + AgentInfo answer = new AgentInfo(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("jid")) { + answer.setJid(parser.nextText()); + } + else if (parser.getName().equals("name")) { + answer.setName(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return answer; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java new file mode 100644 index 0000000..9f49033 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatus.java @@ -0,0 +1,266 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Agent status packet. + * + * @author Matt Tucker + */ +public class AgentStatus implements PacketExtension { + + private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + + static { + UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "agent-status"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private String workgroupJID; + private List currentChats = new ArrayList(); + private int maxChats = -1; + + AgentStatus() { + } + + public String getWorkgroupJID() { + return workgroupJID; + } + + /** + * Returns a collection of ChatInfo where each ChatInfo represents a Chat where this agent + * is participating. + * + * @return a collection of ChatInfo where each ChatInfo represents a Chat where this agent + * is participating. + */ + public List getCurrentChats() { + return Collections.unmodifiableList(currentChats); + } + + public int getMaxChats() { + return maxChats; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\""); + if (workgroupJID != null) { + buf.append(" jid=\"").append(workgroupJID).append("\""); + } + buf.append(">"); + if (maxChats != -1) { + buf.append("").append(maxChats).append(""); + } + if (!currentChats.isEmpty()) { + buf.append(""); + for (Iterator it = currentChats.iterator(); it.hasNext();) { + buf.append(((ChatInfo)it.next()).toXML()); + } + buf.append(""); + } + buf.append(" "); + + return buf.toString(); + } + + /** + * Represents information about a Chat where this Agent is participating. + * + * @author Gaston Dombiak + */ + public static class ChatInfo { + + private String sessionID; + private String userID; + private Date date; + private String email; + private String username; + private String question; + + public ChatInfo(String sessionID, String userID, Date date, String email, String username, String question) { + this.sessionID = sessionID; + this.userID = userID; + this.date = date; + this.email = email; + this.username = username; + this.question = question; + } + + /** + * Returns the sessionID associated to this chat. Each chat will have a unique sessionID + * that could be used for retrieving the whole transcript of the conversation. + * + * @return the sessionID associated to this chat. + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the user unique identification of the user that made the initial request and + * for which this chat was generated. If the user joined using an anonymous connection + * then the userID will be the value of the ID attribute of the USER element. Otherwise, + * the userID will be the bare JID of the user that made the request. + * + * @return the user unique identification of the user that made the initial request. + */ + public String getUserID() { + return userID; + } + + /** + * Returns the date when this agent joined the chat. + * + * @return the date when this agent joined the chat. + */ + public Date getDate() { + return date; + } + + /** + * Returns the email address associated with the user. + * + * @return the email address associated with the user. + */ + public String getEmail() { + return email; + } + + /** + * Returns the username(nickname) associated with the user. + * + * @return the username associated with the user. + */ + public String getUsername() { + return username; + } + + /** + * Returns the question the user asked. + * + * @return the question the user asked, if any. + */ + public String getQuestion() { + return question; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + return buf.toString(); + } + } + + /** + * Packet extension provider for AgentStatus packets. + */ + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + AgentStatus agentStatus = new AgentStatus(); + + agentStatus.workgroupJID = parser.getAttributeValue("", "jid"); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + if ("chat".equals(parser.getName())) { + agentStatus.currentChats.add(parseChatInfo(parser)); + } + else if ("max-chats".equals(parser.getName())) { + agentStatus.maxChats = Integer.parseInt(parser.nextText()); + } + } + else if (eventType == XmlPullParser.END_TAG && + ELEMENT_NAME.equals(parser.getName())) { + done = true; + } + } + return agentStatus; + } + + private ChatInfo parseChatInfo(XmlPullParser parser) { + + String sessionID = parser.getAttributeValue("", "sessionID"); + String userID = parser.getAttributeValue("", "userID"); + Date date = null; + try { + date = UTC_FORMAT.parse(parser.getAttributeValue("", "startTime")); + } + catch (ParseException e) { + } + + String email = parser.getAttributeValue("", "email"); + String username = parser.getAttributeValue("", "username"); + String question = parser.getAttributeValue("", "question"); + + return new ChatInfo(sessionID, userID, date, email, username, question); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java new file mode 100644 index 0000000..48549d2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentStatusRequest.java @@ -0,0 +1,163 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Agent status request packet. This packet is used by agents to request the list of + * agents in a workgroup. The response packet contains a list of packets. Presence + * packets from individual agents follow. + * + * @author Matt Tucker + */ +public class AgentStatusRequest extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "agent-status-request"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private Set agents; + + public AgentStatusRequest() { + agents = new HashSet(); + } + + public int getAgentCount() { + return agents.size(); + } + + public Set getAgents() { + return Collections.unmodifiableSet(agents); + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + synchronized (agents) { + for (Iterator i=agents.iterator(); i.hasNext(); ) { + Item item = (Item) i.next(); + buf.append(""); + if (item.getName() != null) { + buf.append(""); + buf.append(item.getName()); + buf.append(""); + } + buf.append(""); + } + } + buf.append(" "); + return buf.toString(); + } + + public static class Item { + + private String jid; + private String type; + private String name; + + public Item(String jid, String type, String name) { + this.jid = jid; + this.type = type; + this.name = name; + } + + public String getJID() { + return jid; + } + + public String getType() { + return type; + } + + public String getName() { + return name; + } + } + + /** + * Packet extension provider for AgentStatusRequest packets. + */ + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + AgentStatusRequest statusRequest = new AgentStatusRequest(); + + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("agent".equals(parser.getName()))) { + statusRequest.agents.add(parseAgent(parser)); + } + else if (eventType == XmlPullParser.END_TAG && + "agent-status-request".equals(parser.getName())) + { + done = true; + } + } + return statusRequest; + } + + private Item parseAgent(XmlPullParser parser) throws Exception { + + boolean done = false; + String jid = parser.getAttributeValue("", "jid"); + String type = parser.getAttributeValue("", "type"); + String name = null; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) { + name = parser.nextText(); + } + else if (eventType == XmlPullParser.END_TAG && + "agent".equals(parser.getName())) + { + done = true; + } + } + return new Item(jid, type, name); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java new file mode 100644 index 0000000..292a640 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/AgentWorkgroups.java @@ -0,0 +1,129 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents a request for getting the jid of the workgroups where an agent can work or could + * represent the result of such request which will contain the list of workgroups JIDs where the + * agent can work. + * + * @author Gaston Dombiak + */ +public class AgentWorkgroups extends IQ { + + private String agentJID; + private List workgroups; + + /** + * Creates an AgentWorkgroups request for the given agent. This IQ will be sent and an answer + * will be received with the jid of the workgroups where the agent can work. + * + * @param agentJID the id of the agent to get his workgroups. + */ + public AgentWorkgroups(String agentJID) { + this.agentJID = agentJID; + this.workgroups = new ArrayList(); + } + + /** + * Creates an AgentWorkgroups which will contain the JIDs of the workgroups where an agent can + * work. + * + * @param agentJID the id of the agent that can work in the list of workgroups. + * @param workgroups the list of workgroup JIDs where the agent can work. + */ + public AgentWorkgroups(String agentJID, List workgroups) { + this.agentJID = agentJID; + this.workgroups = workgroups; + } + + public String getAgentJID() { + return agentJID; + } + + /** + * Returns a list of workgroup JIDs where the agent can work. + * + * @return a list of workgroup JIDs where the agent can work. + */ + public List getWorkgroups() { + return Collections.unmodifiableList(workgroups); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (Iterator it=workgroups.iterator(); it.hasNext();) { + String workgroupJID = it.next(); + buf.append(""); + } + + buf.append(""); + + return buf.toString(); + } + + /** + * An IQProvider for AgentWorkgroups packets. + * + * @author Gaston Dombiak + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + String agentJID = parser.getAttributeValue("", "jid"); + List workgroups = new ArrayList(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("workgroup")) { + workgroups.add(parser.getAttributeValue("", "jid")); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("workgroups")) { + done = true; + } + } + } + + return new AgentWorkgroups(agentJID, workgroups); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java new file mode 100644 index 0000000..620291c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/DepartQueuePacket.java @@ -0,0 +1,75 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; + +/** + * A IQ packet used to depart a workgroup queue. There are two cases for issuing a depart + * queue request:
        + *
      • The user wants to leave the queue. In this case, an instance of this class + * should be created without passing in a user address. + *
      • An administrator or the server removes wants to remove a user from the queue. + * In that case, the address of the user to remove from the queue should be + * used to create an instance of this class.
      + * + * @author loki der quaeler + */ +public class DepartQueuePacket extends IQ { + + private String user; + + /** + * Creates a depart queue request packet to the specified workgroup. + * + * @param workgroup the workgroup to depart. + */ + public DepartQueuePacket(String workgroup) { + this(workgroup, null); + } + + /** + * Creates a depart queue request to the specified workgroup and for the + * specified user. + * + * @param workgroup the workgroup to depart. + * @param user the user to make depart from the queue. + */ + public DepartQueuePacket(String workgroup, String user) { + this.user = user; + + setTo(workgroup); + setType(IQ.Type.SET); + setFrom(user); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder("").append(this.user).append(""); + } + else { + buf.append("/>"); + } + + return buf.toString(); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java new file mode 100644 index 0000000..eb868b0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MetaDataProvider.java @@ -0,0 +1,48 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import java.util.Map; + +import org.jivesoftware.smackx.workgroup.MetaData; +import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; + +import org.xmlpull.v1.XmlPullParser; + +/** + * This provider parses meta data if it's not contained already in a larger extension provider. + * + * @author loki der quaeler + */ +public class MetaDataProvider implements PacketExtensionProvider { + + /** + * PacketExtensionProvider implementation + */ + public PacketExtension parseExtension (XmlPullParser parser) + throws Exception { + Map metaData = MetaDataUtils.parseMetaData(parser); + + return new MetaData(metaData); + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java new file mode 100644 index 0000000..0ceecae --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/MonitorPacket.java @@ -0,0 +1,113 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +public class MonitorPacket extends IQ { + + private String sessionID; + + private boolean isMonitor; + + public boolean isMonitor() { + return isMonitor; + } + + public void setMonitor(boolean monitor) { + isMonitor = monitor; + } + + public String getSessionID() { + return sessionID; + } + + public void setSessionID(String sessionID) { + this.sessionID = sessionID; + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "monitor"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append(">"); + if (sessionID != null) { + buf.append(""); + } + buf.append(" "); + return buf.toString(); + } + + + /** + * Packet extension provider for Monitor Packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + MonitorPacket packet = new MonitorPacket(); + + boolean done = false; + + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("isMonitor".equals(parser.getName()))) { + String value = parser.nextText(); + if ("false".equalsIgnoreCase(value)) { + packet.setMonitor(false); + } + else { + packet.setMonitor(true); + } + } + else if (eventType == XmlPullParser.END_TAG && "monitor".equals(parser.getName())) { + done = true; + } + } + + return packet; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java new file mode 100644 index 0000000..0f80866 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OccupantsInfo.java @@ -0,0 +1,173 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Packet used for requesting information about occupants of a room or for retrieving information + * such information. + * + * @author Gaston Dombiak + */ +public class OccupantsInfo extends IQ { + + private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + + static { + UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "occupants-info"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + private String roomID; + private final Set occupants; + + public OccupantsInfo(String roomID) { + this.roomID = roomID; + this.occupants = new HashSet(); + } + + public String getRoomID() { + return roomID; + } + + public int getOccupantsCount() { + return occupants.size(); + } + + public Set getOccupants() { + return Collections.unmodifiableSet(occupants); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); + buf.append("\" roomID=\"").append(roomID).append("\">"); + synchronized (occupants) { + for (OccupantInfo occupant : occupants) { + buf.append(""); + // Add the occupant jid + buf.append(""); + buf.append(occupant.getJID()); + buf.append(""); + // Add the occupant nickname + buf.append(""); + buf.append(occupant.getNickname()); + buf.append(""); + // Add the date when the occupant joined the room + buf.append(""); + buf.append(UTC_FORMAT.format(occupant.getJoined())); + buf.append(""); + buf.append(""); + } + } + buf.append(" "); + return buf.toString(); + } + + public static class OccupantInfo { + + private String jid; + private String nickname; + private Date joined; + + public OccupantInfo(String jid, String nickname, Date joined) { + this.jid = jid; + this.nickname = nickname; + this.joined = joined; + } + + public String getJID() { + return jid; + } + + public String getNickname() { + return nickname; + } + + public Date getJoined() { + return joined; + } + } + + /** + * Packet extension provider for AgentStatusRequest packets. + */ + public static class Provider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + OccupantsInfo occupantsInfo = new OccupantsInfo(parser.getAttributeValue("", "roomID")); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && + ("occupant".equals(parser.getName()))) { + occupantsInfo.occupants.add(parseOccupantInfo(parser)); + } else if (eventType == XmlPullParser.END_TAG && + ELEMENT_NAME.equals(parser.getName())) { + done = true; + } + } + return occupantsInfo; + } + + private OccupantInfo parseOccupantInfo(XmlPullParser parser) throws Exception { + + boolean done = false; + String jid = null; + String nickname = null; + Date joined = null; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("jid".equals(parser.getName()))) { + jid = parser.nextText(); + } else if ((eventType == XmlPullParser.START_TAG) && + ("nickname".equals(parser.getName()))) { + nickname = parser.nextText(); + } else if ((eventType == XmlPullParser.START_TAG) && + ("joined".equals(parser.getName()))) { + joined = UTC_FORMAT.parse(parser.nextText()); + } else if (eventType == XmlPullParser.END_TAG && + "occupant".equals(parser.getName())) { + done = true; + } + } + return new OccupantInfo(jid, nickname, joined); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java new file mode 100644 index 0000000..112a2ce --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRequestProvider.java @@ -0,0 +1,210 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smackx.workgroup.MetaData; +import org.jivesoftware.smackx.workgroup.agent.InvitationRequest; +import org.jivesoftware.smackx.workgroup.agent.OfferContent; +import org.jivesoftware.smackx.workgroup.agent.TransferRequest; +import org.jivesoftware.smackx.workgroup.agent.UserRequest; +import org.jivesoftware.smackx.workgroup.util.MetaDataUtils; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Map; + +/** + * An IQProvider for agent offer requests. + * + * @author loki der quaeler + */ +public class OfferRequestProvider implements IQProvider { + + public OfferRequestProvider() { + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + int eventType = parser.getEventType(); + String sessionID = null; + int timeout = -1; + OfferContent content = null; + boolean done = false; + Map metaData = new HashMap(); + + if (eventType != XmlPullParser.START_TAG) { + // throw exception + } + + String userJID = parser.getAttributeValue("", "jid"); + // Default userID to the JID. + String userID = userJID; + + while (!done) { + eventType = parser.next(); + + if (eventType == XmlPullParser.START_TAG) { + String elemName = parser.getName(); + + if ("timeout".equals(elemName)) { + timeout = Integer.parseInt(parser.nextText()); + } + else if (MetaData.ELEMENT_NAME.equals(elemName)) { + metaData = MetaDataUtils.parseMetaData(parser); + } + else if (SessionID.ELEMENT_NAME.equals(elemName)) { + sessionID = parser.getAttributeValue("", "id"); + } + else if (UserID.ELEMENT_NAME.equals(elemName)) { + userID = parser.getAttributeValue("", "id"); + } + else if ("user-request".equals(elemName)) { + content = UserRequest.getInstance(); + } + else if (RoomInvitation.ELEMENT_NAME.equals(elemName)) { + RoomInvitation invitation = (RoomInvitation) PacketParserUtils + .parsePacketExtension(RoomInvitation.ELEMENT_NAME, RoomInvitation.NAMESPACE, parser); + content = new InvitationRequest(invitation.getInviter(), invitation.getRoom(), + invitation.getReason()); + } + else if (RoomTransfer.ELEMENT_NAME.equals(elemName)) { + RoomTransfer transfer = (RoomTransfer) PacketParserUtils + .parsePacketExtension(RoomTransfer.ELEMENT_NAME, RoomTransfer.NAMESPACE, parser); + content = new TransferRequest(transfer.getInviter(), transfer.getRoom(), transfer.getReason()); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if ("offer".equals(parser.getName())) { + done = true; + } + } + } + + OfferRequestPacket offerRequest = + new OfferRequestPacket(userJID, userID, timeout, metaData, sessionID, content); + offerRequest.setType(IQ.Type.SET); + + return offerRequest; + } + + public static class OfferRequestPacket extends IQ { + + private int timeout; + private String userID; + private String userJID; + private Map metaData; + private String sessionID; + private OfferContent content; + + public OfferRequestPacket(String userJID, String userID, int timeout, Map metaData, + String sessionID, OfferContent content) + { + this.userJID = userJID; + this.userID = userID; + this.timeout = timeout; + this.metaData = metaData; + this.sessionID = sessionID; + this.content = content; + } + + /** + * Returns the userID, which is either the same as the userJID or a special + * value that the user provided as part of their "join queue" request. + * + * @return the user ID. + */ + public String getUserID() { + return userID; + } + + /** + * The JID of the user that made the "join queue" request. + * + * @return the user JID. + */ + public String getUserJID() { + return userJID; + } + + /** + * Returns the session ID associated with the request and ensuing chat. If the offer + * does not contain a session ID, null will be returned. + * + * @return the session id associated with the request. + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the number of seconds the agent has to accept the offer before + * it times out. + * + * @return the offer timeout (in seconds). + */ + public int getTimeout() { + return this.timeout; + } + + public OfferContent getContent() { + return content; + } + + /** + * Returns any meta-data associated with the offer. + * + * @return meta-data associated with the offer. + */ + public Map getMetaData() { + return this.metaData; + } + + public String getChildElementXML () { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + buf.append("").append(timeout).append(""); + + if (sessionID != null) { + buf.append('<').append(SessionID.ELEMENT_NAME); + buf.append(" session=\""); + buf.append(getSessionID()).append("\" xmlns=\""); + buf.append(SessionID.NAMESPACE).append("\"/>"); + } + + if (metaData != null) { + buf.append(MetaDataUtils.serializeMetaData(metaData)); + } + + if (userID != null) { + buf.append('<').append(UserID.ELEMENT_NAME); + buf.append(" id=\""); + buf.append(userID).append("\" xmlns=\""); + buf.append(UserID.NAMESPACE).append("\"/>"); + } + + buf.append(""); + + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java new file mode 100644 index 0000000..202824c --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/OfferRevokeProvider.java @@ -0,0 +1,112 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * An IQProvider class which has savvy about the offer-revoke tag.
      + * + * @author loki der quaeler + */ +public class OfferRevokeProvider implements IQProvider { + + public IQ parseIQ (XmlPullParser parser) throws Exception { + // The parser will be positioned on the opening IQ tag, so get the JID attribute. + String userJID = parser.getAttributeValue("", "jid"); + // Default the userID to the JID. + String userID = userJID; + String reason = null; + String sessionID = null; + boolean done = false; + + while (!done) { + int eventType = parser.next(); + + if ((eventType == XmlPullParser.START_TAG) && parser.getName().equals("reason")) { + reason = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) + && parser.getName().equals(SessionID.ELEMENT_NAME)) { + sessionID = parser.getAttributeValue("", "id"); + } + else if ((eventType == XmlPullParser.START_TAG) + && parser.getName().equals(UserID.ELEMENT_NAME)) { + userID = parser.getAttributeValue("", "id"); + } + else if ((eventType == XmlPullParser.END_TAG) && parser.getName().equals( + "offer-revoke")) + { + done = true; + } + } + + return new OfferRevokePacket(userJID, userID, reason, sessionID); + } + + public class OfferRevokePacket extends IQ { + + private String userJID; + private String userID; + private String sessionID; + private String reason; + + public OfferRevokePacket (String userJID, String userID, String cause, String sessionID) { + this.userJID = userJID; + this.userID = userID; + this.reason = cause; + this.sessionID = sessionID; + } + + public String getUserJID() { + return userJID; + } + + public String getUserID() { + return this.userID; + } + + public String getReason() { + return this.reason; + } + + public String getSessionID() { + return this.sessionID; + } + + public String getChildElementXML () { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (reason != null) { + buf.append("").append(reason).append(""); + } + if (sessionID != null) { + buf.append(new SessionID(sessionID).toXML()); + } + if (userID != null) { + buf.append(new UserID(userID).toXML()); + } + buf.append(""); + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java new file mode 100644 index 0000000..ef11a78 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueDetails.java @@ -0,0 +1,199 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smackx.workgroup.QueueUser; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +/** + * Queue details packet extension, which contains details about the users + * currently in a queue. + */ +public class QueueDetails implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "notify-queue-details"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss"; + + private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + /** + * The list of users in the queue. + */ + private Set users; + + /** + * Creates a new QueueDetails packet + */ + private QueueDetails() { + users = new HashSet(); + } + + /** + * Returns the number of users currently in the queue that are waiting to + * be routed to an agent. + * + * @return the number of users in the queue. + */ + public int getUserCount() { + return users.size(); + } + + /** + * Returns the set of users in the queue that are waiting to + * be routed to an agent (as QueueUser objects). + * + * @return a Set for the users waiting in a queue. + */ + public Set getUsers() { + synchronized (users) { + return users; + } + } + + /** + * Adds a user to the packet. + * + * @param user the user. + */ + private void addUser(QueueUser user) { + synchronized (users) { + users.add(user); + } + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + + synchronized (users) { + for (Iterator i=users.iterator(); i.hasNext(); ) { + QueueUser user = (QueueUser)i.next(); + int position = user.getQueuePosition(); + int timeRemaining = user.getEstimatedRemainingTime(); + Date timestamp = user.getQueueJoinTimestamp(); + + buf.append(""); + + if (position != -1) { + buf.append("").append(position).append(""); + } + + if (timeRemaining != -1) { + buf.append(""); + } + + if (timestamp != null) { + buf.append(""); + buf.append(dateFormat.format(timestamp)); + buf.append(""); + } + + buf.append(""); + } + } + buf.append(""); + return buf.toString(); + } + + /** + * Provider class for QueueDetails packet extensions. + */ + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + QueueDetails queueDetails = new QueueDetails(); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.END_TAG && + "notify-queue-details".equals(parser.getName())) + { + eventType = parser.next(); + while ((eventType == XmlPullParser.START_TAG) && "user".equals(parser.getName())) { + String uid = null; + int position = -1; + int time = -1; + Date joinTime = null; + + uid = parser.getAttributeValue("", "jid"); + + if (uid == null) { + // throw exception + } + + eventType = parser.next(); + while ((eventType != XmlPullParser.END_TAG) + || (! "user".equals(parser.getName()))) + { + if ("position".equals(parser.getName())) { + position = Integer.parseInt(parser.nextText()); + } + else if ("time".equals(parser.getName())) { + time = Integer.parseInt(parser.nextText()); + } + else if ("join-time".equals(parser.getName())) { + joinTime = dateFormat.parse(parser.nextText()); + } + else if( parser.getName().equals( "waitTime" ) ) { + Date wait = dateFormat.parse(parser.nextText()); + System.out.println( wait ); + } + + eventType = parser.next(); + + if (eventType != XmlPullParser.END_TAG) { + // throw exception + } + } + + queueDetails.addUser(new QueueUser(uid, position, time, joinTime)); + + eventType = parser.next(); + } + } + return queueDetails; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java new file mode 100644 index 0000000..a559579 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueOverview.java @@ -0,0 +1,160 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smackx.workgroup.agent.WorkgroupQueue; +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.text.SimpleDateFormat; +import java.util.Date; + +public class QueueOverview implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static String ELEMENT_NAME = "notify-queue"; + + /** + * Namespace of the packet extension. + */ + public static String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private static final String DATE_FORMAT = "yyyyMMdd'T'HH:mm:ss"; + private SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + + private int averageWaitTime; + private Date oldestEntry; + private int userCount; + private WorkgroupQueue.Status status; + + QueueOverview() { + this.averageWaitTime = -1; + this.oldestEntry = null; + this.userCount = -1; + this.status = null; + } + + void setAverageWaitTime(int averageWaitTime) { + this.averageWaitTime = averageWaitTime; + } + + public int getAverageWaitTime () { + return averageWaitTime; + } + + void setOldestEntry(Date oldestEntry) { + this.oldestEntry = oldestEntry; + } + + public Date getOldestEntry() { + return oldestEntry; + } + + void setUserCount(int userCount) { + this.userCount = userCount; + } + + public int getUserCount() { + return userCount; + } + + public WorkgroupQueue.Status getStatus() { + return status; + } + + void setStatus(WorkgroupQueue.Status status) { + this.status = status; + } + + public String getElementName () { + return ELEMENT_NAME; + } + + public String getNamespace () { + return NAMESPACE; + } + + public String toXML () { + StringBuilder buf = new StringBuilder(); + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + + if (userCount != -1) { + buf.append("").append(userCount).append(""); + } + if (oldestEntry != null) { + buf.append("").append(dateFormat.format(oldestEntry)).append(""); + } + if (averageWaitTime != -1) { + buf.append(""); + } + if (status != null) { + buf.append("").append(status).append(""); + } + buf.append(""); + + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension (XmlPullParser parser) throws Exception { + int eventType = parser.getEventType(); + QueueOverview queueOverview = new QueueOverview(); + SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT); + + if (eventType != XmlPullParser.START_TAG) { + // throw exception + } + + eventType = parser.next(); + while ((eventType != XmlPullParser.END_TAG) + || (!ELEMENT_NAME.equals(parser.getName()))) + { + if ("count".equals(parser.getName())) { + queueOverview.setUserCount(Integer.parseInt(parser.nextText())); + } + else if ("time".equals(parser.getName())) { + queueOverview.setAverageWaitTime(Integer.parseInt(parser.nextText())); + } + else if ("oldest".equals(parser.getName())) { + queueOverview.setOldestEntry((dateFormat.parse(parser.nextText()))); + } + else if ("status".equals(parser.getName())) { + queueOverview.setStatus(WorkgroupQueue.Status.fromString(parser.nextText())); + } + + eventType = parser.next(); + + if (eventType != XmlPullParser.END_TAG) { + // throw exception + } + } + + if (eventType != XmlPullParser.END_TAG) { + // throw exception + } + + return queueOverview; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java new file mode 100644 index 0000000..c326a57 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/QueueUpdate.java @@ -0,0 +1,122 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * An IQ packet that encapsulates both types of workgroup queue + * status notifications -- position updates, and estimated time + * left in the queue updates. + */ +public class QueueUpdate implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "queue-status"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private int position; + private int remainingTime; + + public QueueUpdate(int position, int remainingTime) { + this.position = position; + this.remainingTime = remainingTime; + } + + /** + * Returns the user's position in the workgroup queue, or -1 if the + * value isn't set on this packet. + * + * @return the position in the workgroup queue. + */ + public int getPosition() { + return this.position; + } + + /** + * Returns the user's estimated time left in the workgroup queue, or + * -1 if the value isn't set on this packet. + * + * @return the estimated time left in the workgroup queue. + */ + public int getRemaingTime() { + return remainingTime; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + buf.append(""); + if (position != -1) { + buf.append("").append(position).append(""); + } + if (remainingTime != -1) { + buf.append(""); + } + buf.append(""); + return buf.toString(); + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + boolean done = false; + int position = -1; + int timeRemaining = -1; + while (!done) { + parser.next(); + String elementName = parser.getName(); + if (parser.getEventType() == XmlPullParser.START_TAG && "position".equals(elementName)) { + try { + position = Integer.parseInt(parser.nextText()); + } + catch (NumberFormatException nfe) { + } + } + else if (parser.getEventType() == XmlPullParser.START_TAG && "time".equals(elementName)) { + try { + timeRemaining = Integer.parseInt(parser.nextText()); + } + catch (NumberFormatException nfe) { + } + } + else if (parser.getEventType() == XmlPullParser.END_TAG && "queue-status".equals(elementName)) { + done = true; + } + } + return new QueueUpdate(position, timeRemaining); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java new file mode 100644 index 0000000..34555de --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomInvitation.java @@ -0,0 +1,177 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.InvitationRequest}. + * + * @author Gaston Dombiak + */ +public class RoomInvitation implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "invite"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + /** + * Type of entity being invited to a groupchat support session. + */ + private Type type; + /** + * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In + * the case of a queue or a workgroup the server will select the best agent to invite. + */ + private String invitee; + /** + * Full JID of the user that sent the invitation. + */ + private String inviter; + /** + * ID of the session that originated the initial user request. + */ + private String sessionID; + /** + * JID of the room to join if offer is accepted. + */ + private String room; + /** + * Text provided by the inviter explaining the reason why the invitee is invited. + */ + private String reason; + + public RoomInvitation(Type type, String invitee, String sessionID, String reason) { + this.type = type; + this.invitee = invitee; + this.sessionID = sessionID; + this.reason = reason; + } + + private RoomInvitation() { + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getInviter() { + return inviter; + } + + public String getRoom() { + return room; + } + + public String getReason() { + return reason; + } + + public String getSessionID() { + return sessionID; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); + buf.append("\" type=\"").append(type).append("\">"); + buf.append(""); + if (invitee != null) { + buf.append("").append(invitee).append(""); + } + if (inviter != null) { + buf.append("").append(inviter).append(""); + } + if (reason != null) { + buf.append("").append(reason).append(""); + } + // Add packet extensions, if any are defined. + buf.append(" "); + + return buf.toString(); + } + + /** + * Type of entity being invited to a groupchat support session. + */ + public static enum Type { + /** + * A user is being invited to a groupchat support session. The user could be another agent + * or just a regular XMPP user. + */ + user, + /** + * Some agent of the specified queue will be invited to the groupchat support session. + */ + queue, + /** + * Some agent of the specified workgroup will be invited to the groupchat support session. + */ + workgroup + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + final RoomInvitation invitation = new RoomInvitation(); + invitation.type = Type.valueOf(parser.getAttributeValue("", "type")); + + boolean done = false; + while (!done) { + parser.next(); + String elementName = parser.getName(); + if (parser.getEventType() == XmlPullParser.START_TAG) { + if ("session".equals(elementName)) { + invitation.sessionID = parser.getAttributeValue("", "id"); + } + else if ("invitee".equals(elementName)) { + invitation.invitee = parser.nextText(); + } + else if ("inviter".equals(elementName)) { + invitation.inviter = parser.nextText(); + } + else if ("reason".equals(elementName)) { + invitation.reason = parser.nextText(); + } + else if ("room".equals(elementName)) { + invitation.room = parser.nextText(); + } + } + else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) { + done = true; + } + } + return invitation; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java new file mode 100644 index 0000000..d1e83e2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/RoomTransfer.java @@ -0,0 +1,177 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * Packet extension for {@link org.jivesoftware.smackx.workgroup.agent.TransferRequest}. + * + * @author Gaston Dombiak + */ +public class RoomTransfer implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "transfer"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + /** + * Type of entity being invited to a groupchat support session. + */ + private RoomTransfer.Type type; + /** + * JID of the entity being invited. The entity could be another agent, user , a queue or a workgroup. In + * the case of a queue or a workgroup the server will select the best agent to invite. + */ + private String invitee; + /** + * Full JID of the user that sent the invitation. + */ + private String inviter; + /** + * ID of the session that originated the initial user request. + */ + private String sessionID; + /** + * JID of the room to join if offer is accepted. + */ + private String room; + /** + * Text provided by the inviter explaining the reason why the invitee is invited. + */ + private String reason; + + public RoomTransfer(RoomTransfer.Type type, String invitee, String sessionID, String reason) { + this.type = type; + this.invitee = invitee; + this.sessionID = sessionID; + this.reason = reason; + } + + private RoomTransfer() { + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String getInviter() { + return inviter; + } + + public String getRoom() { + return room; + } + + public String getReason() { + return reason; + } + + public String getSessionID() { + return sessionID; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE); + buf.append("\" type=\"").append(type).append("\">"); + buf.append(""); + if (invitee != null) { + buf.append("").append(invitee).append(""); + } + if (inviter != null) { + buf.append("").append(inviter).append(""); + } + if (reason != null) { + buf.append("").append(reason).append(""); + } + // Add packet extensions, if any are defined. + buf.append(" "); + + return buf.toString(); + } + + /** + * Type of entity being invited to a groupchat support session. + */ + public static enum Type { + /** + * A user is being invited to a groupchat support session. The user could be another agent + * or just a regular XMPP user. + */ + user, + /** + * Some agent of the specified queue will be invited to the groupchat support session. + */ + queue, + /** + * Some agent of the specified workgroup will be invited to the groupchat support session. + */ + workgroup + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + final RoomTransfer invitation = new RoomTransfer(); + invitation.type = RoomTransfer.Type.valueOf(parser.getAttributeValue("", "type")); + + boolean done = false; + while (!done) { + parser.next(); + String elementName = parser.getName(); + if (parser.getEventType() == XmlPullParser.START_TAG) { + if ("session".equals(elementName)) { + invitation.sessionID = parser.getAttributeValue("", "id"); + } + else if ("invitee".equals(elementName)) { + invitation.invitee = parser.nextText(); + } + else if ("inviter".equals(elementName)) { + invitation.inviter = parser.nextText(); + } + else if ("reason".equals(elementName)) { + invitation.reason = parser.nextText(); + } + else if ("room".equals(elementName)) { + invitation.room = parser.nextText(); + } + } + else if (parser.getEventType() == XmlPullParser.END_TAG && ELEMENT_NAME.equals(elementName)) { + done = true; + } + } + return invitation; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java new file mode 100644 index 0000000..bfd7cfd --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/SessionID.java @@ -0,0 +1,77 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +public class SessionID implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "session"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + private String sessionID; + + public SessionID(String sessionID) { + this.sessionID = sessionID; + } + + public String getSessionID() { + return this.sessionID; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" "); + buf.append("id=\"").append(this.getSessionID()); + buf.append("\"/>"); + + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + String sessionID = parser.getAttributeValue("", "id"); + + // Advance to end of extension. + parser.next(); + + return new SessionID(sessionID); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java new file mode 100644 index 0000000..7f8f29e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcript.java @@ -0,0 +1,98 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Represents the conversation transcript that occured in a group chat room between an Agent + * and a user that requested assistance. The transcript contains all the Messages that were sent + * to the room as well as the sent presences. + * + * @author Gaston Dombiak + */ +public class Transcript extends IQ { + private String sessionID; + private List packets; + + /** + * Creates a transcript request for the given sessionID. + * + * @param sessionID the id of the session to get the conversation transcript. + */ + public Transcript(String sessionID) { + this.sessionID = sessionID; + this.packets = new ArrayList(); + } + + /** + * Creates a new transcript for the given sessionID and list of packets. The list of packets + * may include Messages and/or Presences. + * + * @param sessionID the id of the session that generated this conversation transcript. + * @param packets the list of messages and presences send to the room. + */ + public Transcript(String sessionID, List packets) { + this.sessionID = sessionID; + this.packets = packets; + } + + /** + * Returns id of the session that generated this conversation transcript. The sessionID is a + * value generated by the server when a new request is received. + * + * @return id of the session that generated this conversation transcript. + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the list of Messages and Presences that were sent to the room. + * + * @return the list of Messages and Presences that were sent to the room. + */ + public List getPackets() { + return Collections.unmodifiableList(packets); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (Iterator it=packets.iterator(); it.hasNext();) { + Packet packet = it.next(); + buf.append(packet.toXML()); + } + + buf.append(""); + + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java new file mode 100644 index 0000000..791b06e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptProvider.java @@ -0,0 +1,66 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.packet.Packet; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.List; + +/** + * An IQProvider for transcripts. + * + * @author Gaston Dombiak + */ +public class TranscriptProvider implements IQProvider { + + public TranscriptProvider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + String sessionID = parser.getAttributeValue("", "sessionID"); + List packets = new ArrayList(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("message")) { + packets.add(PacketParserUtils.parseMessage(parser)); + } + else if (parser.getName().equals("presence")) { + packets.add(PacketParserUtils.parsePresence(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("transcript")) { + done = true; + } + } + } + + return new Transcript(sessionID, packets); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java new file mode 100644 index 0000000..72693c4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptSearch.java @@ -0,0 +1,87 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.PacketParserUtils; +import org.xmlpull.v1.XmlPullParser; + +/** + * IQ packet for retrieving the transcript search form, submiting the completed search form + * or retrieving the answer of a transcript search. + * + * @author Gaston Dombiak + */ +public class TranscriptSearch extends IQ { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "transcript-search"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\">"); + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append(" "); + + return buf.toString(); + } + + /** + * An IQProvider for TranscriptSearch packets. + * + * @author Gaston Dombiak + */ + public static class Provider implements IQProvider { + + public Provider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + TranscriptSearch answer = new TranscriptSearch(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + // Parse the packet extension + answer.addExtension(PacketParserUtils.parsePacketExtension(parser.getName(), parser.getNamespace(), parser)); + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals(ELEMENT_NAME)) { + done = true; + } + } + } + + return answer; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java new file mode 100644 index 0000000..66ddaad --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/Transcripts.java @@ -0,0 +1,247 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * Represents a list of conversation transcripts that a user had in all his history. Each + * transcript summary includes the sessionID which may be used for getting more detailed + * information about the conversation. {@link org.jivesoftware.smackx.workgroup.packet.Transcript} + * + * @author Gaston Dombiak + */ +public class Transcripts extends IQ { + + private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + static { + UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); + } + + private String userID; + private List summaries; + + + /** + * Creates a transcripts request for the given userID. + * + * @param userID the id of the user to get his conversations transcripts. + */ + public Transcripts(String userID) { + this.userID = userID; + this.summaries = new ArrayList(); + } + + /** + * Creates a Transcripts which will contain the transcript summaries of the given user. + * + * @param userID the id of the user. Could be a real JID or a unique String that identifies + * anonymous users. + * @param summaries the list of TranscriptSummaries. + */ + public Transcripts(String userID, List summaries) { + this.userID = userID; + this.summaries = summaries; + } + + /** + * Returns the id of the user that was involved in the conversations. The userID could be a + * real JID if the connected user was not anonymous. Otherwise, the userID will be a String + * that was provided by the anonymous user as a way to idenitify the user across many user + * sessions. + * + * @return the id of the user that was involved in the conversations. + */ + public String getUserID() { + return userID; + } + + /** + * Returns a list of TranscriptSummary. A TranscriptSummary does not contain the conversation + * transcript but some summary information like the sessionID and the time when the + * conversation started and finished. Once you have the sessionID it is possible to get the + * full conversation transcript. + * + * @return a list of TranscriptSummary. + */ + public List getSummaries() { + return Collections.unmodifiableList(summaries); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + for (TranscriptSummary transcriptSummary : summaries) { + buf.append(transcriptSummary.toXML()); + } + + buf.append(""); + + return buf.toString(); + } + + /** + * A TranscriptSummary contains some information about a conversation such as the ID of the + * session or the date when the conversation started and finished. You will need to use the + * sessionID to get the full conversation transcript. + */ + public static class TranscriptSummary { + private String sessionID; + private Date joinTime; + private Date leftTime; + private List agentDetails; + + public TranscriptSummary(String sessionID, Date joinTime, Date leftTime, List agentDetails) { + this.sessionID = sessionID; + this.joinTime = joinTime; + this.leftTime = leftTime; + this.agentDetails = agentDetails; + } + + /** + * Returns the ID of the session that is related to this conversation transcript. The + * sessionID could be used for getting the full conversation transcript. + * + * @return the ID of the session that is related to this conversation transcript. + */ + public String getSessionID() { + return sessionID; + } + + /** + * Returns the Date when the conversation started. + * + * @return the Date when the conversation started. + */ + public Date getJoinTime() { + return joinTime; + } + + /** + * Returns the Date when the conversation finished. + * + * @return the Date when the conversation finished. + */ + public Date getLeftTime() { + return leftTime; + } + + /** + * Returns a list of AgentDetails. For each Agent that was involved in the conversation + * the list will include an AgentDetail. An AgentDetail contains the JID of the agent + * as well as the time when the Agent joined and left the conversation. + * + * @return a list of AgentDetails. + */ + public List getAgentDetails() { + return agentDetails; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + if (joinTime != null) { + buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); + } + if (leftTime != null) { + buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); + } + buf.append(""); + for (AgentDetail agentDetail : agentDetails) { + buf.append(agentDetail.toXML()); + } + buf.append(""); + + return buf.toString(); + } + } + + /** + * An AgentDetail contains information of an Agent that was involved in a conversation. + */ + public static class AgentDetail { + private String agentJID; + private Date joinTime; + private Date leftTime; + + public AgentDetail(String agentJID, Date joinTime, Date leftTime) { + this.agentJID = agentJID; + this.joinTime = joinTime; + this.leftTime = leftTime; + } + + /** + * Returns the bare JID of the Agent that was involved in the conversation. + * + * @return the bared JID of the Agent that was involved in the conversation. + */ + public String getAgentJID() { + return agentJID; + } + + /** + * Returns the Date when the Agent joined the conversation. + * + * @return the Date when the Agent joined the conversation. + */ + public Date getJoinTime() { + return joinTime; + } + + /** + * Returns the Date when the Agent left the conversation. + * + * @return the Date when the Agent left the conversation. + */ + public Date getLeftTime() { + return leftTime; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + + if (agentJID != null) { + buf.append("").append(agentJID).append(""); + } + if (joinTime != null) { + buf.append("").append(UTC_FORMAT.format(joinTime)).append(""); + } + if (leftTime != null) { + buf.append("").append(UTC_FORMAT.format(leftTime)).append(""); + } + buf.append(""); + + return buf.toString(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java new file mode 100644 index 0000000..cb8f429 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/TranscriptsProvider.java @@ -0,0 +1,148 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +/** + * An IQProvider for transcripts summaries. + * + * @author Gaston Dombiak + */ +public class TranscriptsProvider implements IQProvider { + + private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss"); + static { + UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0")); + } + + public TranscriptsProvider() { + super(); + } + + public IQ parseIQ(XmlPullParser parser) throws Exception { + String userID = parser.getAttributeValue("", "userID"); + List summaries = new ArrayList(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("transcript")) { + summaries.add(parseSummary(parser)); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("transcripts")) { + done = true; + } + } + } + + return new Transcripts(userID, summaries); + } + + private Transcripts.TranscriptSummary parseSummary(XmlPullParser parser) throws IOException, + XmlPullParserException { + String sessionID = parser.getAttributeValue("", "sessionID"); + Date joinTime = null; + Date leftTime = null; + List agents = new ArrayList(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("joinTime")) { + try { + joinTime = UTC_FORMAT.parse(parser.nextText()); + } catch (ParseException e) {} + } + else if (parser.getName().equals("leftTime")) { + try { + leftTime = UTC_FORMAT.parse(parser.nextText()); + } catch (ParseException e) {} + } + else if (parser.getName().equals("agents")) { + agents = parseAgents(parser); + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("transcript")) { + done = true; + } + } + } + + return new Transcripts.TranscriptSummary(sessionID, joinTime, leftTime, agents); + } + + private List parseAgents(XmlPullParser parser) throws IOException, XmlPullParserException { + List agents = new ArrayList(); + String agentJID = null; + Date joinTime = null; + Date leftTime = null; + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if (eventType == XmlPullParser.START_TAG) { + if (parser.getName().equals("agentJID")) { + agentJID = parser.nextText(); + } + else if (parser.getName().equals("joinTime")) { + try { + joinTime = UTC_FORMAT.parse(parser.nextText()); + } catch (ParseException e) {} + } + else if (parser.getName().equals("leftTime")) { + try { + leftTime = UTC_FORMAT.parse(parser.nextText()); + } catch (ParseException e) {} + } + else if (parser.getName().equals("agent")) { + agentJID = null; + joinTime = null; + leftTime = null; + } + } + else if (eventType == XmlPullParser.END_TAG) { + if (parser.getName().equals("agents")) { + done = true; + } + else if (parser.getName().equals("agent")) { + agents.add(new Transcripts.AgentDetail(agentJID, joinTime, leftTime)); + } + } + } + return agents; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java new file mode 100644 index 0000000..8bf4589 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/UserID.java @@ -0,0 +1,77 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +public class UserID implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "user"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + private String userID; + + public UserID(String userID) { + this.userID = userID; + } + + public String getUserID() { + return this.userID; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns=\"").append(NAMESPACE).append("\" "); + buf.append("id=\"").append(this.getUserID()); + buf.append("\"/>"); + + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + public PacketExtension parseExtension(XmlPullParser parser) throws Exception { + String userID = parser.getAttributeValue("", "id"); + + // Advance to end of extension. + parser.next(); + + return new UserID(userID); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java new file mode 100644 index 0000000..b0ea447 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/packet/WorkgroupInformation.java @@ -0,0 +1,86 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.packet; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smack.provider.PacketExtensionProvider; +import org.xmlpull.v1.XmlPullParser; + +/** + * A packet extension that contains information about the user and agent in a + * workgroup chat. The packet extension is attached to group chat invitations. + */ +public class WorkgroupInformation implements PacketExtension { + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "workgroup"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jabber.org/protocol/workgroup"; + + private String workgroupJID; + + public WorkgroupInformation(String workgroupJID){ + this.workgroupJID = workgroupJID; + } + + public String getWorkgroupJID() { + return workgroupJID; + } + + public String getElementName() { + return ELEMENT_NAME; + } + + public String getNamespace() { + return NAMESPACE; + } + + public String toXML() { + StringBuilder buf = new StringBuilder(); + + buf.append('<').append(ELEMENT_NAME); + buf.append(" jid=\"").append(getWorkgroupJID()).append("\""); + buf.append(" xmlns=\"").append(NAMESPACE).append("\" />"); + + return buf.toString(); + } + + public static class Provider implements PacketExtensionProvider { + + /** + * PacketExtensionProvider implementation + */ + public PacketExtension parseExtension (XmlPullParser parser) + throws Exception { + String workgroupJID = parser.getAttributeValue("", "jid"); + + // since this is a start and end tag, and we arrive on the start, this should guarantee + // we leave on the end + parser.next(); + + return new WorkgroupInformation(workgroupJID); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java new file mode 100644 index 0000000..921134a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSetting.java @@ -0,0 +1,56 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +public class ChatSetting { + private String key; + private String value; + private int type; + + public ChatSetting(String key, String value, int type){ + setKey(key); + setValue(value); + setType(type); + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java new file mode 100644 index 0000000..60e4521 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/ChatSettings.java @@ -0,0 +1,179 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class ChatSettings extends IQ { + + /** + * Defined as image type. + */ + public static final int IMAGE_SETTINGS = 0; + + /** + * Defined as Text settings type. + */ + public static final int TEXT_SETTINGS = 1; + + /** + * Defined as Bot settings type. + */ + public static final int BOT_SETTINGS = 2; + + private List settings; + private String key; + private int type = -1; + + public ChatSettings() { + settings = new ArrayList(); + } + + public ChatSettings(String key) { + setKey(key); + } + + public void setKey(String key) { + this.key = key; + } + + public void setType(int type) { + this.type = type; + } + + public void addSetting(ChatSetting setting) { + settings.add(setting); + } + + public Collection getSettings() { + return settings; + } + + public ChatSetting getChatSetting(String key) { + Collection col = getSettings(); + if (col != null) { + Iterator iter = col.iterator(); + while (iter.hasNext()) { + ChatSetting chatSetting = (ChatSetting)iter.next(); + if (chatSetting.getKey().equals(key)) { + return chatSetting; + } + } + } + return null; + } + + public ChatSetting getFirstEntry() { + if (settings.size() > 0) { + return (ChatSetting)settings.get(0); + } + return null; + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "chat-settings"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + if (key != null) { + buf.append(" key=\"" + key + "\""); + } + + if (type != -1) { + buf.append(" type=\"" + type + "\""); + } + + buf.append("> "); + return buf.toString(); + } + + /** + * Packet extension provider for AgentStatusRequest packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + ChatSettings chatSettings = new ChatSettings(); + + boolean done = false; + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("chat-setting".equals(parser.getName()))) { + chatSettings.addSetting(parseChatSetting(parser)); + + } + else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { + done = true; + } + } + return chatSettings; + } + + private ChatSetting parseChatSetting(XmlPullParser parser) throws Exception { + + boolean done = false; + String key = null; + String value = null; + int type = 0; + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("key".equals(parser.getName()))) { + key = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("value".equals(parser.getName()))) { + value = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("type".equals(parser.getName()))) { + type = Integer.parseInt(parser.nextText()); + } + else if (eventType == XmlPullParser.END_TAG && "chat-setting".equals(parser.getName())) { + done = true; + } + } + return new ChatSetting(key, value, type); + } + } +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java new file mode 100644 index 0000000..dcbd32b --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/GenericSettings.java @@ -0,0 +1,114 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smackx.workgroup.util.ModelUtil; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +import java.util.HashMap; +import java.util.Map; + +public class GenericSettings extends IQ { + + private Map map = new HashMap(); + + private String query; + + public String getQuery() { + return query; + } + + public void setQuery(String query) { + this.query = query; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "generic-metadata"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append(">"); + if (ModelUtil.hasLength(getQuery())) { + buf.append("" + getQuery() + ""); + } + buf.append(" "); + return buf.toString(); + } + + + /** + * Packet extension provider for SoundSetting Packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + GenericSettings setting = new GenericSettings(); + + boolean done = false; + + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("entry".equals(parser.getName()))) { + eventType = parser.next(); + String name = parser.nextText(); + eventType = parser.next(); + String value = parser.nextText(); + setting.getMap().put(name, value); + } + else if (eventType == XmlPullParser.END_TAG && ELEMENT_NAME.equals(parser.getName())) { + done = true; + } + } + + return setting; + } + } + + +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java new file mode 100644 index 0000000..15136fd --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/OfflineSettings.java @@ -0,0 +1,155 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smackx.workgroup.util.ModelUtil; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +public class OfflineSettings extends IQ { + private String redirectURL; + + private String offlineText; + private String emailAddress; + private String subject; + + public String getRedirectURL() { + if (!ModelUtil.hasLength(redirectURL)) { + return ""; + } + return redirectURL; + } + + public void setRedirectURL(String redirectURL) { + this.redirectURL = redirectURL; + } + + public String getOfflineText() { + if (!ModelUtil.hasLength(offlineText)) { + return ""; + } + return offlineText; + } + + public void setOfflineText(String offlineText) { + this.offlineText = offlineText; + } + + public String getEmailAddress() { + if (!ModelUtil.hasLength(emailAddress)) { + return ""; + } + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getSubject() { + if (!ModelUtil.hasLength(subject)) { + return ""; + } + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public boolean redirects() { + return (ModelUtil.hasLength(getRedirectURL())); + } + + public boolean isConfigured(){ + return ModelUtil.hasLength(getEmailAddress()) && + ModelUtil.hasLength(getSubject()) && + ModelUtil.hasLength(getOfflineText()); + } + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "offline-settings"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append("> "); + return buf.toString(); + } + + + /** + * Packet extension provider for AgentStatusRequest packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + OfflineSettings offlineSettings = new OfflineSettings(); + + boolean done = false; + String redirectPage = null; + String subject = null; + String offlineText = null; + String emailAddress = null; + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("redirectPage".equals(parser.getName()))) { + redirectPage = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("subject".equals(parser.getName()))) { + subject = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("offlineText".equals(parser.getName()))) { + offlineText = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("emailAddress".equals(parser.getName()))) { + emailAddress = parser.nextText(); + } + else if (eventType == XmlPullParser.END_TAG && "offline-settings".equals(parser.getName())) { + done = true; + } + } + + offlineSettings.setEmailAddress(emailAddress); + offlineSettings.setRedirectURL(redirectPage); + offlineSettings.setSubject(subject); + offlineSettings.setOfflineText(offlineText); + return offlineSettings; + } + } +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java new file mode 100644 index 0000000..98d59fc --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SearchSettings.java @@ -0,0 +1,112 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smackx.workgroup.util.ModelUtil; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +public class SearchSettings extends IQ { + private String forumsLocation; + private String kbLocation; + + public boolean isSearchEnabled() { + return ModelUtil.hasLength(getForumsLocation()) && ModelUtil.hasLength(getKbLocation()); + } + + public String getForumsLocation() { + return forumsLocation; + } + + public void setForumsLocation(String forumsLocation) { + this.forumsLocation = forumsLocation; + } + + public String getKbLocation() { + return kbLocation; + } + + public void setKbLocation(String kbLocation) { + this.kbLocation = kbLocation; + } + + public boolean hasKB(){ + return ModelUtil.hasLength(getKbLocation()); + } + + public boolean hasForums(){ + return ModelUtil.hasLength(getForumsLocation()); + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "search-settings"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append("> "); + return buf.toString(); + } + + + /** + * Packet extension provider for AgentStatusRequest packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + SearchSettings settings = new SearchSettings(); + + boolean done = false; + String kb = null; + String forums = null; + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("forums".equals(parser.getName()))) { + forums = parser.nextText(); + } + else if ((eventType == XmlPullParser.START_TAG) && ("kb".equals(parser.getName()))) { + kb = parser.nextText(); + } + else if (eventType == XmlPullParser.END_TAG && "search-settings".equals(parser.getName())) { + done = true; + } + } + + settings.setForumsLocation(forums); + settings.setKbLocation(kb); + return settings; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java new file mode 100644 index 0000000..66bec35 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/SoundSettings.java @@ -0,0 +1,103 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.jivesoftware.smack.util.StringUtils; +import org.xmlpull.v1.XmlPullParser; + +public class SoundSettings extends IQ { + private String outgoingSound; + private String incomingSound; + + + public void setOutgoingSound(String outgoingSound) { + this.outgoingSound = outgoingSound; + } + + public void setIncomingSound(String incomingSound) { + this.incomingSound = incomingSound; + } + + public byte[] getIncomingSoundBytes() { + return StringUtils.decodeBase64(incomingSound); + } + + public byte[] getOutgoingSoundBytes() { + return StringUtils.decodeBase64(outgoingSound); + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "sound-settings"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + buf.append("> "); + return buf.toString(); + } + + + /** + * Packet extension provider for SoundSetting Packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + SoundSettings soundSettings = new SoundSettings(); + + boolean done = false; + + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("outgoingSound".equals(parser.getName()))) { + soundSettings.setOutgoingSound(parser.nextText()); + } + else if ((eventType == XmlPullParser.START_TAG) && ("incomingSound".equals(parser.getName()))) { + soundSettings.setIncomingSound(parser.nextText()); + } + else if (eventType == XmlPullParser.END_TAG && "sound-settings".equals(parser.getName())) { + done = true; + } + } + + return soundSettings; + } + } +} + diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java new file mode 100644 index 0000000..8e405bb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/settings/WorkgroupProperties.java @@ -0,0 +1,125 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.settings; + +import org.jivesoftware.smackx.workgroup.util.ModelUtil; +import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.provider.IQProvider; +import org.xmlpull.v1.XmlPullParser; + +public class WorkgroupProperties extends IQ { + + private boolean authRequired; + private String email; + private String fullName; + private String jid; + + public boolean isAuthRequired() { + return authRequired; + } + + public void setAuthRequired(boolean authRequired) { + this.authRequired = authRequired; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getJid() { + return jid; + } + + public void setJid(String jid) { + this.jid = jid; + } + + + /** + * Element name of the packet extension. + */ + public static final String ELEMENT_NAME = "workgroup-properties"; + + /** + * Namespace of the packet extension. + */ + public static final String NAMESPACE = "http://jivesoftware.com/protocol/workgroup"; + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append("<").append(ELEMENT_NAME).append(" xmlns="); + buf.append('"'); + buf.append(NAMESPACE); + buf.append('"'); + if (ModelUtil.hasLength(getJid())) { + buf.append("jid=\"" + getJid() + "\" "); + } + buf.append("> "); + return buf.toString(); + } + + /** + * Packet extension provider for SoundSetting Packets. + */ + public static class InternalProvider implements IQProvider { + + public IQ parseIQ(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException("Parser not in proper position, or bad XML."); + } + + WorkgroupProperties props = new WorkgroupProperties(); + + boolean done = false; + + + while (!done) { + int eventType = parser.next(); + if ((eventType == XmlPullParser.START_TAG) && ("authRequired".equals(parser.getName()))) { + props.setAuthRequired(new Boolean(parser.nextText()).booleanValue()); + } + else if ((eventType == XmlPullParser.START_TAG) && ("email".equals(parser.getName()))) { + props.setEmail(parser.nextText()); + } + else if ((eventType == XmlPullParser.START_TAG) && ("name".equals(parser.getName()))) { + props.setFullName(parser.nextText()); + } + else if (eventType == XmlPullParser.END_TAG && "workgroup-properties".equals(parser.getName())) { + done = true; + } + } + + return props; + } + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java new file mode 100644 index 0000000..fa3e6a6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/QueueListener.java @@ -0,0 +1,55 @@ +/** + * $Revision$ + * $Date$ + * + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.user; + +/** + * Listener interface for those that wish to be notified of workgroup queue events. + * + * @see Workgroup#addQueueListener(QueueListener) + * @author loki der quaeler + */ +public interface QueueListener { + + /** + * The user joined the workgroup queue. + */ + public void joinedQueue(); + + /** + * The user departed the workgroup queue. + */ + public void departedQueue(); + + /** + * The user's queue position has been updated to a new value. + * + * @param currentPosition the user's current position in the queue. + */ + public void queuePositionUpdated(int currentPosition); + + /** + * The user's estimated remaining wait time in the queue has been updated. + * + * @param secondsRemaining the estimated number of seconds remaining until the + * the user is routed to the agent. + */ + public void queueWaitTimeUpdated(int secondsRemaining); + +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java new file mode 100644 index 0000000..a21b996 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/user/Workgroup.java @@ -0,0 +1,870 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.user; + +import org.jivesoftware.smackx.workgroup.MetaData; +import org.jivesoftware.smackx.workgroup.WorkgroupInvitation; +import org.jivesoftware.smackx.workgroup.WorkgroupInvitationListener; +import org.jivesoftware.smackx.workgroup.ext.forms.WorkgroupForm; +import org.jivesoftware.smackx.workgroup.packet.DepartQueuePacket; +import org.jivesoftware.smackx.workgroup.packet.QueueUpdate; +import org.jivesoftware.smackx.workgroup.packet.SessionID; +import org.jivesoftware.smackx.workgroup.packet.UserID; +import org.jivesoftware.smackx.workgroup.settings.*; +import org.jivesoftware.smack.*; +import org.jivesoftware.smack.filter.*; +import org.jivesoftware.smack.packet.*; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.Form; +import org.jivesoftware.smackx.FormField; +import org.jivesoftware.smackx.ServiceDiscoveryManager; +import org.jivesoftware.smackx.muc.MultiUserChat; +import org.jivesoftware.smackx.packet.DataForm; +import org.jivesoftware.smackx.packet.DiscoverInfo; +import org.jivesoftware.smackx.packet.MUCUser; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Provides workgroup services for users. Users can join the workgroup queue, depart the + * queue, find status information about their placement in the queue, and register to + * be notified when they are routed to an agent.

      + *

      + * This class only provides a users perspective into a workgroup and is not intended + * for use by agents. + * + * @author Matt Tucker + * @author Derek DeMoro + */ +public class Workgroup { + + private String workgroupJID; + private Connection connection; + private boolean inQueue; + private List invitationListeners; + private List queueListeners; + private List siteInviteListeners; + + private int queuePosition = -1; + private int queueRemainingTime = -1; + + /** + * Creates a new workgroup instance using the specified workgroup JID + * (eg support@workgroup.example.com) and XMPP connection. The connection must have + * undergone a successful login before being used to construct an instance of + * this class. + * + * @param workgroupJID the JID of the workgroup. + * @param connection an XMPP connection which must have already undergone a + * successful login. + */ + public Workgroup(String workgroupJID, Connection connection) { + // Login must have been done before passing in connection. + if (!connection.isAuthenticated()) { + throw new IllegalStateException("Must login to server before creating workgroup."); + } + + this.workgroupJID = workgroupJID; + this.connection = connection; + inQueue = false; + invitationListeners = new ArrayList(); + queueListeners = new ArrayList(); + siteInviteListeners = new ArrayList(); + + // Register as a queue listener for internal usage by this instance. + addQueueListener(new QueueListener() { + public void joinedQueue() { + inQueue = true; + } + + public void departedQueue() { + inQueue = false; + queuePosition = -1; + queueRemainingTime = -1; + } + + public void queuePositionUpdated(int currentPosition) { + queuePosition = currentPosition; + } + + public void queueWaitTimeUpdated(int secondsRemaining) { + queueRemainingTime = secondsRemaining; + } + }); + + /** + * Internal handling of an invitation.Recieving an invitation removes the user from the queue. + */ + MultiUserChat.addInvitationListener(connection, + new org.jivesoftware.smackx.muc.InvitationListener() { + public void invitationReceived(Connection conn, String room, String inviter, + String reason, String password, Message message) { + inQueue = false; + queuePosition = -1; + queueRemainingTime = -1; + } + }); + + // Register a packet listener for all the messages sent to this client. + PacketFilter typeFilter = new PacketTypeFilter(Message.class); + + connection.addPacketListener(new PacketListener() { + public void processPacket(Packet packet) { + handlePacket(packet); + } + }, typeFilter); + } + + /** + * Returns the name of this workgroup (eg support@example.com). + * + * @return the name of the workgroup. + */ + public String getWorkgroupJID() { + return workgroupJID; + } + + /** + * Returns true if the user is currently waiting in the workgroup queue. + * + * @return true if currently waiting in the queue. + */ + public boolean isInQueue() { + return inQueue; + } + + /** + * Returns true if the workgroup is available for receiving new requests. The workgroup will be + * available only when agents are available for this workgroup. + * + * @return true if the workgroup is available for receiving new requests. + */ + public boolean isAvailable() { + Presence directedPresence = new Presence(Presence.Type.available); + directedPresence.setTo(workgroupJID); + PacketFilter typeFilter = new PacketTypeFilter(Presence.class); + PacketFilter fromFilter = new FromContainsFilter(workgroupJID); + PacketCollector collector = connection.createPacketCollector(new AndFilter(fromFilter, + typeFilter)); + + connection.sendPacket(directedPresence); + + Presence response = (Presence)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + return false; + } + else if (response.getError() != null) { + return false; + } + else { + return Presence.Type.available == response.getType(); + } + } + + /** + * Returns the users current position in the workgroup queue. A value of 0 means + * the user is next in line to be routed; therefore, if the queue position + * is being displayed to the end user it is usually a good idea to add 1 to + * the value this method returns before display. If the user is not currently + * waiting in the workgroup, or no queue position information is available, -1 + * will be returned. + * + * @return the user's current position in the workgroup queue, or -1 if the + * position isn't available or if the user isn't in the queue. + */ + public int getQueuePosition() { + return queuePosition; + } + + /** + * Returns the estimated time (in seconds) that the user has to left wait in + * the workgroup queue before being routed. If the user is not currently waiting + * int he workgroup, or no queue time information is available, -1 will be + * returned. + * + * @return the estimated time remaining (in seconds) that the user has to + * wait inthe workgroupu queue, or -1 if time information isn't available + * or if the user isn't int the queue. + */ + public int getQueueRemainingTime() { + return queueRemainingTime; + } + + /** + * Joins the workgroup queue to wait to be routed to an agent. After joining + * the queue, queue status events will be sent to indicate the user's position and + * estimated time left in the queue. Once joining the queue, there are three ways + * the user can leave the queue:

        + *

        + *

      • The user is routed to an agent, which triggers a GroupChat invitation. + *
      • The user asks to leave the queue by calling the {@link #departQueue} method. + *
      • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
      + *

      + * A user cannot request to join the queue again if already in the queue. Therefore, + * this method will throw an IllegalStateException if the user is already in the queue.

      + *

      + * Some servers may be configured to require certain meta-data in order to + * join the queue. In that case, the {@link #joinQueue(Form)} method should be + * used instead of this method so that meta-data may be passed in.

      + *

      + * The server tracks the conversations that a user has with agents over time. By + * default, that tracking is done using the user's JID. However, this is not always + * possible. For example, when the user is logged in anonymously using a web client. + * In that case the user ID might be a randomly generated value put into a persistent + * cookie or a username obtained via the session. A userID can be explicitly + * passed in by using the {@link #joinQueue(Form, String)} method. When specified, + * that userID will be used instead of the user's JID to track conversations. The + * server will ignore a manually specified userID if the user's connection to the server + * is not anonymous. + * + * @throws XMPPException if an error occured joining the queue. An error may indicate + * that a connection failure occured or that the server explicitly rejected the + * request to join the queue. + */ + public void joinQueue() throws XMPPException { + joinQueue(null); + } + + /** + * Joins the workgroup queue to wait to be routed to an agent. After joining + * the queue, queue status events will be sent to indicate the user's position and + * estimated time left in the queue. Once joining the queue, there are three ways + * the user can leave the queue:

        + *

        + *

      • The user is routed to an agent, which triggers a GroupChat invitation. + *
      • The user asks to leave the queue by calling the {@link #departQueue} method. + *
      • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
      + *

      + * A user cannot request to join the queue again if already in the queue. Therefore, + * this method will throw an IllegalStateException if the user is already in the queue.

      + *

      + * Some servers may be configured to require certain meta-data in order to + * join the queue.

      + *

      + * The server tracks the conversations that a user has with agents over time. By + * default, that tracking is done using the user's JID. However, this is not always + * possible. For example, when the user is logged in anonymously using a web client. + * In that case the user ID might be a randomly generated value put into a persistent + * cookie or a username obtained via the session. A userID can be explicitly + * passed in by using the {@link #joinQueue(Form, String)} method. When specified, + * that userID will be used instead of the user's JID to track conversations. The + * server will ignore a manually specified userID if the user's connection to the server + * is not anonymous. + * + * @param answerForm the completed form the send for the join request. + * @throws XMPPException if an error occured joining the queue. An error may indicate + * that a connection failure occured or that the server explicitly rejected the + * request to join the queue. + */ + public void joinQueue(Form answerForm) throws XMPPException { + joinQueue(answerForm, null); + } + + /** + *

      Joins the workgroup queue to wait to be routed to an agent. After joining + * the queue, queue status events will be sent to indicate the user's position and + * estimated time left in the queue. Once joining the queue, there are three ways + * the user can leave the queue:

        + *

        + *

      • The user is routed to an agent, which triggers a GroupChat invitation. + *
      • The user asks to leave the queue by calling the {@link #departQueue} method. + *
      • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
      + *

      + * A user cannot request to join the queue again if already in the queue. Therefore, + * this method will throw an IllegalStateException if the user is already in the queue.

      + *

      + * Some servers may be configured to require certain meta-data in order to + * join the queue.

      + *

      + * The server tracks the conversations that a user has with agents over time. By + * default, that tracking is done using the user's JID. However, this is not always + * possible. For example, when the user is logged in anonymously using a web client. + * In that case the user ID might be a randomly generated value put into a persistent + * cookie or a username obtained via the session. When specified, that userID will + * be used instead of the user's JID to track conversations. The server will ignore a + * manually specified userID if the user's connection to the server is not anonymous. + * + * @param answerForm the completed form associated with the join reqest. + * @param userID String that represents the ID of the user when using anonymous sessions + * or null if a userID should not be used. + * @throws XMPPException if an error occured joining the queue. An error may indicate + * that a connection failure occured or that the server explicitly rejected the + * request to join the queue. + */ + public void joinQueue(Form answerForm, String userID) throws XMPPException { + // If already in the queue ignore the join request. + if (inQueue) { + throw new IllegalStateException("Already in queue " + workgroupJID); + } + + JoinQueuePacket joinPacket = new JoinQueuePacket(workgroupJID, answerForm, userID); + + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(joinPacket.getPacketID())); + + this.connection.sendPacket(joinPacket); + + IQ response = (IQ)collector.nextResult(10000); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + + // Notify listeners that we've joined the queue. + fireQueueJoinedEvent(); + } + + /** + *

      Joins the workgroup queue to wait to be routed to an agent. After joining + * the queue, queue status events will be sent to indicate the user's position and + * estimated time left in the queue. Once joining the queue, there are three ways + * the user can leave the queue:

        + *

        + *

      • The user is routed to an agent, which triggers a GroupChat invitation. + *
      • The user asks to leave the queue by calling the {@link #departQueue} method. + *
      • A server error occurs, or an administrator explicitly removes the user + * from the queue. + *
      + *

      + * A user cannot request to join the queue again if already in the queue. Therefore, + * this method will throw an IllegalStateException if the user is already in the queue.

      + *

      + * Some servers may be configured to require certain meta-data in order to + * join the queue.

      + *

      + * The server tracks the conversations that a user has with agents over time. By + * default, that tracking is done using the user's JID. However, this is not always + * possible. For example, when the user is logged in anonymously using a web client. + * In that case the user ID might be a randomly generated value put into a persistent + * cookie or a username obtained via the session. When specified, that userID will + * be used instead of the user's JID to track conversations. The server will ignore a + * manually specified userID if the user's connection to the server is not anonymous. + * + * @param metadata metadata to create a dataform from. + * @param userID String that represents the ID of the user when using anonymous sessions + * or null if a userID should not be used. + * @throws XMPPException if an error occured joining the queue. An error may indicate + * that a connection failure occured or that the server explicitly rejected the + * request to join the queue. + */ + public void joinQueue(Map metadata, String userID) throws XMPPException { + // If already in the queue ignore the join request. + if (inQueue) { + throw new IllegalStateException("Already in queue " + workgroupJID); + } + + // Build dataform from metadata + Form form = new Form(Form.TYPE_SUBMIT); + Iterator iter = metadata.keySet().iterator(); + while (iter.hasNext()) { + String name = (String)iter.next(); + String value = (String)metadata.get(name).toString(); + + String escapedName = StringUtils.escapeForXML(name); + String escapedValue = StringUtils.escapeForXML(value); + + FormField field = new FormField(escapedName); + field.setType(FormField.TYPE_TEXT_SINGLE); + form.addField(field); + form.setAnswer(escapedName, escapedValue); + } + joinQueue(form, userID); + } + + /** + * Departs the workgroup queue. If the user is not currently in the queue, this + * method will do nothing.

      + *

      + * Normally, the user would not manually leave the queue. However, they may wish to + * under certain circumstances -- for example, if they no longer wish to be routed + * to an agent because they've been waiting too long. + * + * @throws XMPPException if an error occured trying to send the depart queue + * request to the server. + */ + public void departQueue() throws XMPPException { + // If not in the queue ignore the depart request. + if (!inQueue) { + return; + } + + DepartQueuePacket departPacket = new DepartQueuePacket(this.workgroupJID); + PacketCollector collector = this.connection.createPacketCollector(new PacketIDFilter(departPacket.getPacketID())); + + connection.sendPacket(departPacket); + + IQ response = (IQ)collector.nextResult(5000); + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from the server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + + // Notify listeners that we're no longer in the queue. + fireQueueDepartedEvent(); + } + + /** + * Adds a queue listener that will be notified of queue events for the user + * that created this Workgroup instance. + * + * @param queueListener the queue listener. + */ + public void addQueueListener(QueueListener queueListener) { + synchronized (queueListeners) { + if (!queueListeners.contains(queueListener)) { + queueListeners.add(queueListener); + } + } + } + + /** + * Removes a queue listener. + * + * @param queueListener the queue listener. + */ + public void removeQueueListener(QueueListener queueListener) { + synchronized (queueListeners) { + queueListeners.remove(queueListener); + } + } + + /** + * Adds an invitation listener that will be notified of groupchat invitations + * from the workgroup for the the user that created this Workgroup instance. + * + * @param invitationListener the invitation listener. + */ + public void addInvitationListener(WorkgroupInvitationListener invitationListener) { + synchronized (invitationListeners) { + if (!invitationListeners.contains(invitationListener)) { + invitationListeners.add(invitationListener); + } + } + } + + /** + * Removes an invitation listener. + * + * @param invitationListener the invitation listener. + */ + public void removeQueueListener(WorkgroupInvitationListener invitationListener) { + synchronized (invitationListeners) { + invitationListeners.remove(invitationListener); + } + } + + private void fireInvitationEvent(WorkgroupInvitation invitation) { + synchronized (invitationListeners) { + for (Iterator i = invitationListeners.iterator(); i.hasNext();) { + WorkgroupInvitationListener listener = (WorkgroupInvitationListener)i.next(); + listener.invitationReceived(invitation); + } + } + } + + private void fireQueueJoinedEvent() { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.joinedQueue(); + } + } + } + + private void fireQueueDepartedEvent() { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.departedQueue(); + } + } + } + + private void fireQueuePositionEvent(int currentPosition) { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.queuePositionUpdated(currentPosition); + } + } + } + + private void fireQueueTimeEvent(int secondsRemaining) { + synchronized (queueListeners) { + for (Iterator i = queueListeners.iterator(); i.hasNext();) { + QueueListener listener = (QueueListener)i.next(); + listener.queueWaitTimeUpdated(secondsRemaining); + } + } + } + + // PacketListener Implementation. + + private void handlePacket(Packet packet) { + if (packet instanceof Message) { + Message msg = (Message)packet; + // Check to see if the user left the queue. + PacketExtension pe = msg.getExtension("depart-queue", "http://jabber.org/protocol/workgroup"); + PacketExtension queueStatus = msg.getExtension("queue-status", "http://jabber.org/protocol/workgroup"); + + if (pe != null) { + fireQueueDepartedEvent(); + } + else if (queueStatus != null) { + QueueUpdate queueUpdate = (QueueUpdate)queueStatus; + if (queueUpdate.getPosition() != -1) { + fireQueuePositionEvent(queueUpdate.getPosition()); + } + if (queueUpdate.getRemaingTime() != -1) { + fireQueueTimeEvent(queueUpdate.getRemaingTime()); + } + } + + else { + // Check if a room invitation was sent and if the sender is the workgroup + MUCUser mucUser = (MUCUser)msg.getExtension("x", "http://jabber.org/protocol/muc#user"); + MUCUser.Invite invite = mucUser != null ? mucUser.getInvite() : null; + if (invite != null && workgroupJID.equals(invite.getFrom())) { + String sessionID = null; + Map metaData = null; + + pe = msg.getExtension(SessionID.ELEMENT_NAME, + SessionID.NAMESPACE); + if (pe != null) { + sessionID = ((SessionID)pe).getSessionID(); + } + + pe = msg.getExtension(MetaData.ELEMENT_NAME, + MetaData.NAMESPACE); + if (pe != null) { + metaData = ((MetaData)pe).getMetaData(); + } + + WorkgroupInvitation inv = new WorkgroupInvitation(connection.getUser(), msg.getFrom(), + workgroupJID, sessionID, msg.getBody(), + msg.getFrom(), metaData); + + fireInvitationEvent(inv); + } + } + } + } + + /** + * IQ packet to request joining the workgroup queue. + */ + private class JoinQueuePacket extends IQ { + + private String userID = null; + private DataForm form; + + public JoinQueuePacket(String workgroup, Form answerForm, String userID) { + this.userID = userID; + + setTo(workgroup); + setType(IQ.Type.SET); + + form = answerForm.getDataFormToSend(); + addExtension(form); + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + + buf.append(""); + buf.append(""); + // Add the user unique identification if the session is anonymous + if (connection.isAnonymous()) { + buf.append(new UserID(userID).toXML()); + } + + // Append data form text + buf.append(form.toXML()); + + buf.append(""); + + return buf.toString(); + } + } + + /** + * Returns a single chat setting based on it's identified key. + * + * @param key the key to find. + * @return the ChatSetting if found, otherwise false. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public ChatSetting getChatSetting(String key) throws XMPPException { + ChatSettings chatSettings = getChatSettings(key, -1); + return chatSettings.getFirstEntry(); + } + + /** + * Returns ChatSettings based on type. + * + * @param type the type of ChatSettings to return. + * @return the ChatSettings of given type, otherwise null. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public ChatSettings getChatSettings(int type) throws XMPPException { + return getChatSettings(null, type); + } + + /** + * Returns all ChatSettings. + * + * @return all ChatSettings of a given workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public ChatSettings getChatSettings() throws XMPPException { + return getChatSettings(null, -1); + } + + + /** + * Asks the workgroup for it's Chat Settings. + * + * @return key specify a key to retrieve only that settings. Otherwise for all settings, key should be null. + * @throws XMPPException if an error occurs while getting information from the server. + */ + private ChatSettings getChatSettings(String key, int type) throws XMPPException { + ChatSettings request = new ChatSettings(); + if (key != null) { + request.setKey(key); + } + if (type != -1) { + request.setType(type); + } + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + ChatSettings response = (ChatSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * The workgroup service may be configured to send email. This queries the Workgroup Service + * to see if the email service has been configured and is available. + * + * @return true if the email service is available, otherwise return false. + */ + public boolean isEmailAvailable() { + ServiceDiscoveryManager discoManager = ServiceDiscoveryManager.getInstanceFor(connection); + + try { + String workgroupService = StringUtils.parseServer(workgroupJID); + DiscoverInfo infoResult = discoManager.discoverInfo(workgroupService); + return infoResult.containsFeature("jive:email:provider"); + } + catch (XMPPException e) { + return false; + } + } + + /** + * Asks the workgroup for it's Offline Settings. + * + * @return offlineSettings the offline settings for this workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public OfflineSettings getOfflineSettings() throws XMPPException { + OfflineSettings request = new OfflineSettings(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + OfflineSettings response = (OfflineSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Asks the workgroup for it's Sound Settings. + * + * @return soundSettings the sound settings for the specified workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public SoundSettings getSoundSettings() throws XMPPException { + SoundSettings request = new SoundSettings(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + SoundSettings response = (SoundSettings)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Asks the workgroup for it's Properties + * + * @return the WorkgroupProperties for the specified workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public WorkgroupProperties getWorkgroupProperties() throws XMPPException { + WorkgroupProperties request = new WorkgroupProperties(); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + /** + * Asks the workgroup for it's Properties + * + * @param jid the jid of the user who's information you would like the workgroup to retreive. + * @return the WorkgroupProperties for the specified workgroup. + * @throws XMPPException if an error occurs while getting information from the server. + */ + public WorkgroupProperties getWorkgroupProperties(String jid) throws XMPPException { + WorkgroupProperties request = new WorkgroupProperties(); + request.setJid(jid); + request.setType(IQ.Type.GET); + request.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(request.getPacketID())); + connection.sendPacket(request); + + + WorkgroupProperties response = (WorkgroupProperties)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return response; + } + + + /** + * Returns the Form to use for all clients of a workgroup. It is unlikely that the server + * will change the form (without a restart) so it is safe to keep the returned form + * for future submissions. + * + * @return the Form to use for searching transcripts. + * @throws XMPPException if an error occurs while sending the request to the server. + */ + public Form getWorkgroupForm() throws XMPPException { + WorkgroupForm workgroupForm = new WorkgroupForm(); + workgroupForm.setType(IQ.Type.GET); + workgroupForm.setTo(workgroupJID); + + PacketCollector collector = connection.createPacketCollector(new PacketIDFilter(workgroupForm.getPacketID())); + connection.sendPacket(workgroupForm); + + WorkgroupForm response = (WorkgroupForm)collector.nextResult(SmackConfiguration.getPacketReplyTimeout()); + + // Cancel the collector. + collector.cancel(); + if (response == null) { + throw new XMPPException("No response from server on status set."); + } + if (response.getError() != null) { + throw new XMPPException(response.getError()); + } + return Form.getFormFrom(response); + } + + /* + public static void main(String args[]) throws Exception { + Connection con = new XMPPConnection("anteros"); + con.connect(); + con.loginAnonymously(); + + Workgroup workgroup = new Workgroup("demo@workgroup.anteros", con); + WorkgroupProperties props = workgroup.getWorkgroupProperties("derek@anteros.com"); + + System.out.print(props); + con.disconnect(); + } + */ + + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java new file mode 100644 index 0000000..02ddc6e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ListenerEventDispatcher.java @@ -0,0 +1,131 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.util; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * This class is a very flexible event dispatcher which implements Runnable so that it can + * dispatch easily from a newly created thread. The usage of this in code is more or less: + * create a new instance of this class, use addListenerTriplet to add as many listeners + * as desired to be messaged, create a new Thread using the instance of this class created + * as the argument to the constructor, start the new Thread instance.

      + * + * Also, this is intended to be used to message methods that either return void, or have + * a return which the developer using this class is uninterested in receiving. + * + * @author loki der quaeler + */ +public class ListenerEventDispatcher + implements Runnable { + + protected transient ArrayList triplets; + + protected transient boolean hasFinishedDispatching; + protected transient boolean isRunning; + + public ListenerEventDispatcher () { + super(); + + this.triplets = new ArrayList(); + + this.hasFinishedDispatching = false; + this.isRunning = false; + } + + /** + * Add a listener triplet - the instance of the listener to be messaged, the Method on which + * the listener should be messaged, and the Object array of arguments to be supplied to the + * Method. No attempts are made to determine whether this triplet was already added.
      + * + * Messages are dispatched in the order in which they're added via this method; so if triplet + * X is added after triplet Z, then triplet Z will undergo messaging prior to triplet X.
      + * + * This method should not be called once the owning Thread instance has been started; if it + * is called, the triplet will not be added to the messaging queue.
      + * + * @param listenerInstance the instance of the listener to receive the associated notification + * @param listenerMethod the Method instance representing the method through which notification + * will occur + * @param methodArguments the arguments supplied to the notification method + */ + public void addListenerTriplet(Object listenerInstance, Method listenerMethod, + Object[] methodArguments) + { + if (!this.isRunning) { + this.triplets.add(new TripletContainer(listenerInstance, listenerMethod, + methodArguments)); + } + } + + /** + * @return whether this instance has finished dispatching its messages + */ + public boolean hasFinished() { + return this.hasFinishedDispatching; + } + + public void run() { + ListIterator li = null; + + this.isRunning = true; + + li = this.triplets.listIterator(); + while (li.hasNext()) { + TripletContainer tc = (TripletContainer)li.next(); + + try { + tc.getListenerMethod().invoke(tc.getListenerInstance(), tc.getMethodArguments()); + } catch (Exception e) { + System.err.println("Exception dispatching an event: " + e); + + e.printStackTrace(); + } + } + + this.hasFinishedDispatching = true; + } + + + protected class TripletContainer { + + protected Object listenerInstance; + protected Method listenerMethod; + protected Object[] methodArguments; + + protected TripletContainer (Object inst, Method meth, Object[] args) { + super(); + + this.listenerInstance = inst; + this.listenerMethod = meth; + this.methodArguments = args; + } + + protected Object getListenerInstance() { + return this.listenerInstance; + } + + protected Method getListenerMethod() { + return this.listenerMethod; + } + + protected Object[] getMethodArguments() { + return this.methodArguments; + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java new file mode 100644 index 0000000..46ed793 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/MetaDataUtils.java @@ -0,0 +1,111 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.util; + +import org.jivesoftware.smackx.workgroup.MetaData; +import org.jivesoftware.smack.util.StringUtils; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.*; + +/** + * Utility class for meta-data parsing and writing. + * + * @author Matt Tucker + */ +public class MetaDataUtils { + + /** + * Parses any available meta-data and returns it as a Map of String name/value pairs. The + * parser must be positioned at an opening meta-data tag, or the an empty map will be returned. + * + * @param parser the XML parser positioned at an opening meta-data tag. + * @return the meta-data. + * @throws XmlPullParserException if an error occurs while parsing the XML. + * @throws IOException if an error occurs while parsing the XML. + */ + public static Map parseMetaData(XmlPullParser parser) throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + + // If correctly positioned on an opening meta-data tag, parse meta-data. + if ((eventType == XmlPullParser.START_TAG) + && parser.getName().equals(MetaData.ELEMENT_NAME) + && parser.getNamespace().equals(MetaData.NAMESPACE)) { + Map metaData = new Hashtable(); + + eventType = parser.nextTag(); + + // Keep parsing until we've gotten to end of meta-data. + while ((eventType != XmlPullParser.END_TAG) + || (!parser.getName().equals(MetaData.ELEMENT_NAME))) { + String name = parser.getAttributeValue(0); + String value = parser.nextText(); + + if (metaData.containsKey(name)) { + List values = (List)metaData.get(name); + values.add(value); + } + else { + List values = new ArrayList(); + values.add(value); + metaData.put(name, values); + } + + eventType = parser.nextTag(); + } + + return metaData; + } + + return Collections.EMPTY_MAP; + } + + /** + * Serializes a Map of String name/value pairs into the meta-data XML format. + * + * @param metaData the Map of meta-data. + * @return the meta-data values in XML form. + */ + public static String serializeMetaData(Map metaData) { + StringBuilder buf = new StringBuilder(); + if (metaData != null && metaData.size() > 0) { + buf.append(""); + for (Iterator i = metaData.keySet().iterator(); i.hasNext();) { + Object key = i.next(); + Object value = metaData.get(key); + if (value instanceof List) { + List values = (List)metaData.get(key); + for (Iterator it = values.iterator(); it.hasNext();) { + String v = (String)it.next(); + buf.append(""); + buf.append(StringUtils.escapeForXML(v)); + buf.append(""); + } + } + else if (value instanceof String) { + buf.append(""); + buf.append(StringUtils.escapeForXML((String)value)); + buf.append(""); + } + } + buf.append(""); + } + return buf.toString(); + } +} diff --git a/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java new file mode 100644 index 0000000..fa183a5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jivesoftware/smackx/workgroup/util/ModelUtil.java @@ -0,0 +1,323 @@ +/** + * Copyright 2003-2007 Jive Software. + * + * All rights reserved. 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 org.jivesoftware.smackx.workgroup.util; + +import java.util.*; + +/** + * Utility methods frequently used by data classes and design-time + * classes. + */ +public final class ModelUtil { + private ModelUtil() { + // Prevents instantiation. + } + + /** + * This is a utility method that compares two objects when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

        + *
      1. If o1 and o2 are the same object + * according to the == operator, return + * true. + *
      2. Otherwise, if either o1 or o2 is + * null, return false. + *
      3. Otherwise, return o1.equals(o2). + *
      + *

      + * This method produces the exact logically inverted result as the + * {@link #areDifferent(Object, Object)} method.

      + *

      + * For array types, one of the equals methods in + * {@link java.util.Arrays} should be used instead of this method. + * Note that arrays with more than one dimension will require some + * custom code in order to implement equals properly. + */ + public static final boolean areEqual(Object o1, Object o2) { + if (o1 == o2) { + return true; + } + else if (o1 == null || o2 == null) { + return false; + } + else { + return o1.equals(o2); + } + } + + /** + * This is a utility method that compares two Booleans when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

        + *
      1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return true. + *
      2. Otherwise, return false. + *
      + *

      + */ + public static final boolean areBooleansEqual(Boolean b1, Boolean b2) { + // !jwetherb treat NULL the same as Boolean.FALSE + return (b1 == Boolean.TRUE && b2 == Boolean.TRUE) || + (b1 != Boolean.TRUE && b2 != Boolean.TRUE); + } + + /** + * This is a utility method that compares two objects when one or + * both of the objects might be null. The result + * returned by this method is determined as follows: + *

        + *
      1. If o1 and o2 are the same object + * according to the == operator, return + * false. + *
      2. Otherwise, if either o1 or o2 is + * null, return true. + *
      3. Otherwise, return !o1.equals(o2). + *
      + *

      + * This method produces the exact logically inverted result as the + * {@link #areEqual(Object, Object)} method.

      + *

      + * For array types, one of the equals methods in + * {@link java.util.Arrays} should be used instead of this method. + * Note that arrays with more than one dimension will require some + * custom code in order to implement equals properly. + */ + public static final boolean areDifferent(Object o1, Object o2) { + return !areEqual(o1, o2); + } + + + /** + * This is a utility method that compares two Booleans when one or + * both of the objects might be null The result of + * this method is determined as follows: + *

        + *
      1. If b1 and b2 are both TRUE or + * neither b1 nor b2 is TRUE, + * return false. + *
      2. Otherwise, return true. + *
      + *

      + * This method produces the exact logically inverted result as the + * {@link #areBooleansEqual(Boolean, Boolean)} method.

      + */ + public static final boolean areBooleansDifferent(Boolean b1, Boolean b2) { + return !areBooleansEqual(b1, b2); + } + + + /** + * Returns true if the specified array is not null + * and contains a non-null element. Returns false + * if the array is null or if all the array elements are null. + */ + public static final boolean hasNonNullElement(Object[] array) { + if (array != null) { + final int n = array.length; + for (int i = 0; i < n; i++) { + if (array[i] != null) { + return true; + } + } + } + return false; + } + + /** + * Returns a single string that is the concatenation of all the + * strings in the specified string array. A single space is + * put between each string array element. Null array elements + * are skipped. If the array itself is null, the empty string + * is returned. This method is guaranteed to return a non-null + * value, if no expections are thrown. + */ + public static final String concat(String[] strs) { + return concat(strs, " "); //NOTRANS + } + + /** + * Returns a single string that is the concatenation of all the + * strings in the specified string array. The strings are separated + * by the specified delimiter. Null array elements are skipped. If + * the array itself is null, the empty string is returned. This + * method is guaranteed to return a non-null value, if no expections + * are thrown. + */ + public static final String concat(String[] strs, String delim) { + if (strs != null) { + final StringBuilder buf = new StringBuilder(); + final int n = strs.length; + for (int i = 0; i < n; i++) { + final String str = strs[i]; + if (str != null) { + buf.append(str).append(delim); + } + } + final int length = buf.length(); + if (length > 0) { + // Trim trailing space. + buf.setLength(length - 1); + } + return buf.toString(); + } + else { + return ""; // NOTRANS + } + } + + /** + * Returns true if the specified {@link String} is not + * null and has a length greater than zero. This is + * a very frequently occurring check. + */ + public static final boolean hasLength(String s) { + return (s != null && s.length() > 0); + } + + + /** + * Returns null if the specified string is empty or + * null. Otherwise the string itself is returned. + */ + public static final String nullifyIfEmpty(String s) { + return ModelUtil.hasLength(s) ? s : null; + } + + /** + * Returns null if the specified object is null + * or if its toString() representation is empty. + * Otherwise, the toString() representation of the + * object itself is returned. + */ + public static final String nullifyingToString(Object o) { + return o != null ? nullifyIfEmpty(o.toString()) : null; + } + + /** + * Determines if a string has been changed. + * + * @param oldString is the initial value of the String + * @param newString is the new value of the String + * @return true If both oldString and newString are null or if they are + * both not null and equal to each other. Otherwise returns false. + */ + public static boolean hasStringChanged(String oldString, String newString) { + if (oldString == null && newString == null) { + return false; + } + else if ((oldString == null && newString != null) + || (oldString != null && newString == null)) { + return true; + } + else { + return !oldString.equals(newString); + } + } + + public static String getTimeFromLong(long diff) { + final String HOURS = "h"; + final String MINUTES = "min"; + final String SECONDS = "sec"; + + final long MS_IN_A_DAY = 1000 * 60 * 60 * 24; + final long MS_IN_AN_HOUR = 1000 * 60 * 60; + final long MS_IN_A_MINUTE = 1000 * 60; + final long MS_IN_A_SECOND = 1000; + Date currentTime = new Date(); + long numDays = diff / MS_IN_A_DAY; + diff = diff % MS_IN_A_DAY; + long numHours = diff / MS_IN_AN_HOUR; + diff = diff % MS_IN_AN_HOUR; + long numMinutes = diff / MS_IN_A_MINUTE; + diff = diff % MS_IN_A_MINUTE; + long numSeconds = diff / MS_IN_A_SECOND; + diff = diff % MS_IN_A_SECOND; + long numMilliseconds = diff; + + StringBuilder buf = new StringBuilder(); + if (numHours > 0) { + buf.append(numHours + " " + HOURS + ", "); + } + + if (numMinutes > 0) { + buf.append(numMinutes + " " + MINUTES + ", "); + } + + buf.append(numSeconds + " " + SECONDS); + + String result = buf.toString(); + return result; + } + + + /** + * Build a List of all elements in an Iterator. + */ + public static List iteratorAsList(Iterator i) { + ArrayList list = new ArrayList(10); + while (i.hasNext()) { + list.add(i.next()); + } + return list; + } + + /** + * Creates an Iterator that is the reverse of a ListIterator. + */ + public static Iterator reverseListIterator(ListIterator i) { + return new ReverseListIterator(i); + } +} + +/** + * An Iterator that is the reverse of a ListIterator. + */ +class ReverseListIterator implements Iterator { + private ListIterator _i; + + ReverseListIterator(ListIterator i) { + _i = i; + while (_i.hasNext()) + _i.next(); + } + + public boolean hasNext() { + return _i.hasPrevious(); + } + + public Object next() { + return _i.previous(); + } + + public void remove() { + _i.remove(); + } +} + + + + + + + + + + + diff --git a/external/asmack/build/src/trunk/org/jmdns/JmDNS.java b/external/asmack/build/src/trunk/org/jmdns/JmDNS.java new file mode 100644 index 0000000..946166f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/JmDNS.java @@ -0,0 +1,208 @@ +///Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns; + +import java.io.IOException; +import java.net.InetAddress; + +import org.jmdns.impl.JmDNSImpl; + +/** + * mDNS implementation in Java. + * + * @version %I%, %G% + * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, + * Werner Randelshofer, Pierre Frisch, Scott Lewis, Scott Cytacki + */ +public abstract class JmDNS +{ + /** + * The version of JmDNS. + */ + public static String VERSION = "2.0"; + + /** + * Create an instance of JmDNS. + */ + public static JmDNS create() throws IOException + { + return new JmDNSImpl(); + } + + /** + * Create an instance of JmDNS and bind it to a + * specific network interface given its IP-address. + */ + public static JmDNS create(InetAddress addr) throws IOException + { + return new JmDNSImpl(addr); + } + + /** + * Return the HostName associated with this JmDNS instance. + * Note: May not be the same as what started. The host name is subject to + * negotiation. + */ + public abstract String getHostName(); + + /** + * Return the address of the interface to which this instance of JmDNS is + * bound. + */ + public abstract InetAddress getInterface() throws IOException; + + /** + * Get service information. If the information is not cached, the method + * will block until updated information is received. + *

      + * Usage note: Do not call this method from the AWT event dispatcher thread. + * You will make the user interface unresponsive. + * + * @param type fully qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + * @return null if the service information cannot be obtained + */ + public abstract ServiceInfo getServiceInfo(String type, String name); + + /** + * Get service information. If the information is not cached, the method + * will block for the given timeout until updated information is received. + *

      + * Usage note: If you call this method from the AWT event dispatcher thread, + * use a small timeout, or you will make the user interface unresponsive. + * + * @param type full qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + * @param timeout timeout in milliseconds + * @return null if the service information cannot be obtained + */ + public abstract ServiceInfo getServiceInfo(String type, String name, int timeout); + + /** + * Request service information. The information about the service is + * requested and the ServiceListener.resolveService method is called as soon + * as it is available. + *

      + * Usage note: Do not call this method from the AWT event dispatcher thread. + * You will make the user interface unresponsive. + * + * @param type full qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + */ + public abstract void requestServiceInfo(String type, String name); + + /** + * Request service information. The information about the service is + * requested and the ServiceListener.resolveService method is called as soon + * as it is available. + *

      + * Usage note: Do not call this method from the AWT event dispatcher thread. + * You will make the user interface unresponsive. + * + * @param type full qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + * @param persistent if true ServiceListener.resolveService will be called whenever new new information is received. + */ + public abstract void requestServiceInfo(String type, String name, boolean persistent); + + /** + * Request service information. The information about the service is requested + * and the ServiceListener.resolveService method is called as soon as it is available. + * + * @param type full qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + * @param timeout timeout in milliseconds + */ + public abstract void requestServiceInfo(String type, String name, int timeout); + + /** + * Request service information. The information about the service is requested + * and the ServiceListener.resolveService method is called as soon as it is available. + * + * @param type full qualified service type, such as _http._tcp.local. . + * @param name unqualified service name, such as foobar . + * @param persistent if true ServiceListener.resolveService will be called whenever new new information is received. + * @param timeout timeout in milliseconds + */ + public abstract void requestServiceInfo(String type, String name, boolean persistent, int timeout); + + /** + * Listen for service types. + * + * @param listener listener for service types + */ + public abstract void addServiceTypeListener(ServiceTypeListener listener) throws IOException; + + /** + * Remove listener for service types. + * + * @param listener listener for service types + */ + public abstract void removeServiceTypeListener(ServiceTypeListener listener); + + /** + * Listen for services of a given type. The type has to be a fully qualified + * type name such as _http._tcp.local.. + * + * @param type full qualified service type, such as _http._tcp.local.. + * @param listener listener for service updates + */ + public abstract void addServiceListener(String type, ServiceListener listener); + + /** + * Remove listener for services of a given type. + * + * @param listener listener for service updates + */ + public abstract void removeServiceListener(String type, ServiceListener listener); + + /** + * Register a service. The service is registered for access by other jmdns clients. + * The name of the service may be changed to make it unique. + */ + public abstract void registerService(ServiceInfo info) throws IOException; + + /** Reannounce a service. + */ + public abstract void reannounceService(ServiceInfo info) throws IOException; + + /** + * Unregister a service. The service should have been registered. + */ + public abstract void unregisterService(ServiceInfo info); + + /** + * Unregister all services. + */ + public abstract void unregisterAllServices(); + + /** + * Register a service type. If this service type was not already known, + * all service listeners will be notified of the new service type. Service types + * are automatically registered as they are discovered. + */ + public abstract void registerServiceType(String type); + + /** + * Close down jmdns. Release all resources and unregister all services. + */ + public abstract void close(); + + /** + * List Services and serviceTypes. + * Debugging Only + */ + public abstract void printServices(); + + /** + * Returns a list of service infos of the specified type. + * + * @param type Service type name, such as _http._tcp.local.. + * @return An array of service instance names. + */ + public abstract ServiceInfo[] list(String type); + +} diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java b/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java new file mode 100644 index 0000000..8ad687f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/ServiceEvent.java @@ -0,0 +1,43 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns; + +import java.util.EventObject; + +public abstract class ServiceEvent extends EventObject +{ + + public ServiceEvent(Object source) + { + super(source); + // TODO Auto-generated constructor stub + } + + /** + * Returns the JmDNS instance which originated the event. + */ + public abstract JmDNS getDNS(); + + /** + * Returns the fully qualified type of the service. + */ + public abstract String getType(); + + /** + * Returns the instance name of the service. + * Always returns null, if the event is sent to a service type listener. + */ + public abstract String getName(); + + /** + * Returns the service info record, or null if the service could not be + * resolved. + * Always returns null, if the event is sent to a service type listener. + */ + /** + * @see org.jmdns.ServiceEvent#getInfo() + */ + public abstract ServiceInfo getInfo(); +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java b/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java new file mode 100644 index 0000000..5a71ceb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/ServiceInfo.java @@ -0,0 +1,196 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL +package org.jmdns; + +import java.net.InetAddress; +import java.util.Enumeration; +import java.util.Hashtable; + +import org.jmdns.impl.ServiceInfoImpl; + +public abstract class ServiceInfo +{ + public final static byte[] NO_VALUE = new byte[0]; + + /** + * Construct a service description for registrating with JmDNS. + * + * @param type fully qualified service type name, such as _http._tcp.local.. + * @param name unqualified service instance name, such as foobar + * @param port the local port on which the service runs + * @param text string describing the service + */ + public static ServiceInfo create(String type, String name, int port, String text) + { + return new ServiceInfoImpl(type, name, port, text); + } + + /** + * Construct a service description for registrating with JmDNS. + * + * @param type fully qualified service type name, such as _http._tcp.local.. + * @param name unqualified service instance name, such as foobar + * @param port the local port on which the service runs + * @param weight weight of the service + * @param priority priority of the service + * @param text string describing the service + */ + public static ServiceInfo create(String type, String name, int port, int weight, int priority, String text) + { + return new ServiceInfoImpl(type, name, port, weight, priority, text); + } + + /** + * Construct a service description for registrating with JmDNS. The properties hashtable must + * map property names to either Strings or byte arrays describing the property values. + * + * @param type fully qualified service type name, such as _http._tcp.local.. + * @param name unqualified service instance name, such as foobar + * @param port the local port on which the service runs + * @param weight weight of the service + * @param priority priority of the service + * @param props properties describing the service + */ + public static ServiceInfo create(String type, String name, int port, int weight, int priority, Hashtable props) + { + return new ServiceInfoImpl(type, name, port, weight, priority, props); + } + + /** + * Construct a service description for registrating with JmDNS. + * + * @param type fully qualified service type name, such as _http._tcp.local.. + * @param name unqualified service instance name, such as foobar + * @param port the local port on which the service runs + * @param weight weight of the service + * @param priority priority of the service + * @param text bytes describing the service + */ + public static ServiceInfo create(String type, String name, int port, int weight, int priority, byte text[]) + { + return new ServiceInfoImpl(type, name, port, weight, priority, text); + } + + /** + * Fully qualified service type name, such as _http._tcp.local. . + */ + public abstract String getType(); + + /** + * Unqualified service instance name, such as foobar . + */ + public abstract String getName(); + + /** + * Fully qualified service name, such as foobar._http._tcp.local. . + */ + public abstract String getQualifiedName(); + + /** + * Get the name of the server. + */ + public abstract String getServer(); + + /** + * Get the host address of the service (ie X.X.X.X). + */ + public abstract String getHostAddress(); + + public abstract InetAddress getAddress(); + + /** + * Get the InetAddress of the service. + */ + public abstract InetAddress getInetAddress(); + + /** + * Get the port for the service. + */ + public abstract int getPort(); + + /** + * Get the priority of the service. + */ + public abstract int getPriority(); + + /** + * Get the weight of the service. + */ + public abstract int getWeight(); + + /** + * Get the text for the serivce as raw bytes. + */ + public abstract byte[] getTextBytes(); + + /** + * Get the text for the service. This will interpret the text bytes + * as a UTF8 encoded string. Will return null if the bytes are not + * a valid UTF8 encoded string. + */ + public abstract String getTextString(); + + /** + * Get the URL for this service. An http URL is created by + * combining the address, port, and path properties. + */ + public abstract String getURL(); + + /** + * Get the URL for this service. An URL is created by + * combining the protocol, address, port, and path properties. + */ + public abstract String getURL(String protocol); + + /** + * Get a property of the service. This involves decoding the + * text bytes into a property list. Returns null if the property + * is not found or the text data could not be decoded correctly. + */ + public abstract byte[] getPropertyBytes(String name); + + /** + * Get a property of the service. This involves decoding the + * text bytes into a property list. Returns null if the property + * is not found, the text data could not be decoded correctly, or + * the resulting bytes are not a valid UTF8 string. + */ + public abstract String getPropertyString(String name); + + /** + * Enumeration of the property names. + */ + public abstract Enumeration getPropertyNames(); + + public abstract String getNiceTextString(); + + /** + * Set the text for the service. + * + * @param text the raw byte representation of the text field. + */ + public abstract void setText(byte [] text); + + /** + * Set the text for the service. + * + * @param props a key=value map that will be encoded into raw bytes. + */ + public abstract void setText(Hashtable props); + + /** + * Add a service name listener. A service name listener is notified when + * the service name of a service is changed. + * + * @param listener the listener to be added. + */ + public abstract void addServiceNameListener(ServiceNameListener listener); + + /** + * Remove a service name listener. + * + * @param listener the listener to be removed + */ + public abstract void removeServiceNameListener(ServiceNameListener listener); +} diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java new file mode 100644 index 0000000..fb45385 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/ServiceListener.java @@ -0,0 +1,42 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns; + +import java.util.EventListener; + +/** + * Listener for service updates. + * + * @version %I%, %G% + * @author Arthur van Hoff, Werner Randelshofer + */ +public interface ServiceListener extends EventListener +{ + /** + * A service has been added. + * + * @param event The ServiceEvent providing the name and fully qualified type + * of the service. + */ + void serviceAdded(ServiceEvent event); + + /** + * A service has been removed. + * + * @param event The ServiceEvent providing the name and fully qualified type + * of the service. + */ + void serviceRemoved(ServiceEvent event); + + /** + * A service has been resolved. Its details are now available in the + * ServiceInfo record. + * + * @param event The ServiceEvent providing the name, the fully qualified + * type of the service, and the service info record, or null if the service + * could not be resolved. + */ + void serviceResolved(ServiceEvent event); +} diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java new file mode 100644 index 0000000..4bd6fc3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/ServiceNameListener.java @@ -0,0 +1,16 @@ +//Copyright 2009 Jonas Ã…dahl +//Licensed under Apache License version 2.0 + +package org.jmdns; + +/** + * Listener class used for notifications about service names of service + * being changed. + */ +public interface ServiceNameListener { + + /** + * Called when the name of a service info has been changed. + */ + public void serviceNameChanged(String newName, String oldName); +} diff --git a/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java b/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java new file mode 100644 index 0000000..f4bccda --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/ServiceTypeListener.java @@ -0,0 +1,24 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns; + +import java.util.EventListener; + +/** + * Listener for service types. + * + * @version %I%, %G% + * @author Arthur van Hoff, Werner Randelshofer + */ +public interface ServiceTypeListener extends EventListener +{ + /** + * A new service type was discovered. + * + * @param event The service event providing the fully qualified type of + * the service. + */ + void serviceTypeAdded(ServiceEvent event); +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java new file mode 100644 index 0000000..191e2de --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSCache.java @@ -0,0 +1,252 @@ +//Copyright 2003-2005 Arthur van Hoff Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.logging.Logger; + +/** + * A table of DNS entries. This is a hash table which can handle multiple + * entries with the same name.

      Storing multiple entries with the same name + * is implemented using a linked list of CacheNode's.

      The + * current implementation of the API of DNSCache does expose the cache nodes to + * clients. Clients must explicitly deal with the nodes when iterating over + * entries in the cache. Here's how to iterate over all entries in the cache: + * + *

      + * for (Iterator i=dnscache.iterator(); i.hasNext(); ) {
      + *    for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n.next()) {
      + *       DNSEntry entry = n.getValue();
      + *       ...do something with entry...
      + *    }
      + * }
      + * 
      + * + *

      And here's how to iterate over all entries having a given name: + * + *

      + * for (DNSCache.CacheNode n = (DNSCache.CacheNode) dnscache.find(name); n != null; n.next()) {
      + *     DNSEntry entry = n.getValue();
      + *     ...do something with entry...
      + * }
      + * 
      + * + * @version %I%, %G% + * @author Arthur van Hoff, Werner Randelshofer, Rick Blair + */ +public class DNSCache +{ + private static Logger logger = Logger.getLogger(DNSCache.class.getName()); + // Implementation note: + // We might completely hide the existence of CacheNode's in a future version + // of DNSCache. But this will require to implement two (inner) classes for + // the iterators that will be returned by method iterator() and + // method find(name). + // Since DNSCache is not a public class, it does not seem worth the effort + // to clean its API up that much. + + // [PJYF Oct 15 2004] This should implements Collections that would be amuch + // cleaner implementation + + /** + * The number of DNSEntry's in the cache. + */ + private int size; + + /** + * The hashtable used internally to store the entries of the cache. Keys are + * instances of String. The String contains an unqualified service name. + * Values are linked lists of CacheNode instances. + */ + private final HashMap hashtable; + + /** + * Cache nodes are used to implement storage of multiple DNSEntry's of the + * same name in the cache. + */ + public static class CacheNode + { + private static Logger logger = Logger.getLogger(CacheNode.class.getName()); + private final DNSEntry value; + private CacheNode next; + + public CacheNode(DNSEntry value) + { + this.value = value; + } + + public CacheNode next() + { + return next; + } + + public DNSEntry getValue() + { + return value; + } + } + + /** + * Create a table with a given initial size. + */ + public DNSCache(final int size) + { + hashtable = new HashMap(size); + } + + /** + * Clears the cache. + */ + public synchronized void clear() + { + hashtable.clear(); + size = 0; + } + + /** + * Adds an entry to the table. + */ + public synchronized void add(final DNSEntry entry) + { + // logger.log("DNSCache.add("+entry.getName()+")"); + final CacheNode newValue = new CacheNode(entry); + final CacheNode node = (CacheNode) hashtable.get(entry.getName()); + if (node == null) + { + hashtable.put(entry.getName(), newValue); + } + else + { + newValue.next = node.next; + node.next = newValue; + } + size++; + } + + /** + * Remove a specific entry from the table. Returns true if the entry was + * found. + */ + public synchronized boolean remove(DNSEntry entry) + { + CacheNode node = (CacheNode) hashtable.get(entry.getName()); + if (node != null) + { + if (node.value == entry) + { + if (node.next == null) + { + hashtable.remove(entry.getName()); + } + else + { + hashtable.put(entry.getName(), node.next); + } + size--; + return true; + } + + CacheNode previous = node; + node = node.next; + while (node != null) + { + if (node.value == entry) + { + previous.next = node.next; + size--; + return true; + } + previous = node; + node = node.next; + } + ; + } + return false; + } + + /** + * Get a matching DNS entry from the table (using equals). Returns the entry + * that was found. + */ + public synchronized DNSEntry get(DNSEntry entry) + { + for (CacheNode node = find(entry.getName()); node != null; node = node.next) + { + if (node.value.equals(entry)) + { + return node.value; + } + } + return null; + } + + /** + * Get a matching DNS entry from the table. + */ + public synchronized DNSEntry get(String name, int type, int clazz) + { + for (CacheNode node = find(name); node != null; node = node.next) + { + if (node.value.type == type && node.value.clazz == clazz) + { + return node.value; + } + } + return null; + } + + /** + * Iterates over all cache nodes. The iterator returns instances of + * DNSCache.CacheNode. Each instance returned is the first node of a linked + * list. To retrieve all entries, one must iterate over this linked list. + * See code snippets in the header of the class. + */ + public synchronized Iterator iterator() + { + return new ArrayList(hashtable.values()).iterator(); + } + + /** + * Iterate only over items with matching name. Returns an instance of + * DNSCache.CacheNode or null. If an instance is returned, it is the first + * node of a linked list. To retrieve all entries, one must iterate over + * this linked list. + */ + public synchronized CacheNode find(String name) + { + return (CacheNode) hashtable.get(name); + } + + /** + * List all entries for debugging. + */ + public synchronized void print() + { + for (final Iterator i = iterator(); i.hasNext();) + { + for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next) + { + System.out.println(n.value); + } + } + } + + public synchronized String toString() + { + final StringBuffer aLog = new StringBuffer(); + aLog.append("\t---- cache ----"); + for (final Iterator i = iterator(); i.hasNext();) + { + for (CacheNode n = (CacheNode) i.next(); n != null; n = n.next) + { + aLog.append("\n\t\t" + n.value); + } + } + return aLog.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java new file mode 100644 index 0000000..f4c3799 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSConstants.java @@ -0,0 +1,125 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +/** + * DNS constants. + * + * @version %I%, %G% + * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer, Pierre Frisch, Rick Blair + */ +public final class DNSConstants +{ + + // changed to final class - jeffs + public final static String MDNS_GROUP = "224.0.0.251"; + public final static String MDNS_GROUP_IPV6 = "FF02::FB"; + public final static int MDNS_PORT = 5353; + public final static int DNS_PORT = 53; + public final static int DNS_TTL = 60 * 60; // default one hour TTL + // public final static int DNS_TTL = 120 * 60; // two hour TTL (draft-cheshire-dnsext-multicastdns.txt ch 13) + + public final static int MAX_MSG_TYPICAL = 1460; + public final static int MAX_MSG_ABSOLUTE = 8972; + + public final static int FLAGS_QR_MASK = 0x8000; // Query response mask + public final static int FLAGS_QR_QUERY = 0x0000; // Query + public final static int FLAGS_QR_RESPONSE = 0x8000; // Response + + public final static int FLAGS_AA = 0x0400; // Authorative answer + public final static int FLAGS_TC = 0x0200; // Truncated + public final static int FLAGS_RD = 0x0100; // Recursion desired + public final static int FLAGS_RA = 0x8000; // Recursion available + + public final static int FLAGS_Z = 0x0040; // Zero + public final static int FLAGS_AD = 0x0020; // Authentic data + public final static int FLAGS_CD = 0x0010; // Checking disabled + + public final static int CLASS_IN = 1; // public final static Internet + public final static int CLASS_CS = 2; // CSNET + public final static int CLASS_CH = 3; // CHAOS + public final static int CLASS_HS = 4; // Hesiod + public final static int CLASS_NONE = 254; // Used in DNS UPDATE [RFC 2136] + public final static int CLASS_ANY = 255; // Not a DNS class, but a DNS query class, meaning "all classes" + public final static int CLASS_MASK = 0x7FFF; // Multicast DNS uses the bottom 15 bits to identify the record class... + public final static int CLASS_UNIQUE = 0x8000; // ... and the top bit indicates that all other cached records are now invalid + + public final static int TYPE_IGNORE = 0; // This is a hack to stop further processing + public final static int TYPE_A = 1; // Address + public final static int TYPE_NS = 2; // Name Server + public final static int TYPE_MD = 3; // Mail Destination + public final static int TYPE_MF = 4; // Mail Forwarder + public final static int TYPE_CNAME = 5; // Canonical Name + public final static int TYPE_SOA = 6; // Start of Authority + public final static int TYPE_MB = 7; // Mailbox + public final static int TYPE_MG = 8; // Mail Group + public final static int TYPE_MR = 9; // Mail Rename + public final static int TYPE_NULL = 10; // NULL RR + public final static int TYPE_WKS = 11; // Well-known-service + public final static int TYPE_PTR = 12; // Domain Name popublic final static inter + public final static int TYPE_HINFO = 13; // Host information + public final static int TYPE_MINFO = 14; // Mailbox information + public final static int TYPE_MX = 15; // Mail exchanger + public final static int TYPE_TXT = 16; // Arbitrary text string + public final static int TYPE_RP = 17; // for Responsible Person [RFC1183] + public final static int TYPE_AFSDB = 18; // for AFS Data Base location [RFC1183] + public final static int TYPE_X25 = 19; // for X.25 PSDN address [RFC1183] + public final static int TYPE_ISDN = 20; // for ISDN address [RFC1183] + public final static int TYPE_RT = 21; // for Route Through [RFC1183] + public final static int TYPE_NSAP = 22; // for NSAP address, NSAP style A record [RFC1706] + public final static int TYPE_NSAP_PTR = 23; // + public final static int TYPE_SIG = 24; // for security signature [RFC2931] + public final static int TYPE_KEY = 25; // for security key [RFC2535] + public final static int TYPE_PX = 26; // X.400 mail mapping information [RFC2163] + public final static int TYPE_GPOS = 27; // Geographical Position [RFC1712] + public final static int TYPE_AAAA = 28; // IP6 Address [Thomson] + public final static int TYPE_LOC = 29; // Location Information [Vixie] + public final static int TYPE_NXT = 30; // Next Domain - OBSOLETE [RFC2535, RFC3755] + public final static int TYPE_EID = 31; // Endpoint Identifier [Patton] + public final static int TYPE_NIMLOC = 32; // Nimrod Locator [Patton] + public final static int TYPE_SRV = 33; // Server Selection [RFC2782] + public final static int TYPE_ATMA = 34; // ATM Address [Dobrowski] + public final static int TYPE_NAPTR = 35; // Naming Authority Pointer [RFC2168, RFC2915] + public final static int TYPE_KX = 36; // Key Exchanger [RFC2230] + public final static int TYPE_CERT = 37; // CERT [RFC2538] + public final static int TYPE_A6 = 38; // A6 [RFC2874] + public final static int TYPE_DNAME = 39; // DNAME [RFC2672] + public final static int TYPE_SINK = 40; // SINK [Eastlake] + public final static int TYPE_OPT = 41; // OPT [RFC2671] + public final static int TYPE_APL = 42; // APL [RFC3123] + public final static int TYPE_DS = 43; // Delegation Signer [RFC3658] + public final static int TYPE_SSHFP = 44; // SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt] + public final static int TYPE_RRSIG = 46; // RRSIG [RFC3755] + public final static int TYPE_NSEC = 47; // NSEC [RFC3755] + public final static int TYPE_DNSKEY = 48; // DNSKEY [RFC3755] + public final static int TYPE_UINFO = 100; // [IANA-Reserved] + public final static int TYPE_UID = 101; // [IANA-Reserved] + public final static int TYPE_GID = 102; // [IANA-Reserved] + public final static int TYPE_UNSPEC = 103; // [IANA-Reserved] + public final static int TYPE_TKEY = 249; // Transaction Key [RFC2930] + public final static int TYPE_TSIG = 250; // Transaction Signature [RFC2845] + public final static int TYPE_IXFR = 251; // Incremental transfer [RFC1995] + public final static int TYPE_AXFR = 252; // Transfer of an entire zone [RFC1035] + public final static int TYPE_MAILA = 253; // Mailbox-related records (MB, MG or MR) [RFC1035] + public final static int TYPE_MAILB = 254; // Mail agent RRs (Obsolete - see MX) [RFC1035] + public final static int TYPE_ANY = 255; // Request for all records [RFC1035] + + //Time Intervals for various functions + + public final static int SHARED_QUERY_TIME = 20; //milliseconds before send shared query + public final static int QUERY_WAIT_INTERVAL = 225; //milliseconds between query loops. + public final static int PROBE_WAIT_INTERVAL = 250; //milliseconds between probe loops. + public final static int RESPONSE_MIN_WAIT_INTERVAL = 20; //minimal wait interval for response. + public final static int RESPONSE_MAX_WAIT_INTERVAL = 115; //maximal wait interval for response + public final static int PROBE_CONFLICT_INTERVAL = 1000; //milliseconds to wait after conflict. + public final static int PROBE_THROTTLE_COUNT = 10; //After x tries go 1 time a sec. on probes. + public final static int PROBE_THROTTLE_COUNT_INTERVAL = 5000; //We only increment the throttle count, if + // the previous increment is inside this interval. + public final static int ANNOUNCE_WAIT_INTERVAL = 1000; //milliseconds between Announce loops. + public final static int RECORD_REAPER_INTERVAL = 10000; //milliseconds between cache cleanups. + public final static int KNOWN_ANSWER_TTL = 120; + public final static int ANNOUNCED_RENEWAL_TTL_INTERVAL = DNS_TTL * 500; // 50% of the TTL in milliseconds +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java new file mode 100644 index 0000000..617439f --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSEntry.java @@ -0,0 +1,148 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +import java.util.logging.Logger; + +/** + * DNS entry with a name, type, and class. This is the base + * class for questions and records. + * + * @version %I%, %G% + * @author Arthur van Hoff, Pierre Frisch, Rick Blair + */ +public class DNSEntry +{ + private static Logger logger = Logger.getLogger(DNSEntry.class.getName()); + String key; + String name; + int type; + int clazz; + boolean unique; + + /** + * Create an entry. + */ + DNSEntry(String name, int type, int clazz) + { + this.key = name.toLowerCase(); + this.name = name; + this.type = type; + this.clazz = clazz & DNSConstants.CLASS_MASK; + this.unique = (clazz & DNSConstants.CLASS_UNIQUE) != 0; + } + + /** + * Check if two entries have exactly the same name, type, and class. + */ + public boolean equals(Object obj) + { + if (obj instanceof DNSEntry) + { + DNSEntry other = (DNSEntry) obj; + return name.equals(other.name) && type == other.type && clazz == other.clazz; + } + return false; + } + + public String getName() + { + return name; + } + + public int getType() + { + return type; + } + + /** + * Overriden, to return a value which is consistent with the value returned + * by equals(Object). + */ + public int hashCode() + { + return name.hashCode() + type + clazz; + } + + /** + * Get a string given a clazz. + */ + static String getClazz(int clazz) + { + switch (clazz & DNSConstants.CLASS_MASK) + { + case DNSConstants.CLASS_IN: + return "in"; + case DNSConstants.CLASS_CS: + return "cs"; + case DNSConstants.CLASS_CH: + return "ch"; + case DNSConstants.CLASS_HS: + return "hs"; + case DNSConstants.CLASS_NONE: + return "none"; + case DNSConstants.CLASS_ANY: + return "any"; + default: + return "?"; + } + } + + /** + * Get a string given a type. + */ + static String getType(int type) + { + switch (type) + { + case DNSConstants.TYPE_A: + return "a"; + case DNSConstants.TYPE_AAAA: + return "aaaa"; + case DNSConstants.TYPE_NS: + return "ns"; + case DNSConstants.TYPE_MD: + return "md"; + case DNSConstants.TYPE_MF: + return "mf"; + case DNSConstants.TYPE_CNAME: + return "cname"; + case DNSConstants.TYPE_SOA: + return "soa"; + case DNSConstants.TYPE_MB: + return "mb"; + case DNSConstants.TYPE_MG: + return "mg"; + case DNSConstants.TYPE_MR: + return "mr"; + case DNSConstants.TYPE_NULL: + return "null"; + case DNSConstants.TYPE_WKS: + return "wks"; + case DNSConstants.TYPE_PTR: + return "ptr"; + case DNSConstants.TYPE_HINFO: + return "hinfo"; + case DNSConstants.TYPE_MINFO: + return "minfo"; + case DNSConstants.TYPE_MX: + return "mx"; + case DNSConstants.TYPE_TXT: + return "txt"; + case DNSConstants.TYPE_SRV: + return "srv"; + case DNSConstants.TYPE_ANY: + return "any"; + default: + return "?"; + } + } + + public String toString(String hdr, String other) + { + return hdr + "[" + getType(type) + "," + getClazz(clazz) + (unique ? "-unique," : ",") + name + ((other != null) ? "," + other + "]" : "]"); + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java new file mode 100644 index 0000000..1055850 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSIncoming.java @@ -0,0 +1,530 @@ +///Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + + +package org.jmdns.impl; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Parse an incoming DNS message into its components. + * + * @version %I%, %G% + * @author Arthur van Hoff, Werner Randelshofer, Pierre Frisch, Daniel Bobbert + */ +public final class DNSIncoming +{ + private static Logger logger = Logger.getLogger(DNSIncoming.class.getName()); + + // This is a hack to handle a bug in the BonjourConformanceTest + // It is sending out target strings that don't follow the "domain name" + // format. + public static boolean USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET = true; + + // Implementation note: This vector should be immutable. + // If a client of DNSIncoming changes the contents of this vector, + // we get undesired results. To fix this, we have to migrate to + // the Collections API of Java 1.2. i.e we replace Vector by List. + // final static Vector EMPTY = new Vector(); + + private DatagramPacket packet; + private int off; + private int len; + private byte data[]; + + int id; + private int flags; + private int numQuestions; + int numAnswers; + private int numAuthorities; + private int numAdditionals; + private long receivedTime; + + private List questions; + List answers; + + /** + * Parse a message from a datagram packet. + */ + DNSIncoming(DatagramPacket packet) throws IOException + { + this.packet = packet; + InetAddress source = packet.getAddress(); + this.data = packet.getData(); + this.len = packet.getLength(); + this.off = packet.getOffset(); + this.questions = Collections.EMPTY_LIST; + this.answers = Collections.EMPTY_LIST; + this.receivedTime = System.currentTimeMillis(); + + try + { + id = readUnsignedShort(); + flags = readUnsignedShort(); + numQuestions = readUnsignedShort(); + numAnswers = readUnsignedShort(); + numAuthorities = readUnsignedShort(); + numAdditionals = readUnsignedShort(); + + // parse questions + if (numQuestions > 0) + { + questions = Collections.synchronizedList(new ArrayList(numQuestions)); + for (int i = 0; i < numQuestions; i++) + { + DNSQuestion question = new DNSQuestion(readName(), readUnsignedShort(), readUnsignedShort()); + questions.add(question); + } + } + + // parse answers + int n = numAnswers + numAuthorities + numAdditionals; + if (n > 0) + { + answers = Collections.synchronizedList(new ArrayList(n)); + for (int i = 0; i < n; i++) + { + String domain = readName(); + int type = readUnsignedShort(); + int clazz = readUnsignedShort(); + int ttl = readInt(); + int len = readUnsignedShort(); + int end = off + len; + DNSRecord rec = null; + + switch (type) + { + case DNSConstants.TYPE_A: // IPv4 + case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested + rec = new DNSRecord.Address(domain, type, clazz, ttl, readBytes(off, len)); + break; + case DNSConstants.TYPE_CNAME: + case DNSConstants.TYPE_PTR: + String service = ""; + try { + service = readName(); + } catch (IOException e){ + // there was a problem reading the service name + e.printStackTrace(); + } + rec = new DNSRecord.Pointer(domain, type, clazz, ttl, service); + break; + case DNSConstants.TYPE_TXT: + rec = new DNSRecord.Text(domain, type, clazz, ttl, readBytes(off, len)); + break; + case DNSConstants.TYPE_SRV: + int priority = readUnsignedShort(); + int weight = readUnsignedShort(); + int port = readUnsignedShort(); + String target = ""; + try { + // This is a hack to handle a bug in the BonjourConformanceTest + // It is sending out target strings that don't follow the "domain name" + // format. + + if(USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){ + target = readName(); + } else { + target = readNonNameString(); + } + } catch (IOException e) { + // this can happen if the type of the label + // cannot be handled. + // down below the offset gets advanced to the end + // of the record + e.printStackTrace(); + } + rec = new DNSRecord.Service(domain, type, clazz, ttl, + priority, weight, port, target); + break; + case DNSConstants.TYPE_HINFO: + // Maybe we should do something with those + break; + default : + logger.finer("DNSIncoming() unknown type:" + type); + break; + } + + if (rec != null) + { + rec.setRecordSource(source); + // Add a record, if we were able to create one. + answers.add(rec); + } + else + { + // Addjust the numbers for the skipped record + if (answers.size() < numAnswers) + { + numAnswers--; + } + else + { + if (answers.size() < numAnswers + numAuthorities) + { + numAuthorities--; + } + else + { + if (answers.size() < numAnswers + numAuthorities + numAdditionals) + { + numAdditionals--; + } + } + } + } + off = end; + } + } + } + catch (IOException e) + { + logger.log(Level.WARNING, "DNSIncoming() dump " + print(true) + "\n exception ", e); + throw e; + } + } + + /** + * Check if the message is a query. + */ + boolean isQuery() + { + return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; + } + + /** + * Check if the message is truncated. + */ + public boolean isTruncated() + { + return (flags & DNSConstants.FLAGS_TC) != 0; + } + + /** + * Check if the message is a response. + */ + boolean isResponse() + { + return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_RESPONSE; + } + + private int get(int off) throws IOException + { + if ((off < 0) || (off >= len)) + { + throw new IOException("parser error: offset=" + off); + } + return data[off] & 0xFF; + } + + private int readUnsignedShort() throws IOException + { + return (get(off++) << 8) + get(off++); + } + + private int readInt() throws IOException + { + return (readUnsignedShort() << 16) + readUnsignedShort(); + } + + private byte[] readBytes(int off, int len) throws IOException + { + byte bytes[] = new byte[len]; + System.arraycopy(data, off, bytes, 0, len); + return bytes; + } + + private void readUTF(StringBuffer buf, int off, int len) throws IOException + { + for (int end = off + len; off < end;) + { + int ch = get(off++); + switch (ch >> 4) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + break; + case 12: + case 13: + // 110x xxxx 10xx xxxx + ch = ((ch & 0x1F) << 6) | (get(off++) & 0x3F); + break; + case 14: + // 1110 xxxx 10xx xxxx 10xx xxxx + ch = ((ch & 0x0f) << 12) | ((get(off++) & 0x3F) << 6) | (get(off++) & 0x3F); + break; + default: + // 10xx xxxx, 1111 xxxx + ch = ((ch & 0x3F) << 4) | (get(off++) & 0x0f); + break; + } + buf.append((char) ch); + } + } + + private String readNonNameString() throws IOException + { + StringBuffer buf = new StringBuffer(); + int off = this.off; + int len = get(off++); + readUTF(buf, off, len); + + return buf.toString(); + } + + private String readName() throws IOException + { + StringBuffer buf = new StringBuffer(); + int off = this.off; + int next = -1; + int first = off; + + while (true) + { + int len = get(off++); + if (len == 0) + { + break; + } + switch (len & 0xC0) + { + case 0x00: + //buf.append("[" + off + "]"); + readUTF(buf, off, len); + off += len; + buf.append('.'); + break; + case 0xC0: + //buf.append("<" + (off - 1) + ">"); + if (next < 0) + { + next = off + 1; + } + off = ((len & 0x3F) << 8) | get(off++); + if (off >= first) + { + throw new IOException("bad domain name: possible circular name detected." + + " name start: " + first + + " bad offset: 0x" + Integer.toHexString(off)); + } + first = off; + break; + default: + throw new IOException("unsupported dns label type: '" + Integer.toHexString(len & 0xC0) +"' at " + (off-1)); + } + } + this.off = (next >= 0) ? next : off; + return buf.toString(); + } + + /** + * Debugging. + */ + String print(boolean dump) + { + StringBuffer buf = new StringBuffer(); + buf.append(toString() + "\n"); + for (Iterator iterator = questions.iterator(); iterator.hasNext();) + { + buf.append(" ques:" + iterator.next() + "\n"); + } + int count = 0; + for (Iterator iterator = answers.iterator(); iterator.hasNext(); count++) + { + if (count < numAnswers) + { + buf.append(" answ:"); + } + else + { + if (count < numAnswers + numAuthorities) + { + buf.append(" auth:"); + } + else + { + buf.append(" addi:"); + } + } + buf.append(iterator.next() + "\n"); + } + if (dump) + { + for (int off = 0, len = packet.getLength(); off < len; off += 32) + { + int n = Math.min(32, len - off); + if (off < 10) + { + buf.append(' '); + } + if (off < 100) + { + buf.append(' '); + } + buf.append(off); + buf.append(':'); + for (int i = 0; i < n; i++) + { + if ((i % 8) == 0) + { + buf.append(' '); + } + buf.append(Integer.toHexString((data[off + i] & 0xF0) >> 4)); + buf.append(Integer.toHexString((data[off + i] & 0x0F) >> 0)); + } + buf.append("\n"); + buf.append(" "); + for (int i = 0; i < n; i++) + { + if ((i % 8) == 0) + { + buf.append(' '); + } + buf.append(' '); + int ch = data[off + i] & 0xFF; + buf.append(((ch > ' ') && (ch < 127)) ? (char) ch : '.'); + } + buf.append("\n"); + + // limit message size + if (off + 32 >= 256) + { + buf.append("....\n"); + break; + } + } + } + return buf.toString(); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(isQuery() ? "dns[query," : "dns[response,"); + if (packet.getAddress() != null) + { + buf.append(packet.getAddress().getHostAddress()); + } + buf.append(':'); + buf.append(packet.getPort()); + buf.append(",len="); + buf.append(packet.getLength()); + buf.append(",id=0x"); + buf.append(Integer.toHexString(id)); + if (flags != 0) + { + buf.append(",flags=0x"); + buf.append(Integer.toHexString(flags)); + if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) + { + buf.append(":r"); + } + if ((flags & DNSConstants.FLAGS_AA) != 0) + { + buf.append(":aa"); + } + if ((flags & DNSConstants.FLAGS_TC) != 0) + { + buf.append(":tc"); + } + } + if (numQuestions > 0) + { + buf.append(",questions="); + buf.append(numQuestions); + } + if (numAnswers > 0) + { + buf.append(",answers="); + buf.append(numAnswers); + } + if (numAuthorities > 0) + { + buf.append(",authorities="); + buf.append(numAuthorities); + } + if (numAdditionals > 0) + { + buf.append(",additionals="); + buf.append(numAdditionals); + } + buf.append("]"); + return buf.toString(); + } + + /** + * Appends answers to this Incoming. + * + * @throws IllegalArgumentException If not a query or if Truncated. + */ + void append(DNSIncoming that) + { + if (this.isQuery() && this.isTruncated() && that.isQuery()) + { + if (that.numQuestions > 0) { + if (Collections.EMPTY_LIST.equals(this.questions)) + this.questions = Collections.synchronizedList(new ArrayList(that.numQuestions)); + + this.questions.addAll(that.questions); + this.numQuestions += that.numQuestions; + } + + if (Collections.EMPTY_LIST.equals(answers)) + { + answers = Collections.synchronizedList(new ArrayList()); + } + + if (that.numAnswers > 0) + { + this.answers.addAll(this.numAnswers, that.answers.subList(0, that.numAnswers)); + this.numAnswers += that.numAnswers; + } + if (that.numAuthorities > 0) + { + this.answers.addAll(this.numAnswers + this.numAuthorities, that.answers.subList(that.numAnswers, that.numAnswers + that.numAuthorities)); + this.numAuthorities += that.numAuthorities; + } + if (that.numAdditionals > 0) + { + this.answers.addAll(that.answers.subList(that.numAnswers + that.numAuthorities, that.numAnswers + that.numAuthorities + that.numAdditionals)); + this.numAdditionals += that.numAdditionals; + } + } + else + { + throw new IllegalArgumentException(); + } + } + + public int elapseSinceArrival() + { + return (int) (System.currentTimeMillis() - receivedTime); + } + + public List getQuestions() + { + return questions; + } + + public List getAnswers() + { + return answers; + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java new file mode 100644 index 0000000..d2e3fcb --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSListener.java @@ -0,0 +1,24 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +// REMIND: Listener should follow Java idiom for listener or have a different +// name. + +/** + * DNSListener. + * Listener for record updates. + * + * @author Werner Randelshofer, Rick Blair + * @version 1.0 May 22, 2004 Created. + */ +interface DNSListener +{ + /** + * Update a DNS record. + */ + void updateRecord(JmDNSImpl jmdns, long now, DNSRecord record); +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java new file mode 100644 index 0000000..b4f0c0e --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSOutgoing.java @@ -0,0 +1,394 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +import java.io.IOException; +import java.util.Hashtable; +import java.util.LinkedList; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * An outgoing DNS message. + * + * @version %I%, %G% + * @author Arthur van Hoff, Rick Blair, Werner Randelshofer + */ +public final class DNSOutgoing +{ + /** + * This can be used to turn off domain name compression. This was helpful for + * tracking problems interacting with other mdns implementations. + */ + public static boolean USE_DOMAIN_NAME_COMPRESSION = true; + + private static Logger logger = Logger.getLogger(DNSOutgoing.class.getName()); + int id; + int flags; + private boolean multicast; + private int numQuestions; + private int numAnswers; + private int numAuthorities; + private int numAdditionals; + private Hashtable names; + + byte data[]; + int off; + int len; + + /** + * Create an outgoing multicast query or response. + */ + public DNSOutgoing(int flags) + { + this(flags, true); + } + + /** + * Create an outgoing query or response. + */ + public DNSOutgoing(int flags, boolean multicast) + { + this.flags = flags; + this.multicast = multicast; + names = new Hashtable(); + data = new byte[DNSConstants.MAX_MSG_TYPICAL]; + off = 12; + } + + /** + * Add a question to the message. + */ + public void addQuestion(DNSQuestion rec) throws IOException + { + if (numAnswers > 0 || numAuthorities > 0 || numAdditionals > 0) + { + throw new IllegalStateException("Questions must be added before answers"); + } + numQuestions++; + writeQuestion(rec); + } + + /** + * Add an answer if it is not suppressed. + */ + void addAnswer(DNSIncoming in, DNSRecord rec) throws IOException + { + if (numAuthorities > 0 || numAdditionals > 0) + { + throw new IllegalStateException("Answers must be added before authorities and additionals"); + } + if (!rec.suppressedBy(in)) + { + addAnswer(rec, 0); + } + } + + /** + * Add an additional answer to the record. Omit if there is no room. + */ + void addAdditionalAnswer(DNSIncoming in, DNSRecord rec) throws IOException + { + if ((off < DNSConstants.MAX_MSG_TYPICAL - 200) && !rec.suppressedBy(in)) + { + writeRecord(rec, 0); + numAdditionals++; + } + } + + /** + * Add an answer to the message. + */ + public void addAnswer(DNSRecord rec, long now) throws IOException + { + if (numAuthorities > 0 || numAdditionals > 0) + { + throw new IllegalStateException("Questions must be added before answers"); + } + if (rec != null) + { + if ((now == 0) || !rec.isExpired(now)) + { + writeRecord(rec, now); + numAnswers++; + } + } + } + + private LinkedList authorativeAnswers = new LinkedList(); + + /** + * Add an authorative answer to the message. + */ + public void addAuthorativeAnswer(DNSRecord rec) throws IOException + { + if (numAdditionals > 0) + { + throw new IllegalStateException("Authorative answers must be added before additional answers"); + } + authorativeAnswers.add(rec); + writeRecord(rec, 0); + numAuthorities++; + + // VERIFY: + + } + + void writeByte(int value) throws IOException + { + if (off >= data.length) + { + throw new IOException("buffer full"); + } + data[off++] = (byte) value; + } + + void writeBytes(String str, int off, int len) throws IOException + { + for (int i = 0; i < len; i++) + { + writeByte(str.charAt(off + i)); + } + } + + void writeBytes(byte data[]) throws IOException + { + if (data != null) + { + writeBytes(data, 0, data.length); + } + } + + void writeBytes(byte data[], int off, int len) throws IOException + { + for (int i = 0; i < len; i++) + { + writeByte(data[off + i]); + } + } + + void writeShort(int value) throws IOException + { + writeByte(value >> 8); + writeByte(value); + } + + void writeInt(int value) throws IOException + { + writeShort(value >> 16); + writeShort(value); + } + + void writeUTF(String str, int off, int len) throws IOException + { + // compute utf length + int utflen = 0; + for (int i = 0; i < len; i++) + { + int ch = str.charAt(off + i); + if ((ch >= 0x0001) && (ch <= 0x007F)) + { + utflen += 1; + } + else + { + if (ch > 0x07FF) + { + utflen += 3; + } + else + { + utflen += 2; + } + } + } + // write utf length + writeByte(utflen); + // write utf data + for (int i = 0; i < len; i++) + { + int ch = str.charAt(off + i); + if ((ch >= 0x0001) && (ch <= 0x007F)) + { + writeByte(ch); + } + else + { + if (ch > 0x07FF) + { + writeByte(0xE0 | ((ch >> 12) & 0x0F)); + writeByte(0x80 | ((ch >> 6) & 0x3F)); + writeByte(0x80 | ((ch >> 0) & 0x3F)); + } + else + { + writeByte(0xC0 | ((ch >> 6) & 0x1F)); + writeByte(0x80 | ((ch >> 0) & 0x3F)); + } + } + } + } + + void writeName(String name) throws IOException + { + writeName(name, true); + } + + void writeName(String name, boolean useCompression) throws IOException + { + while (true) + { + int n = name.indexOf('.'); + if (n < 0) + { + n = name.length(); + } + if (n <= 0) + { + writeByte(0); + return; + } + if(useCompression && USE_DOMAIN_NAME_COMPRESSION){ + Integer offset = (Integer) names.get(name); + if (offset != null) + { + int val = offset.intValue(); + + if (val > off) + { + logger.log(Level.WARNING, "DNSOutgoing writeName failed val=" + val + " name=" + name); + } + + writeByte((val >> 8) | 0xC0); + writeByte(val & 0xFF); + return; + } + names.put(name, new Integer(off)); + } + writeUTF(name, 0, n); + name = name.substring(n); + if (name.startsWith(".")) + { + name = name.substring(1); + } + } + } + + void writeQuestion(DNSQuestion question) throws IOException + { + writeName(question.name); + writeShort(question.type); + writeShort(question.clazz); + } + + void writeRecord(DNSRecord rec, long now) throws IOException + { + int save = off; + try + { + writeName(rec.name); + writeShort(rec.type); + writeShort(rec.clazz | ((rec.unique && multicast) ? DNSConstants.CLASS_UNIQUE : 0)); + writeInt((now == 0) ? rec.getTtl() : rec.getRemainingTTL(now)); + writeShort(0); + int start = off; + rec.write(this); + int len = off - start; + data[start - 2] = (byte) (len >> 8); + data[start - 1] = (byte) (len & 0xFF); + } + catch (IOException e) + { + off = save; + throw e; + } + } + + /** + * Finish the message before sending it off. + */ + void finish() throws IOException + { + int save = off; + off = 0; + + writeShort(multicast ? 0 : id); + writeShort(flags); + writeShort(numQuestions); + writeShort(numAnswers); + writeShort(numAuthorities); + writeShort(numAdditionals); + off = save; + } + + boolean isQuery() + { + return (flags & DNSConstants.FLAGS_QR_MASK) == DNSConstants.FLAGS_QR_QUERY; + } + + public boolean isEmpty() + { + return numQuestions == 0 && numAuthorities == 0 + && numAdditionals == 0 && numAnswers == 0; + } + + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append(isQuery() ? "dns[query," : "dns[response,"); + //buf.append(packet.getAddress().getHostAddress()); + buf.append(':'); + //buf.append(packet.getPort()); + //buf.append(",len="); + //buf.append(packet.getLength()); + buf.append(",id=0x"); + buf.append(Integer.toHexString(id)); + if (flags != 0) + { + buf.append(",flags=0x"); + buf.append(Integer.toHexString(flags)); + if ((flags & DNSConstants.FLAGS_QR_RESPONSE) != 0) + { + buf.append(":r"); + } + if ((flags & DNSConstants.FLAGS_AA) != 0) + { + buf.append(":aa"); + } + if ((flags & DNSConstants.FLAGS_TC) != 0) + { + buf.append(":tc"); + } + } + if (numQuestions > 0) + { + buf.append(",questions="); + buf.append(numQuestions); + } + if (numAnswers > 0) + { + buf.append(",answers="); + buf.append(numAnswers); + } + if (numAuthorities > 0) + { + buf.append(",authorities="); + buf.append(numAuthorities); + } + if (numAdditionals > 0) + { + buf.append(",additionals="); + buf.append(numAdditionals); + } + buf.append(",\nnames=" + names); + buf.append(",\nauthorativeAnswers=" + authorativeAnswers); + + buf.append("]"); + return buf.toString(); + } + +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java new file mode 100644 index 0000000..8ea1115 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSQuestion.java @@ -0,0 +1,44 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +import java.util.logging.Logger; + +/** + * A DNS question. + * + * @version %I%, %G% + * @author Arthur van Hoff + */ +public final class DNSQuestion extends DNSEntry +{ + private static Logger logger = Logger.getLogger(DNSQuestion.class.getName()); + + /** + * Create a question. + */ + public DNSQuestion(String name, int type, int clazz) + { + super(name, type, clazz); + } + + /** + * Check if this question is answered by a given DNS record. + */ + boolean answeredBy(DNSRecord rec) + { + return (clazz == rec.clazz) && ((type == rec.type) || (type == DNSConstants.TYPE_ANY)) && + name.equals(rec.name); + } + + /** + * For debugging only. + */ + public String toString() + { + return toString("question", null); + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java new file mode 100644 index 0000000..9b69935 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSRecord.java @@ -0,0 +1,742 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + +package org.jmdns.impl; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Iterator; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * DNS record + * + * @version %I%, %G% + * @author Arthur van Hoff, Rick Blair, Werner Randelshofer, Pierre Frisch + */ +public abstract class DNSRecord extends DNSEntry +{ + private static Logger logger = Logger.getLogger(DNSRecord.class.getName()); + private int ttl; + private long created; + + /** + * This source is mainly for debugging purposes, should be the address that + * sent this record. + */ + private InetAddress source; + + /** + * Create a DNSRecord with a name, type, clazz, and ttl. + */ + DNSRecord(String name, int type, int clazz, int ttl) + { + super(name, type, clazz); + this.ttl = ttl; + this.created = System.currentTimeMillis(); + } + + /** + * True if this record is the same as some other record. + */ + public boolean equals(Object other) + { + return (other instanceof DNSRecord) && sameAs((DNSRecord) other); + } + + /** + * True if this record is the same as some other record. + */ + boolean sameAs(DNSRecord other) + { + return super.equals(other) && sameValue((DNSRecord) other); + } + + /** + * True if this record has the same value as some other record. + */ + abstract boolean sameValue(DNSRecord other); + + /** + * True if this record has the same type as some other record. + */ + boolean sameType(DNSRecord other) + { + return type == other.type; + } + + /** + * Handles a query represented by this record. + * + * @return Returns true if a conflict with one of the services registered + * with JmDNS or with the hostname occured. + */ + abstract boolean handleQuery(JmDNSImpl dns, long expirationTime); + + /** + * Handles a responserepresented by this record. + * + * @return Returns true if a conflict with one of the services registered + * with JmDNS or with the hostname occured. + */ + abstract boolean handleResponse(JmDNSImpl dns); + + /** + * Adds this as an answer to the provided outgoing datagram. + */ + abstract DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException; + + /** + * True if this record is suppressed by the answers in a message. + */ + boolean suppressedBy(DNSIncoming msg) + { + try + { + for (int i = msg.numAnswers; i-- > 0;) + { + if (suppressedBy((DNSRecord) msg.answers.get(i))) + { + return true; + } + } + return false; + } + catch (ArrayIndexOutOfBoundsException e) + { + logger.log(Level.WARNING, "suppressedBy() message " + msg + " exception ", e); + // msg.print(true); + return false; + } + } + + /** + * True if this record would be supressed by an answer. + * This is the case if this record would not have a + * significantly longer TTL. + */ + boolean suppressedBy(DNSRecord other) + { + if (sameAs(other) && (other.ttl > ttl / 2)) + { + return true; + } + return false; + } + + /** + * Get the expiration time of this record. + */ + long getExpirationTime(int percent) + { + return created + (percent * ttl * 10L); + } + + /** + * Get the remaining TTL for this record. + */ + int getRemainingTTL(long now) + { + return (int) Math.max(0, (getExpirationTime(100) - now) / 1000); + } + + /** + * Check if the record is expired. + */ + public boolean isExpired(long now) + { + return getExpirationTime(100) <= now; + } + + /** + * Check if the record is stale, ie it has outlived + * more than half of its TTL. + */ + boolean isStale(long now) + { + return getExpirationTime(50) <= now; + } + + /** + * Reset the TTL of a record. This avoids having to + * update the entire record in the cache. + */ + void resetTTL(DNSRecord other) + { + created = other.created; + ttl = other.ttl; + } + + /** + * Write this record into an outgoing message. + */ + abstract void write(DNSOutgoing out) throws IOException; + + /** + * Address record. + */ + static class Address extends DNSRecord + { + private static Logger logger = Logger.getLogger(Address.class.getName()); + InetAddress addr; + + Address(String name, int type, int clazz, int ttl, InetAddress addr) + { + super(name, type, clazz, ttl); + this.addr = addr; + } + + Address(String name, int type, int clazz, int ttl, byte[] rawAddress) + { + super(name, type, clazz, ttl); + try + { + this.addr = InetAddress.getByAddress(rawAddress); + } + catch (UnknownHostException exception) + { + logger.log(Level.WARNING, "Address() exception ", exception); + } + } + + void write(DNSOutgoing out) throws IOException + { + if (addr != null) + { + byte[] buffer = addr.getAddress(); + if (DNSConstants.TYPE_A == type) + { + // If we have a type A records we should answer with a IPv4 address + if (addr instanceof Inet4Address) + { + // All is good + } + else + { + // Get the last four bytes + byte[] tempbuffer = buffer; + buffer = new byte[4]; + System.arraycopy(tempbuffer, 12, buffer, 0, 4); + } + } + else + { + // If we have a type AAAA records we should answer with a IPv6 address + if (addr instanceof Inet4Address) + { + byte[] tempbuffer = buffer; + buffer = new byte[16]; + for (int i = 0; i < 16; i++) + { + if (i < 11) + { + buffer[i] = tempbuffer[i - 12]; + } + else + { + buffer[i] = 0; + } + } + } + } + int length = buffer.length; + out.writeBytes(buffer, 0, length); + } + } + + boolean same(DNSRecord other) + { + return ((sameName(other)) && ((sameValue(other)))); + } + + boolean sameName(DNSRecord other) + { + return name.equalsIgnoreCase(((Address) other).name); + } + + boolean sameValue(DNSRecord other) + { + return addr.equals(((Address) other).getAddress()); + } + + InetAddress getAddress() + { + return addr; + } + + /** + * Creates a byte array representation of this record. + * This is needed for tie-break tests according to + * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. + */ + private byte[] toByteArray() + { + try + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream dout = new DataOutputStream(bout); + dout.write(name.getBytes("UTF8")); + dout.writeShort(type); + dout.writeShort(clazz); + //dout.writeInt(len); + byte[] buffer = addr.getAddress(); + for (int i = 0; i < buffer.length; i++) + { + dout.writeByte(buffer[i]); + } + dout.close(); + return bout.toByteArray(); + } + catch (IOException e) + { + throw new InternalError(); + } + } + + /** + * Does a lexicographic comparison of the byte array representation + * of this record and that record. + * This is needed for tie-break tests according to + * draft-cheshire-dnsext-multicastdns-04.txt chapter 9.2. + */ + private int lexCompare(DNSRecord.Address that) + { + byte[] thisBytes = this.toByteArray(); + byte[] thatBytes = that.toByteArray(); + for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) + { + if (thisBytes[i] > thatBytes[i]) + { + return 1; + } + else + { + if (thisBytes[i] < thatBytes[i]) + { + return -1; + } + } + } + return thisBytes.length - thatBytes.length; + } + + /** + * Does the necessary actions, when this as a query. + */ + boolean handleQuery(JmDNSImpl dns, long expirationTime) + { + DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this); + if (dnsAddress != null) + { + if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this))) + { + logger.finer("handleQuery() Conflicting probe detected. dns state " + dns.getState() + " lex compare " + lexCompare(dnsAddress)); + // Tie-breaker test + if (dns.getState().isProbing() && lexCompare(dnsAddress) >= 0) + { + // We lost the tie-break. We have to choose a different name. + dns.getLocalHost().incrementHostName(); + dns.getCache().clear(); + for (Iterator i = dns.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + info.revertState(); + } + } + dns.revertState(); + return true; + } + } + return false; + } + + /** + * Does the necessary actions, when this as a response. + */ + boolean handleResponse(JmDNSImpl dns) + { + DNSRecord.Address dnsAddress = dns.getLocalHost().getDNSAddressRecord(this); + if (dnsAddress != null) + { + if (dnsAddress.sameType(this) && dnsAddress.sameName(this) && (!dnsAddress.sameValue(this))) + { + logger.finer("handleResponse() Denial detected"); + + if (dns.getState().isProbing()) + { + dns.getLocalHost().incrementHostName(); + dns.getCache().clear(); + for (Iterator i = dns.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + info.revertState(); + } + } + dns.revertState(); + return true; + } + } + return false; + } + + DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException + { + return out; + } + + public String toString() + { + return toString(" address '" + (addr != null ? addr.getHostAddress() : "null") + "'"); + } + + } + + /** + * Pointer record. + */ + public static class Pointer extends DNSRecord + { + private static Logger logger = Logger.getLogger(Pointer.class.getName()); + String alias; + + public Pointer(String name, int type, int clazz, int ttl, String alias) + { + super(name, type, clazz, ttl); + this.alias = alias; + } + + void write(DNSOutgoing out) throws IOException + { + out.writeName(alias); + } + + boolean sameValue(DNSRecord other) + { + return alias.equals(((Pointer) other).alias); + } + + boolean handleQuery(JmDNSImpl dns, long expirationTime) + { + // Nothing to do (?) + // I think there is no possibility for conflicts for this record type? + return false; + } + + boolean handleResponse(JmDNSImpl dns) + { + // Nothing to do (?) + // I think there is no possibility for conflicts for this record type? + return false; + } + + String getAlias() + { + return alias; + } + + DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException + { + return out; + } + + public String toString() + { + return toString(alias); + } + } + + public static class Text extends DNSRecord + { + private static Logger logger = Logger.getLogger(Text.class.getName()); + byte text[]; + + public Text(String name, int type, int clazz, int ttl, byte text[]) + { + super(name, type, clazz, ttl); + this.text = text; + } + + void write(DNSOutgoing out) throws IOException + { + out.writeBytes(text, 0, text.length); + } + + boolean sameValue(DNSRecord other) + { + Text txt = (Text) other; + if (txt.text.length != text.length) + { + return false; + } + for (int i = text.length; i-- > 0;) + { + if (txt.text[i] != text[i]) + { + return false; + } + } + return true; + } + + boolean handleQuery(JmDNSImpl dns, long expirationTime) + { + // Nothing to do (?) + // I think there is no possibility for conflicts for this record type? + return false; + } + + boolean handleResponse(JmDNSImpl dns) + { + // Nothing to do (?) + // Shouldn't we care if we get a conflict at this level? + /* + ServiceInfo info = (ServiceInfo) dns.services.get(name.toLowerCase()); + if (info != null) { + if (! Arrays.equals(text,info.text)) { + info.revertState(); + return true; + } + }*/ + return false; + } + + DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException + { + return out; + } + + public String toString() + { + return toString((text.length > 10) ? new String(text, 0, 7) + "..." : new String(text)); + } + } + + /** + * Service record. + */ + public static class Service extends DNSRecord + { + private static Logger logger = Logger.getLogger(Service.class.getName()); + int priority; + int weight; + int port; + String server; + + public Service(String name, int type, int clazz, int ttl, int priority, int weight, int port, String server) + { + super(name, type, clazz, ttl); + this.priority = priority; + this.weight = weight; + this.port = port; + this.server = server; + } + + void write(DNSOutgoing out) throws IOException + { + out.writeShort(priority); + out.writeShort(weight); + out.writeShort(port); + if(DNSIncoming.USE_DOMAIN_NAME_FORMAT_FOR_SRV_TARGET){ + out.writeName(server, false); + } else { + out.writeUTF(server, 0, server.length()); + + // add a zero byte to the end just to be safe, this is the strange form + // used by the BonjourConformanceTest + out.writeByte(0); + } + } + + private byte[] toByteArray() + { + try + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + DataOutputStream dout = new DataOutputStream(bout); + dout.write(name.getBytes("UTF8")); + dout.writeShort(type); + dout.writeShort(clazz); + //dout.writeInt(len); + dout.writeShort(priority); + dout.writeShort(weight); + dout.writeShort(port); + dout.write(server.getBytes("UTF8")); + dout.close(); + return bout.toByteArray(); + } + catch (IOException e) + { + throw new InternalError(); + } + } + + private int lexCompare(DNSRecord.Service that) + { + byte[] thisBytes = this.toByteArray(); + byte[] thatBytes = that.toByteArray(); + for (int i = 0, n = Math.min(thisBytes.length, thatBytes.length); i < n; i++) + { + if (thisBytes[i] > thatBytes[i]) + { + return 1; + } + else + { + if (thisBytes[i] < thatBytes[i]) + { + return -1; + } + } + } + return thisBytes.length - thatBytes.length; + } + + boolean sameValue(DNSRecord other) + { + Service s = (Service) other; + return (priority == s.priority) && (weight == s.weight) && (port == s.port) && server.equals(s.server); + } + + boolean handleQuery(JmDNSImpl dns, long expirationTime) + { + ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); + if (info != null + && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName()))) + { + logger.finer("handleQuery() Conflicting probe detected from: " + getRecordSource()); + DNSRecord.Service localService = new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, + DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, + DNSConstants.DNS_TTL, info.priority, + info.weight, info.port, dns.getLocalHost().getName()); + + // This block is useful for debugging race conditions when jmdns is respoding to + // itself. + try + { + if(dns.getInterface().equals(getRecordSource())){ + logger.warning("Got conflicting probe from ourselves\n" + + "incoming: " +this.toString() + "\n" + + "local : " + localService.toString()); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + + int comparison = lexCompare(localService); + + if(comparison == 0){ + // the 2 records are identical this probably means we are seeing our own record. + // With mutliple interfaces on a single computer it is possible to see our + // own records come in on different interfaces than the ones they were sent on. + // see section "10. Conflict Resolution" of mdns draft spec. + logger.finer("handleQuery() Ignoring a identical service query"); + return false; + } + + // Tie breaker test + if (info.getState().isProbing() && comparison > 0) + { + // We lost the tie break + String oldName = info.getQualifiedName().toLowerCase(); + info.setName(dns.incrementName(info.getName())); + dns.getServices().remove(oldName); + dns.getServices().put(info.getQualifiedName().toLowerCase(), info); + logger.finer("handleQuery() Lost tie break: new unique name chosen:" + info.getName()); + + // We revert the state to start probing again with the new name + info.revertState(); + } + else + { + // We won the tie break, so this conflicting probe should be ignored + // See paragraph 3 of section 9.2 in mdns draft spec + return false; + } + + return true; + + } + return false; + } + + boolean handleResponse(JmDNSImpl dns) + { + ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); + if (info != null + && (port != info.port || !server.equalsIgnoreCase(dns.getLocalHost().getName()))) + { + logger.finer("handleResponse() Denial detected"); + + if (info.getState().isProbing()) + { + String oldName = info.getQualifiedName().toLowerCase(); + info.setName(dns.incrementName(info.getName())); + dns.getServices().remove(oldName); + dns.getServices().put(info.getQualifiedName().toLowerCase(), info); + logger.finer("handleResponse() New unique name chose:" + info.getName()); + + } + info.revertState(); + return true; + } + return false; + } + + DNSOutgoing addAnswer(JmDNSImpl dns, DNSIncoming in, InetAddress addr, int port, DNSOutgoing out) throws IOException + { + ServiceInfoImpl info = (ServiceInfoImpl) dns.getServices().get(name.toLowerCase()); + if (info != null) + { + if (this.port == info.port != server.equals(dns.getLocalHost().getName())) + { + return dns.addAnswer(in, addr, port, out, + new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, + DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, + DNSConstants.DNS_TTL, info.priority, + info.weight, info.port, dns.getLocalHost().getName())); + } + } + return out; + } + + public String toString() + { + return toString(server + ":" + port); + } + } + + public void setRecordSource(InetAddress source) + { + this.source = source; + } + + public InetAddress getRecordSource() + { + return source; + } + + public String toString(String other) + { + return toString("record", ttl + "/" + getRemainingTTL(System.currentTimeMillis()) + "," + other); + } + + public void setTtl(int ttl) + { + this.ttl = ttl; + } + + public int getTtl() + { + return ttl; + } +} + diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java b/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java new file mode 100644 index 0000000..cb79088 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/DNSState.java @@ -0,0 +1,111 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.util.ArrayList; +import java.util.logging.Logger; + +/** + * DNSState defines the possible states for services registered with JmDNS. + * + * @author Werner Randelshofer, Rick Blair + * @version 1.0 May 23, 2004 Created. + */ +public class DNSState implements Comparable +{ + private static Logger logger = Logger.getLogger(DNSState.class.getName()); + + private final String name; + + /** + * Ordinal of next state to be created. + */ + private static int nextOrdinal = 0; + /** + * Assign an ordinal to this state. + */ + private final int ordinal = nextOrdinal++; + /** + * Logical sequence of states. + * The sequence is consistent with the ordinal of a state. + * This is used for advancing through states. + */ + private final static ArrayList sequence = new ArrayList(); + + private DNSState(String name) + { + this.name = name; + sequence.add(this); + } + + public final String toString() + { + return name; + } + + public static final DNSState PROBING_1 = new DNSState("probing 1"); + public static final DNSState PROBING_2 = new DNSState("probing 2"); + public static final DNSState PROBING_3 = new DNSState("probing 3"); + public static final DNSState ANNOUNCING_1 = new DNSState("announcing 1"); + public static final DNSState ANNOUNCING_2 = new DNSState("announcing 2"); + public static final DNSState ANNOUNCED = new DNSState("announced"); + public static final DNSState CANCELED = new DNSState("canceled"); + + /** + * Returns the next advanced state. + * In general, this advances one step in the following sequence: PROBING_1, + * PROBING_2, PROBING_3, ANNOUNCING_1, ANNOUNCING_2, ANNOUNCED. + * Does not advance for ANNOUNCED and CANCELED state. + */ + public final DNSState advance() + { + return (isProbing() || isAnnouncing()) ? (DNSState) sequence.get(ordinal + 1) : this; + } + + /** + * Returns to the next reverted state. + * All states except CANCELED revert to PROBING_1. + * Status CANCELED does not revert. + */ + public final DNSState revert() + { + return (this == CANCELED) ? this : PROBING_1; + } + + /** + * Returns true, if this is a probing state. + */ + public boolean isProbing() + { + return compareTo(PROBING_1) >= 0 && compareTo(PROBING_3) <= 0; + } + + /** + * Returns true, if this is an announcing state. + */ + public boolean isAnnouncing() + { + return compareTo(ANNOUNCING_1) >= 0 && compareTo(ANNOUNCING_2) <= 0; + } + + /** + * Returns true, if this is an announced state. + */ + public boolean isAnnounced() + { + return compareTo(ANNOUNCED) == 0; + } + + /** + * Compares two states. + * The states compare as follows: + * PROBING_1 < PROBING_2 < PROBING_3 < ANNOUNCING_1 < + * ANNOUNCING_2 < RESPONDING < ANNOUNCED < CANCELED. + */ + public int compareTo(Object o) + { + return ordinal - ((DNSState) o).ordinal; + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java b/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java new file mode 100755 index 0000000..a3a08f9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/HostInfo.java @@ -0,0 +1,172 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + + + +package org.jmdns.impl; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * HostInfo information on the local host to be able to cope with change of addresses. + * + * @version %I%, %G% + * @author Pierre Frisch, Werner Randelshofer + */ +public class HostInfo +{ + private static Logger logger = Logger.getLogger(HostInfo.class.getName()); + protected String name; + protected InetAddress address; + protected NetworkInterface interfaze; + /** + * This is used to create a unique name for the host name. + */ + private int hostNameCount; + + public HostInfo(InetAddress address, String name) + { + super(); + this.address = address; + this.name = name; + if (address != null) + { + try + { + interfaze = NetworkInterface.getByInetAddress(address); + } + catch (Exception exception) + { + // FIXME Shouldn't we take an action here? + logger.log(Level.WARNING, "LocalHostInfo() exception ", exception); + } + } + } + + public String getName() + { + return name; + } + + public InetAddress getAddress() + { + return address; + } + + public NetworkInterface getInterface() + { + return interfaze; + } + + synchronized String incrementHostName() + { + hostNameCount++; + int plocal = name.indexOf(".local."); + int punder = name.lastIndexOf("-"); + name = name.substring(0, (punder == -1 ? plocal : punder)) + "-" + hostNameCount + ".local."; + return name; + } + + boolean shouldIgnorePacket(DatagramPacket packet) + { + boolean result = false; + if (getAddress() != null) + { + InetAddress from = packet.getAddress(); + if (from != null) + { + if (from.isLinkLocalAddress() && (!getAddress().isLinkLocalAddress())) + { + // Ignore linklocal packets on regular interfaces, unless this is + // also a linklocal interface. This is to avoid duplicates. This is + // a terrible hack caused by the lack of an API to get the address + // of the interface on which the packet was received. + result = true; + } + if (from.isLoopbackAddress() && (!getAddress().isLoopbackAddress())) + { + // Ignore loopback packets on a regular interface unless this is + // also a loopback interface. + result = true; + } + } + } + return result; + } + + DNSRecord.Address getDNSAddressRecord(DNSRecord.Address address) + { + return (DNSConstants.TYPE_AAAA == address.type ? getDNS6AddressRecord() : getDNS4AddressRecord()); + } + + public DNSRecord.Address getDNS4AddressRecord() + { + if ((getAddress() != null) && + ((getAddress() instanceof Inet4Address) || + ((getAddress() instanceof Inet6Address) && (((Inet6Address) getAddress()).isIPv4CompatibleAddress())))) + { + return new DNSRecord.Address(getName(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress()); + } + return null; + } + + public DNSRecord.Address getDNS6AddressRecord() + { + if ((getAddress() != null) && (getAddress() instanceof Inet6Address)) + { + return new DNSRecord.Address(getName(), DNSConstants.TYPE_AAAA, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, getAddress()); + } + return null; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("local host info["); + buf.append(getName() != null ? getName() : "no name"); + buf.append(", "); + buf.append(getInterface() != null ? getInterface().getDisplayName() : "???"); + buf.append(":"); + buf.append(getAddress() != null ? getAddress().getHostAddress() : "no address"); + buf.append("]"); + return buf.toString(); + } + + public void addAddressRecords(DNSOutgoing out, boolean authoritative) throws IOException + { + DNSRecord answer = getDNS4AddressRecord(); + if (answer != null) + { + if (authoritative) + { + out.addAuthorativeAnswer(answer); + } + else + { + out.addAnswer(answer, 0); + } + } + + answer = getDNS6AddressRecord(); + if (answer != null) + { + if (authoritative) + { + out.addAuthorativeAnswer(answer); + } + else + { + out.addAnswer(answer, 0); + } + } + } + +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java new file mode 100644 index 0000000..6ef236a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/JmDNSImpl.java @@ -0,0 +1,1661 @@ +///Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.InetAddress; +import java.net.MulticastSocket; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceEvent; +import org.jmdns.ServiceInfo; +import org.jmdns.ServiceListener; +import org.jmdns.ServiceTypeListener; +import org.jmdns.impl.tasks.Announcer; +import org.jmdns.impl.tasks.TextAnnouncer; +import org.jmdns.impl.tasks.Canceler; +import org.jmdns.impl.tasks.Prober; +import org.jmdns.impl.tasks.RecordReaper; +import org.jmdns.impl.tasks.Renewer; +import org.jmdns.impl.tasks.Responder; +import org.jmdns.impl.tasks.ServiceInfoResolver; +import org.jmdns.impl.tasks.ServiceResolver; +import org.jmdns.impl.tasks.TypeResolver; + +// REMIND: multiple IP addresses + +/** + * mDNS implementation in Java. + * + * @version %I%, %G% + * @author Arthur van Hoff, Rick Blair, Jeff Sonstein, Werner Randelshofer, + * Pierre Frisch, Scott Lewis + */ +public class JmDNSImpl extends JmDNS +{ + private static Logger logger = Logger.getLogger(JmDNSImpl.class.getName()); + + /** + * This is the multicast group, we are listening to for multicast DNS + * messages. + */ + private InetAddress group; + /** + * This is our multicast socket. + */ + private MulticastSocket socket; + + /** + * Used to fix live lock problem on unregester. + */ + + private boolean closed = false; + + /** + * Holds instances of JmDNS.DNSListener. Must by a synchronized collection, + * because it is updated from concurrent threads. + */ + private List listeners; + /** + * Holds instances of ServiceListener's. Keys are Strings holding a fully + * qualified service type. Values are LinkedList's of ServiceListener's. + */ + private Map serviceListeners; + /** + * Holds instances of ServiceTypeListener's. + */ + private List typeListeners; + + /** + * Cache for DNSEntry's. + */ + private DNSCache cache; + + /** + * This hashtable holds the services that have been registered. Keys are + * instances of String which hold an all lower-case version of the fully + * qualified service name. Values are instances of ServiceInfo. + */ + Map services; + + /** + * This hashtable holds the service types that have been registered or that + * have been received in an incoming datagram. Keys are instances of String + * which hold an all lower-case version of the fully qualified service type. + * Values hold the fully qualified service type. + */ + Map serviceTypes; + /** + * This is the shutdown hook, we registered with the java runtime. + */ + private Thread shutdown; + + /** + * Handle on the local host + */ + private HostInfo localHost; + + private Thread incomingListener = null; + + /** + * Throttle count. This is used to count the overall number of probes sent + * by JmDNS. When the last throttle increment happened . + */ + private int throttle; + /** + * Last throttle increment. + */ + private long lastThrottleIncrement; + + /** + * The timer is used to dispatch all outgoing messages of JmDNS. It is also + * used to dispatch maintenance tasks for the DNS cache. + */ + Timer timer; + + /** + * The source for random values. This is used to introduce random delays in + * responses. This reduces the potential for collisions on the network. + */ + private final static Random random = new Random(); + + /** + * This lock is used to coordinate processing of incoming and outgoing + * messages. This is needed, because the Rendezvous Conformance Test does + * not forgive race conditions. + */ + private Object ioLock = new Object(); + + /** + * If an incoming package which needs an answer is truncated, we store it + * here. We add more incoming DNSRecords to it, until the JmDNS.Responder + * timer picks it up. Remind: This does not work well with multiple planned + * answers for packages that came in from different clients. + */ + private DNSIncoming plannedAnswer; + + // State machine + /** + * The state of JmDNS.

      For proper handling of concurrency, this + * variable must be changed only using methods advanceState(), revertState() + * and cancel(). + */ + private DNSState state = DNSState.PROBING_1; + + /** + * Timer task associated to the host name. This is used to prevent from + * having multiple tasks associated to the host name at the same time. + */ + private TimerTask task; + + /** + * This hashtable is used to maintain a list of service types being + * collected by this JmDNS instance. The key of the hashtable is a service + * type name, the value is an instance of JmDNS.ServiceCollector. + * + * @see #list + */ + private final HashMap serviceCollectors = new HashMap(); + + /** + * Create an instance of JmDNS. + */ + public JmDNSImpl() throws IOException + { + logger.finer("JmDNS instance created"); + /*try + {*/ + final InetAddress addr = InetAddress.getLocalHost(); + init(addr.isLoopbackAddress() ? null : addr, addr.getHostName()); // [ + // PJYF + // Oct + // 14 + // 2004 + // ] + // Why + // do + // we + // disallow + // the + // loopback + // address + // ? + /*} + catch (final IOException e) + { + init(null, "computer"); + }*/ + } + + /** + * Create an instance of JmDNS and bind it to a specific network interface + * given its IP-address. + */ + public JmDNSImpl(InetAddress addr) throws IOException + { + /*try + {*/ + String name = addr.getHostName(); + init(addr, name); + /*} + catch (final IOException e) + { + init(null, name); + }*/ + } + + /** + * Initialize everything. + * + * @param address + * The interface to which JmDNS binds to. + * @param name + * The host name of the interface. + */ + private void init(InetAddress address, String name) throws IOException + { + // A host name with "." is illegal. so strip off everything and append . + // local. + final int idx = name.indexOf("."); + if (idx > 0) + { + name = name.substring(0, idx); + } + name += ".local."; + // localHost to IP address binding + localHost = new HostInfo(address, name); + + cache = new DNSCache(100); + + listeners = Collections.synchronizedList(new ArrayList()); + serviceListeners = new HashMap(); + typeListeners = new ArrayList(); + + services = new Hashtable(20); + serviceTypes = new Hashtable(20); + + // REMIND: If I could pass in a name for the Timer thread, + // I would pass' JmDNS.Timer'. + timer = new Timer(); + new RecordReaper(this).start(timer); + shutdown = new Thread(new Shutdown(), "JmDNS.Shutdown"); + Runtime.getRuntime().addShutdownHook(shutdown); + + incomingListener = new Thread(new SocketListener(this), "JmDNS.SocketListener"); + + // Bind to multicast socket + openMulticastSocket(getLocalHost()); + start(getServices().values()); + } + + private void start(Collection serviceInfos) + { + setState(DNSState.PROBING_1); + incomingListener.start(); + new Prober(this).start(timer); + for (final Iterator iterator = serviceInfos.iterator(); iterator.hasNext();) + { + try + { + registerService(new ServiceInfoImpl((ServiceInfoImpl) iterator.next())); + } + catch (final Exception exception) + { + logger.log(Level.WARNING, "start() Registration exception ", exception); + } + } + } + + private void openMulticastSocket(HostInfo hostInfo) throws IOException + { + if (group == null) + { + group = InetAddress.getByName(DNSConstants.MDNS_GROUP); + } + if (socket != null) + { + this.closeMulticastSocket(); + } + socket = new MulticastSocket(DNSConstants.MDNS_PORT); + if ((hostInfo != null) && (localHost.getInterface() != null)) + { + socket.setNetworkInterface(hostInfo.getInterface()); + } + socket.setTimeToLive(255); + socket.joinGroup(group); + } + + private void closeMulticastSocket() + { + logger.finer("closeMulticastSocket()"); + if (socket != null) + { + // close socket + try + { + socket.leaveGroup(group); + socket.close(); + if (incomingListener != null) + { + incomingListener.join(); + } + } + catch (final Exception exception) + { + logger.log(Level.WARNING, "closeMulticastSocket() Close socket exception ", + exception); + } + socket = null; + } + } + + // State machine + /** + * Sets the state and notifies all objects that wait on JmDNS. + */ + public synchronized void advanceState() + { + setState(getState().advance()); + notifyAll(); + } + + /** + * Sets the state and notifies all objects that wait on JmDNS. + */ + synchronized void revertState() + { + setState(getState().revert()); + notifyAll(); + } + + /** + * Sets the state and notifies all objects that wait on JmDNS. + */ + synchronized void cancel() + { + setState(DNSState.CANCELED); + notifyAll(); + } + + /** + * Returns the current state of this info. + */ + public DNSState getState() + { + return state; + } + + /** + * Return the DNSCache associated with the cache variable + */ + public DNSCache getCache() + { + return cache; + } + + /** + * @see org.jmdns.JmDNS#getHostName() + */ + public String getHostName() + { + return localHost.getName(); + } + + public HostInfo getLocalHost() + { + return localHost; + } + + /** + * @see org.jmdns.JmDNS#getInterface() + */ + public InetAddress getInterface() throws IOException + { + return socket.getInterface(); + } + + /** + * @see org.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String) + */ + public ServiceInfo getServiceInfo(String type, String name) + { + return getServiceInfo(type, name, 3 * 1000); + } + + /** + * @see org.jmdns.JmDNS#getServiceInfo(java.lang.String, java.lang.String, + * int) + */ + public ServiceInfo getServiceInfo(String type, String name, int timeout) + { + final ServiceInfoImpl info = new ServiceInfoImpl(type, name); + new ServiceInfoResolver(this, info).start(timer); + + try + { + final long end = System.currentTimeMillis() + timeout; + long delay; + synchronized (info) + { + while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) + { + info.wait(delay); + } + } + } + catch (final InterruptedException e) + { + // empty + } + + return (info.hasData()) ? info : null; + } + + /** + * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, + * java.lang.String) + */ + public void requestServiceInfo(String type, String name) + { + requestServiceInfo(type, name, false, 3 * 1000); + } + /** + * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, + * java.lang.String, boolean) + */ + public void requestServiceInfo(String type, String name, boolean persistent) + { + requestServiceInfo(type, name, persistent, 3 * 1000); + } + + /** + * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, + * java.lang.String, int) + */ + public void requestServiceInfo(String type, String name, int timeout) + { + requestServiceInfo(type, name, false, timeout); + } + + /** + * @see org.jmdns.JmDNS#requestServiceInfo(java.lang.String, + * java.lang.String, boolean, int) + */ + public void requestServiceInfo(String type, String name, + boolean persistent, int timeout) + { + registerServiceType(type); + final ServiceInfoImpl info = new ServiceInfoImpl(type, name); + new ServiceInfoResolver(this, info, persistent).start(timer); + + try + { + final long end = System.currentTimeMillis() + timeout; + long delay; + synchronized (info) + { + while (!info.hasData() && (delay = end - System.currentTimeMillis()) > 0) + { + info.wait(delay); + } + } + } + catch (final InterruptedException e) + { + // empty + } + } + + /** + * @see org.jmdns.JmDNS# + */ + + void handleServiceResolved(ServiceInfoImpl info) + { + List list = null; + ArrayList listCopy = null; + synchronized (serviceListeners) + { + list = (List) serviceListeners.get(info.type.toLowerCase()); + + if (list != null) + { + listCopy = new ArrayList(list); + } + } + if (listCopy != null) + { + final ServiceEvent event = new ServiceEventImpl(this, info.type, info.getName(), info); + for (final Iterator iterator = listCopy.iterator(); iterator.hasNext();) + { + ((ServiceListener) iterator.next()).serviceResolved(event); + } + } + } + + void handleNewTXT(ServiceInfoImpl info) { + handleServiceResolved(info); + } + + /** + * @see + * org.jmdns.JmDNS#addServiceTypeListener(org.jmdns.ServiceTypeListener + * ) + */ + public void addServiceTypeListener(ServiceTypeListener listener) throws IOException + { + synchronized (this) + { + typeListeners.remove(listener); + typeListeners.add(listener); + } + + // report cached service types + for (final Iterator iterator = serviceTypes.values().iterator(); iterator.hasNext();) + { + listener.serviceTypeAdded(new ServiceEventImpl(this, (String) iterator.next(), null, + null)); + } + + new TypeResolver(this).start(timer); + } + + /** + * @see org.jmdns.JmDNS#removeServiceTypeListener(org.jmdns. + * ServiceTypeListener) + */ + public void removeServiceTypeListener(ServiceTypeListener listener) + { + synchronized (this) + { + typeListeners.remove(listener); + } + } + + /** + * @see org.jmdns.JmDNS#addServiceListener(java.lang.String, + * org.jmdns.ServiceListener) + */ + public void addServiceListener(String type, ServiceListener listener) + { + final String lotype = type.toLowerCase(); + removeServiceListener(lotype, listener); + List list = null; + + synchronized (serviceListeners) + { + list = (List) serviceListeners.get(lotype); + if (list == null) + { + list = Collections.synchronizedList(new LinkedList()); + serviceListeners.put(lotype, list); + } + list.add(listener); + } + + // report cached service types + final List serviceEvents = new ArrayList(); + synchronized (cache) + { + for (final Iterator i = cache.iterator(); i.hasNext();) + { + for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) + { + final DNSRecord rec = (DNSRecord) n.getValue(); + if (rec.type == DNSConstants.TYPE_SRV) + { + if (rec.name.endsWith(type)) + { + serviceEvents.add(new ServiceEventImpl(this, type, toUnqualifiedName( + type, rec.name), null)); + } + } + } + } + } + // Actually call listener with all service events added above + for (final Iterator i = serviceEvents.iterator(); i.hasNext();) + { + listener.serviceAdded((ServiceEventImpl) i.next()); + } + // Create/start ServiceResolver + new ServiceResolver(this, type).start(timer); + } + + /** + * @see org.jmdns.JmDNS#removeServiceListener(java.lang.String, + * org.jmdns.ServiceListener) + */ + public void removeServiceListener(String type, ServiceListener listener) + { + type = type.toLowerCase(); + List list = null; + synchronized (serviceListeners) + { + list = (List) serviceListeners.get(type); + if (list != null) + { + list.remove(listener); + if (list.size() == 0) + { + serviceListeners.remove(type); + } + } + } + } + + /** + * @see org.jmdns.JmDNS#registerService(org.jmdns.ServiceInfo) + */ + public void registerService(ServiceInfo infoAbstract) throws IOException + { + final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; + + registerServiceType(info.type); + + // bind the service to this address + info.server = localHost.getName(); + info.addr = localHost.getAddress(); + + synchronized (this) + { + makeServiceNameUnique(info); + services.put(info.getQualifiedName().toLowerCase(), info); + } + + new /* Service */Prober(this).start(timer); + try + { + synchronized (info) + { + while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) + { + info.wait(); + } + } + } + catch (final InterruptedException e) + { + // empty + } + logger.fine("registerService() JmDNS registered service as " + info); + } + + /** + * @see org.jmdns.JMDNS#reannounceService(org.jmdns.ServiceInfo) + */ + public void reannounceService(org.jmdns.ServiceInfo infoAbstract) throws IOException + { + + final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; + + if (services.containsKey(info.getQualifiedName().toLowerCase())) + { + ServiceInfoImpl info_tmp = (ServiceInfoImpl)services.get(info.getQualifiedName().toLowerCase()); + + info.setStateAnnounce(); + + startTextAnnouncer(); + //new /* Service */Prober(this).start(timer); + try + { + synchronized (info) + { + while (info.getState().compareTo(DNSState.ANNOUNCED) < 0) + { + info.wait(); + } + } + } + catch (final InterruptedException e) + { + // empty + } + logger.fine("reannounceService() JmDNS reannounced service " + info); + } + else + { + logger.fine("reannounceService() service to be announced not found: " + info); + } + } + + /** + * @see org.jmdns.JmDNS#unregisterService(org.jmdns.ServiceInfo) + */ + public void unregisterService(ServiceInfo infoAbstract) + { + final ServiceInfoImpl info = (ServiceInfoImpl) infoAbstract; + synchronized (this) + { + services.remove(info.getQualifiedName().toLowerCase()); + } + info.cancel(); + + // Note: We use this lock object to synchronize on it. + // Synchronizing on another object (e.g. the ServiceInfo) does + // not make sense, because the sole purpose of the lock is to + // wait until the canceler has finished. If we synchronized on + // the ServiceInfo or on the Canceler, we would block all + // accesses to synchronized methods on that object. This is not + // what we want! + final Object lock = new Object(); + new Canceler(this, info, lock).start(timer); + + // Remind: We get a deadlock here, if the Canceler does not run! + try + { + synchronized (lock) + { + lock.wait(); + } + } + catch (final InterruptedException e) + { + // empty + } + } + + /** + * @see org.jmdns.JmDNS#unregisterAllServices() + */ + public void unregisterAllServices() + { + logger.finer("unregisterAllServices()"); + if (services.size() == 0) + { + return; + } + + Collection list; + synchronized (this) + { + list = new LinkedList(services.values()); + services.clear(); + } + for (final Iterator iterator = list.iterator(); iterator.hasNext();) + { + ((ServiceInfoImpl) iterator.next()).cancel(); + } + + final Object lock = new Object(); + new Canceler(this, list, lock).start(timer); + // Remind: We get a livelock here, if the Canceler does not run! + try + { + synchronized (lock) + { + if (!closed) + { + lock.wait(); + } + } + } + catch (final InterruptedException e) + { + // empty + } + + } + + /** + * @see org.jmdns.JmDNS#registerServiceType(java.lang.String) + */ + public void registerServiceType(String type) + { + final String name = type.toLowerCase(); + if (serviceTypes.get(name) == null) + { + if ((type.indexOf("._mdns._udp.") < 0) && !type.endsWith(".in-addr.arpa.")) + { + Collection list; + synchronized (this) + { + serviceTypes.put(name, type); + list = new LinkedList(typeListeners); + } + for (final Iterator iterator = list.iterator(); iterator.hasNext();) + { + ((ServiceTypeListener) iterator.next()).serviceTypeAdded(new ServiceEventImpl( + this, type, null, null)); + } + } + } + } + + /** + * Generate a possibly unique name for a host using the information we have + * in the cache. + * + * @return returns true, if the name of the host had to be changed. + */ + private boolean makeHostNameUnique(DNSRecord.Address host) + { + final String originalName = host.getName(); + System.currentTimeMillis(); + + boolean collision; + do + { + collision = false; + + // Check for collision in cache + for (DNSCache.CacheNode j = cache.find(host.getName().toLowerCase()); j != null; j = j + .next()) + { + if (false) + { + host.name = incrementName(host.getName()); + collision = true; + break; + } + } + } + while (collision); + + if (originalName.equals(host.getName())) + { + return false; + } + else + { + return true; + } + } + + /** + * Generate a possibly unique name for a service using the information we + * have in the cache. + * + * @return returns true, if the name of the service info had to be changed. + */ + private boolean makeServiceNameUnique(ServiceInfoImpl info) + { + final String originalQualifiedName = info.getQualifiedName(); + final long now = System.currentTimeMillis(); + + boolean collision; + do + { + collision = false; + + // Check for collision in cache + for (DNSCache.CacheNode j = cache.find(info.getQualifiedName().toLowerCase()); j != null; j = j + .next()) + { + final DNSRecord a = (DNSRecord) j.getValue(); + if ((a.type == DNSConstants.TYPE_SRV) && !a.isExpired(now)) + { + final DNSRecord.Service s = (DNSRecord.Service) a; + if (s.port != info.port || !s.server.equals(localHost.getName())) + { + logger + .finer("makeServiceNameUnique() JmDNS.makeServiceNameUnique srv collision:" + + a + + " s.server=" + + s.server + + " " + + localHost.getName() + + " equals:" + (s.server.equals(localHost.getName()))); + info.setName(incrementName(info.getName())); + collision = true; + break; + } + } + } + + // Check for collision with other service infos published by JmDNS + final Object selfService = services.get(info.getQualifiedName().toLowerCase()); + if (selfService != null && selfService != info) + { + info.setName(incrementName(info.getName())); + collision = true; + } + } + while (collision); + + return !(originalQualifiedName.equals(info.getQualifiedName())); + } + + String incrementName(String name) + { + try + { + final int l = name.lastIndexOf('('); + final int r = name.lastIndexOf(')'); + if ((l >= 0) && (l < r)) + { + name = name.substring(0, l) + "(" + + (Integer.parseInt(name.substring(l + 1, r)) + 1) + ")"; + } + else + { + name += " (2)"; + } + } + catch (final NumberFormatException e) + { + name += " (2)"; + } + return name; + } + + /** + * Add a listener for a question. The listener will receive updates of + * answers to the question as they arrive, or from the cache if they are + * already available. + */ + public void addListener(DNSListener listener, DNSQuestion question) + { + final long now = System.currentTimeMillis(); + + // add the new listener + synchronized (this) + { + listeners.add(listener); + } + + // report existing matched records + if (question != null) + { + for (DNSCache.CacheNode i = cache.find(question.name); i != null; i = i.next()) + { + final DNSRecord c = (DNSRecord) i.getValue(); + if (question.answeredBy(c) && !c.isExpired(now)) + { + listener.updateRecord(this, now, c); + } + } + } + } + + /** + * Remove a listener from all outstanding questions. The listener will no + * longer receive any updates. + */ + public void removeListener(DNSListener listener) + { + synchronized (this) + { + listeners.remove(listener); + } + } + + // Remind: Method updateRecord should receive a better name. + /** + * Notify all listeners that a record was updated. + */ + public void updateRecord(long now, DNSRecord rec) + { + // We do not want to block the entire DNS while we are updating the + // record for each listener (service info) + List listenerList = null; + synchronized (this) + { + listenerList = new ArrayList(listeners); + } + for (final Iterator iterator = listenerList.iterator(); iterator.hasNext();) + { + final DNSListener listener = (DNSListener) iterator.next(); + listener.updateRecord(this, now, rec); + } + if (rec.type == DNSConstants.TYPE_PTR || rec.type == DNSConstants.TYPE_SRV) + { + List serviceListenerList = null; + synchronized (serviceListeners) + { + serviceListenerList = (List) serviceListeners.get(rec.name.toLowerCase()); + // Iterate on a copy in case listeners will modify it + if (serviceListenerList != null) + { + serviceListenerList = new ArrayList(serviceListenerList); + } + } + if (serviceListenerList != null) + { + final boolean expired = rec.isExpired(now); + final String type = rec.getName(); + final String name = ((DNSRecord.Pointer) rec).getAlias(); + // DNSRecord old = (DNSRecord)services.get(name.toLowerCase()); + if (!expired) + { + // new record + final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName( + type, name), null); + for (final Iterator iterator = serviceListenerList.iterator(); iterator + .hasNext();) + { + ((ServiceListener) iterator.next()).serviceAdded(event); + } + } + else + { + // expire record + final ServiceEvent event = new ServiceEventImpl(this, type, toUnqualifiedName( + type, name), null); + for (final Iterator iterator = serviceListenerList.iterator(); iterator + .hasNext();) + { + ((ServiceListener) iterator.next()).serviceRemoved(event); + } + } + } + } + } + + /** + * Handle an incoming response. Cache answers, and pass them on to the + * appropriate questions. + */ + void handleResponse(DNSIncoming msg) throws IOException + { + final long now = System.currentTimeMillis(); + + boolean hostConflictDetected = false; + boolean serviceConflictDetected = false; + + for (final Iterator i = msg.answers.iterator(); i.hasNext();) + { + boolean isInformative = false; + DNSRecord rec = (DNSRecord) i.next(); + final boolean expired = rec.isExpired(now); + + // update the cache + synchronized (cache) + { + final DNSRecord c; + + // Only store / use ONE TXT record in cache. + if (rec instanceof DNSRecord.Text) + { + c = (DNSRecord) cache.get(rec.getName(), + DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN); + } + else + { + c = (DNSRecord) cache.get(rec); + } + + if (c != null) + { + if (expired) + { + isInformative = true; + cache.remove(c); + } + else + { + // If a TXT entry is received, if it has changed + // update the cache and inform the outside world. + if (rec instanceof DNSRecord.Text && + !rec.sameValue(c)) + { + isInformative = true; + cache.remove(c); + cache.add(rec); + } + else + { + c.resetTTL(rec); + rec = c; + } + } + } + else + { + if (!expired) + { + isInformative = true; + cache.add(rec); + } + } + } + switch (rec.type) + { + case DNSConstants.TYPE_PTR: + // handle _mdns._udp records + if (rec.getName().indexOf("._mdns._udp.") >= 0) + { + if (!expired && rec.name.startsWith("_services._mdns._udp.")) + { + isInformative = true; + registerServiceType(((DNSRecord.Pointer) rec).alias); + } + continue; + } + registerServiceType(rec.name); + break; + } + + if ((rec.getType() == DNSConstants.TYPE_A) || (rec.getType() == DNSConstants.TYPE_AAAA)) + { + hostConflictDetected |= rec.handleResponse(this); + } + else + { + serviceConflictDetected |= rec.handleResponse(this); + } + + // notify the listeners + if (isInformative) + { + updateRecord(now, rec); + } + } + + if (hostConflictDetected || serviceConflictDetected) + { + new Prober(this).start(timer); + } + } + + /** + * Handle an incoming query. See if we can answer any part of it given our + * service infos. + */ + void handleQuery(DNSIncoming in, InetAddress addr, int port) throws IOException + { + // Track known answers + boolean hostConflictDetected = false; + boolean serviceConflictDetected = false; + final long expirationTime = System.currentTimeMillis() + DNSConstants.KNOWN_ANSWER_TTL; + for (final Iterator i = in.answers.iterator(); i.hasNext();) + { + final DNSRecord answer = (DNSRecord) i.next(); + if ((answer.getType() == DNSConstants.TYPE_A) + || (answer.getType() == DNSConstants.TYPE_AAAA)) + { + hostConflictDetected |= answer.handleQuery(this, expirationTime); + } + else + { + serviceConflictDetected |= answer.handleQuery(this, expirationTime); + } + } + + if (plannedAnswer != null) + { + plannedAnswer.append(in); + } + else + { + if (in.isTruncated()) + { + plannedAnswer = in; + } + + new Responder(this, in, addr, port).start(); + } + + if (hostConflictDetected || serviceConflictDetected) + { + new Prober(this).start(timer); + } + } + + /** + * Add an answer to a question. Deal with the case when the outgoing packet + * overflows + */ + public DNSOutgoing addAnswer(DNSIncoming in, InetAddress addr, int port, DNSOutgoing out, + DNSRecord rec) throws IOException + { + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + try + { + out.addAnswer(in, rec); + } + catch (final IOException e) + { + out.flags |= DNSConstants.FLAGS_TC; + out.id = in.id; + out.finish(); + send(out); + + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + out.addAnswer(in, rec); + } + return out; + } + + /** + * Send an outgoing multicast DNS message. + */ + public void send(DNSOutgoing out) throws IOException + { + out.finish(); + if (!out.isEmpty()) + { + final DatagramPacket packet = new DatagramPacket(out.data, out.off, group, + DNSConstants.MDNS_PORT); + + try + { + final DNSIncoming msg = new DNSIncoming(packet); + logger.finest("send() JmDNS out:" + msg.print(true)); + } + catch (final IOException e) + { + logger.throwing(getClass().toString(), + "send(DNSOutgoing) - JmDNS can not parse what it sends!!!", e); + } + final MulticastSocket ms = socket; + if (ms != null && !ms.isClosed()) + ms.send(packet); + } + } + + public void startAnnouncer() + { + new Announcer(this).start(timer); + } + + public void startTextAnnouncer() + { + new TextAnnouncer(this).start(timer); + } + + public void startRenewer() + { + new Renewer(this).start(timer); + } + + public void schedule(TimerTask task, int delay) + { + timer.schedule(task, delay); + } + + // REMIND: Why is this not an anonymous inner class? + /** + * Shutdown operations. + */ + private class Shutdown implements Runnable + { + public void run() + { + shutdown = null; + close(); + } + } + + /** + * Recover jmdns when there is an error. + */ + public void recover() + { + logger.finer("recover()"); + // We have an IO error so lets try to recover if anything happens lets + // close it. + // This should cover the case of the IP address changing under our feet + if (DNSState.CANCELED != getState()) + { + synchronized (this) + { // Synchronize only if we are not already in process to prevent + // dead locks + // + logger.finer("recover() Cleanning up"); + // Stop JmDNS + setState(DNSState.CANCELED); // This protects against recursive + // calls + + // We need to keep a copy for reregistration + final Collection oldServiceInfos = new ArrayList(getServices().values()); + + // Cancel all services + unregisterAllServices(); + disposeServiceCollectors(); + // + // close multicast socket + closeMulticastSocket(); + // + cache.clear(); + logger.finer("recover() All is clean"); + // + // All is clear now start the services + // + try + { + openMulticastSocket(getLocalHost()); + start(oldServiceInfos); + } + catch (final Exception exception) + { + logger.log(Level.WARNING, "recover() Start services exception ", exception); + } + logger.log(Level.WARNING, "recover() We are back!"); + } + } + } + + /** + * @see org.jmdns.JmDNS#close() + */ + public void close() + { + if (getState() != DNSState.CANCELED) + { + synchronized (this) + { // Synchronize only if we are not already in process to prevent + // dead locks + // Stop JmDNS + setState(DNSState.CANCELED); // This protects against recursive + // calls + + unregisterAllServices(); + disposeServiceCollectors(); + + // close socket + closeMulticastSocket(); + + // Stop the timer + timer.cancel(); + + // remove the shutdown hook + if (shutdown != null) + { + Runtime.getRuntime().removeShutdownHook(shutdown); + } + + } + } + } + + /** + * List cache entries, for debugging only. + */ + void print() + { + System.out.println("---- cache ----"); + cache.print(); + System.out.println(); + } + + /** + * @see org.jmdns.JmDNS#printServices() + */ + public void printServices() + { + System.err.println(toString()); + } + + public String toString() + { + final StringBuffer aLog = new StringBuffer(); + aLog.append("\t---- Services -----"); + if (services != null) + { + for (final Iterator k = services.keySet().iterator(); k.hasNext();) + { + final Object key = k.next(); + aLog.append("\n\t\tService: " + key + ": " + services.get(key)); + } + } + aLog.append("\n"); + aLog.append("\t---- Types ----"); + if (serviceTypes != null) + { + for (final Iterator k = serviceTypes.keySet().iterator(); k.hasNext();) + { + final Object key = k.next(); + aLog.append("\n\t\tType: " + key + ": " + serviceTypes.get(key)); + } + } + aLog.append("\n"); + aLog.append(cache.toString()); + aLog.append("\n"); + aLog.append("\t---- Service Collectors ----"); + if (serviceCollectors != null) + { + synchronized (serviceCollectors) + { + for (final Iterator k = serviceCollectors.keySet().iterator(); k.hasNext();) + { + final Object key = k.next(); + aLog.append("\n\t\tService Collector: " + key + ": " + + serviceCollectors.get(key)); + } + serviceCollectors.clear(); + } + } + return aLog.toString(); + } + + /** + * @see org.jmdns.JmDNS#list(java.lang.String) + */ + public ServiceInfo[] list(String type) + { + // Implementation note: The first time a list for a given type is + // requested, a ServiceCollector is created which collects service + // infos. This greatly speeds up the performance of subsequent calls + // to this method. The caveats are, that 1) the first call to this + // method + // for a given type is slow, and 2) we spawn a ServiceCollector + // instance for each service type which increases network traffic a + // little. + + ServiceCollector collector; + + boolean newCollectorCreated; + synchronized (serviceCollectors) + { + collector = (ServiceCollector) serviceCollectors.get(type); + if (collector == null) + { + collector = new ServiceCollector(type); + serviceCollectors.put(type, collector); + addServiceListener(type, collector); + newCollectorCreated = true; + } + else + { + newCollectorCreated = false; + } + } + + // After creating a new ServiceCollector, we collect service infos for + // 200 milliseconds. This should be enough time, to get some service + // infos from the network. + if (newCollectorCreated) + { + try + { + Thread.sleep(200); + } + catch (final InterruptedException e) + { + } + } + + return collector.list(); + } + + /** + * This method disposes all ServiceCollector instances which have been + * created by calls to method list(type). + * + * @see #list + */ + private void disposeServiceCollectors() + { + logger.finer("disposeServiceCollectors()"); + synchronized (serviceCollectors) + { + for (final Iterator i = serviceCollectors.values().iterator(); i.hasNext();) + { + final ServiceCollector collector = (ServiceCollector) i.next(); + removeServiceListener(collector.type, collector); + } + serviceCollectors.clear(); + } + } + + /** + * Instances of ServiceCollector are used internally to speed up the + * performance of method list(type). + * + * @see #list + */ + private static class ServiceCollector implements ServiceListener + { + private static Logger logger = Logger.getLogger(ServiceCollector.class.getName()); + /** + * A set of collected service instance names. + */ + private final Map infos = Collections.synchronizedMap(new HashMap()); + + public String type; + + public ServiceCollector(String type) + { + this.type = type; + } + + /** + * A service has been added. + */ + public void serviceAdded(ServiceEvent event) + { + synchronized (infos) + { + event.getDNS().requestServiceInfo(event.getType(), event.getName(), 0); + } + } + + /** + * A service has been removed. + */ + public void serviceRemoved(ServiceEvent event) + { + synchronized (infos) + { + infos.remove(event.getName()); + } + } + + /** + * A service hase been resolved. Its details are now available in the + * ServiceInfo record. + */ + public void serviceResolved(ServiceEvent event) + { + synchronized (infos) + { + infos.put(event.getName(), event.getInfo()); + } + } + + /** + * Returns an array of all service infos which have been collected by + * this ServiceCollector. + */ + public ServiceInfoImpl[] list() + { + synchronized (infos) + { + return (ServiceInfoImpl[]) infos.values() + .toArray(new ServiceInfoImpl[infos.size()]); + } + } + + public String toString() + { + final StringBuffer aLog = new StringBuffer(); + synchronized (infos) + { + for (final Iterator k = infos.keySet().iterator(); k.hasNext();) + { + final Object key = k.next(); + aLog.append("\n\t\tService: " + key + ": " + infos.get(key)); + } + } + return aLog.toString(); + } + }; + + private static String toUnqualifiedName(String type, String qualifiedName) + { + if (qualifiedName.endsWith(type)) + { + return qualifiedName.substring(0, qualifiedName.length() - type.length() - 1); + } + else + { + return qualifiedName; + } + } + + public void setState(DNSState state) + { + this.state = state; + } + + public void setTask(TimerTask task) + { + this.task = task; + } + + public TimerTask getTask() + { + return task; + } + + public Map getServices() + { + return services; + } + + public void setLastThrottleIncrement(long lastThrottleIncrement) + { + this.lastThrottleIncrement = lastThrottleIncrement; + } + + public long getLastThrottleIncrement() + { + return lastThrottleIncrement; + } + + public void setThrottle(int throttle) + { + this.throttle = throttle; + } + + public int getThrottle() + { + return throttle; + } + + public static Random getRandom() + { + return random; + } + + public void setIoLock(Object ioLock) + { + this.ioLock = ioLock; + } + + public Object getIoLock() + { + return ioLock; + } + + public void setPlannedAnswer(DNSIncoming plannedAnswer) + { + this.plannedAnswer = plannedAnswer; + } + + public DNSIncoming getPlannedAnswer() + { + return plannedAnswer; + } + + void setLocalHost(HostInfo localHost) + { + this.localHost = localHost; + } + + public Map getServiceTypes() + { + return serviceTypes; + } + + public void setClosed(boolean closed) + { + this.closed = closed; + } + + public boolean isClosed() + { + return closed; + } + + public MulticastSocket getSocket() + { + return socket; + } + + public InetAddress getGroup() + { + return group; + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java new file mode 100644 index 0000000..8d9fe00 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceEventImpl.java @@ -0,0 +1,99 @@ +///Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.util.logging.Logger; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceEvent; +import org.jmdns.ServiceInfo; + +/** + * ServiceEvent. + * + * @author Werner Randelshofer, Rick Blair + * @version %I%, %G% + */ +public class ServiceEventImpl extends ServiceEvent +{ + private static Logger logger = Logger.getLogger(ServiceEvent.class.getName()); + /** + * The type name of the service. + */ + private String type; + /** + * The instance name of the service. Or null, if the event was + * fired to a service type listener. + */ + private String name; + /** + * The service info record, or null if the service could be be resolved. + * This is also null, if the event was fired to a service type listener. + */ + private ServiceInfoImpl info; + + /** + * Creates a new instance. + * + * @param source the JmDNS instance which originated the event. + * @param type the type name of the service. + * @param name the instance name of the service. + * @param info the service info record, or null if the service could be be resolved. + */ + public ServiceEventImpl(JmDNSImpl source, String type, String name, ServiceInfoImpl info) + { + super(source); + this.type = type; + this.name = name; + this.info = info; + } + + /** + * @see org.jmdns.ServiceEvent#getDNS() + */ + public JmDNS getDNS() + { + return (JmDNS) getSource(); + } + + /** + * @see org.jmdns.ServiceEvent#getType() + */ + public String getType() + { + return type; + } + + /** + * @see org.jmdns.ServiceEvent#getName() + */ + public String getName() + { + return name; + } + + /** + * @see org.jmdns.ServiceEvent#toString() + */ + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("<" + getClass().getName() + "> "); + buf.append(super.toString()); + buf.append(" name "); + buf.append(getName()); + buf.append(" type "); + buf.append(getType()); + buf.append(" info "); + buf.append(getInfo()); + return buf.toString(); + } + + public ServiceInfo getInfo() + { + return info; + } + +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java new file mode 100644 index 0000000..2a852f0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/ServiceInfoImpl.java @@ -0,0 +1,747 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetAddress; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.TimerTask; +import java.util.Vector; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.logging.Logger; + +import org.jmdns.ServiceInfo; +import org.jmdns.ServiceNameListener; +import org.jmdns.impl.DNSRecord.Pointer; +import org.jmdns.impl.DNSRecord.Service; +import org.jmdns.impl.DNSRecord.Text; + +/** + * JmDNS service information. + * + * @version %I%, %G% + * @author Arthur van Hoff, Jeff Sonstein, Werner Randelshofer + */ +public class ServiceInfoImpl extends ServiceInfo implements DNSListener +{ + private static Logger logger = Logger.getLogger(ServiceInfoImpl.class.getName()); + private JmDNSImpl dns; + + // State machine + /** + * The state of this service info. + * This is used only for services announced by JmDNS. + *

      + * For proper handling of concurrency, this variable must be + * changed only using methods advanceState(), revertState() and cancel(). + */ + private DNSState state = DNSState.PROBING_1; + + /** + * Task associated to this service info. + * Possible tasks are JmDNS.Prober, JmDNS.Announcer, JmDNS.Responder, + * JmDNS.Canceler. + */ + private TimerTask task; + + String type; + private String name; + String server; + int port; + int weight; + int priority; + private byte text[]; + Hashtable props; + InetAddress addr; + private boolean handled = false; + + + /** + * Service name change listeners. + */ + private Set serviceNameListeners = new CopyOnWriteArraySet(); + + /** + * @see org.jmdns.ServiceInfo#create(String, String, int, String) + */ + public ServiceInfoImpl(String type, String name, int port, String text) + { + this(type, name, port, 0, 0, text); + } + + /** + * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, String) + */ + public ServiceInfoImpl(String type, String name, int port, int weight, int priority, String text) + { + this(type, name, port, weight, priority, (byte[]) null); + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(text.length()); + writeUTF(out, text); + byte [] data = out.toByteArray(); + this.setText(new byte[data.length + 1]); + this.getText()[0] = (byte) data.length; + System.arraycopy(data, 0, this.getText(), 1, data.length); + } + catch (IOException e) + { + throw new RuntimeException("unexpected exception: " + e); + } + } + + /** + * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, Hashtable) + */ + public ServiceInfoImpl(String type, String name, int port, int weight, int priority, Hashtable props) + { + this(type, name, port, weight, priority, new byte[0]); + if (props != null) + { + this.setText(props); + } + } + + /** + * @see org.jmdns.ServiceInfo#create(String, String, int, int, int, byte[]) + */ + public ServiceInfoImpl(String type, String name, int port, int weight, int priority, byte text[]) + { + this.type = type; + this.name = name; + this.port = port; + this.weight = weight; + this.priority = priority; + this.setText(text); + } + + /** + * Construct a service record during service discovery. + */ + ServiceInfoImpl(String type, String name) + { + if (!type.endsWith(".")) + { + throw new IllegalArgumentException("type must be fully qualified DNS name ending in '.': " + type); + } + + this.type = type; + this.name = name; + } + + /** + * During recovery we need to duplicate service info to reregister them + */ + ServiceInfoImpl(ServiceInfoImpl info) + { + if (info != null) + { + this.type = info.type; + this.name = info.name; + this.port = info.port; + this.weight = info.weight; + this.priority = info.priority; + this.setText(info.getText()); + } + } + + /** + * @see org.jmdns.ServiceInfo#getType() + */ + public String getType() + { + return type; + } + + /** + * @see org.jmdns.ServiceInfo#getName() + */ + public String getName() + { + return name; + } + + /** + * Sets the service instance name. + * + * @param name unqualified service instance name, such as foobar + */ + void setName(String name) + { + String oldName = this.name; + this.name = name; + + if (!oldName.equals(name)) + notifyServiceNameChanged(name, oldName); + } + + /** + * @see org.jmdns.ServiceInfo#getQualifiedName() + */ + public String getQualifiedName() + { + return name + "." + type; + } + + /** + * @see org.jmdns.ServiceInfo#getServer() + */ + public String getServer() + { + return server; + } + + /** + * @see org.jmdns.ServiceInfo#getHostAddress() + */ + public String getHostAddress() + { + return (addr != null ? addr.getHostAddress() : ""); + } + + public InetAddress getAddress() + { + return addr; + } + + /** + * @see org.jmdns.ServiceInfo#getInetAddress() + */ + public InetAddress getInetAddress() + { + return addr; + } + + /** + * @see org.jmdns.ServiceInfo#getPort() + */ + public int getPort() + { + return port; + } + + /** + * @see org.jmdns.ServiceInfo#getPriority() + */ + public int getPriority() + { + return priority; + } + + /** + * @see org.jmdns.ServiceInfo#getWeight() + */ + public int getWeight() + { + return weight; + } + + /** + * @see org.jmdns.ServiceInfo#getTextBytes() + */ + public byte[] getTextBytes() + { + return getText(); + } + + /** + * @see org.jmdns.ServiceInfo#getTextString() + */ + public String getTextString() + { + if ((getText() == null) || (getText().length == 0) || ((getText().length == 1) && (getText()[0] == 0))) + { + return null; + } + return readUTF(getText(), 0, getText().length); + } + + /** + * @see org.jmdns.ServiceInfo#getURL() + */ + public String getURL() + { + return getURL("http"); + } + + /** + * @see org.jmdns.ServiceInfo#getURL(java.lang.String) + */ + public String getURL(String protocol) + { + String url = protocol + "://" + getHostAddress() + ":" + getPort(); + String path = getPropertyString("path"); + if (path != null) + { + if (path.indexOf("://") >= 0) + { + url = path; + } + else + { + url += path.startsWith("/") ? path : "/" + path; + } + } + return url; + } + + /** + * @see org.jmdns.ServiceInfo#getPropertyBytes(java.lang.String) + */ + public synchronized byte[] getPropertyBytes(String name) + { + return (byte[]) getProperties().get(name); + } + + /** + * @see org.jmdns.ServiceInfo#getPropertyString(java.lang.String) + */ + public synchronized String getPropertyString(String name) + { + byte data[] = (byte[]) getProperties().get(name); + if (data == null) + { + return null; + } + if (data == NO_VALUE) + { + return "true"; + } + return readUTF(data, 0, data.length); + } + + /** + * @see org.jmdns.ServiceInfo#getPropertyNames() + */ + public Enumeration getPropertyNames() + { + Hashtable props = getProperties(); + return (props != null) ? props.keys() : new Vector().elements(); + } + + /** + * Set record as handled. + */ + void setHandled() + { + handled = true; + } + + /** + * Check if record has been handled. + * + * @return true if record has been handled. + */ + boolean isHandled() + { + return handled; + } + + /** + * Write a UTF string with a length to a stream. + */ + void writeUTF(OutputStream out, String str) throws IOException + { + for (int i = 0, len = str.length(); i < len; i++) + { + int c = str.charAt(i); + if ((c >= 0x0001) && (c <= 0x007F)) + { + out.write(c); + } + else + { + if (c > 0x07FF) + { + out.write(0xE0 | ((c >> 12) & 0x0F)); + out.write(0x80 | ((c >> 6) & 0x3F)); + out.write(0x80 | ((c >> 0) & 0x3F)); + } + else + { + out.write(0xC0 | ((c >> 6) & 0x1F)); + out.write(0x80 | ((c >> 0) & 0x3F)); + } + } + } + } + + /** + * Read data bytes as a UTF stream. + */ + String readUTF(byte data[], int off, int len) + { + StringBuffer buf = new StringBuffer(); + for (int end = off + len; off < end;) + { + int ch = data[off++] & 0xFF; + switch (ch >> 4) + { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + // 0xxxxxxx + break; + case 12: + case 13: + if (off >= len) + { + return null; + } + // 110x xxxx 10xx xxxx + ch = ((ch & 0x1F) << 6) | (data[off++] & 0x3F); + break; + case 14: + if (off + 2 >= len) + { + return null; + } + // 1110 xxxx 10xx xxxx 10xx xxxx + ch = ((ch & 0x0f) << 12) | ((data[off++] & 0x3F) << 6) | (data[off++] & 0x3F); + break; + default: + if (off + 1 >= len) + { + return null; + } + // 10xx xxxx, 1111 xxxx + ch = ((ch & 0x3F) << 4) | (data[off++] & 0x0f); + break; + } + buf.append((char) ch); + } + return buf.toString(); + } + + synchronized Hashtable getProperties() + { + if ((props == null) && (getText() != null)) + { + Hashtable props = new Hashtable(); + int off = 0; + while (off < getText().length) + { + // length of the next key value pair + int len = getText()[off++] & 0xFF; + if ((len == 0) || (off + len > getText().length)) + { + props.clear(); + break; + } + // look for the '=' + int i = 0; + for (; (i < len) && (getText()[off + i] != '='); i++) + { + ; + } + + // get the property name + String name = readUTF(getText(), off, i); + if (name == null) + { + props.clear(); + break; + } + if (i == len) + { + props.put(name, NO_VALUE); + } + else + { + byte value[] = new byte[len - ++i]; + System.arraycopy(getText(), off + i, value, 0, len - i); + props.put(name, value); + off += len; + } + } + this.props = props; + } + return props; + } + + /** + * JmDNS callback to update a DNS record. + */ + public void updateRecord(JmDNSImpl jmdns, long now, DNSRecord rec) + { + if ((rec != null) && !rec.isExpired(now)) + { + switch (rec.type) + { + case DNSConstants.TYPE_A: // IPv4 + case DNSConstants.TYPE_AAAA: // IPv6 FIXME [PJYF Oct 14 2004] This has not been tested + if (rec.name.equals(server)) + { + addr = ((DNSRecord.Address) rec).getAddress(); + + } + break; + case DNSConstants.TYPE_SRV: + if (rec.name.equals(getQualifiedName())) + { + DNSRecord.Service srv = (DNSRecord.Service) rec; + server = srv.server; + port = srv.port; + weight = srv.weight; + priority = srv.priority; + addr = null; + // changed to use getCache() instead - jeffs + // updateRecord(jmdns, now, (DNSRecord)jmdns.cache.get(server, TYPE_A, CLASS_IN)); + updateRecord(jmdns, now, (DNSRecord) jmdns.getCache().get(server, DNSConstants.TYPE_A, DNSConstants.CLASS_IN)); + } + break; + case DNSConstants.TYPE_TXT: + if (rec.name.equals(getQualifiedName())) + { + DNSRecord.Text txt = (DNSRecord.Text) rec; + setText(txt.text); + + // Only send TXT updates after it's handled the first time + if (isHandled()) + { + getDns().handleNewTXT(this); + } + } + break; + } + + // Future Design Pattern + // This is done, to notify the wait loop in method + // JmDNS.getServiceInfo(type, name, timeout); + // use isHandled / setHandled instead of getDns + //if (hasData() && getDns() != null) + if (hasData() && !isHandled()) + { + getDns().handleServiceResolved(this); + //setDns(null); + setHandled(); + } + synchronized (this) + { + notifyAll(); + } + } + } + + /** + * Returns true if the service info is filled with data. + */ + public boolean hasData() + { + return server != null && addr != null && getText() != null; + } + + + // State machine + /** + * Sets the state and notifies all objects that wait on the ServiceInfo. + */ + public synchronized void advanceState() + { + state = state.advance(); + notifyAll(); + } + + /** + * Sets the state and notifies all objects that wait on the ServiceInfo. + */ + synchronized void revertState() + { + state = state.revert(); + notifyAll(); + } + + /** + * Sets the state to the first announce state. + */ + synchronized void setStateAnnounce() + { + state = DNSState.ANNOUNCING_1; + } + + /** + * Sets the state and notifies all objects that wait on the ServiceInfo. + */ + synchronized void cancel() + { + state = DNSState.CANCELED; + notifyAll(); + } + + /** + * Returns the current state of this info. + */ + public DNSState getState() + { + return state; + } + + + public int hashCode() + { + return getQualifiedName().hashCode(); + } + + public boolean equals(Object obj) + { + return (obj instanceof ServiceInfoImpl) && getQualifiedName().equals(((ServiceInfoImpl) obj).getQualifiedName()); + } + + public String getNiceTextString() + { + StringBuffer buf = new StringBuffer(); + for (int i = 0, len = getText().length; i < len; i++) + { + if (i >= 20) + { + buf.append("..."); + break; + } + int ch = getText()[i] & 0xFF; + if ((ch < ' ') || (ch > 127)) + { + buf.append("\\0"); + buf.append(Integer.toString(ch, 8)); + } + else + { + buf.append((char) ch); + } + } + return buf.toString(); + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append("service["); + buf.append(getQualifiedName()); + buf.append(','); + buf.append(getAddress()); + buf.append(':'); + buf.append(port); + buf.append(','); + buf.append(getNiceTextString()); + buf.append(']'); + return buf.toString(); + } + + public void addAnswers(DNSOutgoing out, int ttl, HostInfo localHost) throws IOException + { + out.addAnswer(new Pointer(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, ttl, + getQualifiedName()), 0); + out.addAnswer(new Service(getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, + ttl, priority, weight, port, localHost.getName()), 0); + out.addAnswer(new Text(getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, + ttl, getText()), 0); + } + + public void addTextAnswer(DNSOutgoing out, int ttl) throws IOException + { + out.addAnswer(new Text(getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN|DNSConstants.CLASS_UNIQUE, + ttl, getText()), 0); + } + + public void setTask(TimerTask task) + { + this.task = task; + } + + public TimerTask getTask() + { + return task; + } + + public void setText(byte [] text) + { + this.text = text; + } + + public void setText(Hashtable props) + { + try + { + ByteArrayOutputStream out = new ByteArrayOutputStream(256); + for (Enumeration e = props.keys(); e.hasMoreElements();) + { + String key = (String) e.nextElement(); + Object val = props.get(key); + ByteArrayOutputStream out2 = new ByteArrayOutputStream(100); + writeUTF(out2, key); + if (val instanceof String) + { + out2.write('='); + writeUTF(out2, (String) val); + } + else + { + if (val instanceof byte[]) + { + out2.write('='); + byte[] bval = (byte[]) val; + out2.write(bval, 0, bval.length); + } + else + { + if (val != NO_VALUE) + { + throw new IllegalArgumentException("invalid property value: " + val); + } + } + } + byte data[] = out2.toByteArray(); + out.write(data.length); + out.write(data, 0, data.length); + } + this.setText(out.toByteArray()); + } + catch (IOException e) + { + throw new RuntimeException("unexpected exception: " + e); + } + } + + public byte [] getText() + { + return text; + } + + public void setDns(JmDNSImpl dns) + { + this.dns = dns; + } + + public JmDNSImpl getDns() + { + return dns; + } + + private void notifyServiceNameChanged(String newName, String oldName) { + Iterator i = serviceNameListeners.iterator(); + while (i.hasNext()) { + ServiceNameListener l = (ServiceNameListener) i.next(); + + l.serviceNameChanged(newName, oldName); + } + } + + public void addServiceNameListener(ServiceNameListener listener) { + serviceNameListeners.add(listener); + } + + public void removeServiceNameListener(ServiceNameListener listener) { + serviceNameListeners.remove(listener); + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java b/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java new file mode 100644 index 0000000..3937d55 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/SocketListener.java @@ -0,0 +1,87 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl; + +import java.io.IOException; +import java.net.DatagramPacket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Listen for multicast packets. + */ +class SocketListener implements Runnable +{ + static Logger logger = Logger.getLogger(SocketListener.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + + /** + * @param jmDNSImpl + */ + SocketListener(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + } + + public void run() + { + try + { + byte buf[] = new byte[DNSConstants.MAX_MSG_ABSOLUTE]; + DatagramPacket packet = new DatagramPacket(buf, buf.length); + while (this.jmDNSImpl.getState() != DNSState.CANCELED) + { + packet.setLength(buf.length); + this.jmDNSImpl.getSocket().receive(packet); + if (this.jmDNSImpl.getState() == DNSState.CANCELED) + { + break; + } + try + { + if (this.jmDNSImpl.getLocalHost().shouldIgnorePacket(packet)) + { + continue; + } + + DNSIncoming msg = new DNSIncoming(packet); + logger.finest("SocketListener.run() JmDNS in:" + msg.print(true)); + + synchronized (this.jmDNSImpl.getIoLock()) + { + if (msg.isQuery()) + { + if (packet.getPort() != DNSConstants.MDNS_PORT) + { + this.jmDNSImpl.handleQuery(msg, packet.getAddress(), packet.getPort()); + } + this.jmDNSImpl.handleQuery(msg, this.jmDNSImpl.getGroup(), DNSConstants.MDNS_PORT); + } + else + { + this.jmDNSImpl.handleResponse(msg); + } + } + } + catch (IOException e) + { + logger.log(Level.WARNING, "run() exception ", e); + } + } + } + catch (IOException e) + { + if (this.jmDNSImpl.getState() != DNSState.CANCELED) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java new file mode 100644 index 0000000..d8a9db9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Announcer.java @@ -0,0 +1,159 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The Announcer sends an accumulated query of all announces, and advances + * the state of all serviceInfos, for which it has sent an announce. + * The Announcer also sends announcements and advances the state of JmDNS itself. + *

      + * When the announcer has run two times, it finishes. + */ +public class Announcer extends TimerTask +{ + static Logger logger = Logger.getLogger(Announcer.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * The state of the announcer. + */ + DNSState taskState = DNSState.ANNOUNCING_1; + + public Announcer(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + // Associate host to this, if it needs announcing + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCING_1) + { + this.jmDNSImpl.setTask(this); + } + // Associate services to this, if they need announcing + synchronized (this.jmDNSImpl) + { + for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) s.next(); + if (info.getState() == DNSState.ANNOUNCING_1) + { + info.setTask(this); + } + } + } + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL); + } + + public boolean cancel() + { + // Remove association from host to this + if (this.jmDNSImpl.getTask() == this) + { + this.jmDNSImpl.setTask(null); + } + + // Remove associations from services to this + synchronized (this.jmDNSImpl) + { + for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + if (info.getTask() == this) + { + info.setTask(null); + } + } + } + + return super.cancel(); + } + + public void run() + { + DNSOutgoing out = null; + try + { + // send probes for JmDNS itself + if (this.jmDNSImpl.getState() == taskState) + { + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); + this.jmDNSImpl.advanceState(); + } + // send announces for services + // Defensively copy the services into a local list, + // to prevent race conditions with methods registerService + // and unregisterService. + List list; + synchronized (this.jmDNSImpl) + { + list = new ArrayList(this.jmDNSImpl.getServices().values()); + } + for (Iterator i = list.iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + synchronized (info) + { + if (info.getState() == taskState && info.getTask() == this) + { + info.advanceState(); + logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState()); + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost()); + } + } + } + if (out != null) + { + logger.finer("run() JmDNS announcing #" + taskState); + this.jmDNSImpl.send(out); + } + else + { + // If we have nothing to send, another timer taskState ahead + // of us has done the job for us. We can cancel. + cancel(); + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + + taskState = taskState.advance(); + if (!taskState.isAnnouncing()) + { + cancel(); + + this.jmDNSImpl.startRenewer(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java new file mode 100644 index 0000000..a2179a5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Canceler.java @@ -0,0 +1,117 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.Collection; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The Canceler sends two announces with TTL=0 for the specified services. + */ +public class Canceler extends TimerTask +{ + static Logger logger = Logger.getLogger(Canceler.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * Counts the number of announces being sent. + */ + int count = 0; + /** + * The services that need cancelling. + * Note: We have to use a local variable here, because the services + * that are canceled, are removed immediately from variable JmDNS.services. + */ + private ServiceInfoImpl[] infos; + /** + * We call notifyAll() on the lock object, when we have canceled the + * service infos. + * This is used by method JmDNS.unregisterService() and + * JmDNS.unregisterAllServices, to ensure that the JmDNS + * socket stays open until the Canceler has canceled all services. + *

      + * Note: We need this lock, because ServiceInfos do the transition from + * state ANNOUNCED to state CANCELED before we get here. We could get + * rid of this lock, if we added a state named CANCELLING to DNSState. + */ + private Object lock; + int ttl = 0; + + public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl info, Object lock) + { + this.jmDNSImpl = jmDNSImpl; + this.infos = new ServiceInfoImpl[]{info}; + this.lock = lock; + this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); + } + + public Canceler(JmDNSImpl jmDNSImpl, ServiceInfoImpl[] infos, Object lock) + { + this.jmDNSImpl = jmDNSImpl; + this.infos = infos; + this.lock = lock; + } + + public Canceler(JmDNSImpl jmDNSImpl, Collection infos, Object lock) + { + this.jmDNSImpl = jmDNSImpl; + this.infos = (ServiceInfoImpl[]) infos.toArray(new ServiceInfoImpl[infos.size()]); + this.lock = lock; + } + + public void start(Timer timer) + { + timer.schedule(this, 0, DNSConstants.ANNOUNCE_WAIT_INTERVAL); + } + + public void run() + { + try + { + if (++count < 3) + { + logger.finer("run() JmDNS canceling service"); + // announce the service + //long now = System.currentTimeMillis(); + DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + for (int i = 0; i < infos.length; i++) + { + ServiceInfoImpl info = infos[i]; + info.addAnswers(out, ttl, this.jmDNSImpl.getLocalHost()); + + this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); + } + this.jmDNSImpl.send(out); + } + else + { + // After three successful announcements, we are finished. + synchronized (lock) + { + this.jmDNSImpl.setClosed(true); + lock.notifyAll(); + } + this.cancel(); + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java new file mode 100644 index 0000000..a8be8e9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Prober.java @@ -0,0 +1,197 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The Prober sends three consecutive probes for all service infos + * that needs probing as well as for the host name. + * The state of each service info of the host name is advanced, when a probe has + * been sent for it. + * When the prober has run three times, it launches an Announcer. + *

      + * If a conflict during probes occurs, the affected service infos (and affected + * host name) are taken away from the prober. This eventually causes the prober + * tho cancel itself. + */ +public class Prober extends TimerTask +{ + static Logger logger = Logger.getLogger(Prober.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * The state of the prober. + */ + DNSState taskState = DNSState.PROBING_1; + + public Prober(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + // Associate the host name to this, if it needs probing + if (this.jmDNSImpl.getState() == DNSState.PROBING_1) + { + this.jmDNSImpl.setTask(this); + } + // Associate services to this, if they need probing + synchronized (this.jmDNSImpl) + { + for (Iterator iterator = this.jmDNSImpl.getServices().values().iterator(); iterator.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) iterator.next(); + if (info.getState() == DNSState.PROBING_1) + { + info.setTask(this); + } + } + } + } + + + public void start(Timer timer) + { + long now = System.currentTimeMillis(); + if (now - this.jmDNSImpl.getLastThrottleIncrement() < DNSConstants.PROBE_THROTTLE_COUNT_INTERVAL) + { + this.jmDNSImpl.setThrottle(this.jmDNSImpl.getThrottle() + 1); + } + else + { + this.jmDNSImpl.setThrottle(1); + } + this.jmDNSImpl.setLastThrottleIncrement(now); + + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED && this.jmDNSImpl.getThrottle() < DNSConstants.PROBE_THROTTLE_COUNT) + { + timer.schedule(this, JmDNSImpl.getRandom().nextInt(1 + DNSConstants.PROBE_WAIT_INTERVAL), DNSConstants.PROBE_WAIT_INTERVAL); + } + else + { + timer.schedule(this, DNSConstants.PROBE_CONFLICT_INTERVAL, DNSConstants.PROBE_CONFLICT_INTERVAL); + } + } + + public boolean cancel() + { + // Remove association from host name to this + if (this.jmDNSImpl.getTask() == this) + { + this.jmDNSImpl.setTask(null); + } + + // Remove associations from services to this + synchronized (this.jmDNSImpl) + { + for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + if (info.getTask() == this) + { + info.setTask(null); + } + } + } + + return super.cancel(); + } + + public void run() + { + synchronized (this.jmDNSImpl.getIoLock()) + { + DNSOutgoing out = null; + try + { + // send probes for JmDNS itself + if (this.jmDNSImpl.getState() == taskState && this.jmDNSImpl.getTask() == this) + { + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); + } + out.addQuestion(new DNSQuestion(this.jmDNSImpl.getLocalHost().getName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); + + this.jmDNSImpl.getLocalHost().addAddressRecords(out, true); + this.jmDNSImpl.advanceState(); + } + // send probes for services + // Defensively copy the services into a local list, + // to prevent race conditions with methods registerService + // and unregisterService. + List list; + synchronized (this.jmDNSImpl) + { + list = new LinkedList(this.jmDNSImpl.getServices().values()); + } + for (Iterator i = list.iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + + synchronized (info) + { + if (info.getState() == taskState && info.getTask() == this) + { + info.advanceState(); + logger.fine("run() JmDNS probing " + info.getQualifiedName() + " state " + info.getState()); + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); + out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); + } + // the "unique" flag should be not set here because these answers haven't been proven unique yet + // this means the record will not exactly match the announcement record + out.addAuthorativeAnswer(new DNSRecord.Service(info.getQualifiedName(), + DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, + info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); + } + } + } + if (out != null) + { + logger.finer("run() JmDNS probing #" + taskState); + this.jmDNSImpl.send(out); + } + else + { + // If we have nothing to send, another timer taskState ahead + // of us has done the job for us. We can cancel. + cancel(); + return; + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + + taskState = taskState.advance(); + if (!taskState.isProbing()) + { + cancel(); + + this.jmDNSImpl.startAnnouncer(); + } + } + } + +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java new file mode 100644 index 0000000..75fa85d --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/RecordReaper.java @@ -0,0 +1,83 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSCache; +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; + +/** + * Periodicaly removes expired entries from the cache. + */ +public class RecordReaper extends TimerTask +{ + static Logger logger = Logger.getLogger(RecordReaper.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + + /** + * @param jmDNSImpl + */ + public RecordReaper(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.RECORD_REAPER_INTERVAL, DNSConstants.RECORD_REAPER_INTERVAL); + } + + public void run() + { + synchronized (this.jmDNSImpl) + { + if (this.jmDNSImpl.getState() == DNSState.CANCELED) + { + return; + } + logger.finest("run() JmDNS reaping cache"); + + // Remove expired answers from the cache + // ------------------------------------- + // To prevent race conditions, we defensively copy all cache + // entries into a list. + List list = new ArrayList(); + synchronized (this.jmDNSImpl.getCache()) + { + for (Iterator i = this.jmDNSImpl.getCache().iterator(); i.hasNext();) + { + for (DNSCache.CacheNode n = (DNSCache.CacheNode) i.next(); n != null; n = n.next()) + { + list.add(n.getValue()); + } + } + } + // Now, we remove them. + long now = System.currentTimeMillis(); + for (Iterator i = list.iterator(); i.hasNext();) + { + DNSRecord c = (DNSRecord) i.next(); + if (c.isExpired(now)) + { + this.jmDNSImpl.updateRecord(now, c); + this.jmDNSImpl.getCache().remove(c); + } + } + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java new file mode 100644 index 0000000..13a5e05 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Renewer.java @@ -0,0 +1,154 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The Renewer is there to send renewal announcment when the record expire for ours infos. + */ +public class Renewer extends TimerTask +{ + static Logger logger = Logger.getLogger(Renewer.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * The state of the announcer. + */ + DNSState taskState = DNSState.ANNOUNCED; + + public Renewer(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + // Associate host to this, if it needs renewal + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) + { + this.jmDNSImpl.setTask(this); + } + // Associate services to this, if they need renewal + synchronized (this.jmDNSImpl) + { + for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) s.next(); + if (info.getState() == DNSState.ANNOUNCED) + { + info.setTask(this); + } + } + } + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL, DNSConstants.ANNOUNCED_RENEWAL_TTL_INTERVAL); + } + + public boolean cancel() + { + // Remove association from host to this + if (this.jmDNSImpl.getTask() == this) + { + this.jmDNSImpl.setTask(null); + } + + // Remove associations from services to this + synchronized (this.jmDNSImpl) + { + for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + if (info.getTask() == this) + { + info.setTask(null); + } + } + } + + return super.cancel(); + } + + public void run() + { + DNSOutgoing out = null; + try + { + // send probes for JmDNS itself + if (this.jmDNSImpl.getState() == taskState) + { + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + this.jmDNSImpl.getLocalHost().addAddressRecords(out, false); + this.jmDNSImpl.advanceState(); + } + // send announces for services + // Defensively copy the services into a local list, + // to prevent race conditions with methods registerService + // and unregisterService. + List list; + synchronized (this.jmDNSImpl) + { + list = new ArrayList(this.jmDNSImpl.getServices().values()); + } + for (Iterator i = list.iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + synchronized (info) + { + if (info.getState() == taskState && info.getTask() == this) + { + info.advanceState(); + logger.finer("run() JmDNS announced " + info.getQualifiedName() + " state " + info.getState()); + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + info.addAnswers(out, DNSConstants.DNS_TTL, this.jmDNSImpl.getLocalHost()); + } + } + } + if (out != null) + { + logger.finer("run() JmDNS announced"); + this.jmDNSImpl.send(out); + } + else + { + // If we have nothing to send, another timer taskState ahead + // of us has done the job for us. We can cancel. + cancel(); + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + + taskState = taskState.advance(); + if (!taskState.isAnnounced()) + { + cancel(); + + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java new file mode 100644 index 0000000..af140ff --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/Responder.java @@ -0,0 +1,291 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.net.InetAddress; +import java.util.HashSet; +import java.util.Iterator; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSEntry; +import org.jmdns.impl.DNSIncoming; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The Responder sends a single answer for the specified service infos + * and for the host name. + */ +public class Responder extends TimerTask +{ + static Logger logger = Logger.getLogger(Responder.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + private DNSIncoming in; + private InetAddress addr; + private int port; + + public Responder(JmDNSImpl jmDNSImpl, DNSIncoming in, InetAddress addr, int port) + { + this.jmDNSImpl = jmDNSImpl; + this.in = in; + this.addr = addr; + this.port = port; + } + + public void start() + { + // According to draft-cheshire-dnsext-multicastdns.txt + // chapter "8 Responding": + // We respond immediately if we know for sure, that we are + // the only one who can respond to the query. + // In all other cases, we respond within 20-120 ms. + // + // According to draft-cheshire-dnsext-multicastdns.txt + // chapter "7.2 Multi-Packet Known Answer Suppression": + // We respond after 20-120 ms if the query is truncated. + + boolean iAmTheOnlyOne = true; + for (Iterator i = in.getQuestions().iterator(); i.hasNext();) + { + DNSEntry entry = (DNSEntry) i.next(); + if (entry instanceof DNSQuestion) + { + DNSQuestion q = (DNSQuestion) entry; + logger.finest("start() question=" + q); + iAmTheOnlyOne &= (q.getType() == DNSConstants.TYPE_SRV + || q.getType() == DNSConstants.TYPE_TXT + || q.getType() == DNSConstants.TYPE_A + || q.getType() == DNSConstants.TYPE_AAAA + || this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName()) + || this.jmDNSImpl.getServices().containsKey(q.getName().toLowerCase())); + if (!iAmTheOnlyOne) + { + break; + } + } + } + int delay = (iAmTheOnlyOne && !in.isTruncated()) ? 0 : DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + JmDNSImpl.getRandom().nextInt(DNSConstants.RESPONSE_MAX_WAIT_INTERVAL - DNSConstants.RESPONSE_MIN_WAIT_INTERVAL + 1) - in.elapseSinceArrival(); + if (delay < 0) + { + delay = 0; + } + logger.finest("start() Responder chosen delay=" + delay); + this.jmDNSImpl.schedule(this, delay); + } + + public void run() + { + synchronized (this.jmDNSImpl.getIoLock()) + { + if (this.jmDNSImpl.getPlannedAnswer() == in) + { + this.jmDNSImpl.setPlannedAnswer(null); + } + + // We use these sets to prevent duplicate records + // FIXME - This should be moved into DNSOutgoing + HashSet questions = new HashSet(); + HashSet answers = new HashSet(); + + + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) + { + try + { + boolean isUnicast = (port != DNSConstants.MDNS_PORT); + + + // Answer questions + for (Iterator iterator = in.getQuestions().iterator(); iterator.hasNext();) + { + DNSEntry entry = (DNSEntry) iterator.next(); + if (entry instanceof DNSQuestion) + { + DNSQuestion q = (DNSQuestion) entry; + + // for unicast responses the question must be included + if (isUnicast) + { + //out.addQuestion(q); + questions.add(q); + } + + int type = q.getType(); + if (type == DNSConstants.TYPE_ANY || type == DNSConstants.TYPE_SRV) + { // I ama not sure of why there is a special case here [PJYF Oct 15 2004] + if (this.jmDNSImpl.getLocalHost().getName().equalsIgnoreCase(q.getName())) + { + // type = DNSConstants.TYPE_A; + DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + type = DNSConstants.TYPE_IGNORE; + } + else + { + if (this.jmDNSImpl.getServiceTypes().containsKey(q.getName().toLowerCase())) + { + type = DNSConstants.TYPE_PTR; + } + } + } + + switch (type) + { + case DNSConstants.TYPE_A: + { + // Answer a query for a domain name + //out = addAnswer( in, addr, port, out, host ); + DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + break; + } + case DNSConstants.TYPE_AAAA: + { + // Answer a query for a domain name + DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + break; + } + case DNSConstants.TYPE_PTR: + { + // Answer a query for services of a given type + + // find matching services + for (Iterator serviceIterator = this.jmDNSImpl.getServices().values().iterator(); serviceIterator.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) serviceIterator.next(); + if (info.getState() == DNSState.ANNOUNCED) + { + if (q.getName().equalsIgnoreCase(info.getType())) + { + DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); + answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, + info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); + answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, + info.getText())); + } + } + } + if (q.getName().equalsIgnoreCase("_services._mdns._udp.local.")) + { + for (Iterator serviceTypeIterator = this.jmDNSImpl.getServiceTypes().values().iterator(); serviceTypeIterator.hasNext();) + { + answers.add(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) serviceTypeIterator.next())); + } + } + break; + } + case DNSConstants.TYPE_SRV: + case DNSConstants.TYPE_ANY: + case DNSConstants.TYPE_TXT: + { + ServiceInfoImpl info = (ServiceInfoImpl) this.jmDNSImpl.getServices().get(q.getName().toLowerCase()); + if (info != null && info.getState() == DNSState.ANNOUNCED) + { + DNSRecord answer = this.jmDNSImpl.getLocalHost().getDNS4AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + answer = this.jmDNSImpl.getLocalHost().getDNS6AddressRecord(); + if (answer != null) + { + answers.add(answer); + } + answers.add(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName())); + answers.add(new DNSRecord.Service(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, + info.getPriority(), info.getWeight(), info.getPort(), this.jmDNSImpl.getLocalHost().getName())); + answers.add(new DNSRecord.Text(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN | DNSConstants.CLASS_UNIQUE, DNSConstants.DNS_TTL, info.getText())); + } + break; + } + default : + { + //System.out.println("JmDNSResponder.unhandled query:"+q); + break; + } + } + } + } + + + // remove known answers, if the ttl is at least half of + // the correct value. (See Draft Cheshire chapter 7.1.). + for (Iterator i = in.getAnswers().iterator(); i.hasNext();) + { + DNSRecord knownAnswer = (DNSRecord) i.next(); + if (knownAnswer.getTtl() > DNSConstants.DNS_TTL / 2 && answers.remove(knownAnswer)) + { + logger.log(Level.FINER, "JmDNS Responder Known Answer Removed"); + } + } + + + // responde if we have answers + if (answers.size() != 0) + { + logger.finer("run() JmDNS responding"); + DNSOutgoing out = null; + if (isUnicast) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA, false); + } + + for (Iterator i = questions.iterator(); i.hasNext();) + { + out.addQuestion((DNSQuestion) i.next()); + } + for (Iterator i = answers.iterator(); i.hasNext();) + { + out = this.jmDNSImpl.addAnswer(in, addr, port, out, (DNSRecord) i.next()); + } + this.jmDNSImpl.send(out); + } + this.cancel(); + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.close(); + } + } + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java new file mode 100644 index 0000000..6478803 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceInfoResolver.java @@ -0,0 +1,118 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The ServiceInfoResolver queries up to three times consecutively for + * a service info, and then removes itself from the timer. + *

      + * The ServiceInfoResolver will run only if JmDNS is in state ANNOUNCED. + * REMIND: Prevent having multiple service resolvers for the same info in the + * timer queue. + */ +public class ServiceInfoResolver extends TimerTask +{ + static Logger logger = Logger.getLogger(ServiceInfoResolver.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * Counts the number of queries being sent. + */ + int count = 0; + private ServiceInfoImpl info; + private boolean persistent = false; + + public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info, boolean persistent) + { + this.persistent = persistent; + this.jmDNSImpl = jmDNSImpl; + this.info = info; + info.setDns(this.jmDNSImpl); + this.jmDNSImpl.addListener(info, new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_ANY, DNSConstants.CLASS_IN)); + } + + public ServiceInfoResolver(JmDNSImpl jmDNSImpl, ServiceInfoImpl info) + { + this(jmDNSImpl, info, false); + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); + } + + public void run() + { + try + { + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) + { + if (count++ < 3 && !info.hasData()) + { + long now = System.currentTimeMillis(); + DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); + out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN)); + out.addQuestion(new DNSQuestion(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN)); + if (info.getServer() != null) + { + out.addQuestion(new DNSQuestion(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN)); + } + out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_SRV, DNSConstants.CLASS_IN), now); + out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getQualifiedName(), DNSConstants.TYPE_TXT, DNSConstants.CLASS_IN), now); + if (info.getServer() != null) + { + out.addAnswer((DNSRecord) this.jmDNSImpl.getCache().get(info.getServer(), DNSConstants.TYPE_A, DNSConstants.CLASS_IN), now); + } + this.jmDNSImpl.send(out); + } + else + { + // After three queries, we can quit. + this.cancel(); + + // if the persistent flag is on, keep listen for packets + if (!isPersistent()) + { + this.jmDNSImpl.removeListener(info); + } + } + } + else + { + if (this.jmDNSImpl.getState() == DNSState.CANCELED) + { + this.cancel(); + this.jmDNSImpl.removeListener(info); + } + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + } + + private boolean isPersistent() + { + return persistent; + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java new file mode 100644 index 0000000..570ab88 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/ServiceResolver.java @@ -0,0 +1,101 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The ServiceResolver queries three times consecutively for services of + * a given type, and then removes itself from the timer. + *

      + * The ServiceResolver will run only if JmDNS is in state ANNOUNCED. + * REMIND: Prevent having multiple service resolvers for the same type in the + * timer queue. + */ +public class ServiceResolver extends TimerTask +{ + static Logger logger = Logger.getLogger(ServiceResolver.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * Counts the number of queries being sent. + */ + int count = 0; + private String type; + + public ServiceResolver(JmDNSImpl jmDNSImpl, String type) + { + this.jmDNSImpl = jmDNSImpl; + this.type = type; + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); + } + + public void run() + { + try + { + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) + { + if (count++ < 3) + { + logger.finer("run() JmDNS querying service"); + long now = System.currentTimeMillis(); + DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); + out.addQuestion(new DNSQuestion(type, DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN)); + for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) + { + final ServiceInfoImpl info = (ServiceInfoImpl) s.next(); + try + { + out.addAnswer(new DNSRecord.Pointer(info.getType(), DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, info.getQualifiedName()), now); + } + catch (IOException ee) + { + break; + } + } + this.jmDNSImpl.send(out); + } + else + { + // After three queries, we can quit. + this.cancel(); + } + } + else + { + if (this.jmDNSImpl.getState() == DNSState.CANCELED) + { + this.cancel(); + } + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java new file mode 100644 index 0000000..0dc8902 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TextAnnouncer.java @@ -0,0 +1,148 @@ +//Copyright 2008-2009 Jonas Ã…dahl +//Licensed under Apache License version 2.0 + +package org.jmdns.impl.tasks; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; +import org.jmdns.impl.ServiceInfoImpl; + +/** + * The TextAnnouncer sends an accumulated query of all announces, and advances + * the state of all serviceInfos, for which it has sent an announce. + * The TextAnnouncer also sends announcements and advances the state of JmDNS itself. + *

      + * When the announcer has run two times, it finishes. + */ +public class TextAnnouncer extends TimerTask +{ + static Logger logger = Logger.getLogger(TextAnnouncer.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + /** + * The state of the announcer. + */ + DNSState taskState = DNSState.ANNOUNCING_1; + + public TextAnnouncer(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + // Associate host to this, if it needs announcing + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCING_1) + { + this.jmDNSImpl.setTask(this); + } + // Associate services to this, if they need announcing + synchronized (this.jmDNSImpl) + { + for (Iterator s = this.jmDNSImpl.getServices().values().iterator(); s.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) s.next(); + if (info.getState() == DNSState.ANNOUNCING_1) + { + info.setTask(this); + } + } + } + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.ANNOUNCE_WAIT_INTERVAL, DNSConstants.ANNOUNCE_WAIT_INTERVAL); + } + + public boolean cancel() + { + // Remove association from host to this + if (this.jmDNSImpl.getTask() == this) + { + this.jmDNSImpl.setTask(null); + } + + // Remove associations from services to this + synchronized (this.jmDNSImpl) + { + for (Iterator i = this.jmDNSImpl.getServices().values().iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + if (info.getTask() == this) + { + info.setTask(null); + } + } + } + + return super.cancel(); + } + + public void run() + { + DNSOutgoing out = null; + try + { + // send announces for services + // Defensively copy the services into a local list, + // to prevent race conditions with methods registerService + // and unregisterService. + List list; + synchronized (this.jmDNSImpl) + { + list = new ArrayList(this.jmDNSImpl.getServices().values()); + } + for (Iterator i = list.iterator(); i.hasNext();) + { + ServiceInfoImpl info = (ServiceInfoImpl) i.next(); + synchronized (info) + { + if (info.getState().isAnnouncing() && info.getTask() == this) + { + info.advanceState(); + logger.finer("run() JmDNS announcing " + info.getQualifiedName() + " state " + info.getState()); + if (out == null) + { + out = new DNSOutgoing(DNSConstants.FLAGS_QR_RESPONSE | DNSConstants.FLAGS_AA); + } + info.addTextAnswer(out, DNSConstants.DNS_TTL); + } + } + } + if (out != null) + { + logger.finer("run() JmDNS announcing #" + taskState); + this.jmDNSImpl.send(out); + } + else + { + // If we have nothing to send, another timer taskState ahead + // of us has done the job for us. We can cancel. + cancel(); + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + + taskState = taskState.advance(); + if (!taskState.isAnnouncing()) + { + cancel(); + + this.jmDNSImpl.startRenewer(); + } + } +} diff --git a/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java new file mode 100644 index 0000000..d0e7c29 --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/impl/tasks/TypeResolver.java @@ -0,0 +1,92 @@ +//Copyright 2003-2005 Arthur van Hoff, Rick Blair +//Licensed under Apache License version 2.0 +//Original license LGPL + +package org.jmdns.impl.tasks; + +import java.util.Iterator; +import java.util.Timer; +import java.util.TimerTask; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jmdns.impl.DNSConstants; +import org.jmdns.impl.DNSOutgoing; +import org.jmdns.impl.DNSQuestion; +import org.jmdns.impl.DNSRecord; +import org.jmdns.impl.DNSState; +import org.jmdns.impl.JmDNSImpl; + +/** + * Helper class to resolve service types. + *

      + * The TypeResolver queries three times consecutively for service types, and then + * removes itself from the timer. + *

      + * The TypeResolver will run only if JmDNS is in state ANNOUNCED. + */ +public class TypeResolver extends TimerTask +{ + static Logger logger = Logger.getLogger(TypeResolver.class.getName()); + + /** + * + */ + private final JmDNSImpl jmDNSImpl; + + /** + * @param jmDNSImpl + */ + public TypeResolver(JmDNSImpl jmDNSImpl) + { + this.jmDNSImpl = jmDNSImpl; + } + + public void start(Timer timer) + { + timer.schedule(this, DNSConstants.QUERY_WAIT_INTERVAL, DNSConstants.QUERY_WAIT_INTERVAL); + } + + /** + * Counts the number of queries that were sent. + */ + int count = 0; + + public void run() + { + try + { + if (this.jmDNSImpl.getState() == DNSState.ANNOUNCED) + { + if (count++ < 3) + { + logger.finer("run() JmDNS querying type"); + DNSOutgoing out = new DNSOutgoing(DNSConstants.FLAGS_QR_QUERY); + out.addQuestion(new DNSQuestion("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN)); + for (Iterator iterator = this.jmDNSImpl.getServiceTypes().values().iterator(); iterator.hasNext();) + { + out.addAnswer(new DNSRecord.Pointer("_services._mdns._udp.local.", DNSConstants.TYPE_PTR, DNSConstants.CLASS_IN, DNSConstants.DNS_TTL, (String) iterator.next()), 0); + } + this.jmDNSImpl.send(out); + } + else + { + // After three queries, we can quit. + this.cancel(); + } + } + else + { + if (this.jmDNSImpl.getState() == DNSState.CANCELED) + { + this.cancel(); + } + } + } + catch (Throwable e) + { + logger.log(Level.WARNING, "run() exception ", e); + this.jmDNSImpl.recover(); + } + } +} \ No newline at end of file diff --git a/external/asmack/build/src/trunk/org/jmdns/package.html b/external/asmack/build/src/trunk/org/jmdns/package.html new file mode 100644 index 0000000..b02f16a --- /dev/null +++ b/external/asmack/build/src/trunk/org/jmdns/package.html @@ -0,0 +1,47 @@ + + + + + + + + +This is an implementation of a multi-cast DNS in Java. It currently +supports service discovery and service registration. It is fully +interoperable with Apple's Rendezvous. + + + + +

      Related Documentation

      + + +For overviews, tutorials, examples, guides, and tool documentation, please see +the Readme file of JmDNS. + + + + + diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java new file mode 100644 index 0000000..a1c613a --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/A6Record.java @@ -0,0 +1,127 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; + +/** + * A6 Record - maps a domain name to an IPv6 address (experimental) + * + * @author Brian Wellington + */ + +public class A6Record extends Record { + +private static final long serialVersionUID = -8815026887337346789L; + +private int prefixBits; +private InetAddress suffix; +private Name prefix; + +A6Record() {} + +Record +getObject() { + return new A6Record(); +} + +/** + * Creates an A6 Record from the given data + * @param prefixBits The number of bits in the address prefix + * @param suffix The address suffix + * @param prefix The name of the prefix + */ +public +A6Record(Name name, int dclass, long ttl, int prefixBits, + InetAddress suffix, Name prefix) +{ + super(name, Type.A6, dclass, ttl); + this.prefixBits = checkU8("prefixBits", prefixBits); + if (suffix != null && Address.familyOf(suffix) != Address.IPv6) + throw new IllegalArgumentException("invalid IPv6 address"); + this.suffix = suffix; + if (prefix != null) + this.prefix = checkName("prefix", prefix); +} + +void +rrFromWire(DNSInput in) throws IOException { + prefixBits = in.readU8(); + int suffixbits = 128 - prefixBits; + int suffixbytes = (suffixbits + 7) / 8; + if (prefixBits < 128) { + byte [] bytes = new byte[16]; + in.readByteArray(bytes, 16 - suffixbytes, suffixbytes); + suffix = InetAddress.getByAddress(bytes); + } + if (prefixBits > 0) + prefix = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + prefixBits = st.getUInt8(); + if (prefixBits > 128) { + throw st.exception("prefix bits must be [0..128]"); + } else if (prefixBits < 128) { + String s = st.getString(); + try { + suffix = Address.getByAddress(s, Address.IPv6); + } + catch (UnknownHostException e) { + throw st.exception("invalid IPv6 address: " + s); + } + } + if (prefixBits > 0) + prefix = st.getName(origin); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(prefixBits); + if (suffix != null) { + sb.append(" "); + sb.append(suffix.getHostAddress()); + } + if (prefix != null) { + sb.append(" "); + sb.append(prefix); + } + return sb.toString(); +} + +/** Returns the number of bits in the prefix */ +public int +getPrefixBits() { + return prefixBits; +} + +/** Returns the address suffix */ +public InetAddress +getSuffix() { + return suffix; +} + +/** Returns the address prefix */ +public Name +getPrefix() { + return prefix; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(prefixBits); + if (suffix != null) { + int suffixbits = 128 - prefixBits; + int suffixbytes = (suffixbits + 7) / 8; + byte [] data = suffix.getAddress(); + out.writeByteArray(data, 16 - suffixbytes, suffixbytes); + } + if (prefix != null) + prefix.toWire(out, null, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java new file mode 100644 index 0000000..eb98ea9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/AAAARecord.java @@ -0,0 +1,66 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; + +/** + * IPv6 Address Record - maps a domain name to an IPv6 address + * + * @author Brian Wellington + */ + +public class AAAARecord extends Record { + +private static final long serialVersionUID = -4588601512069748050L; + +private InetAddress address; + +AAAARecord() {} + +Record +getObject() { + return new AAAARecord(); +} + +/** + * Creates an AAAA Record from the given data + * @param address The address suffix + */ +public +AAAARecord(Name name, int dclass, long ttl, InetAddress address) { + super(name, Type.AAAA, dclass, ttl); + if (Address.familyOf(address) != Address.IPv6) + throw new IllegalArgumentException("invalid IPv6 address"); + this.address = address; +} + +void +rrFromWire(DNSInput in) throws IOException { + address = InetAddress.getByAddress(in.readByteArray(16)); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + address = st.getAddress(Address.IPv6); +} + +/** Converts rdata to a String */ +String +rrToString() { + return address.getHostAddress(); +} + +/** Returns the address */ +public InetAddress +getAddress() { + return address; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(address.getAddress()); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java new file mode 100644 index 0000000..4814faa --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/AFSDBRecord.java @@ -0,0 +1,46 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * AFS Data Base Record - maps a domain name to the name of an AFS cell + * database server. + * + * + * @author Brian Wellington + */ + +public class AFSDBRecord extends U16NameBase { + +private static final long serialVersionUID = 3034379930729102437L; + +AFSDBRecord() {} + +Record +getObject() { + return new AFSDBRecord(); +} + +/** + * Creates an AFSDB Record from the given data. + * @param subtype Indicates the type of service provided by the host. + * @param host The host providing the service. + */ +public +AFSDBRecord(Name name, int dclass, long ttl, int subtype, Name host) { + super(name, Type.AFSDB, dclass, ttl, subtype, "subtype", host, "host"); +} + +/** Gets the subtype indicating the service provided by the host. */ +public int +getSubtype() { + return getU16Field(); +} + +/** Gets the host providing service for the domain. */ +public Name +getHost() { + return getNameField(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java new file mode 100644 index 0000000..5940da2 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/APLRecord.java @@ -0,0 +1,287 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * APL - Address Prefix List. See RFC 3123. + * + * @author Brian Wellington + */ + +/* + * Note: this currently uses the same constants as the Address class; + * this could change if more constants are defined for APL records. + */ + +public class APLRecord extends Record { + +public static class Element { + public final int family; + public final boolean negative; + public final int prefixLength; + public final Object address; + + private + Element(int family, boolean negative, Object address, int prefixLength) + { + this.family = family; + this.negative = negative; + this.address = address; + this.prefixLength = prefixLength; + if (!validatePrefixLength(family, prefixLength)) { + throw new IllegalArgumentException("invalid prefix " + + "length"); + } + } + + /** + * Creates an APL element corresponding to an IPv4 or IPv6 prefix. + * @param negative Indicates if this prefix is a negation. + * @param address The IPv4 or IPv6 address. + * @param prefixLength The length of this prefix, in bits. + * @throws IllegalArgumentException The prefix length is invalid. + */ + public + Element(boolean negative, InetAddress address, int prefixLength) { + this(Address.familyOf(address), negative, address, + prefixLength); + } + + public String + toString() { + StringBuffer sb = new StringBuffer(); + if (negative) + sb.append("!"); + sb.append(family); + sb.append(":"); + if (family == Address.IPv4 || family == Address.IPv6) + sb.append(((InetAddress) address).getHostAddress()); + else + sb.append(base16.toString((byte []) address)); + sb.append("/"); + sb.append(prefixLength); + return sb.toString(); + } + + public boolean + equals(Object arg) { + if (arg == null || !(arg instanceof Element)) + return false; + Element elt = (Element) arg; + return (family == elt.family && + negative == elt.negative && + prefixLength == elt.prefixLength && + address.equals(elt.address)); + } + + public int + hashCode() { + return address.hashCode() + prefixLength + (negative ? 1 : 0); + } +} + +private static final long serialVersionUID = -1348173791712935864L; + +private List elements; + +APLRecord() {} + +Record +getObject() { + return new APLRecord(); +} + +private static boolean +validatePrefixLength(int family, int prefixLength) { + if (prefixLength < 0 || prefixLength >= 256) + return false; + if ((family == Address.IPv4 && prefixLength > 32) || + (family == Address.IPv6 && prefixLength > 128)) + return false; + return true; +} + +/** + * Creates an APL Record from the given data. + * @param elements The list of APL elements. + */ +public +APLRecord(Name name, int dclass, long ttl, List elements) { + super(name, Type.APL, dclass, ttl); + this.elements = new ArrayList(elements.size()); + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Object o = it.next(); + if (!(o instanceof Element)) { + throw new IllegalArgumentException("illegal element"); + } + Element element = (Element) o; + if (element.family != Address.IPv4 && + element.family != Address.IPv6) + { + throw new IllegalArgumentException("unknown family"); + } + this.elements.add(element); + + } +} + +private static byte [] +parseAddress(byte [] in, int length) throws WireParseException { + if (in.length > length) + throw new WireParseException("invalid address length"); + if (in.length == length) + return in; + byte [] out = new byte[length]; + System.arraycopy(in, 0, out, 0, in.length); + return out; +} + +void +rrFromWire(DNSInput in) throws IOException { + elements = new ArrayList(1); + while (in.remaining() != 0) { + int family = in.readU16(); + int prefix = in.readU8(); + int length = in.readU8(); + boolean negative = (length & 0x80) != 0; + length &= ~0x80; + + byte [] data = in.readByteArray(length); + Element element; + if (!validatePrefixLength(family, prefix)) { + throw new WireParseException("invalid prefix length"); + } + + if (family == Address.IPv4 || family == Address.IPv6) { + data = parseAddress(data, + Address.addressLength(family)); + InetAddress addr = InetAddress.getByAddress(data); + element = new Element(negative, addr, prefix); + } else { + element = new Element(family, negative, data, prefix); + } + elements.add(element); + + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + elements = new ArrayList(1); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) + break; + + boolean negative = false; + int family = 0; + int prefix = 0; + + String s = t.value; + int start = 0; + if (s.startsWith("!")) { + negative = true; + start = 1; + } + int colon = s.indexOf(':', start); + if (colon < 0) + throw st.exception("invalid address prefix element"); + int slash = s.indexOf('/', colon); + if (slash < 0) + throw st.exception("invalid address prefix element"); + + String familyString = s.substring(start, colon); + String addressString = s.substring(colon + 1, slash); + String prefixString = s.substring(slash + 1); + + try { + family = Integer.parseInt(familyString); + } + catch (NumberFormatException e) { + throw st.exception("invalid family"); + } + if (family != Address.IPv4 && family != Address.IPv6) + throw st.exception("unknown family"); + + try { + prefix = Integer.parseInt(prefixString); + } + catch (NumberFormatException e) { + throw st.exception("invalid prefix length"); + } + + if (!validatePrefixLength(family, prefix)) { + throw st.exception("invalid prefix length"); + } + + byte [] bytes = Address.toByteArray(addressString, family); + if (bytes == null) + throw st.exception("invalid IP address " + + addressString); + + InetAddress address = InetAddress.getByAddress(bytes); + elements.add(new Element(negative, address, prefix)); + } + st.unget(); +} + +String +rrToString() { + StringBuffer sb = new StringBuffer(); + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Element element = (Element) it.next(); + sb.append(element); + if (it.hasNext()) + sb.append(" "); + } + return sb.toString(); +} + +/** Returns the list of APL elements. */ +public List +getElements() { + return elements; +} + +private static int +addressLength(byte [] addr) { + for (int i = addr.length - 1; i >= 0; i--) { + if (addr[i] != 0) + return i + 1; + } + return 0; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + for (Iterator it = elements.iterator(); it.hasNext(); ) { + Element element = (Element) it.next(); + int length = 0; + byte [] data; + if (element.family == Address.IPv4 || + element.family == Address.IPv6) + { + InetAddress addr = (InetAddress) element.address; + data = addr.getAddress(); + length = addressLength(data); + } else { + data = (byte []) element.address; + length = data.length; + } + int wlength = length; + if (element.negative) { + wlength |= 0x80; + } + out.writeU16(element.family); + out.writeU8(element.prefixLength); + out.writeU8(wlength); + out.writeByteArray(data, 0, length); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java new file mode 100644 index 0000000..7911ea9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ARecord.java @@ -0,0 +1,89 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.net.*; +import java.io.*; + +/** + * Address Record - maps a domain name to an Internet address + * + * @author Brian Wellington + */ + +public class ARecord extends Record { + +private static final long serialVersionUID = -2172609200849142323L; + +private int addr; + +ARecord() {} + +Record +getObject() { + return new ARecord(); +} + +private static final int +fromArray(byte [] array) { + return (((array[0] & 0xFF) << 24) | + ((array[1] & 0xFF) << 16) | + ((array[2] & 0xFF) << 8) | + (array[3] & 0xFF)); +} + +private static final byte [] +toArray(int addr) { + byte [] bytes = new byte[4]; + bytes[0] = (byte) ((addr >>> 24) & 0xFF); + bytes[1] = (byte) ((addr >>> 16) & 0xFF); + bytes[2] = (byte) ((addr >>> 8) & 0xFF); + bytes[3] = (byte) (addr & 0xFF); + return bytes; +} + +/** + * Creates an A Record from the given data + * @param address The address that the name refers to + */ +public +ARecord(Name name, int dclass, long ttl, InetAddress address) { + super(name, Type.A, dclass, ttl); + if (Address.familyOf(address) != Address.IPv4) + throw new IllegalArgumentException("invalid IPv4 address"); + addr = fromArray(address.getAddress()); +} + +void +rrFromWire(DNSInput in) throws IOException { + addr = fromArray(in.readByteArray(4)); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + InetAddress address = st.getAddress(Address.IPv4); + addr = fromArray(address.getAddress()); +} + +/** Converts rdata to a String */ +String +rrToString() { + return (Address.toDottedQuad(toArray(addr))); +} + +/** Returns the Internet address */ +public InetAddress +getAddress() { + try { + return InetAddress.getByAddress(toArray(addr)); + } catch (UnknownHostException e) { + return null; + } +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU32(((long)addr) & 0xFFFFFFFFL); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Address.java b/external/asmack/build/src/trunk/org/xbill/DNS/Address.java new file mode 100644 index 0000000..2316e36 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Address.java @@ -0,0 +1,402 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.net.*; +import java.net.Inet6Address; + +/** + * Routines dealing with IP addresses. Includes functions similar to + * those in the java.net.InetAddress class. + * + * @author Brian Wellington + */ + +public final class Address { + +public static final int IPv4 = 1; +public static final int IPv6 = 2; + +private +Address() {} + +private static byte [] +parseV4(String s) { + int numDigits; + int currentOctet; + byte [] values = new byte[4]; + int currentValue; + int length = s.length(); + + currentOctet = 0; + currentValue = 0; + numDigits = 0; + for (int i = 0; i < length; i++) { + char c = s.charAt(i); + if (c >= '0' && c <= '9') { + /* Can't have more than 3 digits per octet. */ + if (numDigits == 3) + return null; + /* Octets shouldn't start with 0, unless they are 0. */ + if (numDigits > 0 && currentValue == 0) + return null; + numDigits++; + currentValue *= 10; + currentValue += (c - '0'); + /* 255 is the maximum value for an octet. */ + if (currentValue > 255) + return null; + } else if (c == '.') { + /* Can't have more than 3 dots. */ + if (currentOctet == 3) + return null; + /* Two consecutive dots are bad. */ + if (numDigits == 0) + return null; + values[currentOctet++] = (byte) currentValue; + currentValue = 0; + numDigits = 0; + } else + return null; + } + /* Must have 4 octets. */ + if (currentOctet != 3) + return null; + /* The fourth octet can't be empty. */ + if (numDigits == 0) + return null; + values[currentOctet] = (byte) currentValue; + return values; +} + +private static byte [] +parseV6(String s) { + int range = -1; + byte [] data = new byte[16]; + + String [] tokens = s.split(":", -1); + + int first = 0; + int last = tokens.length - 1; + + if (tokens[0].length() == 0) { + // If the first two tokens are empty, it means the string + // started with ::, which is fine. If only the first is + // empty, the string started with :, which is bad. + if (last - first > 0 && tokens[1].length() == 0) + first++; + else + return null; + } + + if (tokens[last].length() == 0) { + // If the last two tokens are empty, it means the string + // ended with ::, which is fine. If only the last is + // empty, the string ended with :, which is bad. + if (last - first > 0 && tokens[last - 1].length() == 0) + last--; + else + return null; + } + + if (last - first + 1 > 8) + return null; + + int i, j; + for (i = first, j = 0; i <= last; i++) { + if (tokens[i].length() == 0) { + if (range >= 0) + return null; + range = j; + continue; + } + + if (tokens[i].indexOf('.') >= 0) { + // An IPv4 address must be the last component + if (i < last) + return null; + // There can't have been more than 6 components. + if (i > 6) + return null; + byte [] v4addr = Address.toByteArray(tokens[i], IPv4); + if (v4addr == null) + return null; + for (int k = 0; k < 4; k++) + data[j++] = v4addr[k]; + break; + } + + try { + for (int k = 0; k < tokens[i].length(); k++) { + char c = tokens[i].charAt(k); + if (Character.digit(c, 16) < 0) + return null; + } + int x = Integer.parseInt(tokens[i], 16); + if (x > 0xFFFF || x < 0) + return null; + data[j++] = (byte)(x >>> 8); + data[j++] = (byte)(x & 0xFF); + } + catch (NumberFormatException e) { + return null; + } + } + + if (j < 16 && range < 0) + return null; + + if (range >= 0) { + int empty = 16 - j; + System.arraycopy(data, range, data, range + empty, j - range); + for (i = range; i < range + empty; i++) + data[i] = 0; + } + + return data; +} + +/** + * Convert a string containing an IP address to an array of 4 or 16 integers. + * @param s The address, in text format. + * @param family The address family. + * @return The address + */ +public static int [] +toArray(String s, int family) { + byte [] byteArray = toByteArray(s, family); + if (byteArray == null) + return null; + int [] intArray = new int[byteArray.length]; + for (int i = 0; i < byteArray.length; i++) + intArray[i] = byteArray[i] & 0xFF; + return intArray; +} + +/** + * Convert a string containing an IPv4 address to an array of 4 integers. + * @param s The address, in text format. + * @return The address + */ +public static int [] +toArray(String s) { + return toArray(s, IPv4); +} + +/** + * Convert a string containing an IP address to an array of 4 or 16 bytes. + * @param s The address, in text format. + * @param family The address family. + * @return The address + */ +public static byte [] +toByteArray(String s, int family) { + if (family == IPv4) + return parseV4(s); + else if (family == IPv6) + return parseV6(s); + else + throw new IllegalArgumentException("unknown address family"); +} + +/** + * Determines if a string contains a valid IP address. + * @param s The string + * @return Whether the string contains a valid IP address + */ +public static boolean +isDottedQuad(String s) { + byte [] address = Address.toByteArray(s, IPv4); + return (address != null); +} + +/** + * Converts a byte array containing an IPv4 address into a dotted quad string. + * @param addr The array + * @return The string representation + */ +public static String +toDottedQuad(byte [] addr) { + return ((addr[0] & 0xFF) + "." + (addr[1] & 0xFF) + "." + + (addr[2] & 0xFF) + "." + (addr[3] & 0xFF)); +} + +/** + * Converts an int array containing an IPv4 address into a dotted quad string. + * @param addr The array + * @return The string representation + */ +public static String +toDottedQuad(int [] addr) { + return (addr[0] + "." + addr[1] + "." + addr[2] + "." + addr[3]); +} + +private static Record [] +lookupHostName(String name) throws UnknownHostException { + try { + Record [] records = new Lookup(name).run(); + if (records == null) + throw new UnknownHostException("unknown host"); + return records; + } + catch (TextParseException e) { + throw new UnknownHostException("invalid name"); + } +} + +private static InetAddress +addrFromRecord(String name, Record r) throws UnknownHostException { + ARecord a = (ARecord) r; + return InetAddress.getByAddress(name, a.getAddress().getAddress()); +} + +/** + * Determines the IP address of a host + * @param name The hostname to look up + * @return The first matching IP address + * @exception UnknownHostException The hostname does not have any addresses + */ +public static InetAddress +getByName(String name) throws UnknownHostException { + try { + return getByAddress(name); + } catch (UnknownHostException e) { + Record [] records = lookupHostName(name); + return addrFromRecord(name, records[0]); + } +} + +/** + * Determines all IP address of a host + * @param name The hostname to look up + * @return All matching IP addresses + * @exception UnknownHostException The hostname does not have any addresses + */ +public static InetAddress [] +getAllByName(String name) throws UnknownHostException { + try { + InetAddress addr = getByAddress(name); + return new InetAddress[] {addr}; + } catch (UnknownHostException e) { + Record [] records = lookupHostName(name); + InetAddress [] addrs = new InetAddress[records.length]; + for (int i = 0; i < records.length; i++) + addrs[i] = addrFromRecord(name, records[i]); + return addrs; + } +} + +/** + * Converts an address from its string representation to an IP address. + * The address can be either IPv4 or IPv6. + * @param addr The address, in string form + * @return The IP addresses + * @exception UnknownHostException The address is not a valid IP address. + */ +public static InetAddress +getByAddress(String addr) throws UnknownHostException { + byte [] bytes; + bytes = toByteArray(addr, IPv4); + if (bytes != null) + return InetAddress.getByAddress(bytes); + bytes = toByteArray(addr, IPv6); + if (bytes != null) + return InetAddress.getByAddress(bytes); + throw new UnknownHostException("Invalid address: " + addr); +} + +/** + * Converts an address from its string representation to an IP address in + * a particular family. + * @param addr The address, in string form + * @param family The address family, either IPv4 or IPv6. + * @return The IP addresses + * @exception UnknownHostException The address is not a valid IP address in + * the specified address family. + */ +public static InetAddress +getByAddress(String addr, int family) throws UnknownHostException { + if (family != IPv4 && family != IPv6) + throw new IllegalArgumentException("unknown address family"); + byte [] bytes; + bytes = toByteArray(addr, family); + if (bytes != null) + return InetAddress.getByAddress(bytes); + throw new UnknownHostException("Invalid address: " + addr); +} + +/** + * Determines the hostname for an address + * @param addr The address to look up + * @return The associated host name + * @exception UnknownHostException There is no hostname for the address + */ +public static String +getHostName(InetAddress addr) throws UnknownHostException { + Name name = ReverseMap.fromAddress(addr); + Record [] records = new Lookup(name, Type.PTR).run(); + if (records == null) + throw new UnknownHostException("unknown address"); + PTRRecord ptr = (PTRRecord) records[0]; + return ptr.getTarget().toString(); +} + +/** + * Returns the family of an InetAddress. + * @param address The supplied address. + * @return The family, either IPv4 or IPv6. + */ +public static int +familyOf(InetAddress address) { + if (address instanceof Inet4Address) + return IPv4; + if (address instanceof Inet6Address) + return IPv6; + throw new IllegalArgumentException("unknown address family"); +} + +/** + * Returns the length of an address in a particular family. + * @param family The address family, either IPv4 or IPv6. + * @return The length of addresses in that family. + */ +public static int +addressLength(int family) { + if (family == IPv4) + return 4; + if (family == IPv6) + return 16; + throw new IllegalArgumentException("unknown address family"); +} + +/** + * Truncates an address to the specified number of bits. For example, + * truncating the address 10.1.2.3 to 8 bits would yield 10.0.0.0. + * @param address The source address + * @param maskLength The number of bits to truncate the address to. + */ +public static InetAddress +truncate(InetAddress address, int maskLength) +{ + int family = familyOf(address); + int maxMaskLength = addressLength(family) * 8; + if (maskLength < 0 || maskLength > maxMaskLength) + throw new IllegalArgumentException("invalid mask length"); + if (maskLength == maxMaskLength) + return address; + byte [] bytes = address.getAddress(); + for (int i = maskLength / 8 + 1; i < bytes.length; i++) + bytes[i] = 0; + int maskBits = maskLength % 8; + int bitmask = 0; + for (int i = 0; i < maskBits; i++) + bitmask |= (1 << (7 - i)); + bytes[maskLength / 8] &= bitmask; + try { + return InetAddress.getByAddress(bytes); + } catch (UnknownHostException e) { + throw new IllegalArgumentException("invalid address"); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java new file mode 100644 index 0000000..39bcef3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/CERTRecord.java @@ -0,0 +1,224 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.*; + +/** + * Certificate Record - Stores a certificate associated with a name. The + * certificate might also be associated with a KEYRecord. + * @see KEYRecord + * + * @author Brian Wellington + */ + +public class CERTRecord extends Record { + +public static class CertificateType { + /** Certificate type identifiers. See RFC 4398 for more detail. */ + + private CertificateType() {} + + /** PKIX (X.509v3) */ + public static final int PKIX = 1; + + /** Simple Public Key Infrastructure */ + public static final int SPKI = 2; + + /** Pretty Good Privacy */ + public static final int PGP = 3; + + /** URL of an X.509 data object */ + public static final int IPKIX = 4; + + /** URL of an SPKI certificate */ + public static final int ISPKI = 5; + + /** Fingerprint and URL of an OpenPGP packet */ + public static final int IPGP = 6; + + /** Attribute Certificate */ + public static final int ACPKIX = 7; + + /** URL of an Attribute Certificate */ + public static final int IACPKIX = 8; + + /** Certificate format defined by URI */ + public static final int URI = 253; + + /** Certificate format defined by OID */ + public static final int OID = 254; + + private static Mnemonic types = new Mnemonic("Certificate type", + Mnemonic.CASE_UPPER); + + static { + types.setMaximum(0xFFFF); + types.setNumericAllowed(true); + + types.add(PKIX, "PKIX"); + types.add(SPKI, "SPKI"); + types.add(PGP, "PGP"); + types.add(PKIX, "IPKIX"); + types.add(SPKI, "ISPKI"); + types.add(PGP, "IPGP"); + types.add(PGP, "ACPKIX"); + types.add(PGP, "IACPKIX"); + types.add(URI, "URI"); + types.add(OID, "OID"); + } + + /** + * Converts a certificate type into its textual representation + */ + public static String + string(int type) { + return types.getText(type); + } + + /** + * Converts a textual representation of an certificate type into its + * numeric code. Integers in the range 0..65535 are also accepted. + * @param s The textual representation of the algorithm + * @return The algorithm code, or -1 on error. + */ + public static int + value(String s) { + return types.getValue(s); + } +} + +/** PKIX (X.509v3) */ +public static final int PKIX = CertificateType.PKIX; + +/** Simple Public Key Infrastructure */ +public static final int SPKI = CertificateType.SPKI; + +/** Pretty Good Privacy */ +public static final int PGP = CertificateType.PGP; + +/** Certificate format defined by URI */ +public static final int URI = CertificateType.URI; + +/** Certificate format defined by IOD */ +public static final int OID = CertificateType.OID; + +private static final long serialVersionUID = 4763014646517016835L; + +private int certType, keyTag; +private int alg; +private byte [] cert; + +CERTRecord() {} + +Record +getObject() { + return new CERTRecord(); +} + +/** + * Creates a CERT Record from the given data + * @param certType The type of certificate (see constants) + * @param keyTag The ID of the associated KEYRecord, if present + * @param alg The algorithm of the associated KEYRecord, if present + * @param cert Binary data representing the certificate + */ +public +CERTRecord(Name name, int dclass, long ttl, int certType, int keyTag, + int alg, byte [] cert) +{ + super(name, Type.CERT, dclass, ttl); + this.certType = checkU16("certType", certType); + this.keyTag = checkU16("keyTag", keyTag); + this.alg = checkU8("alg", alg); + this.cert = cert; +} + +void +rrFromWire(DNSInput in) throws IOException { + certType = in.readU16(); + keyTag = in.readU16(); + alg = in.readU8(); + cert = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String certTypeString = st.getString(); + certType = CertificateType.value(certTypeString); + if (certType < 0) + throw st.exception("Invalid certificate type: " + + certTypeString); + keyTag = st.getUInt16(); + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) + throw st.exception("Invalid algorithm: " + algString); + cert = st.getBase64(); +} + +/** + * Converts rdata to a String + */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append (certType); + sb.append (" "); + sb.append (keyTag); + sb.append (" "); + sb.append (alg); + if (cert != null) { + if (Options.check("multiline")) { + sb.append(" (\n"); + sb.append(base64.formatString(cert, 64, "\t", true)); + } else { + sb.append(" "); + sb.append(base64.toString(cert)); + } + } + return sb.toString(); +} + +/** + * Returns the type of certificate + */ +public int +getCertType() { + return certType; +} + +/** + * Returns the ID of the associated KEYRecord, if present + */ +public int +getKeyTag() { + return keyTag; +} + +/** + * Returns the algorithm of the associated KEYRecord, if present + */ +public int +getAlgorithm() { + return alg; +} + +/** + * Returns the binary representation of the certificate + */ +public byte [] +getCert() { + return cert; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(certType); + out.writeU16(keyTag); + out.writeU8(alg); + out.writeByteArray(cert); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java new file mode 100644 index 0000000..8db9453 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/CNAMERecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * CNAME Record - maps an alias to its real name + * + * @author Brian Wellington + */ + +public class CNAMERecord extends SingleCompressedNameBase { + +private static final long serialVersionUID = -4020373886892538580L; + +CNAMERecord() {} + +Record +getObject() { + return new CNAMERecord(); +} + +/** + * Creates a new CNAMERecord with the given data + * @param alias The name to which the CNAME alias points + */ +public +CNAMERecord(Name name, int dclass, long ttl, Name alias) { + super(name, Type.CNAME, dclass, ttl, alias, "alias"); +} + +/** + * Gets the target of the CNAME Record + */ +public Name +getTarget() { + return getSingleName(); +} + +/** Gets the alias specified by the CNAME Record */ +public Name +getAlias() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java b/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java new file mode 100644 index 0000000..5497f45 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Cache.java @@ -0,0 +1,846 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A cache of DNS records. The cache obeys TTLs, so items are purged after + * their validity period is complete. Negative answers are cached, to + * avoid repeated failed DNS queries. The credibility of each RRset is + * maintained, so that more credible records replace less credible records, + * and lookups can specify the minimum credibility of data they are requesting. + * @see RRset + * @see Credibility + * + * @author Brian Wellington + */ + +public class Cache { + +private interface Element { + public boolean expired(); + public int compareCredibility(int cred); + public int getType(); +} + +private static int +limitExpire(long ttl, long maxttl) { + if (maxttl >= 0 && maxttl < ttl) + ttl = maxttl; + long expire = (System.currentTimeMillis() / 1000) + ttl; + if (expire < 0 || expire > Integer.MAX_VALUE) + return Integer.MAX_VALUE; + return (int)expire; +} + +private static class CacheRRset extends RRset implements Element { + private static final long serialVersionUID = 5971755205903597024L; + + int credibility; + int expire; + + public + CacheRRset(Record rec, int cred, long maxttl) { + super(); + this.credibility = cred; + this.expire = limitExpire(rec.getTTL(), maxttl); + addRR(rec); + } + + public + CacheRRset(RRset rrset, int cred, long maxttl) { + super(rrset); + this.credibility = cred; + this.expire = limitExpire(rrset.getTTL(), maxttl); + } + + public final boolean + expired() { + int now = (int)(System.currentTimeMillis() / 1000); + return (now >= expire); + } + + public final int + compareCredibility(int cred) { + return credibility - cred; + } + + public String + toString() { + StringBuffer sb = new StringBuffer(); + sb.append(super.toString()); + sb.append(" cl = "); + sb.append(credibility); + return sb.toString(); + } +} + +private static class NegativeElement implements Element { + int type; + Name name; + int credibility; + int expire; + + public + NegativeElement(Name name, int type, SOARecord soa, int cred, + long maxttl) + { + this.name = name; + this.type = type; + long cttl = 0; + if (soa != null) + cttl = soa.getMinimum(); + this.credibility = cred; + this.expire = limitExpire(cttl, maxttl); + } + + public int + getType() { + return type; + } + + public final boolean + expired() { + int now = (int)(System.currentTimeMillis() / 1000); + return (now >= expire); + } + + public final int + compareCredibility(int cred) { + return credibility - cred; + } + + public String + toString() { + StringBuffer sb = new StringBuffer(); + if (type == 0) + sb.append("NXDOMAIN " + name); + else + sb.append("NXRRSET " + name + " " + Type.string(type)); + sb.append(" cl = "); + sb.append(credibility); + return sb.toString(); + } +} + +private static class CacheMap extends LinkedHashMap { + private int maxsize = -1; + + CacheMap(int maxsize) { + super(16, (float) 0.75, true); + this.maxsize = maxsize; + } + + int + getMaxSize() { + return maxsize; + } + + void + setMaxSize(int maxsize) { + /* + * Note that this doesn't shrink the size of the map if + * the maximum size is lowered, but it should shrink as + * entries expire. + */ + this.maxsize = maxsize; + } + + protected boolean removeEldestEntry(Map.Entry eldest) { + return maxsize >= 0 && size() > maxsize; + } +} + +private CacheMap data; +private int maxncache = -1; +private int maxcache = -1; +private int dclass; + +private static final int defaultMaxEntries = 50000; + +/** + * Creates an empty Cache + * + * @param dclass The DNS class of this cache + * @see DClass + */ +public +Cache(int dclass) { + this.dclass = dclass; + data = new CacheMap(defaultMaxEntries); +} + +/** + * Creates an empty Cache for class IN. + * @see DClass + */ +public +Cache() { + this(DClass.IN); +} + +/** + * Creates a Cache which initially contains all records in the specified file. + */ +public +Cache(String file) throws IOException { + data = new CacheMap(defaultMaxEntries); + Master m = new Master(file); + Record record; + while ((record = m.nextRecord()) != null) + addRecord(record, Credibility.HINT, m); +} + +private synchronized Object +exactName(Name name) { + return data.get(name); +} + +private synchronized void +removeName(Name name) { + data.remove(name); +} + +private synchronized Element [] +allElements(Object types) { + if (types instanceof List) { + List typelist = (List) types; + int size = typelist.size(); + return (Element []) typelist.toArray(new Element[size]); + } else { + Element set = (Element) types; + return new Element[] {set}; + } +} + +private synchronized Element +oneElement(Name name, Object types, int type, int minCred) { + Element found = null; + + if (type == Type.ANY) + throw new IllegalArgumentException("oneElement(ANY)"); + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + Element set = (Element) list.get(i); + if (set.getType() == type) { + found = set; + break; + } + } + } else { + Element set = (Element) types; + if (set.getType() == type) + found = set; + } + if (found == null) + return null; + if (found.expired()) { + removeElement(name, type); + return null; + } + if (found.compareCredibility(minCred) < 0) + return null; + return found; +} + +private synchronized Element +findElement(Name name, int type, int minCred) { + Object types = exactName(name); + if (types == null) + return null; + return oneElement(name, types, type, minCred); +} + +private synchronized void +addElement(Name name, Element element) { + Object types = data.get(name); + if (types == null) { + data.put(name, element); + return; + } + int type = element.getType(); + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + Element elt = (Element) list.get(i); + if (elt.getType() == type) { + list.set(i, element); + return; + } + } + list.add(element); + } else { + Element elt = (Element) types; + if (elt.getType() == type) + data.put(name, element); + else { + LinkedList list = new LinkedList(); + list.add(elt); + list.add(element); + data.put(name, list); + } + } +} + +private synchronized void +removeElement(Name name, int type) { + Object types = data.get(name); + if (types == null) { + return; + } + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + Element elt = (Element) list.get(i); + if (elt.getType() == type) { + list.remove(i); + if (list.size() == 0) + data.remove(name); + return; + } + } + } else { + Element elt = (Element) types; + if (elt.getType() != type) + return; + data.remove(name); + } +} + +/** Empties the Cache. */ +public synchronized void +clearCache() { + data.clear(); +} + +/** + * Adds a record to the Cache. + * @param r The record to be added + * @param cred The credibility of the record + * @param o The source of the record (this could be a Message, for example) + * @see Record + */ +public synchronized void +addRecord(Record r, int cred, Object o) { + Name name = r.getName(); + int type = r.getRRsetType(); + if (!Type.isRR(type)) + return; + Element element = findElement(name, type, cred); + if (element == null) { + CacheRRset crrset = new CacheRRset(r, cred, maxcache); + addRRset(crrset, cred); + } else if (element.compareCredibility(cred) == 0) { + if (element instanceof CacheRRset) { + CacheRRset crrset = (CacheRRset) element; + crrset.addRR(r); + } + } +} + +/** + * Adds an RRset to the Cache. + * @param rrset The RRset to be added + * @param cred The credibility of these records + * @see RRset + */ +public synchronized void +addRRset(RRset rrset, int cred) { + long ttl = rrset.getTTL(); + Name name = rrset.getName(); + int type = rrset.getType(); + Element element = findElement(name, type, 0); + if (ttl == 0) { + if (element != null && element.compareCredibility(cred) <= 0) + removeElement(name, type); + } else { + if (element != null && element.compareCredibility(cred) <= 0) + element = null; + if (element == null) { + CacheRRset crrset; + if (rrset instanceof CacheRRset) + crrset = (CacheRRset) rrset; + else + crrset = new CacheRRset(rrset, cred, maxcache); + addElement(name, crrset); + } + } +} + +/** + * Adds a negative entry to the Cache. + * @param name The name of the negative entry + * @param type The type of the negative entry + * @param soa The SOA record to add to the negative cache entry, or null. + * The negative cache ttl is derived from the SOA. + * @param cred The credibility of the negative entry + */ +public synchronized void +addNegative(Name name, int type, SOARecord soa, int cred) { + long ttl = 0; + if (soa != null) + ttl = soa.getTTL(); + Element element = findElement(name, type, 0); + if (ttl == 0) { + if (element != null && element.compareCredibility(cred) <= 0) + removeElement(name, type); + } else { + if (element != null && element.compareCredibility(cred) <= 0) + element = null; + if (element == null) + addElement(name, new NegativeElement(name, type, + soa, cred, + maxncache)); + } +} + +/** + * Finds all matching sets or something that causes the lookup to stop. + */ +protected synchronized SetResponse +lookup(Name name, int type, int minCred) { + int labels; + int tlabels; + Element element; + Name tname; + Object types; + SetResponse sr; + + labels = name.labels(); + + for (tlabels = labels; tlabels >= 1; tlabels--) { + boolean isRoot = (tlabels == 1); + boolean isExact = (tlabels == labels); + + if (isRoot) + tname = Name.root; + else if (isExact) + tname = name; + else + tname = new Name(name, labels - tlabels); + + types = data.get(tname); + if (types == null) + continue; + + /* + * If this is the name, look for the actual type or a CNAME + * (unless it's an ANY query, where we return everything). + * Otherwise, look for a DNAME. + */ + if (isExact && type == Type.ANY) { + sr = new SetResponse(SetResponse.SUCCESSFUL); + Element [] elements = allElements(types); + int added = 0; + for (int i = 0; i < elements.length; i++) { + element = elements[i]; + if (element.expired()) { + removeElement(tname, element.getType()); + continue; + } + if (!(element instanceof CacheRRset)) + continue; + if (element.compareCredibility(minCred) < 0) + continue; + sr.addRRset((CacheRRset)element); + added++; + } + /* There were positive entries */ + if (added > 0) + return sr; + } else if (isExact) { + element = oneElement(tname, types, type, minCred); + if (element != null && + element instanceof CacheRRset) + { + sr = new SetResponse(SetResponse.SUCCESSFUL); + sr.addRRset((CacheRRset) element); + return sr; + } else if (element != null) { + sr = new SetResponse(SetResponse.NXRRSET); + return sr; + } + + element = oneElement(tname, types, Type.CNAME, minCred); + if (element != null && + element instanceof CacheRRset) + { + return new SetResponse(SetResponse.CNAME, + (CacheRRset) element); + } + } else { + element = oneElement(tname, types, Type.DNAME, minCred); + if (element != null && + element instanceof CacheRRset) + { + return new SetResponse(SetResponse.DNAME, + (CacheRRset) element); + } + } + + /* Look for an NS */ + element = oneElement(tname, types, Type.NS, minCred); + if (element != null && element instanceof CacheRRset) + return new SetResponse(SetResponse.DELEGATION, + (CacheRRset) element); + + /* Check for the special NXDOMAIN element. */ + if (isExact) { + element = oneElement(tname, types, 0, minCred); + if (element != null) + return SetResponse.ofType(SetResponse.NXDOMAIN); + } + + } + return SetResponse.ofType(SetResponse.UNKNOWN); +} + +/** + * Looks up Records in the Cache. This follows CNAMEs and handles negatively + * cached data. + * @param name The name to look up + * @param type The type to look up + * @param minCred The minimum acceptable credibility + * @return A SetResponse object + * @see SetResponse + * @see Credibility + */ +public SetResponse +lookupRecords(Name name, int type, int minCred) { + return lookup(name, type, minCred); +} + +private RRset [] +findRecords(Name name, int type, int minCred) { + SetResponse cr = lookupRecords(name, type, minCred); + if (cr.isSuccessful()) + return cr.answers(); + else + return null; +} + +/** + * Looks up credible Records in the Cache (a wrapper around lookupRecords). + * Unlike lookupRecords, this given no indication of why failure occurred. + * @param name The name to look up + * @param type The type to look up + * @return An array of RRsets, or null + * @see Credibility + */ +public RRset [] +findRecords(Name name, int type) { + return findRecords(name, type, Credibility.NORMAL); +} + +/** + * Looks up Records in the Cache (a wrapper around lookupRecords). Unlike + * lookupRecords, this given no indication of why failure occurred. + * @param name The name to look up + * @param type The type to look up + * @return An array of RRsets, or null + * @see Credibility + */ +public RRset [] +findAnyRecords(Name name, int type) { + return findRecords(name, type, Credibility.GLUE); +} + +private final int +getCred(int section, boolean isAuth) { + if (section == Section.ANSWER) { + if (isAuth) + return Credibility.AUTH_ANSWER; + else + return Credibility.NONAUTH_ANSWER; + } else if (section == Section.AUTHORITY) { + if (isAuth) + return Credibility.AUTH_AUTHORITY; + else + return Credibility.NONAUTH_AUTHORITY; + } else if (section == Section.ADDITIONAL) { + return Credibility.ADDITIONAL; + } else + throw new IllegalArgumentException("getCred: invalid section"); +} + +private static void +markAdditional(RRset rrset, Set names) { + Record first = rrset.first(); + if (first.getAdditionalName() == null) + return; + + Iterator it = rrset.rrs(); + while (it.hasNext()) { + Record r = (Record) it.next(); + Name name = r.getAdditionalName(); + if (name != null) + names.add(name); + } +} + +/** + * Adds all data from a Message into the Cache. Each record is added with + * the appropriate credibility, and negative answers are cached as such. + * @param in The Message to be added + * @return A SetResponse that reflects what would be returned from a cache + * lookup, or null if nothing useful could be cached from the message. + * @see Message + */ +public SetResponse +addMessage(Message in) { + boolean isAuth = in.getHeader().getFlag(Flags.AA); + Record question = in.getQuestion(); + Name qname; + Name curname; + int qtype; + int qclass; + int cred; + int rcode = in.getHeader().getRcode(); + boolean completed = false; + RRset [] answers, auth, addl; + SetResponse response = null; + boolean verbose = Options.check("verbosecache"); + HashSet additionalNames; + + if ((rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) || + question == null) + return null; + + qname = question.getName(); + qtype = question.getType(); + qclass = question.getDClass(); + + curname = qname; + + additionalNames = new HashSet(); + + answers = in.getSectionRRsets(Section.ANSWER); + for (int i = 0; i < answers.length; i++) { + if (answers[i].getDClass() != qclass) + continue; + int type = answers[i].getType(); + Name name = answers[i].getName(); + cred = getCred(Section.ANSWER, isAuth); + if ((type == qtype || qtype == Type.ANY) && + name.equals(curname)) + { + addRRset(answers[i], cred); + completed = true; + if (curname == qname) { + if (response == null) + response = new SetResponse( + SetResponse.SUCCESSFUL); + response.addRRset(answers[i]); + } + markAdditional(answers[i], additionalNames); + } else if (type == Type.CNAME && name.equals(curname)) { + CNAMERecord cname; + addRRset(answers[i], cred); + if (curname == qname) + response = new SetResponse(SetResponse.CNAME, + answers[i]); + cname = (CNAMERecord) answers[i].first(); + curname = cname.getTarget(); + } else if (type == Type.DNAME && curname.subdomain(name)) { + DNAMERecord dname; + addRRset(answers[i], cred); + if (curname == qname) + response = new SetResponse(SetResponse.DNAME, + answers[i]); + dname = (DNAMERecord) answers[i].first(); + try { + curname = curname.fromDNAME(dname); + } + catch (NameTooLongException e) { + break; + } + } + } + + auth = in.getSectionRRsets(Section.AUTHORITY); + RRset soa = null, ns = null; + for (int i = 0; i < auth.length; i++) { + if (auth[i].getType() == Type.SOA && + curname.subdomain(auth[i].getName())) + soa = auth[i]; + else if (auth[i].getType() == Type.NS && + curname.subdomain(auth[i].getName())) + ns = auth[i]; + } + if (!completed) { + /* This is a negative response or a referral. */ + int cachetype = (rcode == Rcode.NXDOMAIN) ? 0 : qtype; + if (rcode == Rcode.NXDOMAIN || soa != null || ns == null) { + /* Negative response */ + cred = getCred(Section.AUTHORITY, isAuth); + SOARecord soarec = null; + if (soa != null) + soarec = (SOARecord) soa.first(); + addNegative(curname, cachetype, soarec, cred); + if (response == null) { + int responseType; + if (rcode == Rcode.NXDOMAIN) + responseType = SetResponse.NXDOMAIN; + else + responseType = SetResponse.NXRRSET; + response = SetResponse.ofType(responseType); + } + /* DNSSEC records are not cached. */ + } else { + /* Referral response */ + cred = getCred(Section.AUTHORITY, isAuth); + addRRset(ns, cred); + markAdditional(ns, additionalNames); + if (response == null) + response = new SetResponse( + SetResponse.DELEGATION, + ns); + } + } else if (rcode == Rcode.NOERROR && ns != null) { + /* Cache the NS set from a positive response. */ + cred = getCred(Section.AUTHORITY, isAuth); + addRRset(ns, cred); + markAdditional(ns, additionalNames); + } + + addl = in.getSectionRRsets(Section.ADDITIONAL); + for (int i = 0; i < addl.length; i++) { + int type = addl[i].getType(); + if (type != Type.A && type != Type.AAAA && type != Type.A6) + continue; + Name name = addl[i].getName(); + if (!additionalNames.contains(name)) + continue; + cred = getCred(Section.ADDITIONAL, isAuth); + addRRset(addl[i], cred); + } + if (verbose) + System.out.println("addMessage: " + response); + return (response); +} + +/** + * Flushes an RRset from the cache + * @param name The name of the records to be flushed + * @param type The type of the records to be flushed + * @see RRset + */ +public void +flushSet(Name name, int type) { + removeElement(name, type); +} + +/** + * Flushes all RRsets with a given name from the cache + * @param name The name of the records to be flushed + * @see RRset + */ +public void +flushName(Name name) { + removeName(name); +} + +/** + * Sets the maximum length of time that a negative response will be stored + * in this Cache. A negative value disables this feature (that is, sets + * no limit). + */ +public void +setMaxNCache(int seconds) { + maxncache = seconds; +} + +/** + * Gets the maximum length of time that a negative response will be stored + * in this Cache. A negative value indicates no limit. + */ +public int +getMaxNCache() { + return maxncache; +} + +/** + * Sets the maximum length of time that records will be stored in this + * Cache. A negative value disables this feature (that is, sets no limit). + */ +public void +setMaxCache(int seconds) { + maxcache = seconds; +} + +/** + * Gets the maximum length of time that records will be stored + * in this Cache. A negative value indicates no limit. + */ +public int +getMaxCache() { + return maxcache; +} + +/** + * Gets the current number of entries in the Cache, where an entry consists + * of all records with a specific Name. + */ +public int +getSize() { + return data.size(); +} + +/** + * Gets the maximum number of entries in the Cache, where an entry consists + * of all records with a specific Name. A negative value is treated as an + * infinite limit. + */ +public int +getMaxEntries() { + return data.getMaxSize(); +} + +/** + * Sets the maximum number of entries in the Cache, where an entry consists + * of all records with a specific Name. A negative value is treated as an + * infinite limit. + * + * Note that setting this to a value lower than the current number + * of entries will not cause the Cache to shrink immediately. + * + * The default maximum number of entries is 50000. + * + * @param entries The maximum number of entries in the Cache. + */ +public void +setMaxEntries(int entries) { + data.setMaxSize(entries); +} + +/** + * Returns the DNS class of this cache. + */ +public int +getDClass() { + return dclass; +} + +/** + * Returns the contents of the Cache as a string. + */ +public String +toString() { + StringBuffer sb = new StringBuffer(); + synchronized (this) { + Iterator it = data.values().iterator(); + while (it.hasNext()) { + Element [] elements = allElements(it.next()); + for (int i = 0; i < elements.length; i++) { + sb.append(elements[i]); + sb.append("\n"); + } + } + } + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Client.java b/external/asmack/build/src/trunk/org/xbill/DNS/Client.java new file mode 100644 index 0000000..2eef44f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Client.java @@ -0,0 +1,58 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import java.nio.channels.*; +import org.xbill.DNS.utils.hexdump; + +class Client { + +protected long endTime; +protected SelectionKey key; + +protected +Client(SelectableChannel channel, long endTime) throws IOException { + boolean done = false; + Selector selector = null; + this.endTime = endTime; + try { + selector = Selector.open(); + channel.configureBlocking(false); + key = channel.register(selector, SelectionKey.OP_READ); + done = true; + } + finally { + if (!done && selector != null) + selector.close(); + if (!done) + channel.close(); + } +} + +static protected void +blockUntil(SelectionKey key, long endTime) throws IOException { + long timeout = endTime - System.currentTimeMillis(); + int nkeys = 0; + if (timeout > 0) + nkeys = key.selector().select(timeout); + else if (timeout == 0) + nkeys = key.selector().selectNow(); + if (nkeys == 0) + throw new SocketTimeoutException(); +} + +static protected void +verboseLog(String prefix, byte [] data) { + if (Options.check("verbosemsg")) + System.err.println(hexdump.dump(prefix, data)); +} + +void +cleanup() throws IOException { + key.selector().close(); + key.channel().close(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java new file mode 100644 index 0000000..4a98a12 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ClientSubnetOption.java @@ -0,0 +1,175 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.net.*; +import java.util.regex.*; + +/** + * The Client Subnet EDNS Option, defined in + * http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-00 + * ("Client subnet in DNS requests"). + * + * The option is used to convey information about the IP address of the + * originating client, so that an authoritative server can make decisions + * based on this address, rather than the address of the intermediate + * caching name server. + * + * The option is transmitted as part of an OPTRecord in the additional section + * of a DNS message, as defined by RFC 2671 (EDNS0). + * + * An option code has not been assigned by IANA; the value 20730 (used here) is + * also used by several other implementations. + * + * The wire format of the option contains a 2-byte length field (1 for IPv4, 2 + * for IPv6), a 1-byte source netmask, a 1-byte scope netmask, and an address + * truncated to the source netmask length (where the final octet is padded with + * bits set to 0) + * + * + * @see OPTRecord + * + * @author Brian Wellington + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + */ +public class ClientSubnetOption extends EDNSOption { + +private static final long serialVersionUID = -3868158449890266347L; + +private int family; +private int sourceNetmask; +private int scopeNetmask; +private InetAddress address; + +ClientSubnetOption() { + super(EDNSOption.Code.CLIENT_SUBNET); +} + +private static int +checkMaskLength(String field, int family, int val) { + int max = Address.addressLength(family) * 8; + if (val < 0 || val > max) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be in the range " + + "[0.." + max + "]"); + return val; +} + +/** + * Construct a Client Subnet option. Note that the number of significant bits in + * the address must not be greater than the supplied source netmask. + * XXX something about Java's mapped addresses + * @param sourceNetmask The length of the netmask pertaining to the query. + * In replies, it mirrors the same value as in the requests. + * @param scopeNetmask The length of the netmask pertaining to the reply. + * In requests, it MUST be set to 0. In responses, this may or may not match + * the source netmask. + * @param address The address of the client. + */ +public +ClientSubnetOption(int sourceNetmask, int scopeNetmask, InetAddress address) { + super(EDNSOption.Code.CLIENT_SUBNET); + + this.family = Address.familyOf(address); + this.sourceNetmask = checkMaskLength("source netmask", this.family, + sourceNetmask); + this.scopeNetmask = checkMaskLength("scope netmask", this.family, + scopeNetmask); + this.address = Address.truncate(address, sourceNetmask); + + if (!address.equals(this.address)) + throw new IllegalArgumentException("source netmask is not " + + "valid for address"); +} + +/** + * Construct a Client Subnet option with scope netmask set to 0. + * @param sourceNetmask The length of the netmask pertaining to the query. + * In replies, it mirrors the same value as in the requests. + * @param address The address of the client. + * @see ClientSubnetOption + */ +public +ClientSubnetOption(int sourceNetmask, InetAddress address) { + this(sourceNetmask, 0, address); +} + +/** + * Returns the family of the network address. This will be either IPv4 (1) + * or IPv6 (2). + */ +public int +getFamily() { + return family; +} + +/** Returns the source netmask. */ +public int +getSourceNetmask() { + return sourceNetmask; +} + +/** Returns the scope netmask. */ +public int +getScopeNetmask() { + return scopeNetmask; +} + +/** Returns the IP address of the client. */ +public InetAddress +getAddress() { + return address; +} + +void +optionFromWire(DNSInput in) throws WireParseException { + family = in.readU16(); + if (family != Address.IPv4 && family != Address.IPv6) + throw new WireParseException("unknown address family"); + sourceNetmask = in.readU8(); + if (sourceNetmask > Address.addressLength(family) * 8) + throw new WireParseException("invalid source netmask"); + scopeNetmask = in.readU8(); + if (scopeNetmask > Address.addressLength(family) * 8) + throw new WireParseException("invalid scope netmask"); + + // Read the truncated address + byte [] addr = in.readByteArray(); + if (addr.length != (sourceNetmask + 7) / 8) + throw new WireParseException("invalid address"); + + // Convert it to a full length address. + byte [] fulladdr = new byte[Address.addressLength(family)]; + System.arraycopy(addr, 0, fulladdr, 0, addr.length); + + try { + address = InetAddress.getByAddress(fulladdr); + } catch (UnknownHostException e) { + throw new WireParseException("invalid address", e); + } + + InetAddress tmp = Address.truncate(address, sourceNetmask); + if (!tmp.equals(address)) + throw new WireParseException("invalid padding"); +} + +void +optionToWire(DNSOutput out) { + out.writeU16(family); + out.writeU8(sourceNetmask); + out.writeU8(scopeNetmask); + out.writeByteArray(address.getAddress(), 0, (sourceNetmask + 7) / 8); +} + +String +optionToString() { + StringBuffer sb = new StringBuffer(); + sb.append(address.getHostAddress()); + sb.append("/"); + sb.append(sourceNetmask); + sb.append(", scope netmask "); + sb.append(scopeNetmask); + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java b/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java new file mode 100644 index 0000000..e3e81c0 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Compression.java @@ -0,0 +1,72 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * DNS Name Compression object. + * @see Message + * @see Name + * + * @author Brian Wellington + */ + +public class Compression { + +private static class Entry { + Name name; + int pos; + Entry next; +} + +private static final int TABLE_SIZE = 17; +private static final int MAX_POINTER = 0x3FFF; +private Entry [] table; +private boolean verbose = Options.check("verbosecompression"); + +/** + * Creates a new Compression object. + */ +public +Compression() { + table = new Entry[TABLE_SIZE]; +} + +/** + * Adds a compression entry mapping a name to a position in a message. + * @param pos The position at which the name is added. + * @param name The name being added to the message. + */ +public void +add(int pos, Name name) { + if (pos > MAX_POINTER) + return; + int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; + Entry entry = new Entry(); + entry.name = name; + entry.pos = pos; + entry.next = table[row]; + table[row] = entry; + if (verbose) + System.err.println("Adding " + name + " at " + pos); +} + +/** + * Retrieves the position of the given name, if it has been previously + * included in the message. + * @param name The name to find in the compression table. + * @return The position of the name, or -1 if not found. + */ +public int +get(Name name) { + int row = (name.hashCode() & 0x7FFFFFFF) % TABLE_SIZE; + int pos = -1; + for (Entry entry = table[row]; entry != null; entry = entry.next) { + if (entry.name.equals(name)) + pos = entry.pos; + } + if (verbose) + System.err.println("Looking for " + name + ", found " + pos); + return pos; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java b/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java new file mode 100644 index 0000000..fa10686 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Credibility.java @@ -0,0 +1,50 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants relating to the credibility of cached data, which is based on + * the data's source. The constants NORMAL and ANY should be used by most + * callers. + * @see Cache + * @see Section + * + * @author Brian Wellington + */ + +public final class Credibility { + +private +Credibility() {} + +/** A hint or cache file on disk. */ +public static final int HINT = 0; + +/** The additional section of a response. */ +public static final int ADDITIONAL = 1; + +/** The additional section of a response. */ +public static final int GLUE = 2; + +/** The authority section of a nonauthoritative response. */ +public static final int NONAUTH_AUTHORITY = 3; + +/** The answer section of a nonauthoritative response. */ +public static final int NONAUTH_ANSWER = 3; + +/** The authority section of an authoritative response. */ +public static final int AUTH_AUTHORITY = 4; + +/** The answer section of a authoritative response. */ +public static final int AUTH_ANSWER = 4; + +/** A zone. */ +public static final int ZONE = 5; + +/** Credible data. */ +public static final int NORMAL = 3; + +/** Data not required to be credible. */ +public static final int ANY = 1; + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java b/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java new file mode 100644 index 0000000..22180cf --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DClass.java @@ -0,0 +1,92 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to DNS classes. This is called DClass + * to avoid confusion with Class. + * + * @author Brian Wellington + */ + +public final class DClass { + +/** Internet */ +public static final int IN = 1; + +/** Chaos network (MIT) */ +public static final int CH = 3; + +/** Chaos network (MIT, alternate name) */ +public static final int CHAOS = 3; + +/** Hesiod name server (MIT) */ +public static final int HS = 4; + +/** Hesiod name server (MIT, alternate name) */ +public static final int HESIOD = 4; + +/** Special value used in dynamic update messages */ +public static final int NONE = 254; + +/** Matches any class */ +public static final int ANY = 255; + +private static class DClassMnemonic extends Mnemonic { + public + DClassMnemonic() { + super("DClass", CASE_UPPER); + setPrefix("CLASS"); + } + + public void + check(int val) { + DClass.check(val); + } +} + +private static Mnemonic classes = new DClassMnemonic(); + +static { + classes.add(IN, "IN"); + classes.add(CH, "CH"); + classes.addAlias(CH, "CHAOS"); + classes.add(HS, "HS"); + classes.addAlias(HS, "HESIOD"); + classes.add(NONE, "NONE"); + classes.add(ANY, "ANY"); +} + +private +DClass() {} + +/** + * Checks that a numeric DClass is valid. + * @throws InvalidDClassException The class is out of range. + */ +public static void +check(int i) { + if (i < 0 || i > 0xFFFF) + throw new InvalidDClassException(i); +} + +/** + * Converts a numeric DClass into a String + * @return The canonical string representation of the class + * @throws InvalidDClassException The class is out of range. + */ +public static String +string(int i) { + return classes.getText(i); +} + +/** + * Converts a String representation of a DClass into its numeric value + * @return The class code, or -1 on error. + */ +public static int +value(String s) { + return classes.getValue(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java new file mode 100644 index 0000000..e160a8c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DHCIDRecord.java @@ -0,0 +1,65 @@ +// Copyright (c) 2008 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.base64; + +/** + * DHCID - Dynamic Host Configuration Protocol (DHCP) ID (RFC 4701) + * + * @author Brian Wellington + */ + +public class DHCIDRecord extends Record { + +private static final long serialVersionUID = -8214820200808997707L; + +private byte [] data; + +DHCIDRecord() {} + +Record +getObject() { + return new DHCIDRecord(); +} + +/** + * Creates an DHCID Record from the given data + * @param data The binary data, which is opaque to DNS. + */ +public +DHCIDRecord(Name name, int dclass, long ttl, byte [] data) { + super(name, Type.DHCID, dclass, ttl); + this.data = data; +} + +void +rrFromWire(DNSInput in) throws IOException { + data = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + data = st.getBase64(); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); +} + +String +rrToString() { + return base64.toString(data); +} + +/** + * Returns the binary data. + */ +public byte [] +getData() { + return data; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java new file mode 100644 index 0000000..8acc90f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DLVRecord.java @@ -0,0 +1,132 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.*; + +/** + * DLV - contains a Delegation Lookaside Validation record, which acts + * as the equivalent of a DS record in a lookaside zone. + * @see DNSSEC + * @see DSRecord + * + * @author David Blacka + * @author Brian Wellington + */ + +public class DLVRecord extends Record { + +public static final int SHA1_DIGEST_ID = DSRecord.Digest.SHA1; +public static final int SHA256_DIGEST_ID = DSRecord.Digest.SHA1; + +private static final long serialVersionUID = 1960742375677534148L; + +private int footprint; +private int alg; +private int digestid; +private byte [] digest; + +DLVRecord() {} + +Record +getObject() { + return new DLVRecord(); +} + +/** + * Creates a DLV Record from the given data + * @param footprint The original KEY record's footprint (keyid). + * @param alg The original key algorithm. + * @param digestid The digest id code. + * @param digest A hash of the original key. + */ +public +DLVRecord(Name name, int dclass, long ttl, int footprint, int alg, + int digestid, byte [] digest) +{ + super(name, Type.DLV, dclass, ttl); + this.footprint = checkU16("footprint", footprint); + this.alg = checkU8("alg", alg); + this.digestid = checkU8("digestid", digestid); + this.digest = digest; +} + +void +rrFromWire(DNSInput in) throws IOException { + footprint = in.readU16(); + alg = in.readU8(); + digestid = in.readU8(); + digest = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + footprint = st.getUInt16(); + alg = st.getUInt8(); + digestid = st.getUInt8(); + digest = st.getHex(); +} + +/** + * Converts rdata to a String + */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(footprint); + sb.append(" "); + sb.append(alg); + sb.append(" "); + sb.append(digestid); + if (digest != null) { + sb.append(" "); + sb.append(base16.toString(digest)); + } + + return sb.toString(); +} + +/** + * Returns the key's algorithm. + */ +public int +getAlgorithm() { + return alg; +} + +/** + * Returns the key's Digest ID. + */ +public int +getDigestID() +{ + return digestid; +} + +/** + * Returns the binary hash of the key. + */ +public byte [] +getDigest() { + return digest; +} + +/** + * Returns the key's footprint. + */ +public int +getFootprint() { + return footprint; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(footprint); + out.writeU8(alg); + out.writeU8(digestid); + if (digest != null) + out.writeByteArray(digest); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java new file mode 100644 index 0000000..cbb322f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNAMERecord.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * DNAME Record - maps a nonterminal alias (subtree) to a different domain + * + * @author Brian Wellington + */ + +public class DNAMERecord extends SingleNameBase { + +private static final long serialVersionUID = 2670767677200844154L; + +DNAMERecord() {} + +Record +getObject() { + return new DNAMERecord(); +} + +/** + * Creates a new DNAMERecord with the given data + * @param alias The name to which the DNAME alias points + */ +public +DNAMERecord(Name name, int dclass, long ttl, Name alias) { + super(name, Type.DNAME, dclass, ttl, alias, "alias"); +} + +/** + * Gets the target of the DNAME Record + */ +public Name +getTarget() { + return getSingleName(); +} + +/** Gets the alias specified by the DNAME Record */ +public Name +getAlias() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java new file mode 100644 index 0000000..d3134ed --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSInput.java @@ -0,0 +1,239 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An class for parsing DNS messages. + * + * @author Brian Wellington + */ + +public class DNSInput { + +private byte [] array; +private int pos; +private int end; +private int saved_pos; +private int saved_end; + +/** + * Creates a new DNSInput + * @param input The byte array to read from + */ +public +DNSInput(byte [] input) { + array = input; + pos = 0; + end = array.length; + saved_pos = -1; + saved_end = -1; +} + +/** + * Returns the current position. + */ +public int +current() { + return pos; +} + +/** + * Returns the number of bytes that can be read from this stream before + * reaching the end. + */ +public int +remaining() { + return end - pos; +} + +private void +require(int n) throws WireParseException{ + if (n > remaining()) { + throw new WireParseException("end of input"); + } +} + +/** + * Marks the following bytes in the stream as active. + * @param len The number of bytes in the active region. + * @throws IllegalArgumentException The number of bytes in the active region + * is longer than the remainder of the input. + */ +public void +setActive(int len) { + if (len > array.length - pos) { + throw new IllegalArgumentException("cannot set active " + + "region past end of input"); + } + end = pos + len; +} + +/** + * Clears the active region of the string. Further operations are not + * restricted to part of the input. + */ +public void +clearActive() { + end = array.length; +} + +/** + * Returns the position of the end of the current active region. + */ +public int +saveActive() { + return end; +} + +/** + * Restores the previously set active region. This differs from setActive() in + * that restoreActive() takes an absolute position, and setActive takes an + * offset from the current location. + * @param pos The end of the active region. + */ +public void +restoreActive(int pos) { + if (pos > array.length) { + throw new IllegalArgumentException("cannot set active " + + "region past end of input"); + } + end = pos; +} + +/** + * Resets the current position of the input stream to the specified index, + * and clears the active region. + * @param index The position to continue parsing at. + * @throws IllegalArgumentException The index is not within the input. + */ +public void +jump(int index) { + if (index >= array.length) { + throw new IllegalArgumentException("cannot jump past " + + "end of input"); + } + pos = index; + end = array.length; +} + +/** + * Saves the current state of the input stream. Both the current position and + * the end of the active region are saved. + * @throws IllegalArgumentException The index is not within the input. + */ +public void +save() { + saved_pos = pos; + saved_end = end; +} + +/** + * Restores the input stream to its state before the call to {@link #save}. + */ +public void +restore() { + if (saved_pos < 0) { + throw new IllegalStateException("no previous state"); + } + pos = saved_pos; + end = saved_end; + saved_pos = -1; + saved_end = -1; +} + +/** + * Reads an unsigned 8 bit value from the stream, as an int. + * @return An unsigned 8 bit value. + * @throws WireParseException The end of the stream was reached. + */ +public int +readU8() throws WireParseException { + require(1); + return (array[pos++] & 0xFF); +} + +/** + * Reads an unsigned 16 bit value from the stream, as an int. + * @return An unsigned 16 bit value. + * @throws WireParseException The end of the stream was reached. + */ +public int +readU16() throws WireParseException { + require(2); + int b1 = array[pos++] & 0xFF; + int b2 = array[pos++] & 0xFF; + return ((b1 << 8) + b2); +} + +/** + * Reads an unsigned 32 bit value from the stream, as a long. + * @return An unsigned 32 bit value. + * @throws WireParseException The end of the stream was reached. + */ +public long +readU32() throws WireParseException { + require(4); + int b1 = array[pos++] & 0xFF; + int b2 = array[pos++] & 0xFF; + int b3 = array[pos++] & 0xFF; + int b4 = array[pos++] & 0xFF; + return (((long)b1 << 24) + (b2 << 16) + (b3 << 8) + b4); +} + +/** + * Reads a byte array of a specified length from the stream into an existing + * array. + * @param b The array to read into. + * @param off The offset of the array to start copying data into. + * @param len The number of bytes to copy. + * @throws WireParseException The end of the stream was reached. + */ +public void +readByteArray(byte [] b, int off, int len) throws WireParseException { + require(len); + System.arraycopy(array, pos, b, off, len); + pos += len; +} + +/** + * Reads a byte array of a specified length from the stream. + * @return The byte array. + * @throws WireParseException The end of the stream was reached. + */ +public byte [] +readByteArray(int len) throws WireParseException { + require(len); + byte [] out = new byte[len]; + System.arraycopy(array, pos, out, 0, len); + pos += len; + return out; +} + +/** + * Reads a byte array consisting of the remainder of the stream (or the + * active region, if one is set. + * @return The byte array. + */ +public byte [] +readByteArray() { + int len = remaining(); + byte [] out = new byte[len]; + System.arraycopy(array, pos, out, 0, len); + pos += len; + return out; +} + +/** + * Reads a counted string from the stream. A counted string is a one byte + * value indicating string length, followed by bytes of data. + * @return A byte array containing the string. + * @throws WireParseException The end of the stream was reached. + */ +public byte [] +readCountedString() throws WireParseException { + require(1); + int len = array[pos++] & 0xFF; + return readByteArray(len); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java new file mode 100644 index 0000000..6e9bafd --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSKEYRecord.java @@ -0,0 +1,91 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.security.PublicKey; + +/** + * Key - contains a cryptographic public key for use by DNS. + * The data can be converted to objects implementing + * java.security.interfaces.PublicKey + * @see DNSSEC + * + * @author Brian Wellington + */ + +public class DNSKEYRecord extends KEYBase { + +public static class Protocol { + private Protocol() {} + + /** Key will be used for DNSSEC */ + public static final int DNSSEC = 3; +} + +public static class Flags { + private Flags() {} + + /** Key is a zone key */ + public static final int ZONE_KEY = 0x100; + + /** Key is a secure entry point key */ + public static final int SEP_KEY = 0x1; + + /** Key has been revoked */ + public static final int REVOKE = 0x80; +} + +private static final long serialVersionUID = -8679800040426675002L; + +DNSKEYRecord() {} + +Record +getObject() { + return new DNSKEYRecord(); +} + +/** + * Creates a DNSKEY Record from the given data + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key Binary representation of the key + */ +public +DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, + byte [] key) +{ + super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg, key); +} + +/** + * Creates a DNSKEY Record from the given data + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key The key as a PublicKey + * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS + * format. + */ +public +DNSKEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, + PublicKey key) throws DNSSEC.DNSSECException +{ + super(name, Type.DNSKEY, dclass, ttl, flags, proto, alg, + DNSSEC.fromPublicKey(key, alg)); + publicKey = key; +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + flags = st.getUInt16(); + proto = st.getUInt8(); + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) + throw st.exception("Invalid algorithm: " + algString); + key = st.getBase64(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java new file mode 100644 index 0000000..29a8f68 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSOutput.java @@ -0,0 +1,203 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * A class for rendering DNS messages. + * + * @author Brian Wellington + */ + + +public class DNSOutput { + +private byte [] array; +private int pos; +private int saved_pos; + +/** + * Create a new DNSOutput with a specified size. + * @param size The initial size + */ +public +DNSOutput(int size) { + array = new byte[size]; + pos = 0; + saved_pos = -1; +} + +/** + * Create a new DNSOutput + */ +public +DNSOutput() { + this(32); +} + +/** + * Returns the current position. + */ +public int +current() { + return pos; +} + +private void +check(long val, int bits) { + long max = 1; + max <<= bits; + if (val < 0 || val > max) { + throw new IllegalArgumentException(val + " out of range for " + + bits + " bit value"); + } +} + +private void +need(int n) { + if (array.length - pos >= n) { + return; + } + int newsize = array.length * 2; + if (newsize < pos + n) { + newsize = pos + n; + } + byte [] newarray = new byte[newsize]; + System.arraycopy(array, 0, newarray, 0, pos); + array = newarray; +} + +/** + * Resets the current position of the output stream to the specified index. + * @param index The new current position. + * @throws IllegalArgumentException The index is not within the output. + */ +public void +jump(int index) { + if (index > pos) { + throw new IllegalArgumentException("cannot jump past " + + "end of data"); + } + pos = index; +} + +/** + * Saves the current state of the output stream. + * @throws IllegalArgumentException The index is not within the output. + */ +public void +save() { + saved_pos = pos; +} + +/** + * Restores the input stream to its state before the call to {@link #save}. + */ +public void +restore() { + if (saved_pos < 0) { + throw new IllegalStateException("no previous state"); + } + pos = saved_pos; + saved_pos = -1; +} + +/** + * Writes an unsigned 8 bit value to the stream. + * @param val The value to be written + */ +public void +writeU8(int val) { + check(val, 8); + need(1); + array[pos++] = (byte)(val & 0xFF); +} + +/** + * Writes an unsigned 16 bit value to the stream. + * @param val The value to be written + */ +public void +writeU16(int val) { + check(val, 16); + need(2); + array[pos++] = (byte)((val >>> 8) & 0xFF); + array[pos++] = (byte)(val & 0xFF); +} + +/** + * Writes an unsigned 16 bit value to the specified position in the stream. + * @param val The value to be written + * @param where The position to write the value. + */ +public void +writeU16At(int val, int where) { + check(val, 16); + if (where > pos - 2) + throw new IllegalArgumentException("cannot write past " + + "end of data"); + array[where++] = (byte)((val >>> 8) & 0xFF); + array[where++] = (byte)(val & 0xFF); +} + +/** + * Writes an unsigned 32 bit value to the stream. + * @param val The value to be written + */ +public void +writeU32(long val) { + check(val, 32); + need(4); + array[pos++] = (byte)((val >>> 24) & 0xFF); + array[pos++] = (byte)((val >>> 16) & 0xFF); + array[pos++] = (byte)((val >>> 8) & 0xFF); + array[pos++] = (byte)(val & 0xFF); +} + +/** + * Writes a byte array to the stream. + * @param b The array to write. + * @param off The offset of the array to start copying data from. + * @param len The number of bytes to write. + */ +public void +writeByteArray(byte [] b, int off, int len) { + need(len); + System.arraycopy(b, off, array, pos, len); + pos += len; +} + +/** + * Writes a byte array to the stream. + * @param b The array to write. + */ +public void +writeByteArray(byte [] b) { + writeByteArray(b, 0, b.length); +} + +/** + * Writes a counted string from the stream. A counted string is a one byte + * value indicating string length, followed by bytes of data. + * @param s The string to write. + */ +public void +writeCountedString(byte [] s) { + if (s.length > 0xFF) { + throw new IllegalArgumentException("Invalid counted string"); + } + need(1 + s.length); + array[pos++] = (byte)(s.length & 0xFF); + writeByteArray(s, 0, s.length); +} + +/** + * Returns a byte array containing the current contents of the stream. + */ +public byte [] +toByteArray() { + byte [] out = new byte[pos]; + System.arraycopy(array, 0, out, 0, pos); + return out; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java b/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java new file mode 100644 index 0000000..082c9e9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DNSSEC.java @@ -0,0 +1,824 @@ +// Copyright (c) 1999-2010 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.math.*; +import java.security.*; +import java.security.interfaces.*; +import java.security.spec.*; +import java.util.*; + +/** + * Constants and methods relating to DNSSEC. + * + * DNSSEC provides authentication for DNS information. + * @see RRSIGRecord + * @see DNSKEYRecord + * @see RRset + * + * @author Brian Wellington + */ + +public class DNSSEC { + +public static class Algorithm { + private Algorithm() {} + + /** RSA/MD5 public key (deprecated) */ + public static final int RSAMD5 = 1; + + /** Diffie Hellman key */ + public static final int DH = 2; + + /** DSA public key */ + public static final int DSA = 3; + + /** Elliptic Curve key */ + public static final int ECC = 4; + + /** RSA/SHA1 public key */ + public static final int RSASHA1 = 5; + + /** DSA/SHA1, NSEC3-aware public key */ + public static final int DSA_NSEC3_SHA1 = 6; + + /** RSA/SHA1, NSEC3-aware public key */ + public static final int RSA_NSEC3_SHA1 = 7; + + /** RSA/SHA256 public key */ + public static final int RSASHA256 = 8; + + /** RSA/SHA512 public key */ + public static final int RSASHA512 = 10; + + /** Indirect keys; the actual key is elsewhere. */ + public static final int INDIRECT = 252; + + /** Private algorithm, specified by domain name */ + public static final int PRIVATEDNS = 253; + + /** Private algorithm, specified by OID */ + public static final int PRIVATEOID = 254; + + private static Mnemonic algs = new Mnemonic("DNSSEC algorithm", + Mnemonic.CASE_UPPER); + + static { + algs.setMaximum(0xFF); + algs.setNumericAllowed(true); + + algs.add(RSAMD5, "RSAMD5"); + algs.add(DH, "DH"); + algs.add(DSA, "DSA"); + algs.add(ECC, "ECC"); + algs.add(RSASHA1, "RSASHA1"); + algs.add(DSA_NSEC3_SHA1, "DSA-NSEC3-SHA1"); + algs.add(RSA_NSEC3_SHA1, "RSA-NSEC3-SHA1"); + algs.add(RSASHA256, "RSASHA256"); + algs.add(RSASHA512, "RSASHA512"); + algs.add(INDIRECT, "INDIRECT"); + algs.add(PRIVATEDNS, "PRIVATEDNS"); + algs.add(PRIVATEOID, "PRIVATEOID"); + } + + /** + * Converts an algorithm into its textual representation + */ + public static String + string(int alg) { + return algs.getText(alg); + } + + /** + * Converts a textual representation of an algorithm into its numeric + * code. Integers in the range 0..255 are also accepted. + * @param s The textual representation of the algorithm + * @return The algorithm code, or -1 on error. + */ + public static int + value(String s) { + return algs.getValue(s); + } +} + +private +DNSSEC() { } + +private static void +digestSIG(DNSOutput out, SIGBase sig) { + out.writeU16(sig.getTypeCovered()); + out.writeU8(sig.getAlgorithm()); + out.writeU8(sig.getLabels()); + out.writeU32(sig.getOrigTTL()); + out.writeU32(sig.getExpire().getTime() / 1000); + out.writeU32(sig.getTimeSigned().getTime() / 1000); + out.writeU16(sig.getFootprint()); + sig.getSigner().toWireCanonical(out); +} + +/** + * Creates a byte array containing the concatenation of the fields of the + * SIG record and the RRsets to be signed/verified. This does not perform + * a cryptographic digest. + * @param rrsig The RRSIG record used to sign/verify the rrset. + * @param rrset The data to be signed/verified. + * @return The data to be cryptographically signed or verified. + */ +public static byte [] +digestRRset(RRSIGRecord rrsig, RRset rrset) { + DNSOutput out = new DNSOutput(); + digestSIG(out, rrsig); + + int size = rrset.size(); + Record [] records = new Record[size]; + + Iterator it = rrset.rrs(); + Name name = rrset.getName(); + Name wild = null; + int sigLabels = rrsig.getLabels() + 1; // Add the root label back. + if (name.labels() > sigLabels) + wild = name.wild(name.labels() - sigLabels); + while (it.hasNext()) + records[--size] = (Record) it.next(); + Arrays.sort(records); + + DNSOutput header = new DNSOutput(); + if (wild != null) + wild.toWireCanonical(header); + else + name.toWireCanonical(header); + header.writeU16(rrset.getType()); + header.writeU16(rrset.getDClass()); + header.writeU32(rrsig.getOrigTTL()); + for (int i = 0; i < records.length; i++) { + out.writeByteArray(header.toByteArray()); + int lengthPosition = out.current(); + out.writeU16(0); + out.writeByteArray(records[i].rdataToWireCanonical()); + int rrlength = out.current() - lengthPosition - 2; + out.save(); + out.jump(lengthPosition); + out.writeU16(rrlength); + out.restore(); + } + return out.toByteArray(); +} + +/** + * Creates a byte array containing the concatenation of the fields of the + * SIG(0) record and the message to be signed. This does not perform + * a cryptographic digest. + * @param sig The SIG record used to sign the rrset. + * @param msg The message to be signed. + * @param previous If this is a response, the signature from the query. + * @return The data to be cryptographically signed. + */ +public static byte [] +digestMessage(SIGRecord sig, Message msg, byte [] previous) { + DNSOutput out = new DNSOutput(); + digestSIG(out, sig); + + if (previous != null) + out.writeByteArray(previous); + + msg.toWire(out); + return out.toByteArray(); +} + +/** + * A DNSSEC exception. + */ +public static class DNSSECException extends Exception { + DNSSECException(String s) { + super(s); + } +} + +/** + * An algorithm is unsupported by this DNSSEC implementation. + */ +public static class UnsupportedAlgorithmException extends DNSSECException { + UnsupportedAlgorithmException(int alg) { + super("Unsupported algorithm: " + alg); + } +} + +/** + * The cryptographic data in a DNSSEC key is malformed. + */ +public static class MalformedKeyException extends DNSSECException { + MalformedKeyException(KEYBase rec) { + super("Invalid key data: " + rec.rdataToString()); + } +} + +/** + * A DNSSEC verification failed because fields in the DNSKEY and RRSIG records + * do not match. + */ +public static class KeyMismatchException extends DNSSECException { + private KEYBase key; + private SIGBase sig; + + KeyMismatchException(KEYBase key, SIGBase sig) { + super("key " + + key.getName() + "/" + + DNSSEC.Algorithm.string(key.getAlgorithm()) + "/" + + key.getFootprint() + " " + + "does not match signature " + + sig.getSigner() + "/" + + DNSSEC.Algorithm.string(sig.getAlgorithm()) + "/" + + sig.getFootprint()); + } +} + +/** + * A DNSSEC verification failed because the signature has expired. + */ +public static class SignatureExpiredException extends DNSSECException { + private Date when, now; + + SignatureExpiredException(Date when, Date now) { + super("signature expired"); + this.when = when; + this.now = now; + } + + /** + * @return When the signature expired + */ + public Date + getExpiration() { + return when; + } + + /** + * @return When the verification was attempted + */ + public Date + getVerifyTime() { + return now; + } +} + +/** + * A DNSSEC verification failed because the signature has not yet become valid. + */ +public static class SignatureNotYetValidException extends DNSSECException { + private Date when, now; + + SignatureNotYetValidException(Date when, Date now) { + super("signature is not yet valid"); + this.when = when; + this.now = now; + } + + /** + * @return When the signature will become valid + */ + public Date + getExpiration() { + return when; + } + + /** + * @return When the verification was attempted + */ + public Date + getVerifyTime() { + return now; + } +} + +/** + * A DNSSEC verification failed because the cryptographic signature + * verification failed. + */ +public static class SignatureVerificationException extends DNSSECException { + SignatureVerificationException() { + super("signature verification failed"); + } +} + +/** + * The key data provided is inconsistent. + */ +public static class IncompatibleKeyException extends IllegalArgumentException { + IncompatibleKeyException() { + super("incompatible keys"); + } +} + +private static int +BigIntegerLength(BigInteger i) { + return (i.bitLength() + 7) / 8; +} + +private static BigInteger +readBigInteger(DNSInput in, int len) throws IOException { + byte [] b = in.readByteArray(len); + return new BigInteger(1, b); +} + +private static BigInteger +readBigInteger(DNSInput in) { + byte [] b = in.readByteArray(); + return new BigInteger(1, b); +} + +private static void +writeBigInteger(DNSOutput out, BigInteger val) { + byte [] b = val.toByteArray(); + if (b[0] == 0) + out.writeByteArray(b, 1, b.length - 1); + else + out.writeByteArray(b); +} + +private static PublicKey +toRSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException { + DNSInput in = new DNSInput(r.getKey()); + int exponentLength = in.readU8(); + if (exponentLength == 0) + exponentLength = in.readU16(); + BigInteger exponent = readBigInteger(in, exponentLength); + BigInteger modulus = readBigInteger(in); + + KeyFactory factory = KeyFactory.getInstance("RSA"); + return factory.generatePublic(new RSAPublicKeySpec(modulus, exponent)); +} + +private static PublicKey +toDSAPublicKey(KEYBase r) throws IOException, GeneralSecurityException, + MalformedKeyException +{ + DNSInput in = new DNSInput(r.getKey()); + + int t = in.readU8(); + if (t > 8) + throw new MalformedKeyException(r); + + BigInteger q = readBigInteger(in, 20); + BigInteger p = readBigInteger(in, 64 + t*8); + BigInteger g = readBigInteger(in, 64 + t*8); + BigInteger y = readBigInteger(in, 64 + t*8); + + KeyFactory factory = KeyFactory.getInstance("DSA"); + return factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)); +} + +/** Converts a KEY/DNSKEY record into a PublicKey */ +static PublicKey +toPublicKey(KEYBase r) throws DNSSECException { + int alg = r.getAlgorithm(); + try { + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + return toRSAPublicKey(r); + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + return toDSAPublicKey(r); + default: + throw new UnsupportedAlgorithmException(alg); + } + } + catch (IOException e) { + throw new MalformedKeyException(r); + } + catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } +} + +private static byte [] +fromRSAPublicKey(RSAPublicKey key) { + DNSOutput out = new DNSOutput(); + BigInteger exponent = key.getPublicExponent(); + BigInteger modulus = key.getModulus(); + int exponentLength = BigIntegerLength(exponent); + + if (exponentLength < 256) + out.writeU8(exponentLength); + else { + out.writeU8(0); + out.writeU16(exponentLength); + } + writeBigInteger(out, exponent); + writeBigInteger(out, modulus); + + return out.toByteArray(); +} + +private static byte [] +fromDSAPublicKey(DSAPublicKey key) { + DNSOutput out = new DNSOutput(); + BigInteger q = key.getParams().getQ(); + BigInteger p = key.getParams().getP(); + BigInteger g = key.getParams().getG(); + BigInteger y = key.getY(); + int t = (p.toByteArray().length - 64) / 8; + + out.writeU8(t); + writeBigInteger(out, q); + writeBigInteger(out, p); + writeBigInteger(out, g); + writeBigInteger(out, y); + + return out.toByteArray(); +} + +/** Builds a DNSKEY record from a PublicKey */ +static byte [] +fromPublicKey(PublicKey key, int alg) throws DNSSECException +{ + byte [] data = null; + + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + if (! (key instanceof RSAPublicKey)) + throw new IncompatibleKeyException(); + return fromRSAPublicKey((RSAPublicKey) key); + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + if (! (key instanceof DSAPublicKey)) + throw new IncompatibleKeyException(); + return fromDSAPublicKey((DSAPublicKey) key); + default: + throw new UnsupportedAlgorithmException(alg); + } +} + +/** + * Convert an algorithm number to the corresponding JCA string. + * @param alg The algorithm number. + * @throws UnsupportedAlgorithmException The algorithm is unknown. + */ +public static String +algString(int alg) throws UnsupportedAlgorithmException { + switch (alg) { + case Algorithm.RSAMD5: + return "MD5withRSA"; + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + return "SHA1withDSA"; + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + return "SHA1withRSA"; + case Algorithm.RSASHA256: + return "SHA256withRSA"; + case Algorithm.RSASHA512: + return "SHA512withRSA"; + default: + throw new UnsupportedAlgorithmException(alg); + } +} + +private static final int ASN1_SEQ = 0x30; +private static final int ASN1_INT = 0x2; + +private static final int DSA_LEN = 20; + +private static byte [] +DSASignaturefromDNS(byte [] dns) throws DNSSECException, IOException { + if (dns.length != 1 + DSA_LEN * 2) + throw new SignatureVerificationException(); + + DNSInput in = new DNSInput(dns); + DNSOutput out = new DNSOutput(); + + int t = in.readU8(); + + byte [] r = in.readByteArray(DSA_LEN); + int rlen = DSA_LEN; + if (r[0] < 0) + rlen++; + + byte [] s = in.readByteArray(DSA_LEN); + int slen = DSA_LEN; + if (s[0] < 0) + slen++; + + out.writeU8(ASN1_SEQ); + out.writeU8(rlen + slen + 4); + + out.writeU8(ASN1_INT); + out.writeU8(rlen); + if (rlen > DSA_LEN) + out.writeU8(0); + out.writeByteArray(r); + + out.writeU8(ASN1_INT); + out.writeU8(slen); + if (slen > DSA_LEN) + out.writeU8(0); + out.writeByteArray(s); + + return out.toByteArray(); +} + +private static byte [] +DSASignaturetoDNS(byte [] key, int t) throws IOException { + DNSInput in = new DNSInput(key); + DNSOutput out = new DNSOutput(); + + out.writeU8(t); + + int tmp = in.readU8(); + if (tmp != ASN1_SEQ) + throw new IOException(); + int seqlen = in.readU8(); + + tmp = in.readU8(); + if (tmp != ASN1_INT) + throw new IOException(); + int rlen = in.readU8(); + if (rlen == DSA_LEN + 1) { + if (in.readU8() != 0) + throw new IOException(); + } else if (rlen != DSA_LEN) + throw new IOException(); + byte [] bytes = in.readByteArray(DSA_LEN); + out.writeByteArray(bytes); + + tmp = in.readU8(); + if (tmp != ASN1_INT) + throw new IOException(); + int slen = in.readU8(); + if (slen == DSA_LEN + 1) { + if (in.readU8() != 0) + throw new IOException(); + } else if (slen != DSA_LEN) + throw new IOException(); + bytes = in.readByteArray(DSA_LEN); + out.writeByteArray(bytes); + + return out.toByteArray(); +} + +private static void +verify(PublicKey key, int alg, byte [] data, byte [] signature) +throws DNSSECException +{ + if (key instanceof DSAPublicKey) { + try { + signature = DSASignaturefromDNS(signature); + } + catch (IOException e) { + throw new IllegalStateException(); + } + } + + try { + Signature s = Signature.getInstance(algString(alg)); + s.initVerify(key); + s.update(data); + if (!s.verify(signature)) + throw new SignatureVerificationException(); + } + catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } +} + +private static boolean +matches(SIGBase sig, KEYBase key) +{ + return (key.getAlgorithm() == sig.getAlgorithm() && + key.getFootprint() == sig.getFootprint() && + key.getName().equals(sig.getSigner())); +} + +/** + * Verify a DNSSEC signature. + * @param rrset The data to be verified. + * @param rrsig The RRSIG record containing the signature. + * @param key The DNSKEY record to verify the signature with. + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws KeyMismatchException The key and signature do not match + * @throws SignatureExpiredException The signature has expired + * @throws SignatureNotYetValidException The signature is not yet valid + * @throws SignatureVerificationException The signature does not verify. + * @throws DNSSECException Some other error occurred. + */ +public static void +verify(RRset rrset, RRSIGRecord rrsig, DNSKEYRecord key) throws DNSSECException +{ + if (!matches(rrsig, key)) + throw new KeyMismatchException(key, rrsig); + + Date now = new Date(); + if (now.compareTo(rrsig.getExpire()) > 0) + throw new SignatureExpiredException(rrsig.getExpire(), now); + if (now.compareTo(rrsig.getTimeSigned()) < 0) + throw new SignatureNotYetValidException(rrsig.getTimeSigned(), + now); + + verify(key.getPublicKey(), rrsig.getAlgorithm(), + digestRRset(rrsig, rrset), rrsig.getSignature()); +} + +private static byte [] +sign(PrivateKey privkey, PublicKey pubkey, int alg, byte [] data, + String provider) throws DNSSECException +{ + byte [] signature; + try { + Signature s; + if (provider != null) + s = Signature.getInstance(algString(alg), provider); + else + s = Signature.getInstance(algString(alg)); + s.initSign(privkey); + s.update(data); + signature = s.sign(); + } + catch (GeneralSecurityException e) { + throw new DNSSECException(e.toString()); + } + + if (pubkey instanceof DSAPublicKey) { + try { + DSAPublicKey dsa = (DSAPublicKey) pubkey; + BigInteger P = dsa.getParams().getP(); + int t = (BigIntegerLength(P) - 64) / 8; + signature = DSASignaturetoDNS(signature, t); + } + catch (IOException e) { + throw new IllegalStateException(); + } + } + + return signature; +} + +static void +checkAlgorithm(PrivateKey key, int alg) throws UnsupportedAlgorithmException +{ + switch (alg) { + case Algorithm.RSAMD5: + case Algorithm.RSASHA1: + case Algorithm.RSA_NSEC3_SHA1: + case Algorithm.RSASHA256: + case Algorithm.RSASHA512: + if (! (key instanceof RSAPrivateKey)) + throw new IncompatibleKeyException(); + break; + case Algorithm.DSA: + case Algorithm.DSA_NSEC3_SHA1: + if (! (key instanceof DSAPrivateKey)) + throw new IncompatibleKeyException(); + break; + default: + throw new UnsupportedAlgorithmException(alg); + } +} + +/** + * Generate a DNSSEC signature. key and privateKey must refer to the + * same underlying cryptographic key. + * @param rrset The data to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param inception The time at which the signatures should become valid + * @param expiration The time at which the signatures should expire + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws DNSSECException Some other error occurred. + * @return The generated signature + */ +public static RRSIGRecord +sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, + Date inception, Date expiration) throws DNSSECException +{ + return sign(rrset, key, privkey, inception, expiration, null); +} + +/** + * Generate a DNSSEC signature. key and privateKey must refer to the + * same underlying cryptographic key. + * @param rrset The data to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param inception The time at which the signatures should become valid + * @param expiration The time at which the signatures should expire + * @param provider The name of the JCA provider. If non-null, it will be + * passed to JCA getInstance() methods. + * @throws UnsupportedAlgorithmException The algorithm is unknown + * @throws MalformedKeyException The key is malformed + * @throws DNSSECException Some other error occurred. + * @return The generated signature + */ +public static RRSIGRecord +sign(RRset rrset, DNSKEYRecord key, PrivateKey privkey, + Date inception, Date expiration, String provider) throws DNSSECException +{ + int alg = key.getAlgorithm(); + checkAlgorithm(privkey, alg); + + RRSIGRecord rrsig = new RRSIGRecord(rrset.getName(), rrset.getDClass(), + rrset.getTTL(), rrset.getType(), + alg, rrset.getTTL(), + expiration, inception, + key.getFootprint(), + key.getName(), null); + + rrsig.setSignature(sign(privkey, key.getPublicKey(), alg, + digestRRset(rrsig, rrset), provider)); + return rrsig; +} + +static SIGRecord +signMessage(Message message, SIGRecord previous, KEYRecord key, + PrivateKey privkey, Date inception, Date expiration) + throws DNSSECException +{ + int alg = key.getAlgorithm(); + checkAlgorithm(privkey, alg); + + SIGRecord sig = new SIGRecord(Name.root, DClass.ANY, 0, 0, + alg, 0, expiration, inception, + key.getFootprint(), + key.getName(), null); + DNSOutput out = new DNSOutput(); + digestSIG(out, sig); + if (previous != null) + out.writeByteArray(previous.getSignature()); + message.toWire(out); + + sig.setSignature(sign(privkey, key.getPublicKey(), + alg, out.toByteArray(), null)); + return sig; +} + +static void +verifyMessage(Message message, byte [] bytes, SIGRecord sig, SIGRecord previous, + KEYRecord key) throws DNSSECException +{ + if (!matches(sig, key)) + throw new KeyMismatchException(key, sig); + + Date now = new Date(); + if (now.compareTo(sig.getExpire()) > 0) + throw new SignatureExpiredException(sig.getExpire(), now); + if (now.compareTo(sig.getTimeSigned()) < 0) + throw new SignatureNotYetValidException(sig.getTimeSigned(), + now); + + DNSOutput out = new DNSOutput(); + digestSIG(out, sig); + if (previous != null) + out.writeByteArray(previous.getSignature()); + + Header header = (Header) message.getHeader().clone(); + header.decCount(Section.ADDITIONAL); + out.writeByteArray(header.toWire()); + + out.writeByteArray(bytes, Header.LENGTH, + message.sig0start - Header.LENGTH); + + verify(key.getPublicKey(), sig.getAlgorithm(), + out.toByteArray(), sig.getSignature()); +} + +/** + * Generate the digest value for a DS key + * @param key Which is covered by the DS record + * @param digestid The type of digest + * @return The digest value as an array of bytes + */ +static byte [] +generateDSDigest(DNSKEYRecord key, int digestid) +{ + MessageDigest digest; + try { + switch (digestid) { + case DSRecord.Digest.SHA1: + digest = MessageDigest.getInstance("sha-1"); + break; + case DSRecord.Digest.SHA256: + digest = MessageDigest.getInstance("sha-256"); + break; + default: + throw new IllegalArgumentException( + "unknown DS digest type " + digestid); + } + } + catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("no message digest support"); + } + digest.update(key.getName().toWire()); + digest.update(key.rdataToWireCanonical()); + return digest.digest(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java new file mode 100644 index 0000000..8058658 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/DSRecord.java @@ -0,0 +1,153 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.*; + +/** + * DS - contains a Delegation Signer record, which acts as a + * placeholder for KEY records in the parent zone. + * @see DNSSEC + * + * @author David Blacka + * @author Brian Wellington + */ + +public class DSRecord extends Record { + +public static class Digest { + private Digest() {} + + /** SHA-1 */ + public static final int SHA1 = 1; + + /** SHA-256 */ + public static final int SHA256 = 2; +} + +public static final int SHA1_DIGEST_ID = Digest.SHA1; +public static final int SHA256_DIGEST_ID = Digest.SHA256; + +private static final long serialVersionUID = -9001819329700081493L; + +private int footprint; +private int alg; +private int digestid; +private byte [] digest; + +DSRecord() {} + +Record +getObject() { + return new DSRecord(); +} + +/** + * Creates a DS Record from the given data + * @param footprint The original KEY record's footprint (keyid). + * @param alg The original key algorithm. + * @param digestid The digest id code. + * @param digest A hash of the original key. + */ +public +DSRecord(Name name, int dclass, long ttl, int footprint, int alg, + int digestid, byte [] digest) +{ + super(name, Type.DS, dclass, ttl); + this.footprint = checkU16("footprint", footprint); + this.alg = checkU8("alg", alg); + this.digestid = checkU8("digestid", digestid); + this.digest = digest; +} + +/** + * Creates a DS Record from the given data + * @param digestid The digest id code. + * @param key The key to digest + */ +public +DSRecord(Name name, int dclass, long ttl, int digestid, DNSKEYRecord key) +{ + this(name, dclass, ttl, key.getFootprint(), key.getAlgorithm(), + digestid, DNSSEC.generateDSDigest(key, digestid)); +} + +void +rrFromWire(DNSInput in) throws IOException { + footprint = in.readU16(); + alg = in.readU8(); + digestid = in.readU8(); + digest = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + footprint = st.getUInt16(); + alg = st.getUInt8(); + digestid = st.getUInt8(); + digest = st.getHex(); +} + +/** + * Converts rdata to a String + */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(footprint); + sb.append(" "); + sb.append(alg); + sb.append(" "); + sb.append(digestid); + if (digest != null) { + sb.append(" "); + sb.append(base16.toString(digest)); + } + + return sb.toString(); +} + +/** + * Returns the key's algorithm. + */ +public int +getAlgorithm() { + return alg; +} + +/** + * Returns the key's Digest ID. + */ +public int +getDigestID() +{ + return digestid; +} + +/** + * Returns the binary hash of the key. + */ +public byte [] +getDigest() { + return digest; +} + +/** + * Returns the key's footprint. + */ +public int +getFootprint() { + return footprint; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(footprint); + out.writeU8(alg); + out.writeU8(digestid); + if (digest != null) + out.writeByteArray(digest); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java new file mode 100644 index 0000000..a65bd19 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/EDNSOption.java @@ -0,0 +1,215 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) +package org.xbill.DNS; + +import java.io.*; +import java.util.Arrays; + +/** + * DNS extension options, as described in RFC 2671. The rdata of an OPT record + * is defined as a list of options; this represents a single option. + * + * @author Brian Wellington + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + */ +public abstract class EDNSOption { + +public static class Code { + private Code() {} + + /** Name Server Identifier, RFC 5001 */ + public final static int NSID = 3; + + /** Client Subnet, defined in draft-vandergaast-edns-client-subnet-00 */ + public final static int CLIENT_SUBNET = 20730; + + private static Mnemonic codes = new Mnemonic("EDNS Option Codes", + Mnemonic.CASE_UPPER); + + static { + codes.setMaximum(0xFFFF); + codes.setPrefix("CODE"); + codes.setNumericAllowed(true); + + codes.add(NSID, "NSID"); + codes.add(CLIENT_SUBNET, "CLIENT_SUBNET"); + } + + /** + * Converts an EDNS Option Code into its textual representation + */ + public static String + string(int code) { + return codes.getText(code); + } + + /** + * Converts a textual representation of an EDNS Option Code into its + * numeric value. + * @param s The textual representation of the option code + * @return The option code, or -1 on error. + */ + public static int + value(String s) { + return codes.getValue(s); + } +} + +private final int code; + +/** + * + * Creates an option with the given option code and data. + */ +public +EDNSOption(int code) { + this.code = Record.checkU16("code", code); +} + +public String +toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("{"); + sb.append(EDNSOption.Code.string(code)); + sb.append(": "); + sb.append(optionToString()); + sb.append("}"); + + return sb.toString(); +} + +/** + * Returns the EDNS Option's code. + * + * @return the option code + */ +public int +getCode() { + return code; +} + +/** + * Returns the EDNS Option's data, as a byte array. + * + * @return the option data + */ +byte [] +getData() { + DNSOutput out = new DNSOutput(); + optionToWire(out); + return out.toByteArray(); +} + +/** + * Converts the wire format of an EDNS Option (the option data only) into the + * type-specific format. + * @param in The input Stream. + */ +abstract void +optionFromWire(DNSInput in) throws IOException; + +/** + * Converts the wire format of an EDNS Option (including code and length) into + * the type-specific format. + * @param out The input stream. + */ +static EDNSOption +fromWire(DNSInput in) throws IOException { + int code, length; + + code = in.readU16(); + length = in.readU16(); + if (in.remaining() < length) + throw new WireParseException("truncated option"); + int save = in.saveActive(); + in.setActive(length); + EDNSOption option; + switch (code) { + case Code.NSID: + option = new NSIDOption(); + break; + case Code.CLIENT_SUBNET: + option = new ClientSubnetOption(); + break; + default: + option = new GenericEDNSOption(code); + break; + } + option.optionFromWire(in); + in.restoreActive(save); + + return option; +} + +/** + * Converts the wire format of an EDNS Option (including code and length) into + * the type-specific format. + * @return The option, in wire format. + */ +public static EDNSOption +fromWire(byte [] b) throws IOException { + return fromWire(new DNSInput(b)); +} + +/** + * Converts an EDNS Option (the type-specific option data only) into wire format. + * @param out The output stream. + */ +abstract void +optionToWire(DNSOutput out); + +/** + * Converts an EDNS Option (including code and length) into wire format. + * @param out The output stream. + */ +void +toWire(DNSOutput out) { + out.writeU16(code); + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + optionToWire(out); + int length = out.current() - lengthPosition - 2; + out.writeU16At(length, lengthPosition); +} + +/** + * Converts an EDNS Option (including code and length) into wire format. + * @return The option, in wire format. + */ +public byte [] +toWire() throws IOException { + DNSOutput out = new DNSOutput(); + toWire(out); + return out.toByteArray(); +} + +/** + * Determines if two EDNS Options are identical. + * @param arg The option to compare to + * @return true if the options are equal, false otherwise. + */ +public boolean +equals(Object arg) { + if (arg == null || !(arg instanceof EDNSOption)) + return false; + EDNSOption opt = (EDNSOption) arg; + if (code != opt.code) + return false; + return Arrays.equals(getData(), opt.getData()); +} + +/** + * Generates a hash code based on the EDNS Option's data. + */ +public int +hashCode() { + byte [] array = getData(); + int hashval = 0; + for (int i = 0; i < array.length; i++) + hashval += ((hashval << 3) + (array[i] & 0xFF)); + return hashval; +} + +abstract String optionToString(); + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java new file mode 100644 index 0000000..f5e61e8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/EmptyRecord.java @@ -0,0 +1,42 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * A class implementing Records with no data; that is, records used in + * the question section of messages and meta-records in dynamic update. + * + * @author Brian Wellington + */ + +class EmptyRecord extends Record { + +private static final long serialVersionUID = 3601852050646429582L; + +EmptyRecord() {} + +Record +getObject() { + return new EmptyRecord(); +} + +void +rrFromWire(DNSInput in) throws IOException { +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { +} + +String +rrToString() { + return ""; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java new file mode 100644 index 0000000..8f3bbab --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedFlags.java @@ -0,0 +1,45 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to EDNS flags. + * + * @author Brian Wellington + */ + +public final class ExtendedFlags { + +private static Mnemonic extflags = new Mnemonic("EDNS Flag", + Mnemonic.CASE_LOWER); + +/** dnssec ok */ +public static final int DO = 0x8000; + +static { + extflags.setMaximum(0xFFFF); + extflags.setPrefix("FLAG"); + extflags.setNumericAllowed(true); + + extflags.add(DO, "do"); +} + +private +ExtendedFlags() {} + +/** Converts a numeric extended flag into a String */ +public static String +string(int i) { + return extflags.getText(i); +} + +/** + * Converts a textual representation of an extended flag into its numeric + * value + */ +public static int +value(String s) { + return extflags.getValue(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java new file mode 100644 index 0000000..f762b84 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ExtendedResolver.java @@ -0,0 +1,419 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * An implementation of Resolver that can send queries to multiple servers, + * sending the queries multiple times if necessary. + * @see Resolver + * + * @author Brian Wellington + */ + +public class ExtendedResolver implements Resolver { + +private static class Resolution implements ResolverListener { + Resolver [] resolvers; + int [] sent; + Object [] inprogress; + int retries; + int outstanding; + boolean done; + Message query; + Message response; + Throwable thrown; + ResolverListener listener; + + public + Resolution(ExtendedResolver eres, Message query) { + List l = eres.resolvers; + resolvers = (Resolver []) l.toArray (new Resolver[l.size()]); + if (eres.loadBalance) { + int nresolvers = resolvers.length; + /* + * Note: this is not synchronized, since the + * worst thing that can happen is a random + * ordering, which is ok. + */ + int start = eres.lbStart++ % nresolvers; + if (eres.lbStart > nresolvers) + eres.lbStart %= nresolvers; + if (start > 0) { + Resolver [] shuffle = new Resolver[nresolvers]; + for (int i = 0; i < nresolvers; i++) { + int pos = (i + start) % nresolvers; + shuffle[i] = resolvers[pos]; + } + resolvers = shuffle; + } + } + sent = new int[resolvers.length]; + inprogress = new Object[resolvers.length]; + retries = eres.retries; + this.query = query; + } + + /* Asynchronously sends a message. */ + public void + send(int n) { + sent[n]++; + outstanding++; + try { + inprogress[n] = resolvers[n].sendAsync(query, this); + } + catch (Throwable t) { + synchronized (this) { + thrown = t; + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + } + } + + /* Start a synchronous resolution */ + public Message + start() throws IOException { + try { + /* + * First, try sending synchronously. If this works, + * we're done. Otherwise, we'll get an exception + * and continue. It would be easier to call send(0), + * but this avoids a thread creation. If and when + * SimpleResolver.sendAsync() can be made to not + * create a thread, this could be changed. + */ + sent[0]++; + outstanding++; + inprogress[0] = new Object(); + return resolvers[0].send(query); + } + catch (Exception e) { + /* + * This will either cause more queries to be sent + * asynchronously or will set the 'done' flag. + */ + handleException(inprogress[0], e); + } + /* + * Wait for a successful response or for each + * subresolver to fail. + */ + synchronized (this) { + while (!done) { + try { + wait(); + } + catch (InterruptedException e) { + } + } + } + /* Return the response or throw an exception */ + if (response != null) + return response; + else if (thrown instanceof IOException) + throw (IOException) thrown; + else if (thrown instanceof RuntimeException) + throw (RuntimeException) thrown; + else if (thrown instanceof Error) + throw (Error) thrown; + else + throw new IllegalStateException + ("ExtendedResolver failure"); + } + + /* Start an asynchronous resolution */ + public void + startAsync(ResolverListener listener) { + this.listener = listener; + send(0); + } + + /* + * Receive a response. If the resolution hasn't been completed, + * either wake up the blocking thread or call the callback. + */ + public void + receiveMessage(Object id, Message m) { + if (Options.check("verbose")) + System.err.println("ExtendedResolver: " + + "received message"); + synchronized (this) { + if (done) + return; + response = m; + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + listener.receiveMessage(this, response); + } + + /* + * Receive an exception. If the resolution has been completed, + * do nothing. Otherwise make progress. + */ + public void + handleException(Object id, Exception e) { + if (Options.check("verbose")) + System.err.println("ExtendedResolver: got " + e); + synchronized (this) { + outstanding--; + if (done) + return; + int n; + for (n = 0; n < inprogress.length; n++) + if (inprogress[n] == id) + break; + /* If we don't know what this is, do nothing. */ + if (n == inprogress.length) + return; + boolean startnext = false; + /* + * If this is the first response from server n, + * we should start sending queries to server n + 1. + */ + if (sent[n] == 1 && n < resolvers.length - 1) + startnext = true; + if (e instanceof InterruptedIOException) { + /* Got a timeout; resend */ + if (sent[n] < retries) + send(n); + if (thrown == null) + thrown = e; + } else if (e instanceof SocketException) { + /* + * Problem with the socket; don't resend + * on it + */ + if (thrown == null || + thrown instanceof InterruptedIOException) + thrown = e; + } else { + /* + * Problem with the response; don't resend + * on the same socket. + */ + thrown = e; + } + if (done) + return; + if (startnext) + send(n + 1); + if (done) + return; + if (outstanding == 0) { + /* + * If we're done and this is synchronous, + * wake up the blocking thread. + */ + done = true; + if (listener == null) { + notifyAll(); + return; + } + } + if (!done) + return; + } + /* If we're done and this is asynchronous, call the callback. */ + if (!(thrown instanceof Exception)) + thrown = new RuntimeException(thrown.getMessage()); + listener.handleException(this, (Exception) thrown); + } +} + +private static final int quantum = 5; + +private List resolvers; +private boolean loadBalance = false; +private int lbStart = 0; +private int retries = 3; + +private void +init() { + resolvers = new ArrayList(); +} + +/** + * Creates a new Extended Resolver. The default ResolverConfig is used to + * determine the servers for which SimpleResolver contexts should be + * initialized. + * @see SimpleResolver + * @see ResolverConfig + * @exception UnknownHostException Failure occured initializing SimpleResolvers + */ +public +ExtendedResolver() throws UnknownHostException { + init(); + String [] servers = ResolverConfig.getCurrentConfig().servers(); + if (servers != null) { + for (int i = 0; i < servers.length; i++) { + Resolver r = new SimpleResolver(servers[i]); + r.setTimeout(quantum); + resolvers.add(r); + } + } + else + resolvers.add(new SimpleResolver()); +} + +/** + * Creates a new Extended Resolver + * @param servers An array of server names for which SimpleResolver + * contexts should be initialized. + * @see SimpleResolver + * @exception UnknownHostException Failure occured initializing SimpleResolvers + */ +public +ExtendedResolver(String [] servers) throws UnknownHostException { + init(); + for (int i = 0; i < servers.length; i++) { + Resolver r = new SimpleResolver(servers[i]); + r.setTimeout(quantum); + resolvers.add(r); + } +} + +/** + * Creates a new Extended Resolver + * @param res An array of pre-initialized Resolvers is provided. + * @see SimpleResolver + * @exception UnknownHostException Failure occured initializing SimpleResolvers + */ +public +ExtendedResolver(Resolver [] res) throws UnknownHostException { + init(); + for (int i = 0; i < res.length; i++) + resolvers.add(res[i]); +} + +public void +setPort(int port) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setPort(port); +} + +public void +setTCP(boolean flag) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setTCP(flag); +} + +public void +setIgnoreTruncation(boolean flag) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setIgnoreTruncation(flag); +} + +public void +setEDNS(int level) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setEDNS(level); +} + +public void +setEDNS(int level, int payloadSize, int flags, List options) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setEDNS(level, payloadSize, + flags, options); +} + +public void +setTSIGKey(TSIG key) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setTSIGKey(key); +} + +public void +setTimeout(int secs, int msecs) { + for (int i = 0; i < resolvers.size(); i++) + ((Resolver)resolvers.get(i)).setTimeout(secs, msecs); +} + +public void +setTimeout(int secs) { + setTimeout(secs, 0); +} + +/** + * Sends a message and waits for a response. Multiple servers are queried, + * and queries are sent multiple times until either a successful response + * is received, or it is clear that there is no successful response. + * @param query The query to send. + * @return The response. + * @throws IOException An error occurred while sending or receiving. + */ +public Message +send(Message query) throws IOException { + Resolution res = new Resolution(this, query); + return res.start(); +} + +/** + * Asynchronously sends a message to multiple servers, potentially multiple + * times, registering a listener to receive a callback on success or exception. + * Multiple asynchronous lookups can be performed in parallel. Since the + * callback may be invoked before the function returns, external + * synchronization is necessary. + * @param query The query to send + * @param listener The object containing the callbacks. + * @return An identifier, which is also a parameter in the callback + */ +public Object +sendAsync(final Message query, final ResolverListener listener) { + Resolution res = new Resolution(this, query); + res.startAsync(listener); + return res; +} + +/** Returns the nth resolver used by this ExtendedResolver */ +public Resolver +getResolver(int n) { + if (n < resolvers.size()) + return (Resolver)resolvers.get(n); + return null; +} + +/** Returns all resolvers used by this ExtendedResolver */ +public Resolver [] +getResolvers() { + return (Resolver []) resolvers.toArray(new Resolver[resolvers.size()]); +} + +/** Adds a new resolver to be used by this ExtendedResolver */ +public void +addResolver(Resolver r) { + resolvers.add(r); +} + +/** Deletes a resolver used by this ExtendedResolver */ +public void +deleteResolver(Resolver r) { + resolvers.remove(r); +} + +/** Sets whether the servers should be load balanced. + * @param flag If true, servers will be tried in round-robin order. If false, + * servers will always be queried in the same order. + */ +public void +setLoadBalance(boolean flag) { + loadBalance = flag; +} + +/** Sets the number of retries sent to each server per query */ +public void +setRetries(int retries) { + this.retries = retries; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java b/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java new file mode 100644 index 0000000..964ce23 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Flags.java @@ -0,0 +1,81 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to flags in the DNS header. + * + * @author Brian Wellington + */ + +public final class Flags { + +private static Mnemonic flags = new Mnemonic("DNS Header Flag", + Mnemonic.CASE_LOWER); + +/** query/response */ +public static final byte QR = 0; + +/** authoritative answer */ +public static final byte AA = 5; + +/** truncated */ +public static final byte TC = 6; + +/** recursion desired */ +public static final byte RD = 7; + +/** recursion available */ +public static final byte RA = 8; + +/** authenticated data */ +public static final byte AD = 10; + +/** (security) checking disabled */ +public static final byte CD = 11; + +/** dnssec ok (extended) */ +public static final int DO = ExtendedFlags.DO; + +static { + flags.setMaximum(0xF); + flags.setPrefix("FLAG"); + flags.setNumericAllowed(true); + + flags.add(QR, "qr"); + flags.add(AA, "aa"); + flags.add(TC, "tc"); + flags.add(RD, "rd"); + flags.add(RA, "ra"); + flags.add(AD, "ad"); + flags.add(CD, "cd"); +} + +private +Flags() {} + +/** Converts a numeric Flag into a String */ +public static String +string(int i) { + return flags.getText(i); +} + +/** Converts a String representation of an Flag into its numeric value */ +public static int +value(String s) { + return flags.getValue(s); +} + +/** + * Indicates if a bit in the flags field is a flag or not. If it's part of + * the rcode or opcode, it's not. + */ +public static boolean +isFlag(int index) { + flags.check(index); + if ((index >= 1 && index <= 4) || (index >= 12)) + return false; + return true; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java b/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java new file mode 100644 index 0000000..c76a846 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/FormattedTime.java @@ -0,0 +1,79 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Routines for converting time values to and from YYYYMMDDHHMMSS format. + * + * @author Brian Wellington + */ + +import java.util.*; +import java.text.*; + +final class FormattedTime { + +private static NumberFormat w2, w4; + +static { + w2 = new DecimalFormat(); + w2.setMinimumIntegerDigits(2); + + w4 = new DecimalFormat(); + w4.setMinimumIntegerDigits(4); + w4.setGroupingUsed(false); +} + +private +FormattedTime() {} + +/** + * Converts a Date into a formatted string. + * @param date The Date to convert. + * @return The formatted string. + */ +public static String +format(Date date) { + Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + StringBuffer sb = new StringBuffer(); + + c.setTime(date); + sb.append(w4.format(c.get(Calendar.YEAR))); + sb.append(w2.format(c.get(Calendar.MONTH)+1)); + sb.append(w2.format(c.get(Calendar.DAY_OF_MONTH))); + sb.append(w2.format(c.get(Calendar.HOUR_OF_DAY))); + sb.append(w2.format(c.get(Calendar.MINUTE))); + sb.append(w2.format(c.get(Calendar.SECOND))); + return sb.toString(); +} + +/** + * Parses a formatted time string into a Date. + * @param s The string, in the form YYYYMMDDHHMMSS. + * @return The Date object. + * @throws TextParseExcetption The string was invalid. + */ +public static Date +parse(String s) throws TextParseException { + if (s.length() != 14) { + throw new TextParseException("Invalid time encoding: " + s); + } + + Calendar c = new GregorianCalendar(TimeZone.getTimeZone("UTC")); + c.clear(); + try { + int year = Integer.parseInt(s.substring(0, 4)); + int month = Integer.parseInt(s.substring(4, 6)) - 1; + int date = Integer.parseInt(s.substring(6, 8)); + int hour = Integer.parseInt(s.substring(8, 10)); + int minute = Integer.parseInt(s.substring(10, 12)); + int second = Integer.parseInt(s.substring(12, 14)); + c.set(year, month, date, hour, minute, second); + } + catch (NumberFormatException e) { + throw new TextParseException("Invalid time encoding: " + s); + } + return c.getTime(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java new file mode 100644 index 0000000..688d567 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/GPOSRecord.java @@ -0,0 +1,178 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Geographical Location - describes the physical location of a host. + * + * @author Brian Wellington + */ + +public class GPOSRecord extends Record { + +private static final long serialVersionUID = -6349714958085750705L; + +private byte [] latitude, longitude, altitude; + +GPOSRecord() {} + +Record +getObject() { + return new GPOSRecord(); +} + +private void +validate(double longitude, double latitude) throws IllegalArgumentException +{ + if (longitude < -90.0 || longitude > 90.0) { + throw new IllegalArgumentException("illegal longitude " + + longitude); + } + if (latitude < -180.0 || latitude > 180.0) { + throw new IllegalArgumentException("illegal latitude " + + latitude); + } +} + +/** + * Creates an GPOS Record from the given data + * @param longitude The longitude component of the location. + * @param latitude The latitude component of the location. + * @param altitude The altitude component of the location (in meters above sea + * level). +*/ +public +GPOSRecord(Name name, int dclass, long ttl, double longitude, double latitude, + double altitude) +{ + super(name, Type.GPOS, dclass, ttl); + validate(longitude, latitude); + this.longitude = Double.toString(longitude).getBytes(); + this.latitude = Double.toString(latitude).getBytes(); + this.altitude = Double.toString(altitude).getBytes(); +} + +/** + * Creates an GPOS Record from the given data + * @param longitude The longitude component of the location. + * @param latitude The latitude component of the location. + * @param altitude The altitude component of the location (in meters above sea + * level). +*/ +public +GPOSRecord(Name name, int dclass, long ttl, String longitude, String latitude, + String altitude) +{ + super(name, Type.GPOS, dclass, ttl); + try { + this.longitude = byteArrayFromString(longitude); + this.latitude = byteArrayFromString(latitude); + validate(getLongitude(), getLatitude()); + this.altitude = byteArrayFromString(altitude); + } + catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } +} + +void +rrFromWire(DNSInput in) throws IOException { + longitude = in.readCountedString(); + latitude = in.readCountedString(); + altitude = in.readCountedString(); + try { + validate(getLongitude(), getLatitude()); + } + catch(IllegalArgumentException e) { + throw new WireParseException(e.getMessage()); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + longitude = byteArrayFromString(st.getString()); + latitude = byteArrayFromString(st.getString()); + altitude = byteArrayFromString(st.getString()); + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + try { + validate(getLongitude(), getLatitude()); + } + catch(IllegalArgumentException e) { + throw new WireParseException(e.getMessage()); + } +} + +/** Convert to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(byteArrayToString(longitude, true)); + sb.append(" "); + sb.append(byteArrayToString(latitude, true)); + sb.append(" "); + sb.append(byteArrayToString(altitude, true)); + return sb.toString(); +} + +/** Returns the longitude as a string */ +public String +getLongitudeString() { + return byteArrayToString(longitude, false); +} + +/** + * Returns the longitude as a double + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ +public double +getLongitude() { + return Double.parseDouble(getLongitudeString()); +} + +/** Returns the latitude as a string */ +public String +getLatitudeString() { + return byteArrayToString(latitude, false); +} + +/** + * Returns the latitude as a double + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ +public double +getLatitude() { + return Double.parseDouble(getLatitudeString()); +} + +/** Returns the altitude as a string */ +public String +getAltitudeString() { + return byteArrayToString(altitude, false); +} + +/** + * Returns the altitude as a double + * @throws NumberFormatException The string does not contain a valid numeric + * value. + */ +public double +getAltitude() { + return Double.parseDouble(getAltitudeString()); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeCountedString(longitude); + out.writeCountedString(latitude); + out.writeCountedString(altitude); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java b/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java new file mode 100644 index 0000000..a08d343 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Generator.java @@ -0,0 +1,264 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A representation of a $GENERATE statement in a master file. + * + * @author Brian Wellington + */ + +public class Generator { + +/** The start of the range. */ +public long start; + +/** The end of the range. */ +public long end; + +/** The step value of the range. */ +public long step; + +/** The pattern to use for generating record names. */ +public final String namePattern; + +/** The type of the generated records. */ +public final int type; + +/** The class of the generated records. */ +public final int dclass; + +/** The ttl of the generated records. */ +public final long ttl; + +/** The pattern to use for generating record data. */ +public final String rdataPattern; + +/** The origin to append to relative names. */ +public final Name origin; + +private long current; + +/** + * Indicates whether generation is supported for this type. + * @throws InvalidTypeException The type is out of range. + */ +public static boolean +supportedType(int type) { + Type.check(type); + return (type == Type.PTR || type == Type.CNAME || type == Type.DNAME || + type == Type.A || type == Type.AAAA || type == Type.NS); +} + +/** + * Creates a specification for generating records, as a $GENERATE + * statement in a master file. + * @param start The start of the range. + * @param end The end of the range. + * @param step The step value of the range. + * @param namePattern The pattern to use for generating record names. + * @param type The type of the generated records. The supported types are + * PTR, CNAME, DNAME, A, AAAA, and NS. + * @param dclass The class of the generated records. + * @param ttl The ttl of the generated records. + * @param rdataPattern The pattern to use for generating record data. + * @param origin The origin to append to relative names. + * @throws IllegalArgumentException The range is invalid. + * @throws IllegalArgumentException The type does not support generation. + * @throws IllegalArgumentException The dclass is not a valid class. + */ +public +Generator(long start, long end, long step, String namePattern, + int type, int dclass, long ttl, String rdataPattern, Name origin) +{ + if (start < 0 || end < 0 || start > end || step <= 0) + throw new IllegalArgumentException + ("invalid range specification"); + if (!supportedType(type)) + throw new IllegalArgumentException("unsupported type"); + DClass.check(dclass); + + this.start = start; + this.end = end; + this.step = step; + this.namePattern = namePattern; + this.type = type; + this.dclass = dclass; + this.ttl = ttl; + this.rdataPattern = rdataPattern; + this.origin = origin; + this.current = start; +} + +private String +substitute(String spec, long n) throws IOException { + boolean escaped = false; + byte [] str = spec.getBytes(); + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < str.length; i++) { + char c = (char)(str[i] & 0xFF); + if (escaped) { + sb.append(c); + escaped = false; + } else if (c == '\\') { + if (i + 1 == str.length) + throw new TextParseException + ("invalid escape character"); + escaped = true; + } else if (c == '$') { + boolean negative = false; + long offset = 0; + long width = 0; + long base = 10; + boolean wantUpperCase = false; + if (i + 1 < str.length && str[i + 1] == '$') { + // '$$' == literal '$' for backwards + // compatibility with old versions of BIND. + c = (char)(str[++i] & 0xFF); + sb.append(c); + continue; + } else if (i + 1 < str.length && str[i + 1] == '{') { + // It's a substitution with modifiers. + i++; + if (i + 1 < str.length && str[i + 1] == '-') { + negative = true; + i++; + } + while (i + 1 < str.length) { + c = (char)(str[++i] & 0xFF); + if (c == ',' || c == '}') + break; + if (c < '0' || c > '9') + throw new TextParseException( + "invalid offset"); + c -= '0'; + offset *= 10; + offset += c; + } + if (negative) + offset = -offset; + + if (c == ',') { + while (i + 1 < str.length) { + c = (char)(str[++i] & 0xFF); + if (c == ',' || c == '}') + break; + if (c < '0' || c > '9') + throw new + TextParseException( + "invalid width"); + c -= '0'; + width *= 10; + width += c; + } + } + + if (c == ',') { + if (i + 1 == str.length) + throw new TextParseException( + "invalid base"); + c = (char)(str[++i] & 0xFF); + if (c == 'o') + base = 8; + else if (c == 'x') + base = 16; + else if (c == 'X') { + base = 16; + wantUpperCase = true; + } + else if (c != 'd') + throw new TextParseException( + "invalid base"); + } + + if (i + 1 == str.length || str[i + 1] != '}') + throw new TextParseException + ("invalid modifiers"); + i++; + } + long v = n + offset; + if (v < 0) + throw new TextParseException + ("invalid offset expansion"); + String number; + if (base == 8) + number = Long.toOctalString(v); + else if (base == 16) + number = Long.toHexString(v); + else + number = Long.toString(v); + if (wantUpperCase) + number = number.toUpperCase(); + if (width != 0 && width > number.length()) { + int zeros = (int)width - number.length(); + while (zeros-- > 0) + sb.append('0'); + } + sb.append(number); + } else { + sb.append(c); + } + } + return sb.toString(); +} + +/** + * Constructs and returns the next record in the expansion. + * @throws IOException The name or rdata was invalid after substitutions were + * performed. + */ +public Record +nextRecord() throws IOException { + if (current > end) + return null; + String namestr = substitute(namePattern, current); + Name name = Name.fromString(namestr, origin); + String rdata = substitute(rdataPattern, current); + current += step; + return Record.fromString(name, type, dclass, ttl, rdata, origin); +} + +/** + * Constructs and returns all records in the expansion. + * @throws IOException The name or rdata of a record was invalid after + * substitutions were performed. + */ +public Record [] +expand() throws IOException { + List list = new ArrayList(); + for (long i = start; i < end; i += step) { + String namestr = substitute(namePattern, current); + Name name = Name.fromString(namestr, origin); + String rdata = substitute(rdataPattern, current); + list.add(Record.fromString(name, type, dclass, ttl, + rdata, origin)); + } + return (Record []) list.toArray(new Record[list.size()]); +} + +/** + * Converts the generate specification to a string containing the corresponding + * $GENERATE statement. + */ +public String +toString() { + StringBuffer sb = new StringBuffer(); + sb.append("$GENERATE "); + sb.append(start + "-" + end); + if (step > 1) + sb.append("/" + step); + sb.append(" "); + sb.append(namePattern + " "); + sb.append(ttl + " "); + if (dclass != DClass.IN || !Options.check("noPrintIN")) + sb.append(DClass.string(dclass) + " "); + sb.append(Type.string(type) + " "); + sb.append(rdataPattern + " "); + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java new file mode 100644 index 0000000..7c8b51b --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/GenericEDNSOption.java @@ -0,0 +1,47 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) +package org.xbill.DNS; + +import java.io.*; + +import org.xbill.DNS.utils.base16; + +/** + * An EDNSOption with no internal structure. + * + * @author Ming Zhou <mizhou@bnivideo.com>, Beaumaris Networks + * @author Brian Wellington + */ +public class GenericEDNSOption extends EDNSOption { + +private byte [] data; + +GenericEDNSOption(int code) { + super(code); +} + +/** + * Construct a generic EDNS option. + * @param data The contents of the option. + */ +public +GenericEDNSOption(int code, byte [] data) { + super(code); + this.data = Record.checkByteArrayLength("option data", data, 0xFFFF); +} + +void +optionFromWire(DNSInput in) throws IOException { + data = in.readByteArray(); +} + +void +optionToWire(DNSOutput out) { + out.writeByteArray(data); +} + +String +optionToString() { + return "<" + base16.toString(data) + ">"; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java new file mode 100644 index 0000000..18fed32 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/HINFORecord.java @@ -0,0 +1,95 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Host Information - describes the CPU and OS of a host + * + * @author Brian Wellington + */ + +public class HINFORecord extends Record { + +private static final long serialVersionUID = -4732870630947452112L; + +private byte [] cpu, os; + +HINFORecord() {} + +Record +getObject() { + return new HINFORecord(); +} + +/** + * Creates an HINFO Record from the given data + * @param cpu A string describing the host's CPU + * @param os A string describing the host's OS + * @throws IllegalArgumentException One of the strings has invalid escapes + */ +public +HINFORecord(Name name, int dclass, long ttl, String cpu, String os) { + super(name, Type.HINFO, dclass, ttl); + try { + this.cpu = byteArrayFromString(cpu); + this.os = byteArrayFromString(os); + } + catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } +} + +void +rrFromWire(DNSInput in) throws IOException { + cpu = in.readCountedString(); + os = in.readCountedString(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + cpu = byteArrayFromString(st.getString()); + os = byteArrayFromString(st.getString()); + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } +} + +/** + * Returns the host's CPU + */ +public String +getCPU() { + return byteArrayToString(cpu, false); +} + +/** + * Returns the host's OS + */ +public String +getOS() { + return byteArrayToString(os, false); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeCountedString(cpu); + out.writeCountedString(os); +} + +/** + * Converts to a string + */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(byteArrayToString(cpu, true)); + sb.append(" "); + sb.append(byteArrayToString(os, true)); + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Header.java b/external/asmack/build/src/trunk/org/xbill/DNS/Header.java new file mode 100644 index 0000000..2a44d08 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Header.java @@ -0,0 +1,286 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A DNS message header + * @see Message + * + * @author Brian Wellington + */ + +public class Header implements Cloneable { + +private int id; +private int flags; +private int [] counts; + +private static Random random = new Random(); + +/** The length of a DNS Header in wire format. */ +public static final int LENGTH = 12; + +private void +init() { + counts = new int[4]; + flags = 0; + id = -1; +} + +/** + * Create a new empty header. + * @param id The message id + */ +public +Header(int id) { + init(); + setID(id); +} + +/** + * Create a new empty header with a random message id + */ +public +Header() { + init(); +} + +/** + * Parses a Header from a stream containing DNS wire format. + */ +Header(DNSInput in) throws IOException { + this(in.readU16()); + flags = in.readU16(); + for (int i = 0; i < counts.length; i++) + counts[i] = in.readU16(); +} + +/** + * Creates a new Header from its DNS wire format representation + * @param b A byte array containing the DNS Header. + */ +public +Header(byte [] b) throws IOException { + this(new DNSInput(b)); +} + +void +toWire(DNSOutput out) { + out.writeU16(getID()); + out.writeU16(flags); + for (int i = 0; i < counts.length; i++) + out.writeU16(counts[i]); +} + +public byte [] +toWire() { + DNSOutput out = new DNSOutput(); + toWire(out); + return out.toByteArray(); +} + +static private boolean +validFlag(int bit) { + return (bit >= 0 && bit <= 0xF && Flags.isFlag(bit)); +} + +static private void +checkFlag(int bit) { + if (!validFlag(bit)) + throw new IllegalArgumentException("invalid flag bit " + bit); +} + +/** + * Sets a flag to the supplied value + * @see Flags + */ +public void +setFlag(int bit) { + checkFlag(bit); + // bits are indexed from left to right + flags |= (1 << (15 - bit)); +} + +/** + * Sets a flag to the supplied value + * @see Flags + */ +public void +unsetFlag(int bit) { + checkFlag(bit); + // bits are indexed from left to right + flags &= ~(1 << (15 - bit)); +} + +/** + * Retrieves a flag + * @see Flags + */ +public boolean +getFlag(int bit) { + checkFlag(bit); + // bits are indexed from left to right + return (flags & (1 << (15 - bit))) != 0; +} + +boolean [] +getFlags() { + boolean [] array = new boolean[16]; + for (int i = 0; i < array.length; i++) + if (validFlag(i)) + array[i] = getFlag(i); + return array; +} + +/** + * Retrieves the message ID + */ +public int +getID() { + if (id >= 0) + return id; + synchronized (this) { + if (id < 0) + id = random.nextInt(0xffff); + return id; + } +} + +/** + * Sets the message ID + */ +public void +setID(int id) { + if (id < 0 || id > 0xffff) + throw new IllegalArgumentException("DNS message ID " + id + + " is out of range"); + this.id = id; +} + +/** + * Sets the message's rcode + * @see Rcode + */ +public void +setRcode(int value) { + if (value < 0 || value > 0xF) + throw new IllegalArgumentException("DNS Rcode " + value + + " is out of range"); + flags &= ~0xF; + flags |= value; +} + +/** + * Retrieves the mesasge's rcode + * @see Rcode + */ +public int +getRcode() { + return flags & 0xF; +} + +/** + * Sets the message's opcode + * @see Opcode + */ +public void +setOpcode(int value) { + if (value < 0 || value > 0xF) + throw new IllegalArgumentException("DNS Opcode " + value + + "is out of range"); + flags &= 0x87FF; + flags |= (value << 11); +} + +/** + * Retrieves the mesasge's opcode + * @see Opcode + */ +public int +getOpcode() { + return (flags >> 11) & 0xF; +} + +void +setCount(int field, int value) { + if (value < 0 || value > 0xFFFF) + throw new IllegalArgumentException("DNS section count " + + value + " is out of range"); + counts[field] = value; +} + +void +incCount(int field) { + if (counts[field] == 0xFFFF) + throw new IllegalStateException("DNS section count cannot " + + "be incremented"); + counts[field]++; +} + +void +decCount(int field) { + if (counts[field] == 0) + throw new IllegalStateException("DNS section count cannot " + + "be decremented"); + counts[field]--; +} + +/** + * Retrieves the record count for the given section + * @see Section + */ +public int +getCount(int field) { + return counts[field]; +} + +/** Converts the header's flags into a String */ +public String +printFlags() { + StringBuffer sb = new StringBuffer(); + + for (int i = 0; i < 16; i++) + if (validFlag(i) && getFlag(i)) { + sb.append(Flags.string(i)); + sb.append(" "); + } + return sb.toString(); +} + +String +toStringWithRcode(int newrcode) { + StringBuffer sb = new StringBuffer(); + + sb.append(";; ->>HEADER<<- "); + sb.append("opcode: " + Opcode.string(getOpcode())); + sb.append(", status: " + Rcode.string(newrcode)); + sb.append(", id: " + getID()); + sb.append("\n"); + + sb.append(";; flags: " + printFlags()); + sb.append("; "); + for (int i = 0; i < 4; i++) + sb.append(Section.string(i) + ": " + getCount(i) + " "); + return sb.toString(); +} + +/** Converts the header into a String */ +public String +toString() { + return toStringWithRcode(getRcode()); +} + +/* Creates a new Header identical to the current one */ +public Object +clone() { + Header h = new Header(); + h.id = id; + h.flags = flags; + System.arraycopy(counts, 0, h.counts, 0, counts.length); + return h; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java new file mode 100644 index 0000000..7eb2956 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/IPSECKEYRecord.java @@ -0,0 +1,231 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import org.xbill.DNS.utils.*; + +/** + * IPsec Keying Material (RFC 4025) + * + * @author Brian Wellington + */ + +public class IPSECKEYRecord extends Record { + +private static final long serialVersionUID = 3050449702765909687L; + +public static class Algorithm { + private Algorithm() {} + + public static final int DSA = 1; + public static final int RSA = 2; +} + +public static class Gateway { + private Gateway() {} + + public static final int None = 0; + public static final int IPv4 = 1; + public static final int IPv6 = 2; + public static final int Name = 3; +} + +private int precedence; +private int gatewayType; +private int algorithmType; +private Object gateway; +private byte [] key; + +IPSECKEYRecord() {} + +Record +getObject() { + return new IPSECKEYRecord(); +} + +/** + * Creates an IPSECKEY Record from the given data. + * @param precedence The record's precedence. + * @param gatewayType The record's gateway type. + * @param algorithmType The record's algorithm type. + * @param gateway The record's gateway. + * @param key The record's public key. + */ +public +IPSECKEYRecord(Name name, int dclass, long ttl, int precedence, + int gatewayType, int algorithmType, Object gateway, + byte [] key) +{ + super(name, Type.IPSECKEY, dclass, ttl); + this.precedence = checkU8("precedence", precedence); + this.gatewayType = checkU8("gatewayType", gatewayType); + this.algorithmType = checkU8("algorithmType", algorithmType); + switch (gatewayType) { + case Gateway.None: + this.gateway = null; + break; + case Gateway.IPv4: + if (!(gateway instanceof InetAddress)) + throw new IllegalArgumentException("\"gateway\" " + + "must be an IPv4 " + + "address"); + this.gateway = gateway; + break; + case Gateway.IPv6: + if (!(gateway instanceof Inet6Address)) + throw new IllegalArgumentException("\"gateway\" " + + "must be an IPv6 " + + "address"); + this.gateway = gateway; + break; + case Gateway.Name: + if (!(gateway instanceof Name)) + throw new IllegalArgumentException("\"gateway\" " + + "must be a DNS " + + "name"); + this.gateway = checkName("gateway", (Name) gateway); + break; + default: + throw new IllegalArgumentException("\"gatewayType\" " + + "must be between 0 and 3"); + } + + this.key = key; +} + +void +rrFromWire(DNSInput in) throws IOException { + precedence = in.readU8(); + gatewayType = in.readU8(); + algorithmType = in.readU8(); + switch (gatewayType) { + case Gateway.None: + gateway = null; + break; + case Gateway.IPv4: + gateway = InetAddress.getByAddress(in.readByteArray(4)); + break; + case Gateway.IPv6: + gateway = InetAddress.getByAddress(in.readByteArray(16)); + break; + case Gateway.Name: + gateway = new Name(in); + break; + default: + throw new WireParseException("invalid gateway type"); + } + if (in.remaining() > 0) + key = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + precedence = st.getUInt8(); + gatewayType = st.getUInt8(); + algorithmType = st.getUInt8(); + switch (gatewayType) { + case Gateway.None: + String s = st.getString(); + if (!s.equals(".")) + throw new TextParseException("invalid gateway format"); + gateway = null; + break; + case Gateway.IPv4: + gateway = st.getAddress(Address.IPv4); + break; + case Gateway.IPv6: + gateway = st.getAddress(Address.IPv6); + break; + case Gateway.Name: + gateway = st.getName(origin); + break; + default: + throw new WireParseException("invalid gateway type"); + } + key = st.getBase64(false); +} + +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(precedence); + sb.append(" "); + sb.append(gatewayType); + sb.append(" "); + sb.append(algorithmType); + sb.append(" "); + switch (gatewayType) { + case Gateway.None: + sb.append("."); + break; + case Gateway.IPv4: + case Gateway.IPv6: + InetAddress gatewayAddr = (InetAddress) gateway; + sb.append(gatewayAddr.getHostAddress()); + break; + case Gateway.Name: + sb.append(gateway); + break; + } + if (key != null) { + sb.append(" "); + sb.append(base64.toString(key)); + } + return sb.toString(); +} + +/** Returns the record's precedence. */ +public int +getPrecedence() { + return precedence; +} + +/** Returns the record's gateway type. */ +public int +getGatewayType() { + return gatewayType; +} + +/** Returns the record's algorithm type. */ +public int +getAlgorithmType() { + return algorithmType; +} + +/** Returns the record's gateway. */ +public Object +getGateway() { + return gateway; +} + +/** Returns the record's public key */ +public byte [] +getKey() { + return key; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(precedence); + out.writeU8(gatewayType); + out.writeU8(algorithmType); + switch (gatewayType) { + case Gateway.None: + break; + case Gateway.IPv4: + case Gateway.IPv6: + InetAddress gatewayAddr = (InetAddress) gateway; + out.writeByteArray(gatewayAddr.getAddress()); + break; + case Gateway.Name: + Name gatewayName = (Name) gateway; + gatewayName.toWire(out, null, canonical); + break; + } + if (key != null) + out.writeByteArray(key); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java new file mode 100644 index 0000000..8f9b629 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ISDNRecord.java @@ -0,0 +1,105 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * ISDN - identifies the ISDN number and subaddress associated with a name. + * + * @author Brian Wellington + */ + +public class ISDNRecord extends Record { + +private static final long serialVersionUID = -8730801385178968798L; + +private byte [] address; +private byte [] subAddress; + +ISDNRecord() {} + +Record +getObject() { + return new ISDNRecord(); +} + +/** + * Creates an ISDN Record from the given data + * @param address The ISDN number associated with the domain. + * @param subAddress The subaddress, if any. + * @throws IllegalArgumentException One of the strings is invalid. + */ +public +ISDNRecord(Name name, int dclass, long ttl, String address, String subAddress) { + super(name, Type.ISDN, dclass, ttl); + try { + this.address = byteArrayFromString(address); + if (subAddress != null) + this.subAddress = byteArrayFromString(subAddress); + } + catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } +} + +void +rrFromWire(DNSInput in) throws IOException { + address = in.readCountedString(); + if (in.remaining() > 0) + subAddress = in.readCountedString(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + try { + address = byteArrayFromString(st.getString()); + Tokenizer.Token t = st.get(); + if (t.isString()) { + subAddress = byteArrayFromString(t.value); + } else { + st.unget(); + } + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } +} + +/** + * Returns the ISDN number associated with the domain. + */ +public String +getAddress() { + return byteArrayToString(address, false); +} + +/** + * Returns the ISDN subaddress, or null if there is none. + */ +public String +getSubAddress() { + if (subAddress == null) + return null; + return byteArrayToString(subAddress, false); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeCountedString(address); + if (subAddress != null) + out.writeCountedString(subAddress); +} + +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(byteArrayToString(address, true)); + if (subAddress != null) { + sb.append(" "); + sb.append(byteArrayToString(subAddress, true)); + } + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java new file mode 100644 index 0000000..6c95cd4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidDClassException.java @@ -0,0 +1,18 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when an invalid dclass code is specified. + * + * @author Brian Wellington + */ + +public class InvalidDClassException extends IllegalArgumentException { + +public +InvalidDClassException(int dclass) { + super("Invalid DNS class: " + dclass); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java new file mode 100644 index 0000000..95776fe --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTTLException.java @@ -0,0 +1,18 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when an invalid TTL is specified. + * + * @author Brian Wellington + */ + +public class InvalidTTLException extends IllegalArgumentException { + +public +InvalidTTLException(long ttl) { + super("Invalid DNS TTL: " + ttl); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java new file mode 100644 index 0000000..7c61276 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/InvalidTypeException.java @@ -0,0 +1,18 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when an invalid type code is specified. + * + * @author Brian Wellington + */ + +public class InvalidTypeException extends IllegalArgumentException { + +public +InvalidTypeException(int type) { + super("Invalid DNS type: " + type); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java new file mode 100644 index 0000000..59a2c6c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/KEYBase.java @@ -0,0 +1,161 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.security.PublicKey; + +import org.xbill.DNS.utils.*; + +/** + * The base class for KEY/DNSKEY records, which have identical formats + * + * @author Brian Wellington + */ + +abstract class KEYBase extends Record { + +private static final long serialVersionUID = 3469321722693285454L; + +protected int flags, proto, alg; +protected byte [] key; +protected int footprint = -1; +protected PublicKey publicKey = null; + +protected +KEYBase() {} + +public +KEYBase(Name name, int type, int dclass, long ttl, int flags, int proto, + int alg, byte [] key) +{ + super(name, type, dclass, ttl); + this.flags = checkU16("flags", flags); + this.proto = checkU8("proto", proto); + this.alg = checkU8("alg", alg); + this.key = key; +} + +void +rrFromWire(DNSInput in) throws IOException { + flags = in.readU16(); + proto = in.readU8(); + alg = in.readU8(); + if (in.remaining() > 0) + key = in.readByteArray(); +} + +/** Converts the DNSKEY/KEY Record to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(flags); + sb.append(" "); + sb.append(proto); + sb.append(" "); + sb.append(alg); + if (key != null) { + if (Options.check("multiline")) { + sb.append(" (\n"); + sb.append(base64.formatString(key, 64, "\t", true)); + sb.append(" ; key_tag = "); + sb.append(getFootprint()); + } else { + sb.append(" "); + sb.append(base64.toString(key)); + } + } + return sb.toString(); +} + +/** + * Returns the flags describing the key's properties + */ +public int +getFlags() { + return flags; +} + +/** + * Returns the protocol that the key was created for + */ +public int +getProtocol() { + return proto; +} + +/** + * Returns the key's algorithm + */ +public int +getAlgorithm() { + return alg; +} + +/** + * Returns the binary data representing the key + */ +public byte [] +getKey() { + return key; +} + +/** + * Returns the key's footprint (after computing it) + */ +public int +getFootprint() { + if (footprint >= 0) + return footprint; + + int foot = 0; + + DNSOutput out = new DNSOutput(); + rrToWire(out, null, false); + byte [] rdata = out.toByteArray(); + + if (alg == DNSSEC.Algorithm.RSAMD5) { + int d1 = rdata[rdata.length - 3] & 0xFF; + int d2 = rdata[rdata.length - 2] & 0xFF; + foot = (d1 << 8) + d2; + } + else { + int i; + for (i = 0; i < rdata.length - 1; i += 2) { + int d1 = rdata[i] & 0xFF; + int d2 = rdata[i + 1] & 0xFF; + foot += ((d1 << 8) + d2); + } + if (i < rdata.length) { + int d1 = rdata[i] & 0xFF; + foot += (d1 << 8); + } + foot += ((foot >> 16) & 0xFFFF); + } + footprint = (foot & 0xFFFF); + return footprint; +} + +/** + * Returns a PublicKey corresponding to the data in this key. + * @throws DNSSEC.DNSSECException The key could not be converted. + */ +public PublicKey +getPublicKey() throws DNSSEC.DNSSECException { + if (publicKey != null) + return publicKey; + + publicKey = DNSSEC.toPublicKey(this); + return publicKey; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(flags); + out.writeU8(proto); + out.writeU8(alg); + if (key != null) + out.writeByteArray(key); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java new file mode 100644 index 0000000..3d2e01c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/KEYRecord.java @@ -0,0 +1,352 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.security.PublicKey; +import java.util.*; + +/** + * Key - contains a cryptographic public key. The data can be converted + * to objects implementing java.security.interfaces.PublicKey + * @see DNSSEC + * + * @author Brian Wellington + */ + +public class KEYRecord extends KEYBase { + +private static final long serialVersionUID = 6385613447571488906L; + +public static class Protocol { + /** + * KEY protocol identifiers. + */ + + private Protocol() {} + + /** No defined protocol. */ + public static final int NONE = 0; + + /** Transaction Level Security */ + public static final int TLS = 1; + + /** Email */ + public static final int EMAIL = 2; + + /** DNSSEC */ + public static final int DNSSEC = 3; + + /** IPSEC Control */ + public static final int IPSEC = 4; + + /** Any protocol */ + public static final int ANY = 255; + + private static Mnemonic protocols = new Mnemonic("KEY protocol", + Mnemonic.CASE_UPPER); + + static { + protocols.setMaximum(0xFF); + protocols.setNumericAllowed(true); + + protocols.add(NONE, "NONE"); + protocols.add(TLS, "TLS"); + protocols.add(EMAIL, "EMAIL"); + protocols.add(DNSSEC, "DNSSEC"); + protocols.add(IPSEC, "IPSEC"); + protocols.add(ANY, "ANY"); + } + + /** + * Converts an KEY protocol value into its textual representation + */ + public static String + string(int type) { + return protocols.getText(type); + } + + /** + * Converts a textual representation of a KEY protocol into its + * numeric code. Integers in the range 0..255 are also accepted. + * @param s The textual representation of the protocol + * @return The protocol code, or -1 on error. + */ + public static int + value(String s) { + return protocols.getValue(s); + } +} + +public static class Flags { + /** + * KEY flags identifiers. + */ + + private Flags() {} + + /** KEY cannot be used for confidentiality */ + public static final int NOCONF = 0x4000; + + /** KEY cannot be used for authentication */ + public static final int NOAUTH = 0x8000; + + /** No key present */ + public static final int NOKEY = 0xC000; + + /** Bitmask of the use fields */ + public static final int USE_MASK = 0xC000; + + /** Flag 2 (unused) */ + public static final int FLAG2 = 0x2000; + + /** Flags extension */ + public static final int EXTEND = 0x1000; + + /** Flag 4 (unused) */ + public static final int FLAG4 = 0x0800; + + /** Flag 5 (unused) */ + public static final int FLAG5 = 0x0400; + + /** Key is owned by a user. */ + public static final int USER = 0x0000; + + /** Key is owned by a zone. */ + public static final int ZONE = 0x0100; + + /** Key is owned by a host. */ + public static final int HOST = 0x0200; + + /** Key owner type 3 (reserved). */ + public static final int NTYP3 = 0x0300; + + /** Key owner bitmask. */ + public static final int OWNER_MASK = 0x0300; + + /** Flag 8 (unused) */ + public static final int FLAG8 = 0x0080; + + /** Flag 9 (unused) */ + public static final int FLAG9 = 0x0040; + + /** Flag 10 (unused) */ + public static final int FLAG10 = 0x0020; + + /** Flag 11 (unused) */ + public static final int FLAG11 = 0x0010; + + /** Signatory value 0 */ + public static final int SIG0 = 0; + + /** Signatory value 1 */ + public static final int SIG1 = 1; + + /** Signatory value 2 */ + public static final int SIG2 = 2; + + /** Signatory value 3 */ + public static final int SIG3 = 3; + + /** Signatory value 4 */ + public static final int SIG4 = 4; + + /** Signatory value 5 */ + public static final int SIG5 = 5; + + /** Signatory value 6 */ + public static final int SIG6 = 6; + + /** Signatory value 7 */ + public static final int SIG7 = 7; + + /** Signatory value 8 */ + public static final int SIG8 = 8; + + /** Signatory value 9 */ + public static final int SIG9 = 9; + + /** Signatory value 10 */ + public static final int SIG10 = 10; + + /** Signatory value 11 */ + public static final int SIG11 = 11; + + /** Signatory value 12 */ + public static final int SIG12 = 12; + + /** Signatory value 13 */ + public static final int SIG13 = 13; + + /** Signatory value 14 */ + public static final int SIG14 = 14; + + /** Signatory value 15 */ + public static final int SIG15 = 15; + + private static Mnemonic flags = new Mnemonic("KEY flags", + Mnemonic.CASE_UPPER); + + static { + flags.setMaximum(0xFFFF); + flags.setNumericAllowed(false); + + flags.add(NOCONF, "NOCONF"); + flags.add(NOAUTH, "NOAUTH"); + flags.add(NOKEY, "NOKEY"); + flags.add(FLAG2, "FLAG2"); + flags.add(EXTEND, "EXTEND"); + flags.add(FLAG4, "FLAG4"); + flags.add(FLAG5, "FLAG5"); + flags.add(USER, "USER"); + flags.add(ZONE, "ZONE"); + flags.add(HOST, "HOST"); + flags.add(NTYP3, "NTYP3"); + flags.add(FLAG8, "FLAG8"); + flags.add(FLAG9, "FLAG9"); + flags.add(FLAG10, "FLAG10"); + flags.add(FLAG11, "FLAG11"); + flags.add(SIG0, "SIG0"); + flags.add(SIG1, "SIG1"); + flags.add(SIG2, "SIG2"); + flags.add(SIG3, "SIG3"); + flags.add(SIG4, "SIG4"); + flags.add(SIG5, "SIG5"); + flags.add(SIG6, "SIG6"); + flags.add(SIG7, "SIG7"); + flags.add(SIG8, "SIG8"); + flags.add(SIG9, "SIG9"); + flags.add(SIG10, "SIG10"); + flags.add(SIG11, "SIG11"); + flags.add(SIG12, "SIG12"); + flags.add(SIG13, "SIG13"); + flags.add(SIG14, "SIG14"); + flags.add(SIG15, "SIG15"); + } + + /** + * Converts a textual representation of KEY flags into its + * numeric code. Integers in the range 0..65535 are also accepted. + * @param s The textual representation of the protocol + * @return The protocol code, or -1 on error. + */ + public static int + value(String s) { + int value; + try { + value = Integer.parseInt(s); + if (value >= 0 && value <= 0xFFFF) { + return value; + } + return -1; + } catch (NumberFormatException e) { + } + StringTokenizer st = new StringTokenizer(s, "|"); + value = 0; + while (st.hasMoreTokens()) { + int val = flags.getValue(st.nextToken()); + if (val < 0) { + return -1; + } + value |= val; + } + return value; + } +} + +/* flags */ +/** This key cannot be used for confidentiality (encryption) */ +public static final int FLAG_NOCONF = Flags.NOCONF; + +/** This key cannot be used for authentication */ +public static final int FLAG_NOAUTH = Flags.NOAUTH; + +/** This key cannot be used for authentication or confidentiality */ +public static final int FLAG_NOKEY = Flags.NOKEY; + +/** A zone key */ +public static final int OWNER_ZONE = Flags.ZONE; + +/** A host/end entity key */ +public static final int OWNER_HOST = Flags.HOST; + +/** A user key */ +public static final int OWNER_USER = Flags.USER; + +/* protocols */ +/** Key was created for use with transaction level security */ +public static final int PROTOCOL_TLS = Protocol.TLS; + +/** Key was created for use with email */ +public static final int PROTOCOL_EMAIL = Protocol.EMAIL; + +/** Key was created for use with DNSSEC */ +public static final int PROTOCOL_DNSSEC = Protocol.DNSSEC; + +/** Key was created for use with IPSEC */ +public static final int PROTOCOL_IPSEC = Protocol.IPSEC; + +/** Key was created for use with any protocol */ +public static final int PROTOCOL_ANY = Protocol.ANY; + +KEYRecord() {} + +Record +getObject() { + return new KEYRecord(); +} + +/** + * Creates a KEY Record from the given data + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key Binary data representing the key + */ +public +KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, + byte [] key) +{ + super(name, Type.KEY, dclass, ttl, flags, proto, alg, key); +} + +/** + * Creates a KEY Record from the given data + * @param flags Flags describing the key's properties + * @param proto The protocol that the key was created for + * @param alg The key's algorithm + * @param key The key as a PublicKey + * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS + * format. + */ +public +KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, + PublicKey key) throws DNSSEC.DNSSECException +{ + super(name, Type.KEY, dclass, ttl, flags, proto, alg, + DNSSEC.fromPublicKey(key, alg)); + publicKey = key; +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String flagString = st.getIdentifier(); + flags = Flags.value(flagString); + if (flags < 0) + throw st.exception("Invalid flags: " + flagString); + String protoString = st.getIdentifier(); + proto = Protocol.value(protoString); + if (proto < 0) + throw st.exception("Invalid protocol: " + protoString); + String algString = st.getIdentifier(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) + throw st.exception("Invalid algorithm: " + algString); + /* If this is a null KEY, there's no key data */ + if ((flags & Flags.USE_MASK) == Flags.NOKEY) + key = null; + else + key = st.getBase64(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java new file mode 100644 index 0000000..481d21b --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/KXRecord.java @@ -0,0 +1,51 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Key Exchange - delegation of authority + * + * @author Brian Wellington + */ + +public class KXRecord extends U16NameBase { + +private static final long serialVersionUID = 7448568832769757809L; + +KXRecord() {} + +Record +getObject() { + return new KXRecord(); +} + +/** + * Creates a KX Record from the given data + * @param preference The preference of this KX. Records with lower priority + * are preferred. + * @param target The host that authority is delegated to + */ +public +KXRecord(Name name, int dclass, long ttl, int preference, Name target) { + super(name, Type.KX, dclass, ttl, preference, "preference", + target, "target"); +} + +/** Returns the target of the KX record */ +public Name +getTarget() { + return getNameField(); +} + +/** Returns the preference of this KX record */ +public int +getPreference() { + return getU16Field(); +} + +public Name +getAdditionalName() { + return getNameField(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java new file mode 100644 index 0000000..4eddc15 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/LOCRecord.java @@ -0,0 +1,314 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.text.*; + +/** + * Location - describes the physical location of hosts, networks, subnets. + * + * @author Brian Wellington + */ + +public class LOCRecord extends Record { + +private static final long serialVersionUID = 9058224788126750409L; + +private static NumberFormat w2, w3; + +private long size, hPrecision, vPrecision; +private long latitude, longitude, altitude; + +static { + w2 = new DecimalFormat(); + w2.setMinimumIntegerDigits(2); + + w3 = new DecimalFormat(); + w3.setMinimumIntegerDigits(3); +} + +LOCRecord() {} + +Record +getObject() { + return new LOCRecord(); +} + +/** + * Creates an LOC Record from the given data + * @param latitude The latitude of the center of the sphere + * @param longitude The longitude of the center of the sphere + * @param altitude The altitude of the center of the sphere, in m + * @param size The diameter of a sphere enclosing the described entity, in m. + * @param hPrecision The horizontal precision of the data, in m. + * @param vPrecision The vertical precision of the data, in m. +*/ +public +LOCRecord(Name name, int dclass, long ttl, double latitude, double longitude, + double altitude, double size, double hPrecision, double vPrecision) +{ + super(name, Type.LOC, dclass, ttl); + this.latitude = (long)(latitude * 3600 * 1000 + (1L << 31)); + this.longitude = (long)(longitude * 3600 * 1000 + (1L << 31)); + this.altitude = (long)((altitude + 100000) * 100); + this.size = (long)(size * 100); + this.hPrecision = (long)(hPrecision * 100); + this.vPrecision = (long)(vPrecision * 100); +} + +void +rrFromWire(DNSInput in) throws IOException { + int version; + + version = in.readU8(); + if (version != 0) + throw new WireParseException("Invalid LOC version"); + + size = parseLOCformat(in.readU8()); + hPrecision = parseLOCformat(in.readU8()); + vPrecision = parseLOCformat(in.readU8()); + latitude = in.readU32(); + longitude = in.readU32(); + altitude = in.readU32(); +} + +private double +parseFixedPoint(String s) +{ + if (s.matches("^-?\\d+$")) + return Integer.parseInt(s); + else if (s.matches("^-?\\d+\\.\\d*$")) { + String [] parts = s.split("\\."); + double value = Integer.parseInt(parts[0]); + double fraction = Integer.parseInt(parts[1]); + if (value < 0) + fraction *= -1; + int digits = parts[1].length(); + return value + (fraction / Math.pow(10, digits)); + } else + throw new NumberFormatException(); +} + +private long +parsePosition(Tokenizer st, String type) throws IOException { + boolean isLatitude = type.equals("latitude"); + int deg = 0, min = 0; + double sec = 0; + long value; + String s; + + deg = st.getUInt16(); + if (deg > 180 || (deg > 90 && isLatitude)) + throw st.exception("Invalid LOC " + type + " degrees"); + + s = st.getString(); + try { + min = Integer.parseInt(s); + if (min < 0 || min > 59) + throw st.exception("Invalid LOC " + type + " minutes"); + s = st.getString(); + sec = parseFixedPoint(s); + if (sec < 0 || sec >= 60) + throw st.exception("Invalid LOC " + type + " seconds"); + s = st.getString(); + } catch (NumberFormatException e) { + } + + if (s.length() != 1) + throw st.exception("Invalid LOC " + type); + + value = (long) (1000 * (sec + 60L * (min + 60L * deg))); + + char c = Character.toUpperCase(s.charAt(0)); + if ((isLatitude && c == 'S') || (!isLatitude && c == 'W')) + value = -value; + else if ((isLatitude && c != 'N') || (!isLatitude && c != 'E')) + throw st.exception("Invalid LOC " + type); + + value += (1L << 31); + + return value; +} + +private long +parseDouble(Tokenizer st, String type, boolean required, long min, long max, + long defaultValue) +throws IOException +{ + Tokenizer.Token token = st.get(); + if (token.isEOL()) { + if (required) + throw st.exception("Invalid LOC " + type); + st.unget(); + return defaultValue; + } + String s = token.value; + if (s.length() > 1 && s.charAt(s.length() - 1) == 'm') + s = s.substring(0, s.length() - 1); + try { + long value = (long)(100 * parseFixedPoint(s)); + if (value < min || value > max) + throw st.exception("Invalid LOC " + type); + return value; + } + catch (NumberFormatException e) { + throw st.exception("Invalid LOC " + type); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + latitude = parsePosition(st, "latitude"); + longitude = parsePosition(st, "longitude"); + altitude = parseDouble(st, "altitude", true, + -10000000, 4284967295L, 0) + 10000000; + size = parseDouble(st, "size", false, 0, 9000000000L, 100); + hPrecision = parseDouble(st, "horizontal precision", false, + 0, 9000000000L, 1000000); + vPrecision = parseDouble(st, "vertical precision", false, + 0, 9000000000L, 1000); +} + +private void +renderFixedPoint(StringBuffer sb, NumberFormat formatter, long value, + long divisor) +{ + sb.append(value / divisor); + value %= divisor; + if (value != 0) { + sb.append("."); + sb.append(formatter.format(value)); + } +} + +private String +positionToString(long value, char pos, char neg) { + StringBuffer sb = new StringBuffer(); + char direction; + + long temp = value - (1L << 31); + if (temp < 0) { + temp = -temp; + direction = neg; + } else + direction = pos; + + sb.append(temp / (3600 * 1000)); /* degrees */ + temp = temp % (3600 * 1000); + sb.append(" "); + + sb.append(temp / (60 * 1000)); /* minutes */ + temp = temp % (60 * 1000); + sb.append(" "); + + renderFixedPoint(sb, w3, temp, 1000); /* seconds */ + sb.append(" "); + + sb.append(direction); + + return sb.toString(); +} + + +/** Convert to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + + /* Latitude */ + sb.append(positionToString(latitude, 'N', 'S')); + sb.append(" "); + + /* Latitude */ + sb.append(positionToString(longitude, 'E', 'W')); + sb.append(" "); + + /* Altitude */ + renderFixedPoint(sb, w2, altitude - 10000000, 100); + sb.append("m "); + + /* Size */ + renderFixedPoint(sb, w2, size, 100); + sb.append("m "); + + /* Horizontal precision */ + renderFixedPoint(sb, w2, hPrecision, 100); + sb.append("m "); + + /* Vertical precision */ + renderFixedPoint(sb, w2, vPrecision, 100); + sb.append("m"); + + return sb.toString(); +} + +/** Returns the latitude */ +public double +getLatitude() { + return ((double)(latitude - (1L << 31))) / (3600 * 1000); +} + +/** Returns the longitude */ +public double +getLongitude() { + return ((double)(longitude - (1L << 31))) / (3600 * 1000); +} + +/** Returns the altitude */ +public double +getAltitude() { + return ((double)(altitude - 10000000)) / 100; +} + +/** Returns the diameter of the enclosing sphere */ +public double +getSize() { + return ((double)size) / 100; +} + +/** Returns the horizontal precision */ +public double +getHPrecision() { + return ((double)hPrecision) / 100; +} + +/** Returns the horizontal precision */ +public double +getVPrecision() { + return ((double)vPrecision) / 100; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(0); /* version */ + out.writeU8(toLOCformat(size)); + out.writeU8(toLOCformat(hPrecision)); + out.writeU8(toLOCformat(vPrecision)); + out.writeU32(latitude); + out.writeU32(longitude); + out.writeU32(altitude); +} + +private static long +parseLOCformat(int b) throws WireParseException { + long out = b >> 4; + int exp = b & 0xF; + if (out > 9 || exp > 9) + throw new WireParseException("Invalid LOC Encoding"); + while (exp-- > 0) + out *= 10; + return (out); +} + +private int +toLOCformat(long l) { + byte exp = 0; + while (l > 9) { + exp++; + l /= 10; + } + return (int)((l << 4) + exp); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java b/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java new file mode 100644 index 0000000..0beba59 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Lookup.java @@ -0,0 +1,647 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * The Lookup object issues queries to caching DNS servers. The input consists + * of a name, an optional type, and an optional class. Caching is enabled + * by default and used when possible to reduce the number of DNS requests. + * A Resolver, which defaults to an ExtendedResolver initialized with the + * resolvers located by the ResolverConfig class, performs the queries. A + * search path of domain suffixes is used to resolve relative names, and is + * also determined by the ResolverConfig class. + * + * A Lookup object may be reused, but should not be used by multiple threads. + * + * @see Cache + * @see Resolver + * @see ResolverConfig + * + * @author Brian Wellington + */ + +public final class Lookup { + +private static Resolver defaultResolver; +private static Name [] defaultSearchPath; +private static Map defaultCaches; +private static int defaultNdots; + +private Resolver resolver; +private Name [] searchPath; +private Cache cache; +private boolean temporary_cache; +private int credibility; +private Name name; +private int type; +private int dclass; +private boolean verbose; +private int iterations; +private boolean foundAlias; +private boolean done; +private boolean doneCurrent; +private List aliases; +private Record [] answers; +private int result; +private String error; +private boolean nxdomain; +private boolean badresponse; +private String badresponse_error; +private boolean networkerror; +private boolean timedout; +private boolean nametoolong; +private boolean referral; + +private static final Name [] noAliases = new Name[0]; + +/** The lookup was successful. */ +public static final int SUCCESSFUL = 0; + +/** + * The lookup failed due to a data or server error. Repeating the lookup + * would not be helpful. + */ +public static final int UNRECOVERABLE = 1; + +/** + * The lookup failed due to a network error. Repeating the lookup may be + * helpful. + */ +public static final int TRY_AGAIN = 2; + +/** The host does not exist. */ +public static final int HOST_NOT_FOUND = 3; + +/** The host exists, but has no records associated with the queried type. */ +public static final int TYPE_NOT_FOUND = 4; + +public static synchronized void +refreshDefault() { + + try { + defaultResolver = new ExtendedResolver(); + } + catch (UnknownHostException e) { + throw new RuntimeException("Failed to initialize resolver"); + } + defaultSearchPath = ResolverConfig.getCurrentConfig().searchPath(); + defaultCaches = new HashMap(); + defaultNdots = ResolverConfig.getCurrentConfig().ndots(); +} + +static { + refreshDefault(); +} + +/** + * Gets the Resolver that will be used as the default by future Lookups. + * @return The default resolver. + */ +public static synchronized Resolver +getDefaultResolver() { + return defaultResolver; +} + +/** + * Sets the default Resolver to be used as the default by future Lookups. + * @param resolver The default resolver. + */ +public static synchronized void +setDefaultResolver(Resolver resolver) { + defaultResolver = resolver; +} + +/** + * Gets the Cache that will be used as the default for the specified + * class by future Lookups. + * @param dclass The class whose cache is being retrieved. + * @return The default cache for the specified class. + */ +public static synchronized Cache +getDefaultCache(int dclass) { + DClass.check(dclass); + Cache c = (Cache) defaultCaches.get(Mnemonic.toInteger(dclass)); + if (c == null) { + c = new Cache(dclass); + defaultCaches.put(Mnemonic.toInteger(dclass), c); + } + return c; +} + +/** + * Sets the Cache to be used as the default for the specified class by future + * Lookups. + * @param cache The default cache for the specified class. + * @param dclass The class whose cache is being set. + */ +public static synchronized void +setDefaultCache(Cache cache, int dclass) { + DClass.check(dclass); + defaultCaches.put(Mnemonic.toInteger(dclass), cache); +} + +/** + * Gets the search path that will be used as the default by future Lookups. + * @return The default search path. + */ +public static synchronized Name [] +getDefaultSearchPath() { + return defaultSearchPath; +} + +/** + * Sets the search path to be used as the default by future Lookups. + * @param domains The default search path. + */ +public static synchronized void +setDefaultSearchPath(Name [] domains) { + defaultSearchPath = domains; +} + +/** + * Sets the search path that will be used as the default by future Lookups. + * @param domains The default search path. + * @throws TextParseException A name in the array is not a valid DNS name. + */ +public static synchronized void +setDefaultSearchPath(String [] domains) throws TextParseException { + if (domains == null) { + defaultSearchPath = null; + return; + } + Name [] newdomains = new Name[domains.length]; + for (int i = 0; i < domains.length; i++) + newdomains[i] = Name.fromString(domains[i], Name.root); + defaultSearchPath = newdomains; +} + +private final void +reset() { + iterations = 0; + foundAlias = false; + done = false; + doneCurrent = false; + aliases = null; + answers = null; + result = -1; + error = null; + nxdomain = false; + badresponse = false; + badresponse_error = null; + networkerror = false; + timedout = false; + nametoolong = false; + referral = false; + if (temporary_cache) + cache.clearCache(); +} + +/** + * Create a Lookup object that will find records of the given name, type, + * and class. The lookup will use the default cache, resolver, and search + * path, and look for records that are reasonably credible. + * @param name The name of the desired records + * @param type The type of the desired records + * @param dclass The class of the desired records + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see Cache + * @see Resolver + * @see Credibility + * @see Name + * @see Type + * @see DClass + */ +public +Lookup(Name name, int type, int dclass) { + Type.check(type); + DClass.check(dclass); + if (!Type.isRR(type) && type != Type.ANY) + throw new IllegalArgumentException("Cannot query for " + + "meta-types other than ANY"); + this.name = name; + this.type = type; + this.dclass = dclass; + synchronized (Lookup.class) { + this.resolver = getDefaultResolver(); + this.searchPath = getDefaultSearchPath(); + this.cache = getDefaultCache(dclass); + } + this.credibility = Credibility.NORMAL; + this.verbose = Options.check("verbose"); + this.result = -1; +} + +/** + * Create a Lookup object that will find records of the given name and type + * in the IN class. + * @param name The name of the desired records + * @param type The type of the desired records + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(Name name, int type) { + this(name, type, DClass.IN); +} + +/** + * Create a Lookup object that will find records of type A at the given name + * in the IN class. + * @param name The name of the desired records + * @see #Lookup(Name,int,int) + */ +public +Lookup(Name name) { + this(name, Type.A, DClass.IN); +} + +/** + * Create a Lookup object that will find records of the given name, type, + * and class. + * @param name The name of the desired records + * @param type The type of the desired records + * @param dclass The class of the desired records + * @throws TextParseException The name is not a valid DNS name + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name, int type, int dclass) throws TextParseException { + this(Name.fromString(name), type, dclass); +} + +/** + * Create a Lookup object that will find records of the given name and type + * in the IN class. + * @param name The name of the desired records + * @param type The type of the desired records + * @throws TextParseException The name is not a valid DNS name + * @throws IllegalArgumentException The type is a meta type other than ANY. + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name, int type) throws TextParseException { + this(Name.fromString(name), type, DClass.IN); +} + +/** + * Create a Lookup object that will find records of type A at the given name + * in the IN class. + * @param name The name of the desired records + * @throws TextParseException The name is not a valid DNS name + * @see #Lookup(Name,int,int) + */ +public +Lookup(String name) throws TextParseException { + this(Name.fromString(name), Type.A, DClass.IN); +} + +/** + * Sets the resolver to use when performing this lookup. This overrides the + * default value. + * @param resolver The resolver to use. + */ +public void +setResolver(Resolver resolver) { + this.resolver = resolver; +} + +/** + * Sets the search path to use when performing this lookup. This overrides the + * default value. + * @param domains An array of names containing the search path. + */ +public void +setSearchPath(Name [] domains) { + this.searchPath = domains; +} + +/** + * Sets the search path to use when performing this lookup. This overrides the + * default value. + * @param domains An array of names containing the search path. + * @throws TextParseException A name in the array is not a valid DNS name. + */ +public void +setSearchPath(String [] domains) throws TextParseException { + if (domains == null) { + this.searchPath = null; + return; + } + Name [] newdomains = new Name[domains.length]; + for (int i = 0; i < domains.length; i++) + newdomains[i] = Name.fromString(domains[i], Name.root); + this.searchPath = newdomains; +} + +/** + * Sets the cache to use when performing this lookup. This overrides the + * default value. If the results of this lookup should not be permanently + * cached, null can be provided here. + * @param cache The cache to use. + */ +public void +setCache(Cache cache) { + if (cache == null) { + this.cache = new Cache(dclass); + this.temporary_cache = true; + } else { + this.cache = cache; + this.temporary_cache = false; + } +} + +/** + * Sets ndots to use when performing this lookup, overriding the default value. + * Specifically, this refers to the number of "dots" which, if present in a + * name, indicate that a lookup for the absolute name should be attempted + * before appending any search path elements. + * @param ndots The ndots value to use, which must be greater than or equal to + * 0. + */ +public void +setNdots(int ndots) { + if (ndots < 0) + throw new IllegalArgumentException("Illegal ndots value: " + + ndots); + defaultNdots = ndots; +} + +/** + * Sets the minimum credibility level that will be accepted when performing + * the lookup. This defaults to Credibility.NORMAL. + * @param credibility The minimum credibility level. + */ +public void +setCredibility(int credibility) { + this.credibility = credibility; +} + +private void +follow(Name name, Name oldname) { + foundAlias = true; + badresponse = false; + networkerror = false; + timedout = false; + nxdomain = false; + referral = false; + iterations++; + if (iterations >= 6 || name.equals(oldname)) { + result = UNRECOVERABLE; + error = "CNAME loop"; + done = true; + return; + } + if (aliases == null) + aliases = new ArrayList(); + aliases.add(oldname); + lookup(name); +} + +private void +processResponse(Name name, SetResponse response) { + if (response.isSuccessful()) { + RRset [] rrsets = response.answers(); + List l = new ArrayList(); + Iterator it; + int i; + + for (i = 0; i < rrsets.length; i++) { + it = rrsets[i].rrs(); + while (it.hasNext()) + l.add(it.next()); + } + + result = SUCCESSFUL; + answers = (Record []) l.toArray(new Record[l.size()]); + done = true; + } else if (response.isNXDOMAIN()) { + nxdomain = true; + doneCurrent = true; + if (iterations > 0) { + result = HOST_NOT_FOUND; + done = true; + } + } else if (response.isNXRRSET()) { + result = TYPE_NOT_FOUND; + answers = null; + done = true; + } else if (response.isCNAME()) { + CNAMERecord cname = response.getCNAME(); + follow(cname.getTarget(), name); + } else if (response.isDNAME()) { + DNAMERecord dname = response.getDNAME(); + try { + follow(name.fromDNAME(dname), name); + } catch (NameTooLongException e) { + result = UNRECOVERABLE; + error = "Invalid DNAME target"; + done = true; + } + } else if (response.isDelegation()) { + // We shouldn't get a referral. Ignore it. + referral = true; + } +} + +private void +lookup(Name current) { + SetResponse sr = cache.lookupRecords(current, type, credibility); + if (verbose) { + System.err.println("lookup " + current + " " + + Type.string(type)); + System.err.println(sr); + } + processResponse(current, sr); + if (done || doneCurrent) + return; + + Record question = Record.newRecord(current, type, dclass); + Message query = Message.newQuery(question); + Message response = null; + try { + response = resolver.send(query); + } + catch (IOException e) { + // A network error occurred. Press on. + if (e instanceof InterruptedIOException) + timedout = true; + else + networkerror = true; + return; + } + int rcode = response.getHeader().getRcode(); + if (rcode != Rcode.NOERROR && rcode != Rcode.NXDOMAIN) { + // The server we contacted is broken or otherwise unhelpful. + // Press on. + badresponse = true; + badresponse_error = Rcode.string(rcode); + return; + } + + if (!query.getQuestion().equals(response.getQuestion())) { + // The answer doesn't match the question. That's not good. + badresponse = true; + badresponse_error = "response does not match query"; + return; + } + + sr = cache.addMessage(response); + if (sr == null) + sr = cache.lookupRecords(current, type, credibility); + if (verbose) { + System.err.println("queried " + current + " " + + Type.string(type)); + System.err.println(sr); + } + processResponse(current, sr); +} + +private void +resolve(Name current, Name suffix) { + doneCurrent = false; + Name tname = null; + if (suffix == null) + tname = current; + else { + try { + tname = Name.concatenate(current, suffix); + } + catch (NameTooLongException e) { + nametoolong = true; + return; + } + } + lookup(tname); +} + +/** + * Performs the lookup, using the specified Cache, Resolver, and search path. + * @return The answers, or null if none are found. + */ +public Record [] +run() { + if (done) + reset(); + if (name.isAbsolute()) + resolve(name, null); + else if (searchPath == null) + resolve(name, Name.root); + else { + if (name.labels() > defaultNdots) + resolve(name, Name.root); + if (done) + return answers; + + for (int i = 0; i < searchPath.length; i++) { + resolve(name, searchPath[i]); + if (done) + return answers; + else if (foundAlias) + break; + } + } + if (!done) { + if (badresponse) { + result = TRY_AGAIN; + error = badresponse_error; + done = true; + } else if (timedout) { + result = TRY_AGAIN; + error = "timed out"; + done = true; + } else if (networkerror) { + result = TRY_AGAIN; + error = "network error"; + done = true; + } else if (nxdomain) { + result = HOST_NOT_FOUND; + done = true; + } else if (referral) { + result = UNRECOVERABLE; + error = "referral"; + done = true; + } else if (nametoolong) { + result = UNRECOVERABLE; + error = "name too long"; + done = true; + } + } + return answers; +} + +private void +checkDone() { + if (done && result != -1) + return; + StringBuffer sb = new StringBuffer("Lookup of " + name + " "); + if (dclass != DClass.IN) + sb.append(DClass.string(dclass) + " "); + sb.append(Type.string(type) + " isn't done"); + throw new IllegalStateException(sb.toString()); +} + +/** + * Returns the answers from the lookup. + * @return The answers, or null if none are found. + * @throws IllegalStateException The lookup has not completed. + */ +public Record [] +getAnswers() { + checkDone(); + return answers; +} + +/** + * Returns all known aliases for this name. Whenever a CNAME/DNAME is + * followed, an alias is added to this array. The last element in this + * array will be the owner name for records in the answer, if there are any. + * @return The aliases. + * @throws IllegalStateException The lookup has not completed. + */ +public Name [] +getAliases() { + checkDone(); + if (aliases == null) + return noAliases; + return (Name []) aliases.toArray(new Name[aliases.size()]); +} + +/** + * Returns the result code of the lookup. + * @return The result code, which can be SUCCESSFUL, UNRECOVERABLE, TRY_AGAIN, + * HOST_NOT_FOUND, or TYPE_NOT_FOUND. + * @throws IllegalStateException The lookup has not completed. + */ +public int +getResult() { + checkDone(); + return result; +} + +/** + * Returns an error string describing the result code of this lookup. + * @return A string, which may either directly correspond the result code + * or be more specific. + * @throws IllegalStateException The lookup has not completed. + */ +public String +getErrorString() { + checkDone(); + if (error != null) + return error; + switch (result) { + case SUCCESSFUL: return "successful"; + case UNRECOVERABLE: return "unrecoverable error"; + case TRY_AGAIN: return "try again"; + case HOST_NOT_FOUND: return "host not found"; + case TYPE_NOT_FOUND: return "type not found"; + } + throw new IllegalStateException("unknown result"); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java new file mode 100644 index 0000000..6b65edf --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MBRecord.java @@ -0,0 +1,42 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mailbox Record - specifies a host containing a mailbox. + * + * @author Brian Wellington + */ + +public class MBRecord extends SingleNameBase { + +private static final long serialVersionUID = 532349543479150419L; + +MBRecord() {} + +Record +getObject() { + return new MBRecord(); +} + +/** + * Creates a new MB Record with the given data + * @param mailbox The host containing the mailbox for the domain. + */ +public +MBRecord(Name name, int dclass, long ttl, Name mailbox) { + super(name, Type.MB, dclass, ttl, mailbox, "mailbox"); +} + +/** Gets the mailbox for the domain */ +public Name +getMailbox() { + return getSingleName(); +} + +public Name +getAdditionalName() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java new file mode 100644 index 0000000..dbf51af --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MDRecord.java @@ -0,0 +1,43 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mail Destination Record - specifies a mail agent which delivers mail + * for a domain (obsolete) + * + * @author Brian Wellington + */ + +public class MDRecord extends SingleNameBase { + +private static final long serialVersionUID = 5268878603762942202L; + +MDRecord() {} + +Record +getObject() { + return new MDRecord(); +} + +/** + * Creates a new MD Record with the given data + * @param mailAgent The mail agent that delivers mail for the domain. + */ +public +MDRecord(Name name, int dclass, long ttl, Name mailAgent) { + super(name, Type.MD, dclass, ttl, mailAgent, "mail agent"); +} + +/** Gets the mail agent for the domain */ +public Name +getMailAgent() { + return getSingleName(); +} + +public Name +getAdditionalName() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java new file mode 100644 index 0000000..ff293d7 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MFRecord.java @@ -0,0 +1,43 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mail Forwarder Record - specifies a mail agent which forwards mail + * for a domain (obsolete) + * + * @author Brian Wellington + */ + +public class MFRecord extends SingleNameBase { + +private static final long serialVersionUID = -6670449036843028169L; + +MFRecord() {} + +Record +getObject() { + return new MFRecord(); +} + +/** + * Creates a new MF Record with the given data + * @param mailAgent The mail agent that forwards mail for the domain. + */ +public +MFRecord(Name name, int dclass, long ttl, Name mailAgent) { + super(name, Type.MF, dclass, ttl, mailAgent, "mail agent"); +} + +/** Gets the mail agent for the domain */ +public Name +getMailAgent() { + return getSingleName(); +} + +public Name +getAdditionalName() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java new file mode 100644 index 0000000..5752f49 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MGRecord.java @@ -0,0 +1,38 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mail Group Record - specifies a mailbox which is a member of a mail group. + * + * @author Brian Wellington + */ + +public class MGRecord extends SingleNameBase { + +private static final long serialVersionUID = -3980055550863644582L; + +MGRecord() {} + +Record +getObject() { + return new MGRecord(); +} + +/** + * Creates a new MG Record with the given data + * @param mailbox The mailbox that is a member of the group specified by the + * domain. + */ +public +MGRecord(Name name, int dclass, long ttl, Name mailbox) { + super(name, Type.MG, dclass, ttl, mailbox, "mailbox"); +} + +/** Gets the mailbox in the mail group specified by the domain */ +public Name +getMailbox() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java new file mode 100644 index 0000000..4324cda --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MINFORecord.java @@ -0,0 +1,90 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Mailbox information Record - lists the address responsible for a mailing + * list/mailbox and the address to receive error messages relating to the + * mailing list/mailbox. + * + * @author Brian Wellington + */ + +public class MINFORecord extends Record { + +private static final long serialVersionUID = -3962147172340353796L; + +private Name responsibleAddress; +private Name errorAddress; + +MINFORecord() {} + +Record +getObject() { + return new MINFORecord(); +} + +/** + * Creates an MINFO Record from the given data + * @param responsibleAddress The address responsible for the + * mailing list/mailbox. + * @param errorAddress The address to receive error messages relating to the + * mailing list/mailbox. + */ +public +MINFORecord(Name name, int dclass, long ttl, + Name responsibleAddress, Name errorAddress) +{ + super(name, Type.MINFO, dclass, ttl); + + this.responsibleAddress = checkName("responsibleAddress", + responsibleAddress); + this.errorAddress = checkName("errorAddress", errorAddress); +} + +void +rrFromWire(DNSInput in) throws IOException { + responsibleAddress = new Name(in); + errorAddress = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + responsibleAddress = st.getName(origin); + errorAddress = st.getName(origin); +} + +/** Converts the MINFO Record to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(responsibleAddress); + sb.append(" "); + sb.append(errorAddress); + return sb.toString(); +} + +/** Gets the address responsible for the mailing list/mailbox. */ +public Name +getResponsibleAddress() { + return responsibleAddress; +} + +/** + * Gets the address to receive error messages relating to the mailing + * list/mailbox. + */ +public Name +getErrorAddress() { + return errorAddress; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + responsibleAddress.toWire(out, null, canonical); + errorAddress.toWire(out, null, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java new file mode 100644 index 0000000..a7ff4fc --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MRRecord.java @@ -0,0 +1,38 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mailbox Rename Record - specifies a rename of a mailbox. + * + * @author Brian Wellington + */ + +public class MRRecord extends SingleNameBase { + +private static final long serialVersionUID = -5617939094209927533L; + +MRRecord() {} + +Record +getObject() { + return new MRRecord(); +} + +/** + * Creates a new MR Record with the given data + * @param newName The new name of the mailbox specified by the domain. + * domain. + */ +public +MRRecord(Name name, int dclass, long ttl, Name newName) { + super(name, Type.MR, dclass, ttl, newName, "new name"); +} + +/** Gets the new name of the mailbox specified by the domain */ +public Name +getNewName() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java new file mode 100644 index 0000000..111977d --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/MXRecord.java @@ -0,0 +1,57 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Mail Exchange - specifies where mail to a domain is sent + * + * @author Brian Wellington + */ + +public class MXRecord extends U16NameBase { + +private static final long serialVersionUID = 2914841027584208546L; + +MXRecord() {} + +Record +getObject() { + return new MXRecord(); +} + +/** + * Creates an MX Record from the given data + * @param priority The priority of this MX. Records with lower priority + * are preferred. + * @param target The host that mail is sent to + */ +public +MXRecord(Name name, int dclass, long ttl, int priority, Name target) { + super(name, Type.MX, dclass, ttl, priority, "priority", + target, "target"); +} + +/** Returns the target of the MX record */ +public Name +getTarget() { + return getNameField(); +} + +/** Returns the priority of this MX record */ +public int +getPriority() { + return getU16Field(); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(u16Field); + nameField.toWire(out, c, canonical); +} + +public Name +getAdditionalName() { + return getNameField(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Master.java b/external/asmack/build/src/trunk/org/xbill/DNS/Master.java new file mode 100644 index 0000000..c795a9c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Master.java @@ -0,0 +1,427 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A DNS master file parser. This incrementally parses the file, returning + * one record at a time. When directives are seen, they are added to the + * state and used when parsing future records. + * + * @author Brian Wellington + */ + +public class Master { + +private Name origin; +private File file; +private Record last = null; +private long defaultTTL; +private Master included = null; +private Tokenizer st; +private int currentType; +private int currentDClass; +private long currentTTL; +private boolean needSOATTL; + +private Generator generator; +private List generators; +private boolean noExpandGenerate; + +Master(File file, Name origin, long initialTTL) throws IOException { + if (origin != null && !origin.isAbsolute()) { + throw new RelativeNameException(origin); + } + this.file = file; + st = new Tokenizer(file); + this.origin = origin; + defaultTTL = initialTTL; +} + +/** + * Initializes the master file reader and opens the specified master file. + * @param filename The master file. + * @param origin The initial origin to append to relative names. + * @param ttl The initial default TTL. + * @throws IOException The master file could not be opened. + */ +public +Master(String filename, Name origin, long ttl) throws IOException { + this(new File(filename), origin, ttl); +} + +/** + * Initializes the master file reader and opens the specified master file. + * @param filename The master file. + * @param origin The initial origin to append to relative names. + * @throws IOException The master file could not be opened. + */ +public +Master(String filename, Name origin) throws IOException { + this(new File(filename), origin, -1); +} + +/** + * Initializes the master file reader and opens the specified master file. + * @param filename The master file. + * @throws IOException The master file could not be opened. + */ +public +Master(String filename) throws IOException { + this(new File(filename), null, -1); +} + +/** + * Initializes the master file reader. + * @param in The input stream containing a master file. + * @param origin The initial origin to append to relative names. + * @param ttl The initial default TTL. + */ +public +Master(InputStream in, Name origin, long ttl) { + if (origin != null && !origin.isAbsolute()) { + throw new RelativeNameException(origin); + } + st = new Tokenizer(in); + this.origin = origin; + defaultTTL = ttl; +} + +/** + * Initializes the master file reader. + * @param in The input stream containing a master file. + * @param origin The initial origin to append to relative names. + */ +public +Master(InputStream in, Name origin) { + this(in, origin, -1); +} + +/** + * Initializes the master file reader. + * @param in The input stream containing a master file. + */ +public +Master(InputStream in) { + this(in, null, -1); +} + +private Name +parseName(String s, Name origin) throws TextParseException { + try { + return Name.fromString(s, origin); + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } +} + +private void +parseTTLClassAndType() throws IOException { + String s; + boolean seen_class = false; + + + // This is a bit messy, since any of the following are legal: + // class ttl type + // ttl class type + // class type + // ttl type + // type + seen_class = false; + s = st.getString(); + if ((currentDClass = DClass.value(s)) >= 0) { + s = st.getString(); + seen_class = true; + } + + currentTTL = -1; + try { + currentTTL = TTL.parseTTL(s); + s = st.getString(); + } + catch (NumberFormatException e) { + if (defaultTTL >= 0) + currentTTL = defaultTTL; + else if (last != null) + currentTTL = last.getTTL(); + } + + if (!seen_class) { + if ((currentDClass = DClass.value(s)) >= 0) { + s = st.getString(); + } else { + currentDClass = DClass.IN; + } + } + + if ((currentType = Type.value(s)) < 0) + throw st.exception("Invalid type '" + s + "'"); + + // BIND allows a missing TTL for the initial SOA record, and uses + // the SOA minimum value. If the SOA is not the first record, + // this is an error. + if (currentTTL < 0) { + if (currentType != Type.SOA) + throw st.exception("missing TTL"); + needSOATTL = true; + currentTTL = 0; + } +} + +private long +parseUInt32(String s) { + if (!Character.isDigit(s.charAt(0))) + return -1; + try { + long l = Long.parseLong(s); + if (l < 0 || l > 0xFFFFFFFFL) + return -1; + return l; + } + catch (NumberFormatException e) { + return -1; + } +} + +private void +startGenerate() throws IOException { + String s; + int n; + + // The first field is of the form start-end[/step] + // Regexes would be useful here. + s = st.getIdentifier(); + n = s.indexOf("-"); + if (n < 0) + throw st.exception("Invalid $GENERATE range specifier: " + s); + String startstr = s.substring(0, n); + String endstr = s.substring(n + 1); + String stepstr = null; + n = endstr.indexOf("/"); + if (n >= 0) { + stepstr = endstr.substring(n + 1); + endstr = endstr.substring(0, n); + } + long start = parseUInt32(startstr); + long end = parseUInt32(endstr); + long step; + if (stepstr != null) + step = parseUInt32(stepstr); + else + step = 1; + if (start < 0 || end < 0 || start > end || step <= 0) + throw st.exception("Invalid $GENERATE range specifier: " + s); + + // The next field is the name specification. + String nameSpec = st.getIdentifier(); + + // Then the ttl/class/type, in the same form as a normal record. + // Only some types are supported. + parseTTLClassAndType(); + if (!Generator.supportedType(currentType)) + throw st.exception("$GENERATE does not support " + + Type.string(currentType) + " records"); + + // Next comes the rdata specification. + String rdataSpec = st.getIdentifier(); + + // That should be the end. However, we don't want to move past the + // line yet, so put back the EOL after reading it. + st.getEOL(); + st.unget(); + + generator = new Generator(start, end, step, nameSpec, + currentType, currentDClass, currentTTL, + rdataSpec, origin); + if (generators == null) + generators = new ArrayList(1); + generators.add(generator); +} + +private void +endGenerate() throws IOException { + // Read the EOL that we put back before. + st.getEOL(); + + generator = null; +} + +private Record +nextGenerated() throws IOException { + try { + return generator.nextRecord(); + } + catch (Tokenizer.TokenizerException e) { + throw st.exception("Parsing $GENERATE: " + e.getBaseMessage()); + } + catch (TextParseException e) { + throw st.exception("Parsing $GENERATE: " + e.getMessage()); + } +} + +/** + * Returns the next record in the master file. This will process any + * directives before the next record. + * @return The next record. + * @throws IOException The master file could not be read, or was syntactically + * invalid. + */ +public Record +_nextRecord() throws IOException { + Tokenizer.Token token; + String s; + + if (included != null) { + Record rec = included.nextRecord(); + if (rec != null) + return rec; + included = null; + } + if (generator != null) { + Record rec = nextGenerated(); + if (rec != null) + return rec; + endGenerate(); + } + while (true) { + Name name; + + token = st.get(true, false); + if (token.type == Tokenizer.WHITESPACE) { + Tokenizer.Token next = st.get(); + if (next.type == Tokenizer.EOL) + continue; + else if (next.type == Tokenizer.EOF) + return null; + else + st.unget(); + if (last == null) + throw st.exception("no owner"); + name = last.getName(); + } + else if (token.type == Tokenizer.EOL) + continue; + else if (token.type == Tokenizer.EOF) + return null; + else if (((String) token.value).charAt(0) == '$') { + s = token.value; + + if (s.equalsIgnoreCase("$ORIGIN")) { + origin = st.getName(Name.root); + st.getEOL(); + continue; + } else if (s.equalsIgnoreCase("$TTL")) { + defaultTTL = st.getTTL(); + st.getEOL(); + continue; + } else if (s.equalsIgnoreCase("$INCLUDE")) { + String filename = st.getString(); + File newfile; + if (file != null) { + String parent = file.getParent(); + newfile = new File(parent, filename); + } else { + newfile = new File(filename); + } + Name incorigin = origin; + token = st.get(); + if (token.isString()) { + incorigin = parseName(token.value, + Name.root); + st.getEOL(); + } + included = new Master(newfile, incorigin, + defaultTTL); + /* + * If we continued, we wouldn't be looking in + * the new file. Recursing works better. + */ + return nextRecord(); + } else if (s.equalsIgnoreCase("$GENERATE")) { + if (generator != null) + throw new IllegalStateException + ("cannot nest $GENERATE"); + startGenerate(); + if (noExpandGenerate) { + endGenerate(); + continue; + } + return nextGenerated(); + } else { + throw st.exception("Invalid directive: " + s); + } + } else { + s = token.value; + name = parseName(s, origin); + if (last != null && name.equals(last.getName())) { + name = last.getName(); + } + } + + parseTTLClassAndType(); + last = Record.fromString(name, currentType, currentDClass, + currentTTL, st, origin); + if (needSOATTL) { + long ttl = ((SOARecord)last).getMinimum(); + last.setTTL(ttl); + defaultTTL = ttl; + needSOATTL = false; + } + return last; + } +} + +/** + * Returns the next record in the master file. This will process any + * directives before the next record. + * @return The next record. + * @throws IOException The master file could not be read, or was syntactically + * invalid. + */ +public Record +nextRecord() throws IOException { + Record rec = null; + try { + rec = _nextRecord(); + } + finally { + if (rec == null) { + st.close(); + } + } + return rec; +} + +/** + * Specifies whether $GENERATE statements should be expanded. Whether + * expanded or not, the specifications for generated records are available + * by calling {@link #generators}. This must be called before a $GENERATE + * statement is seen during iteration to have an effect. + */ +public void +expandGenerate(boolean wantExpand) { + noExpandGenerate = !wantExpand; +} + +/** + * Returns an iterator over the generators specified in the master file; that + * is, the parsed contents of $GENERATE statements. + * @see Generator + */ +public Iterator +generators() { + if (generators != null) + return Collections.unmodifiableList(generators).iterator(); + else + return Collections.EMPTY_LIST.iterator(); +} + +protected void +finalize() { + st.close(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Message.java b/external/asmack/build/src/trunk/org/xbill/DNS/Message.java new file mode 100644 index 0000000..7967675 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Message.java @@ -0,0 +1,607 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; + +/** + * A DNS Message. A message is the basic unit of communication between + * the client and server of a DNS operation. A message consists of a Header + * and 4 message sections. + * @see Resolver + * @see Header + * @see Section + * + * @author Brian Wellington + */ + +public class Message implements Cloneable { + +/** The maximum length of a message in wire format. */ +public static final int MAXLENGTH = 65535; + +private Header header; +private List [] sections; +private int size; +private TSIG tsigkey; +private TSIGRecord querytsig; +private int tsigerror; + +int tsigstart; +int tsigState; +int sig0start; + +/* The message was not signed */ +static final int TSIG_UNSIGNED = 0; + +/* The message was signed and verification succeeded */ +static final int TSIG_VERIFIED = 1; + +/* The message was an unsigned message in multiple-message response */ +static final int TSIG_INTERMEDIATE = 2; + +/* The message was signed and no verification was attempted. */ +static final int TSIG_SIGNED = 3; + +/* + * The message was signed and verification failed, or was not signed + * when it should have been. + */ +static final int TSIG_FAILED = 4; + +private static Record [] emptyRecordArray = new Record[0]; +private static RRset [] emptyRRsetArray = new RRset[0]; + +private +Message(Header header) { + sections = new List[4]; + this.header = header; +} + +/** Creates a new Message with the specified Message ID */ +public +Message(int id) { + this(new Header(id)); +} + +/** Creates a new Message with a random Message ID */ +public +Message() { + this(new Header()); +} + +/** + * Creates a new Message with a random Message ID suitable for sending as a + * query. + * @param r A record containing the question + */ +public static Message +newQuery(Record r) { + Message m = new Message(); + m.header.setOpcode(Opcode.QUERY); + m.header.setFlag(Flags.RD); + m.addRecord(r, Section.QUESTION); + return m; +} + +/** + * Creates a new Message to contain a dynamic update. A random Message ID + * and the zone are filled in. + * @param zone The zone to be updated + */ +public static Message +newUpdate(Name zone) { + return new Update(zone); +} + +Message(DNSInput in) throws IOException { + this(new Header(in)); + boolean isUpdate = (header.getOpcode() == Opcode.UPDATE); + boolean truncated = header.getFlag(Flags.TC); + try { + for (int i = 0; i < 4; i++) { + int count = header.getCount(i); + if (count > 0) + sections[i] = new ArrayList(count); + for (int j = 0; j < count; j++) { + int pos = in.current(); + Record rec = Record.fromWire(in, i, isUpdate); + sections[i].add(rec); + if (rec.getType() == Type.TSIG) + tsigstart = pos; + if (rec.getType() == Type.SIG && + ((SIGRecord) rec).getTypeCovered() == 0) + sig0start = pos; + } + } + } catch (WireParseException e) { + if (!truncated) + throw e; + } + size = in.current(); +} + +/** + * Creates a new Message from its DNS wire format representation + * @param b A byte array containing the DNS Message. + */ +public +Message(byte [] b) throws IOException { + this(new DNSInput(b)); +} + +/** + * Replaces the Header with a new one. + * @see Header + */ +public void +setHeader(Header h) { + header = h; +} + +/** + * Retrieves the Header. + * @see Header + */ +public Header +getHeader() { + return header; +} + +/** + * Adds a record to a section of the Message, and adjusts the header. + * @see Record + * @see Section + */ +public void +addRecord(Record r, int section) { + if (sections[section] == null) + sections[section] = new LinkedList(); + header.incCount(section); + sections[section].add(r); +} + +/** + * Removes a record from a section of the Message, and adjusts the header. + * @see Record + * @see Section + */ +public boolean +removeRecord(Record r, int section) { + if (sections[section] != null && sections[section].remove(r)) { + header.decCount(section); + return true; + } + else + return false; +} + +/** + * Removes all records from a section of the Message, and adjusts the header. + * @see Record + * @see Section + */ +public void +removeAllRecords(int section) { + sections[section] = null; + header.setCount(section, 0); +} + +/** + * Determines if the given record is already present in the given section. + * @see Record + * @see Section + */ +public boolean +findRecord(Record r, int section) { + return (sections[section] != null && sections[section].contains(r)); +} + +/** + * Determines if the given record is already present in any section. + * @see Record + * @see Section + */ +public boolean +findRecord(Record r) { + for (int i = Section.ANSWER; i <= Section.ADDITIONAL; i++) + if (sections[i] != null && sections[i].contains(r)) + return true; + return false; +} + +/** + * Determines if an RRset with the given name and type is already + * present in the given section. + * @see RRset + * @see Section + */ +public boolean +findRRset(Name name, int type, int section) { + if (sections[section] == null) + return false; + for (int i = 0; i < sections[section].size(); i++) { + Record r = (Record) sections[section].get(i); + if (r.getType() == type && name.equals(r.getName())) + return true; + } + return false; +} + +/** + * Determines if an RRset with the given name and type is already + * present in any section. + * @see RRset + * @see Section + */ +public boolean +findRRset(Name name, int type) { + return (findRRset(name, type, Section.ANSWER) || + findRRset(name, type, Section.AUTHORITY) || + findRRset(name, type, Section.ADDITIONAL)); +} + +/** + * Returns the first record in the QUESTION section. + * @see Record + * @see Section + */ +public Record +getQuestion() { + List l = sections[Section.QUESTION]; + if (l == null || l.size() == 0) + return null; + return (Record) l.get(0); +} + +/** + * Returns the TSIG record from the ADDITIONAL section, if one is present. + * @see TSIGRecord + * @see TSIG + * @see Section + */ +public TSIGRecord +getTSIG() { + int count = header.getCount(Section.ADDITIONAL); + if (count == 0) + return null; + List l = sections[Section.ADDITIONAL]; + Record rec = (Record) l.get(count - 1); + if (rec.type != Type.TSIG) + return null; + return (TSIGRecord) rec; +} + +/** + * Was this message signed by a TSIG? + * @see TSIG + */ +public boolean +isSigned() { + return (tsigState == TSIG_SIGNED || + tsigState == TSIG_VERIFIED || + tsigState == TSIG_FAILED); +} + +/** + * If this message was signed by a TSIG, was the TSIG verified? + * @see TSIG + */ +public boolean +isVerified() { + return (tsigState == TSIG_VERIFIED); +} + +/** + * Returns the OPT record from the ADDITIONAL section, if one is present. + * @see OPTRecord + * @see Section + */ +public OPTRecord +getOPT() { + Record [] additional = getSectionArray(Section.ADDITIONAL); + for (int i = 0; i < additional.length; i++) + if (additional[i] instanceof OPTRecord) + return (OPTRecord) additional[i]; + return null; +} + +/** + * Returns the message's rcode (error code). This incorporates the EDNS + * extended rcode. + */ +public int +getRcode() { + int rcode = header.getRcode(); + OPTRecord opt = getOPT(); + if (opt != null) + rcode += (opt.getExtendedRcode() << 4); + return rcode; +} + +/** + * Returns an array containing all records in the given section, or an + * empty array if the section is empty. + * @see Record + * @see Section + */ +public Record [] +getSectionArray(int section) { + if (sections[section] == null) + return emptyRecordArray; + List l = sections[section]; + return (Record []) l.toArray(new Record[l.size()]); +} + +private static boolean +sameSet(Record r1, Record r2) { + return (r1.getRRsetType() == r2.getRRsetType() && + r1.getDClass() == r2.getDClass() && + r1.getName().equals(r2.getName())); +} + +/** + * Returns an array containing all records in the given section grouped into + * RRsets. + * @see RRset + * @see Section + */ +public RRset [] +getSectionRRsets(int section) { + if (sections[section] == null) + return emptyRRsetArray; + List sets = new LinkedList(); + Record [] recs = getSectionArray(section); + Set hash = new HashSet(); + for (int i = 0; i < recs.length; i++) { + Name name = recs[i].getName(); + boolean newset = true; + if (hash.contains(name)) { + for (int j = sets.size() - 1; j >= 0; j--) { + RRset set = (RRset) sets.get(j); + if (set.getType() == recs[i].getRRsetType() && + set.getDClass() == recs[i].getDClass() && + set.getName().equals(name)) + { + set.addRR(recs[i]); + newset = false; + break; + } + } + } + if (newset) { + RRset set = new RRset(recs[i]); + sets.add(set); + hash.add(name); + } + } + return (RRset []) sets.toArray(new RRset[sets.size()]); +} + +void +toWire(DNSOutput out) { + header.toWire(out); + Compression c = new Compression(); + for (int i = 0; i < 4; i++) { + if (sections[i] == null) + continue; + for (int j = 0; j < sections[i].size(); j++) { + Record rec = (Record)sections[i].get(j); + rec.toWire(out, i, c); + } + } +} + +/* Returns the number of records not successfully rendered. */ +private int +sectionToWire(DNSOutput out, int section, Compression c, + int maxLength) +{ + int n = sections[section].size(); + int pos = out.current(); + int rendered = 0; + Record lastrec = null; + + for (int i = 0; i < n; i++) { + Record rec = (Record)sections[section].get(i); + if (lastrec != null && !sameSet(rec, lastrec)) { + pos = out.current(); + rendered = i; + } + lastrec = rec; + rec.toWire(out, section, c); + if (out.current() > maxLength) { + out.jump(pos); + return n - rendered; + } + } + return 0; +} + +/* Returns true if the message could be rendered. */ +private boolean +toWire(DNSOutput out, int maxLength) { + if (maxLength < Header.LENGTH) + return false; + + Header newheader = null; + + int tempMaxLength = maxLength; + if (tsigkey != null) + tempMaxLength -= tsigkey.recordLength(); + + int startpos = out.current(); + header.toWire(out); + Compression c = new Compression(); + for (int i = 0; i < 4; i++) { + int skipped; + if (sections[i] == null) + continue; + skipped = sectionToWire(out, i, c, tempMaxLength); + if (skipped != 0) { + if (newheader == null) + newheader = (Header) header.clone(); + if (i != Section.ADDITIONAL) + newheader.setFlag(Flags.TC); + int count = newheader.getCount(i); + newheader.setCount(i, count - skipped); + for (int j = i + 1; j < 4; j++) + newheader.setCount(j, 0); + + out.save(); + out.jump(startpos); + newheader.toWire(out); + out.restore(); + break; + } + } + + if (tsigkey != null) { + TSIGRecord tsigrec = tsigkey.generate(this, out.toByteArray(), + tsigerror, querytsig); + + if (newheader == null) + newheader = (Header) header.clone(); + tsigrec.toWire(out, Section.ADDITIONAL, c); + newheader.incCount(Section.ADDITIONAL); + + out.save(); + out.jump(startpos); + newheader.toWire(out); + out.restore(); + } + + return true; +} + +/** + * Returns an array containing the wire format representation of the Message. + */ +public byte [] +toWire() { + DNSOutput out = new DNSOutput(); + toWire(out); + size = out.current(); + return out.toByteArray(); +} + +/** + * Returns an array containing the wire format representation of the Message + * with the specified maximum length. This will generate a truncated + * message (with the TC bit) if the message doesn't fit, and will also + * sign the message with the TSIG key set by a call to setTSIG(). This + * method may return null if the message could not be rendered at all; this + * could happen if maxLength is smaller than a DNS header, for example. + * @param maxLength The maximum length of the message. + * @return The wire format of the message, or null if the message could not be + * rendered into the specified length. + * @see Flags + * @see TSIG + */ +public byte [] +toWire(int maxLength) { + DNSOutput out = new DNSOutput(); + toWire(out, maxLength); + size = out.current(); + return out.toByteArray(); +} + +/** + * Sets the TSIG key and other necessary information to sign a message. + * @param key The TSIG key. + * @param error The value of the TSIG error field. + * @param querytsig If this is a response, the TSIG from the request. + */ +public void +setTSIG(TSIG key, int error, TSIGRecord querytsig) { + this.tsigkey = key; + this.tsigerror = error; + this.querytsig = querytsig; +} + +/** + * Returns the size of the message. Only valid if the message has been + * converted to or from wire format. + */ +public int +numBytes() { + return size; +} + +/** + * Converts the given section of the Message to a String. + * @see Section + */ +public String +sectionToString(int i) { + if (i > 3) + return null; + + StringBuffer sb = new StringBuffer(); + + Record [] records = getSectionArray(i); + for (int j = 0; j < records.length; j++) { + Record rec = records[j]; + if (i == Section.QUESTION) { + sb.append(";;\t" + rec.name); + sb.append(", type = " + Type.string(rec.type)); + sb.append(", class = " + DClass.string(rec.dclass)); + } + else + sb.append(rec); + sb.append("\n"); + } + return sb.toString(); +} + +/** + * Converts the Message to a String. + */ +public String +toString() { + StringBuffer sb = new StringBuffer(); + OPTRecord opt = getOPT(); + if (opt != null) + sb.append(header.toStringWithRcode(getRcode()) + "\n"); + else + sb.append(header + "\n"); + if (isSigned()) { + sb.append(";; TSIG "); + if (isVerified()) + sb.append("ok"); + else + sb.append("invalid"); + sb.append('\n'); + } + for (int i = 0; i < 4; i++) { + if (header.getOpcode() != Opcode.UPDATE) + sb.append(";; " + Section.longString(i) + ":\n"); + else + sb.append(";; " + Section.updString(i) + ":\n"); + sb.append(sectionToString(i) + "\n"); + } + sb.append(";; Message size: " + numBytes() + " bytes"); + return sb.toString(); +} + +/** + * Creates a copy of this Message. This is done by the Resolver before adding + * TSIG and OPT records, for example. + * @see Resolver + * @see TSIGRecord + * @see OPTRecord + */ +public Object +clone() { + Message m = new Message(); + for (int i = 0; i < sections.length; i++) { + if (sections[i] != null) + m.sections[i] = new LinkedList(sections[i]); + } + m.header = (Header) header.clone(); + m.size = size; + return m; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java b/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java new file mode 100644 index 0000000..dd60f62 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Mnemonic.java @@ -0,0 +1,210 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.HashMap; + +/** + * A utility class for converting between numeric codes and mnemonics + * for those codes. Mnemonics are case insensitive. + * + * @author Brian Wellington + */ + +class Mnemonic { + +private static Integer cachedInts[] = new Integer[64]; + +static { + for (int i = 0; i < cachedInts.length; i++) { + cachedInts[i] = new Integer(i); + } +} + +/* Strings are case-sensitive. */ +static final int CASE_SENSITIVE = 1; + +/* Strings will be stored/searched for in uppercase. */ +static final int CASE_UPPER = 2; + +/* Strings will be stored/searched for in lowercase. */ +static final int CASE_LOWER = 3; + +private HashMap strings; +private HashMap values; +private String description; +private int wordcase; +private String prefix; +private int max; +private boolean numericok; + +/** + * Creates a new Mnemonic table. + * @param description A short description of the mnemonic to use when + * @param wordcase Whether to convert strings into uppercase, lowercase, + * or leave them unchanged. + * throwing exceptions. + */ +public +Mnemonic(String description, int wordcase) { + this.description = description; + this.wordcase = wordcase; + strings = new HashMap(); + values = new HashMap(); + max = Integer.MAX_VALUE; +} + +/** Sets the maximum numeric value */ +public void +setMaximum(int max) { + this.max = max; +} + +/** + * Sets the prefix to use when converting to and from values that don't + * have mnemonics. + */ +public void +setPrefix(String prefix) { + this.prefix = sanitize(prefix); +} + +/** + * Sets whether numeric values stored in strings are acceptable. + */ +public void +setNumericAllowed(boolean numeric) { + this.numericok = numeric; +} + +/** + * Converts an int into a possibly cached Integer. + */ +public static Integer +toInteger(int val) { + if (val >= 0 && val < cachedInts.length) + return (cachedInts[val]); + return new Integer(val); +} + +/** + * Checks that a numeric value is within the range [0..max] + */ +public void +check(int val) { + if (val < 0 || val > max) { + throw new IllegalArgumentException(description + " " + val + + "is out of range"); + } +} + +/* Converts a String to the correct case. */ +private String +sanitize(String str) { + if (wordcase == CASE_UPPER) + return str.toUpperCase(); + else if (wordcase == CASE_LOWER) + return str.toLowerCase(); + return str; +} + +private int +parseNumeric(String s) { + try { + int val = Integer.parseInt(s); + if (val >= 0 && val <= max) + return val; + } + catch (NumberFormatException e) { + } + return -1; +} + +/** + * Defines the text representation of a numeric value. + * @param val The numeric value + * @param string The text string + */ +public void +add(int val, String str) { + check(val); + Integer value = toInteger(val); + str = sanitize(str); + strings.put(str, value); + values.put(value, str); +} + +/** + * Defines an additional text representation of a numeric value. This will + * be used by getValue(), but not getText(). + * @param val The numeric value + * @param string The text string + */ +public void +addAlias(int val, String str) { + check(val); + Integer value = toInteger(val); + str = sanitize(str); + strings.put(str, value); +} + +/** + * Copies all mnemonics from one table into another. + * @param val The numeric value + * @param string The text string + * @throws IllegalArgumentException The wordcases of the Mnemonics do not + * match. + */ +public void +addAll(Mnemonic source) { + if (wordcase != source.wordcase) + throw new IllegalArgumentException(source.description + + ": wordcases do not match"); + strings.putAll(source.strings); + values.putAll(source.values); +} + +/** + * Gets the text mnemonic corresponding to a numeric value. + * @param val The numeric value + * @return The corresponding text mnemonic. + */ +public String +getText(int val) { + check(val); + String str = (String) values.get(toInteger(val)); + if (str != null) + return str; + str = Integer.toString(val); + if (prefix != null) + return prefix + str; + return str; +} + +/** + * Gets the numeric value corresponding to a text mnemonic. + * @param str The text mnemonic + * @return The corresponding numeric value, or -1 if there is none + */ +public int +getValue(String str) { + str = sanitize(str); + Integer value = (Integer) strings.get(str); + if (value != null) { + return value.intValue(); + } + if (prefix != null) { + if (str.startsWith(prefix)) { + int val = parseNumeric(str.substring(prefix.length())); + if (val >= 0) { + return val; + } + } + } + if (numericok) { + return parseNumeric(str); + } + return -1; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java new file mode 100644 index 0000000..da2ec6d --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NAPTRRecord.java @@ -0,0 +1,154 @@ +// Copyright (c) 2000-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Name Authority Pointer Record - specifies rewrite rule, that when applied + * to an existing string will produce a new domain. + * + * @author Chuck Santos + */ + +public class NAPTRRecord extends Record { + +private static final long serialVersionUID = 5191232392044947002L; + +private int order, preference; +private byte [] flags, service, regexp; +private Name replacement; + +NAPTRRecord() {} + +Record +getObject() { + return new NAPTRRecord(); +} + +/** + * Creates an NAPTR Record from the given data + * @param order The order of this NAPTR. Records with lower order are + * preferred. + * @param preference The preference, used to select between records at the + * same order. + * @param flags The control aspects of the NAPTRRecord. + * @param service The service or protocol available down the rewrite path. + * @param regexp The regular/substitution expression. + * @param replacement The domain-name to query for the next DNS resource + * record, depending on the value of the flags field. + * @throws IllegalArgumentException One of the strings has invalid escapes + */ +public +NAPTRRecord(Name name, int dclass, long ttl, int order, int preference, + String flags, String service, String regexp, Name replacement) +{ + super(name, Type.NAPTR, dclass, ttl); + this.order = checkU16("order", order); + this.preference = checkU16("preference", preference); + try { + this.flags = byteArrayFromString(flags); + this.service = byteArrayFromString(service); + this.regexp = byteArrayFromString(regexp); + } + catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } + this.replacement = checkName("replacement", replacement); +} + +void +rrFromWire(DNSInput in) throws IOException { + order = in.readU16(); + preference = in.readU16(); + flags = in.readCountedString(); + service = in.readCountedString(); + regexp = in.readCountedString(); + replacement = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + order = st.getUInt16(); + preference = st.getUInt16(); + try { + flags = byteArrayFromString(st.getString()); + service = byteArrayFromString(st.getString()); + regexp = byteArrayFromString(st.getString()); + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + replacement = st.getName(origin); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(order); + sb.append(" "); + sb.append(preference); + sb.append(" "); + sb.append(byteArrayToString(flags, true)); + sb.append(" "); + sb.append(byteArrayToString(service, true)); + sb.append(" "); + sb.append(byteArrayToString(regexp, true)); + sb.append(" "); + sb.append(replacement); + return sb.toString(); +} + +/** Returns the order */ +public int +getOrder() { + return order; +} + +/** Returns the preference */ +public int +getPreference() { + return preference; +} + +/** Returns flags */ +public String +getFlags() { + return byteArrayToString(flags, false); +} + +/** Returns service */ +public String +getService() { + return byteArrayToString(service, false); +} + +/** Returns regexp */ +public String +getRegexp() { + return byteArrayToString(regexp, false); +} + +/** Returns the replacement domain-name */ +public Name +getReplacement() { + return replacement; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(order); + out.writeU16(preference); + out.writeCountedString(flags); + out.writeCountedString(service); + out.writeCountedString(regexp); + replacement.toWire(out, null, canonical); +} + +public Name +getAdditionalName() { + return replacement; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java new file mode 100644 index 0000000..a6b2031 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSAPRecord.java @@ -0,0 +1,106 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.*; + +/** + * NSAP Address Record. + * + * @author Brian Wellington + */ + +public class NSAPRecord extends Record { + +private static final long serialVersionUID = -1037209403185658593L; + +private byte [] address; + +NSAPRecord() {} + +Record +getObject() { + return new NSAPRecord(); +} + +private static final byte [] +checkAndConvertAddress(String address) { + if (!address.substring(0, 2).equalsIgnoreCase("0x")) { + return null; + } + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + boolean partial = false; + int current = 0; + for (int i = 2; i < address.length(); i++) { + char c = address.charAt(i); + if (c == '.') { + continue; + } + int value = Character.digit(c, 16); + if (value == -1) { + return null; + } + if (partial) { + current += value; + bytes.write(current); + partial = false; + } else { + current = value << 4; + partial = true; + } + + } + if (partial) { + return null; + } + return bytes.toByteArray(); +} + +/** + * Creates an NSAP Record from the given data + * @param address The NSAP address. + * @throws IllegalArgumentException The address is not a valid NSAP address. + */ +public +NSAPRecord(Name name, int dclass, long ttl, String address) { + super(name, Type.NSAP, dclass, ttl); + this.address = checkAndConvertAddress(address); + if (this.address == null) { + throw new IllegalArgumentException("invalid NSAP address " + + address); + } +} + +void +rrFromWire(DNSInput in) throws IOException { + address = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String addr = st.getString(); + this.address = checkAndConvertAddress(addr); + if (this.address == null) + throw st.exception("invalid NSAP address " + addr); +} + +/** + * Returns the NSAP address. + */ +public String +getAddress() { + return byteArrayToString(address, false); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(address); +} + +String +rrToString() { + return "0x" + base16.toString(address); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java new file mode 100644 index 0000000..ecc609f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSAP_PTRRecord.java @@ -0,0 +1,38 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * NSAP Pointer Record - maps a domain name representing an NSAP Address to + * a hostname. + * + * @author Brian Wellington + */ + +public class NSAP_PTRRecord extends SingleNameBase { + +private static final long serialVersionUID = 2386284746382064904L; + +NSAP_PTRRecord() {} + +Record +getObject() { + return new NSAP_PTRRecord(); +} + +/** + * Creates a new NSAP_PTR Record with the given data + * @param target The name of the host with this address + */ +public +NSAP_PTRRecord(Name name, int dclass, long ttl, Name target) { + super(name, Type.NSAP_PTR, dclass, ttl, target, "target"); +} + +/** Gets the target of the NSAP_PTR Record */ +public Name +getTarget() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java new file mode 100644 index 0000000..d663a62 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3PARAMRecord.java @@ -0,0 +1,165 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.IOException; +import java.security.NoSuchAlgorithmException; + +import org.xbill.DNS.utils.base16; + +/** + * Next SECure name 3 Parameters - this record contains the parameters (hash + * algorithm, salt, iterations) used for a valid, complete NSEC3 chain present + * in a zone. Zones signed using NSEC3 must include this record at the zone apex + * to inform authoritative servers that NSEC3 is being used with the given + * parameters. + * + * @author Brian Wellington + * @author David Blacka + */ + +public class NSEC3PARAMRecord extends Record { + +private static final long serialVersionUID = -8689038598776316533L; + +private int hashAlg; +private int flags; +private int iterations; +private byte salt[]; + +NSEC3PARAMRecord() {} + +Record getObject() { + return new NSEC3PARAMRecord(); +} + +/** + * Creates an NSEC3PARAM record from the given data. + * + * @param name The ownername of the NSEC3PARAM record (generally the zone name). + * @param dclass The class. + * @param ttl The TTL. + * @param hashAlg The hash algorithm. + * @param flags The value of the flags field. + * @param iterations The number of hash iterations. + * @param salt The salt to use (may be null). + */ +public NSEC3PARAMRecord(Name name, int dclass, long ttl, int hashAlg, + int flags, int iterations, byte [] salt) +{ + super(name, Type.NSEC3PARAM, dclass, ttl); + this.hashAlg = checkU8("hashAlg", hashAlg); + this.flags = checkU8("flags", flags); + this.iterations = checkU16("iterations", iterations); + + if (salt != null) { + if (salt.length > 255) + throw new IllegalArgumentException("Invalid salt " + + "length"); + if (salt.length > 0) { + this.salt = new byte[salt.length]; + System.arraycopy(salt, 0, this.salt, 0, salt.length); + } + } +} + +void +rrFromWire(DNSInput in) throws IOException { + hashAlg = in.readU8(); + flags = in.readU8(); + iterations = in.readU16(); + + int salt_length = in.readU8(); + if (salt_length > 0) + salt = in.readByteArray(salt_length); + else + salt = null; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(hashAlg); + out.writeU8(flags); + out.writeU16(iterations); + + if (salt != null) { + out.writeU8(salt.length); + out.writeByteArray(salt); + } else + out.writeU8(0); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException +{ + hashAlg = st.getUInt8(); + flags = st.getUInt8(); + iterations = st.getUInt16(); + + String s = st.getString(); + if (s.equals("-")) + salt = null; + else { + st.unget(); + salt = st.getHexString(); + if (salt.length > 255) + throw st.exception("salt value too long"); + } +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(hashAlg); + sb.append(' '); + sb.append(flags); + sb.append(' '); + sb.append(iterations); + sb.append(' '); + if (salt == null) + sb.append('-'); + else + sb.append(base16.toString(salt)); + + return sb.toString(); +} + +/** Returns the hash algorithm */ +public int +getHashAlgorithm() { + return hashAlg; +} + +/** Returns the flags */ +public int +getFlags() { + return flags; +} + +/** Returns the number of iterations */ +public int +getIterations() { + return iterations; +} + +/** Returns the salt */ +public byte [] +getSalt() +{ + return salt; +} + +/** + * Hashes a name with the parameters of this NSEC3PARAM record. + * @param name The name to hash + * @return The hashed version of the name + * @throws NoSuchAlgorithmException The hash algorithm is unknown. + */ +public byte [] +hashName(Name name) throws NoSuchAlgorithmException +{ + return NSEC3Record.hashName(name, hashAlg, iterations, salt); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java new file mode 100644 index 0000000..aa086b8 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSEC3Record.java @@ -0,0 +1,266 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.security.*; + +import org.xbill.DNS.utils.*; + +/** + * Next SECure name 3 - this record contains the next hashed name in an + * ordered list of hashed names in the zone, and a set of types for which + * records exist for this name. The presence of this record in a response + * signifies a negative response from a DNSSEC-signed zone. + * + * This replaces the NSEC and NXT records, when used. + * + * @author Brian Wellington + * @author David Blacka + */ + +public class NSEC3Record extends Record { + +public static class Flags { + /** + * NSEC3 flags identifiers. + */ + + private Flags() {} + + /** Unsigned delegation are not included in the NSEC3 chain. + * + */ + public static final int OPT_OUT = 0x01; +} + +public static class Digest { + private Digest() {} + + /** SHA-1 */ + public static final int SHA1 = 1; +} + +public static final int SHA1_DIGEST_ID = Digest.SHA1; + +private static final long serialVersionUID = -7123504635968932855L; + +private int hashAlg; +private int flags; +private int iterations; +private byte [] salt; +private byte [] next; +private TypeBitmap types; + +private static final base32 b32 = new base32(base32.Alphabet.BASE32HEX, + false, false); + +NSEC3Record() {} + +Record getObject() { + return new NSEC3Record(); +} + +/** + * Creates an NSEC3 record from the given data. + * + * @param name The ownername of the NSEC3 record (base32'd hash plus zonename). + * @param dclass The class. + * @param ttl The TTL. + * @param hashAlg The hash algorithm. + * @param flags The value of the flags field. + * @param iterations The number of hash iterations. + * @param salt The salt to use (may be null). + * @param next The next hash (may not be null). + * @param types The types present at the original ownername. + */ +public NSEC3Record(Name name, int dclass, long ttl, int hashAlg, + int flags, int iterations, byte [] salt, byte [] next, + int [] types) +{ + super(name, Type.NSEC3, dclass, ttl); + this.hashAlg = checkU8("hashAlg", hashAlg); + this.flags = checkU8("flags", flags); + this.iterations = checkU16("iterations", iterations); + + if (salt != null) { + if (salt.length > 255) + throw new IllegalArgumentException("Invalid salt"); + if (salt.length > 0) { + this.salt = new byte[salt.length]; + System.arraycopy(salt, 0, this.salt, 0, salt.length); + } + } + + if (next.length > 255) { + throw new IllegalArgumentException("Invalid next hash"); + } + this.next = new byte[next.length]; + System.arraycopy(next, 0, this.next, 0, next.length); + this.types = new TypeBitmap(types); +} + +void +rrFromWire(DNSInput in) throws IOException { + hashAlg = in.readU8(); + flags = in.readU8(); + iterations = in.readU16(); + + int salt_length = in.readU8(); + if (salt_length > 0) + salt = in.readByteArray(salt_length); + else + salt = null; + + int next_length = in.readU8(); + next = in.readByteArray(next_length); + types = new TypeBitmap(in); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(hashAlg); + out.writeU8(flags); + out.writeU16(iterations); + + if (salt != null) { + out.writeU8(salt.length); + out.writeByteArray(salt); + } else + out.writeU8(0); + + out.writeU8(next.length); + out.writeByteArray(next); + types.toWire(out); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + hashAlg = st.getUInt8(); + flags = st.getUInt8(); + iterations = st.getUInt16(); + + String s = st.getString(); + if (s.equals("-")) + salt = null; + else { + st.unget(); + salt = st.getHexString(); + if (salt.length > 255) + throw st.exception("salt value too long"); + } + + next = st.getBase32String(b32); + types = new TypeBitmap(st); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(hashAlg); + sb.append(' '); + sb.append(flags); + sb.append(' '); + sb.append(iterations); + sb.append(' '); + if (salt == null) + sb.append('-'); + else + sb.append(base16.toString(salt)); + sb.append(' '); + sb.append(b32.toString(next)); + + if (!types.empty()) { + sb.append(' '); + sb.append(types.toString()); + } + + return sb.toString(); +} + +/** Returns the hash algorithm */ +public int +getHashAlgorithm() { + return hashAlg; +} + +/** Returns the flags */ +public int +getFlags() { + return flags; +} + +/** Returns the number of iterations */ +public int +getIterations() { + return iterations; +} + +/** Returns the salt */ +public byte [] +getSalt() +{ + return salt; +} + +/** Returns the next hash */ +public byte [] +getNext() { + return next; +} + + /** Returns the set of types defined for this name */ +public int [] +getTypes() { + return types.toArray(); +} + +/** Returns whether a specific type is in the set of types. */ +public boolean +hasType(int type) +{ + return types.contains(type); +} + +static byte [] +hashName(Name name, int hashAlg, int iterations, byte [] salt) +throws NoSuchAlgorithmException +{ + MessageDigest digest; + switch (hashAlg) { + case Digest.SHA1: + digest = MessageDigest.getInstance("sha-1"); + break; + default: + throw new NoSuchAlgorithmException("Unknown NSEC3 algorithm" + + "identifier: " + + hashAlg); + } + byte [] hash = null; + for (int i = 0; i <= iterations; i++) { + digest.reset(); + if (i == 0) + digest.update(name.toWireCanonical()); + else + digest.update(hash); + if (salt != null) + digest.update(salt); + hash = digest.digest(); + } + return hash; +} + +/** + * Hashes a name with the parameters of this NSEC3 record. + * @param name The name to hash + * @return The hashed version of the name + * @throws NoSuchAlgorithmException The hash algorithm is unknown. + */ +public byte [] +hashName(Name name) throws NoSuchAlgorithmException +{ + return hashName(name, hashAlg, iterations, salt); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java new file mode 100644 index 0000000..e523e37 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSECRecord.java @@ -0,0 +1,98 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Next SECure name - this record contains the following name in an + * ordered list of names in the zone, and a set of types for which + * records exist for this name. The presence of this record in a response + * signifies a negative response from a DNSSEC-signed zone. + * + * This replaces the NXT record. + * + * @author Brian Wellington + * @author David Blacka + */ + +public class NSECRecord extends Record { + +private static final long serialVersionUID = -5165065768816265385L; + +private Name next; +private TypeBitmap types; + +NSECRecord() {} + +Record +getObject() { + return new NSECRecord(); +} + +/** + * Creates an NSEC Record from the given data. + * @param next The following name in an ordered list of the zone + * @param types An array containing the types present. + */ +public +NSECRecord(Name name, int dclass, long ttl, Name next, int [] types) { + super(name, Type.NSEC, dclass, ttl); + this.next = checkName("next", next); + for (int i = 0; i < types.length; i++) { + Type.check(types[i]); + } + this.types = new TypeBitmap(types); +} + +void +rrFromWire(DNSInput in) throws IOException { + next = new Name(in); + types = new TypeBitmap(in); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + // Note: The next name is not lowercased. + next.toWire(out, null, false); + types.toWire(out); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + next = st.getName(origin); + types = new TypeBitmap(st); +} + +/** Converts rdata to a String */ +String +rrToString() +{ + StringBuffer sb = new StringBuffer(); + sb.append(next); + if (!types.empty()) { + sb.append(' '); + sb.append(types.toString()); + } + return sb.toString(); +} + +/** Returns the next name */ +public Name +getNext() { + return next; +} + +/** Returns the set of types defined for this name */ +public int [] +getTypes() { + return types.toArray(); +} + +/** Returns whether a specific type is in the set of types. */ +public boolean +hasType(int type) { + return types.contains(type); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java new file mode 100644 index 0000000..7bcbcd5 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSIDOption.java @@ -0,0 +1,29 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * The Name Server Identifier Option, define in RFC 5001. + * + * @see OPTRecord + * + * @author Brian Wellington + */ +public class NSIDOption extends GenericEDNSOption { + +private static final long serialVersionUID = 74739759292589056L; + +NSIDOption() { + super(EDNSOption.Code.NSID); +} + +/** + * Construct an NSID option. + * @param data The contents of the option. + */ +public +NSIDOption(byte [] data) { + super(EDNSOption.Code.NSID, data); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java new file mode 100644 index 0000000..2908da4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NSRecord.java @@ -0,0 +1,42 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Name Server Record - contains the name server serving the named zone + * + * @author Brian Wellington + */ + +public class NSRecord extends SingleCompressedNameBase { + +private static final long serialVersionUID = 487170758138268838L; + +NSRecord() {} + +Record +getObject() { + return new NSRecord(); +} + +/** + * Creates a new NS Record with the given data + * @param target The name server for the given domain + */ +public +NSRecord(Name name, int dclass, long ttl, Name target) { + super(name, Type.NS, dclass, ttl, target, "target"); +} + +/** Gets the target of the NS Record */ +public Name +getTarget() { + return getSingleName(); +} + +public Name +getAdditionalName() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java new file mode 100644 index 0000000..fa46d61 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NULLRecord.java @@ -0,0 +1,67 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * The NULL Record. This has no defined purpose, but can be used to + * hold arbitrary data. + * + * @author Brian Wellington + */ + +public class NULLRecord extends Record { + +private static final long serialVersionUID = -5796493183235216538L; + +private byte [] data; + +NULLRecord() {} + +Record +getObject() { + return new NULLRecord(); +} + +/** + * Creates a NULL record from the given data. + * @param data The contents of the record. + */ +public +NULLRecord(Name name, int dclass, long ttl, byte [] data) { + super(name, Type.NULL, dclass, ttl); + + if (data.length > 0xFFFF) { + throw new IllegalArgumentException("data must be <65536 bytes"); + } + this.data = data; +} + +void +rrFromWire(DNSInput in) throws IOException { + data = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no defined text format for NULL records"); +} + +String +rrToString() { + return unknownToString(data); +} + +/** Returns the contents of this record. */ +public byte [] +getData() { + return data; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java new file mode 100644 index 0000000..ad04e01 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NXTRecord.java @@ -0,0 +1,111 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * Next name - this record contains the following name in an ordered list + * of names in the zone, and a set of types for which records exist for + * this name. The presence of this record in a response signifies a + * failed query for data in a DNSSEC-signed zone. + * + * @author Brian Wellington + */ + +public class NXTRecord extends Record { + +private static final long serialVersionUID = -8851454400765507520L; + +private Name next; +private BitSet bitmap; + +NXTRecord() {} + +Record +getObject() { + return new NXTRecord(); +} + +/** + * Creates an NXT Record from the given data + * @param next The following name in an ordered list of the zone + * @param bitmap The set of type for which records exist at this name +*/ +public +NXTRecord(Name name, int dclass, long ttl, Name next, BitSet bitmap) { + super(name, Type.NXT, dclass, ttl); + this.next = checkName("next", next); + this.bitmap = bitmap; +} + +void +rrFromWire(DNSInput in) throws IOException { + next = new Name(in); + bitmap = new BitSet(); + int bitmapLength = in.remaining(); + for (int i = 0; i < bitmapLength; i++) { + int t = in.readU8(); + for (int j = 0; j < 8; j++) + if ((t & (1 << (7 - j))) != 0) + bitmap.set(i * 8 + j); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + next = st.getName(origin); + bitmap = new BitSet(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) + break; + int typecode = Type.value(t.value, true); + if (typecode <= 0 || typecode > 128) + throw st.exception("Invalid type: " + t.value); + bitmap.set(typecode); + } + st.unget(); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(next); + int length = bitmap.length(); + for (short i = 0; i < length; i++) + if (bitmap.get(i)) { + sb.append(" "); + sb.append(Type.string(i)); + } + return sb.toString(); +} + +/** Returns the next name */ +public Name +getNext() { + return next; +} + +/** Returns the set of types defined for this name */ +public BitSet +getBitmap() { + return bitmap; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + next.toWire(out, null, canonical); + int length = bitmap.length(); + for (int i = 0, t = 0; i < length; i++) { + t |= (bitmap.get(i) ? (1 << (7 - i % 8)) : 0); + if (i % 8 == 7 || i == length - 1) { + out.writeU8(t); + t = 0; + } + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Name.java b/external/asmack/build/src/trunk/org/xbill/DNS/Name.java new file mode 100644 index 0000000..1331ad9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Name.java @@ -0,0 +1,822 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.text.*; + +/** + * A representation of a domain name. It may either be absolute (fully + * qualified) or relative. + * + * @author Brian Wellington + */ + +public class Name implements Comparable, Serializable { + +private static final long serialVersionUID = -7257019940971525644L; + +private static final int LABEL_NORMAL = 0; +private static final int LABEL_COMPRESSION = 0xC0; +private static final int LABEL_MASK = 0xC0; + +/* The name data */ +private byte [] name; + +/* + * Effectively an 8 byte array, where the low order byte stores the number + * of labels and the 7 higher order bytes store per-label offsets. + */ +private long offsets; + +/* Precomputed hashcode. */ +private int hashcode; + +private static final byte [] emptyLabel = new byte[] {(byte)0}; +private static final byte [] wildLabel = new byte[] {(byte)1, (byte)'*'}; + +/** The root name */ +public static final Name root; + +/** The root name */ +public static final Name empty; + +/** The maximum length of a Name */ +private static final int MAXNAME = 255; + +/** The maximum length of a label a Name */ +private static final int MAXLABEL = 63; + +/** The maximum number of labels in a Name */ +private static final int MAXLABELS = 128; + +/** The maximum number of cached offsets */ +private static final int MAXOFFSETS = 7; + +/* Used for printing non-printable characters */ +private static final DecimalFormat byteFormat = new DecimalFormat(); + +/* Used to efficiently convert bytes to lowercase */ +private static final byte lowercase[] = new byte[256]; + +/* Used in wildcard names. */ +private static final Name wild; + +static { + byteFormat.setMinimumIntegerDigits(3); + for (int i = 0; i < lowercase.length; i++) { + if (i < 'A' || i > 'Z') + lowercase[i] = (byte)i; + else + lowercase[i] = (byte)(i - 'A' + 'a'); + } + root = new Name(); + root.appendSafe(emptyLabel, 0, 1); + empty = new Name(); + empty.name = new byte[0]; + wild = new Name(); + wild.appendSafe(wildLabel, 0, 1); +} + +private +Name() { +} + +private final void +setoffset(int n, int offset) { + if (n >= MAXOFFSETS) + return; + int shift = 8 * (7 - n); + offsets &= (~(0xFFL << shift)); + offsets |= ((long)offset << shift); +} + +private final int +offset(int n) { + if (n == 0 && getlabels() == 0) + return 0; + if (n < 0 || n >= getlabels()) + throw new IllegalArgumentException("label out of range"); + if (n < MAXOFFSETS) { + int shift = 8 * (7 - n); + return ((int)(offsets >>> shift) & 0xFF); + } else { + int pos = offset(MAXOFFSETS - 1); + for (int i = MAXOFFSETS - 1; i < n; i++) + pos += (name[pos] + 1); + return (pos); + } +} + +private final void +setlabels(int labels) { + offsets &= ~(0xFF); + offsets |= labels; +} + +private final int +getlabels() { + return (int)(offsets & 0xFF); +} + +private static final void +copy(Name src, Name dst) { + if (src.offset(0) == 0) { + dst.name = src.name; + dst.offsets = src.offsets; + } else { + int offset0 = src.offset(0); + int namelen = src.name.length - offset0; + int labels = src.labels(); + dst.name = new byte[namelen]; + System.arraycopy(src.name, offset0, dst.name, 0, namelen); + for (int i = 0; i < labels && i < MAXOFFSETS; i++) + dst.setoffset(i, src.offset(i) - offset0); + dst.setlabels(labels); + } +} + +private final void +append(byte [] array, int start, int n) throws NameTooLongException { + int length = (name == null ? 0 : (name.length - offset(0))); + int alength = 0; + for (int i = 0, pos = start; i < n; i++) { + int len = array[pos]; + if (len > MAXLABEL) + throw new IllegalStateException("invalid label"); + len++; + pos += len; + alength += len; + } + int newlength = length + alength; + if (newlength > MAXNAME) + throw new NameTooLongException(); + int labels = getlabels(); + int newlabels = labels + n; + if (newlabels > MAXLABELS) + throw new IllegalStateException("too many labels"); + byte [] newname = new byte[newlength]; + if (length != 0) + System.arraycopy(name, offset(0), newname, 0, length); + System.arraycopy(array, start, newname, length, alength); + name = newname; + for (int i = 0, pos = length; i < n; i++) { + setoffset(labels + i, pos); + pos += (newname[pos] + 1); + } + setlabels(newlabels); +} + +private static TextParseException +parseException(String str, String message) { + return new TextParseException("'" + str + "': " + message); +} + +private final void +appendFromString(String fullName, byte [] array, int start, int n) +throws TextParseException +{ + try { + append(array, start, n); + } + catch (NameTooLongException e) { + throw parseException(fullName, "Name too long"); + } +} + +private final void +appendSafe(byte [] array, int start, int n) { + try { + append(array, start, n); + } + catch (NameTooLongException e) { + } +} + +/** + * Create a new name from a string and an origin. This does not automatically + * make the name absolute; it will be absolute if it has a trailing dot or an + * absolute origin is appended. + * @param s The string to be converted + * @param origin If the name is not absolute, the origin to be appended. + * @throws TextParseException The name is invalid. + */ +public +Name(String s, Name origin) throws TextParseException { + if (s.equals("")) + throw parseException(s, "empty name"); + else if (s.equals("@")) { + if (origin == null) + copy(empty, this); + else + copy(origin, this); + return; + } else if (s.equals(".")) { + copy(root, this); + return; + } + int labelstart = -1; + int pos = 1; + byte [] label = new byte[MAXLABEL + 1]; + boolean escaped = false; + int digits = 0; + int intval = 0; + boolean absolute = false; + for (int i = 0; i < s.length(); i++) { + byte b = (byte) s.charAt(i); + if (escaped) { + if (b >= '0' && b <= '9' && digits < 3) { + digits++; + intval *= 10; + intval += (b - '0'); + if (intval > 255) + throw parseException(s, "bad escape"); + if (digits < 3) + continue; + b = (byte) intval; + } + else if (digits > 0 && digits < 3) + throw parseException(s, "bad escape"); + if (pos > MAXLABEL) + throw parseException(s, "label too long"); + labelstart = pos; + label[pos++] = b; + escaped = false; + } else if (b == '\\') { + escaped = true; + digits = 0; + intval = 0; + } else if (b == '.') { + if (labelstart == -1) + throw parseException(s, "invalid empty label"); + label[0] = (byte)(pos - 1); + appendFromString(s, label, 0, 1); + labelstart = -1; + pos = 1; + } else { + if (labelstart == -1) + labelstart = i; + if (pos > MAXLABEL) + throw parseException(s, "label too long"); + label[pos++] = b; + } + } + if (digits > 0 && digits < 3) + throw parseException(s, "bad escape"); + if (escaped) + throw parseException(s, "bad escape"); + if (labelstart == -1) { + appendFromString(s, emptyLabel, 0, 1); + absolute = true; + } else { + label[0] = (byte)(pos - 1); + appendFromString(s, label, 0, 1); + } + if (origin != null && !absolute) + appendFromString(s, origin.name, 0, origin.getlabels()); +} + +/** + * Create a new name from a string. This does not automatically make the name + * absolute; it will be absolute if it has a trailing dot. + * @param s The string to be converted + * @throws TextParseException The name is invalid. + */ +public +Name(String s) throws TextParseException { + this(s, null); +} + +/** + * Create a new name from a string and an origin. This does not automatically + * make the name absolute; it will be absolute if it has a trailing dot or an + * absolute origin is appended. This is identical to the constructor, except + * that it will avoid creating new objects in some cases. + * @param s The string to be converted + * @param origin If the name is not absolute, the origin to be appended. + * @throws TextParseException The name is invalid. + */ +public static Name +fromString(String s, Name origin) throws TextParseException { + if (s.equals("@") && origin != null) + return origin; + else if (s.equals(".")) + return (root); + + return new Name(s, origin); +} + +/** + * Create a new name from a string. This does not automatically make the name + * absolute; it will be absolute if it has a trailing dot. This is identical + * to the constructor, except that it will avoid creating new objects in some + * cases. + * @param s The string to be converted + * @throws TextParseException The name is invalid. + */ +public static Name +fromString(String s) throws TextParseException { + return fromString(s, null); +} + +/** + * Create a new name from a constant string. This should only be used when + the name is known to be good - that is, when it is constant. + * @param s The string to be converted + * @throws IllegalArgumentException The name is invalid. + */ +public static Name +fromConstantString(String s) { + try { + return fromString(s, null); + } + catch (TextParseException e) { + throw new IllegalArgumentException("Invalid name '" + s + "'"); + } +} + +/** + * Create a new name from DNS a wire format message + * @param in A stream containing the DNS message which is currently + * positioned at the start of the name to be read. + */ +public +Name(DNSInput in) throws WireParseException { + int len, pos; + boolean done = false; + byte [] label = new byte[MAXLABEL + 1]; + boolean savedState = false; + + while (!done) { + len = in.readU8(); + switch (len & LABEL_MASK) { + case LABEL_NORMAL: + if (getlabels() >= MAXLABELS) + throw new WireParseException("too many labels"); + if (len == 0) { + append(emptyLabel, 0, 1); + done = true; + } else { + label[0] = (byte)len; + in.readByteArray(label, 1, len); + append(label, 0, 1); + } + break; + case LABEL_COMPRESSION: + pos = in.readU8(); + pos += ((len & ~LABEL_MASK) << 8); + if (Options.check("verbosecompression")) + System.err.println("currently " + in.current() + + ", pointer to " + pos); + + if (pos >= in.current() - 2) + throw new WireParseException("bad compression"); + if (!savedState) { + in.save(); + savedState = true; + } + in.jump(pos); + if (Options.check("verbosecompression")) + System.err.println("current name '" + this + + "', seeking to " + pos); + break; + default: + throw new WireParseException("bad label type"); + } + } + if (savedState) { + in.restore(); + } +} + +/** + * Create a new name from DNS wire format + * @param b A byte array containing the wire format of the name. + */ +public +Name(byte [] b) throws IOException { + this(new DNSInput(b)); +} + +/** + * Create a new name by removing labels from the beginning of an existing Name + * @param src An existing Name + * @param n The number of labels to remove from the beginning in the copy + */ +public +Name(Name src, int n) { + int slabels = src.labels(); + if (n > slabels) + throw new IllegalArgumentException("attempted to remove too " + + "many labels"); + name = src.name; + setlabels(slabels - n); + for (int i = 0; i < MAXOFFSETS && i < slabels - n; i++) + setoffset(i, src.offset(i + n)); +} + +/** + * Creates a new name by concatenating two existing names. + * @param prefix The prefix name. + * @param suffix The suffix name. + * @return The concatenated name. + * @throws NameTooLongException The name is too long. + */ +public static Name +concatenate(Name prefix, Name suffix) throws NameTooLongException { + if (prefix.isAbsolute()) + return (prefix); + Name newname = new Name(); + copy(prefix, newname); + newname.append(suffix.name, suffix.offset(0), suffix.getlabels()); + return newname; +} + +/** + * If this name is a subdomain of origin, return a new name relative to + * origin with the same value. Otherwise, return the existing name. + * @param origin The origin to remove. + * @return The possibly relativized name. + */ +public Name +relativize(Name origin) { + if (origin == null || !subdomain(origin)) + return this; + Name newname = new Name(); + copy(this, newname); + int length = length() - origin.length(); + int labels = newname.labels() - origin.labels(); + newname.setlabels(labels); + newname.name = new byte[length]; + System.arraycopy(name, offset(0), newname.name, 0, length); + return newname; +} + +/** + * Generates a new Name with the first n labels replaced by a wildcard + * @return The wildcard name + */ +public Name +wild(int n) { + if (n < 1) + throw new IllegalArgumentException("must replace 1 or more " + + "labels"); + try { + Name newname = new Name(); + copy(wild, newname); + newname.append(name, offset(n), getlabels() - n); + return newname; + } + catch (NameTooLongException e) { + throw new IllegalStateException + ("Name.wild: concatenate failed"); + } +} + +/** + * Generates a new Name to be used when following a DNAME. + * @param dname The DNAME record to follow. + * @return The constructed name. + * @throws NameTooLongException The resulting name is too long. + */ +public Name +fromDNAME(DNAMERecord dname) throws NameTooLongException { + Name dnameowner = dname.getName(); + Name dnametarget = dname.getTarget(); + if (!subdomain(dnameowner)) + return null; + + int plabels = labels() - dnameowner.labels(); + int plength = length() - dnameowner.length(); + int pstart = offset(0); + + int dlabels = dnametarget.labels(); + int dlength = dnametarget.length(); + + if (plength + dlength > MAXNAME) + throw new NameTooLongException(); + + Name newname = new Name(); + newname.setlabels(plabels + dlabels); + newname.name = new byte[plength + dlength]; + System.arraycopy(name, pstart, newname.name, 0, plength); + System.arraycopy(dnametarget.name, 0, newname.name, plength, dlength); + + for (int i = 0, pos = 0; i < MAXOFFSETS && i < plabels + dlabels; i++) { + newname.setoffset(i, pos); + pos += (newname.name[pos] + 1); + } + return newname; +} + +/** + * Is this name a wildcard? + */ +public boolean +isWild() { + if (labels() == 0) + return false; + return (name[0] == (byte)1 && name[1] == (byte)'*'); +} + +/** + * Is this name absolute? + */ +public boolean +isAbsolute() { + if (labels() == 0) + return false; + return (name[name.length - 1] == 0); +} + +/** + * The length of the name. + */ +public short +length() { + if (getlabels() == 0) + return 0; + return (short)(name.length - offset(0)); +} + +/** + * The number of labels in the name. + */ +public int +labels() { + return getlabels(); +} + +/** + * Is the current Name a subdomain of the specified name? + */ +public boolean +subdomain(Name domain) { + int labels = labels(); + int dlabels = domain.labels(); + if (dlabels > labels) + return false; + if (dlabels == labels) + return equals(domain); + return domain.equals(name, offset(labels - dlabels)); +} + +private String +byteString(byte [] array, int pos) { + StringBuffer sb = new StringBuffer(); + int len = array[pos++]; + for (int i = pos; i < pos + len; i++) { + int b = array[i] & 0xFF; + if (b <= 0x20 || b >= 0x7f) { + sb.append('\\'); + sb.append(byteFormat.format(b)); + } + else if (b == '"' || b == '(' || b == ')' || b == '.' || + b == ';' || b == '\\' || b == '@' || b == '$') + { + sb.append('\\'); + sb.append((char)b); + } + else + sb.append((char)b); + } + return sb.toString(); +} + +/** + * Convert a Name to a String + * @return The representation of this name as a (printable) String. + */ +public String +toString() { + int labels = labels(); + if (labels == 0) + return "@"; + else if (labels == 1 && name[offset(0)] == 0) + return "."; + StringBuffer sb = new StringBuffer(); + for (int i = 0, pos = offset(0); i < labels; i++) { + int len = name[pos]; + if (len > MAXLABEL) + throw new IllegalStateException("invalid label"); + if (len == 0) + break; + sb.append(byteString(name, pos)); + sb.append('.'); + pos += (1 + len); + } + if (!isAbsolute()) + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); +} + +/** + * Retrieve the nth label of a Name. This makes a copy of the label; changing + * this does not change the Name. + * @param n The label to be retrieved. The first label is 0. + */ +public byte [] +getLabel(int n) { + int pos = offset(n); + byte len = (byte)(name[pos] + 1); + byte [] label = new byte[len]; + System.arraycopy(name, pos, label, 0, len); + return label; +} + +/** + * Convert the nth label in a Name to a String + * @param n The label to be converted to a (printable) String. The first + * label is 0. + */ +public String +getLabelString(int n) { + int pos = offset(n); + return byteString(name, pos); +} + +/** + * Emit a Name in DNS wire format + * @param out The output stream containing the DNS message. + * @param c The compression context, or null of no compression is desired. + * @throws IllegalArgumentException The name is not absolute. + */ +public void +toWire(DNSOutput out, Compression c) { + if (!isAbsolute()) + throw new IllegalArgumentException("toWire() called on " + + "non-absolute name"); + + int labels = labels(); + for (int i = 0; i < labels - 1; i++) { + Name tname; + if (i == 0) + tname = this; + else + tname = new Name(this, i); + int pos = -1; + if (c != null) + pos = c.get(tname); + if (pos >= 0) { + pos |= (LABEL_MASK << 8); + out.writeU16(pos); + return; + } else { + if (c != null) + c.add(out.current(), tname); + int off = offset(i); + out.writeByteArray(name, off, name[off] + 1); + } + } + out.writeU8(0); +} + +/** + * Emit a Name in DNS wire format + * @throws IllegalArgumentException The name is not absolute. + */ +public byte [] +toWire() { + DNSOutput out = new DNSOutput(); + toWire(out, null); + return out.toByteArray(); +} + +/** + * Emit a Name in canonical DNS wire format (all lowercase) + * @param out The output stream to which the message is written. + */ +public void +toWireCanonical(DNSOutput out) { + byte [] b = toWireCanonical(); + out.writeByteArray(b); +} + +/** + * Emit a Name in canonical DNS wire format (all lowercase) + * @return The canonical form of the name. + */ +public byte [] +toWireCanonical() { + int labels = labels(); + if (labels == 0) + return (new byte[0]); + byte [] b = new byte[name.length - offset(0)]; + for (int i = 0, spos = offset(0), dpos = 0; i < labels; i++) { + int len = name[spos]; + if (len > MAXLABEL) + throw new IllegalStateException("invalid label"); + b[dpos++] = name[spos++]; + for (int j = 0; j < len; j++) + b[dpos++] = lowercase[(name[spos++] & 0xFF)]; + } + return b; +} + +/** + * Emit a Name in DNS wire format + * @param out The output stream containing the DNS message. + * @param c The compression context, or null of no compression is desired. + * @param canonical If true, emit the name in canonicalized form + * (all lowercase). + * @throws IllegalArgumentException The name is not absolute. + */ +public void +toWire(DNSOutput out, Compression c, boolean canonical) { + if (canonical) + toWireCanonical(out); + else + toWire(out, c); +} + +private final boolean +equals(byte [] b, int bpos) { + int labels = labels(); + for (int i = 0, pos = offset(0); i < labels; i++) { + if (name[pos] != b[bpos]) + return false; + int len = name[pos++]; + bpos++; + if (len > MAXLABEL) + throw new IllegalStateException("invalid label"); + for (int j = 0; j < len; j++) + if (lowercase[(name[pos++] & 0xFF)] != + lowercase[(b[bpos++] & 0xFF)]) + return false; + } + return true; +} + +/** + * Are these two Names equivalent? + */ +public boolean +equals(Object arg) { + if (arg == this) + return true; + if (arg == null || !(arg instanceof Name)) + return false; + Name d = (Name) arg; + if (d.hashcode == 0) + d.hashCode(); + if (hashcode == 0) + hashCode(); + if (d.hashcode != hashcode) + return false; + if (d.labels() != labels()) + return false; + return equals(d.name, d.offset(0)); +} + +/** + * Computes a hashcode based on the value + */ +public int +hashCode() { + if (hashcode != 0) + return (hashcode); + int code = 0; + for (int i = offset(0); i < name.length; i++) + code += ((code << 3) + lowercase[(name[i] & 0xFF)]); + hashcode = code; + return hashcode; +} + +/** + * Compares this Name to another Object. + * @param o The Object to be compared. + * @return The value 0 if the argument is a name equivalent to this name; + * a value less than 0 if the argument is less than this name in the canonical + * ordering, and a value greater than 0 if the argument is greater than this + * name in the canonical ordering. + * @throws ClassCastException if the argument is not a Name. + */ +public int +compareTo(Object o) { + Name arg = (Name) o; + + if (this == arg) + return (0); + + int labels = labels(); + int alabels = arg.labels(); + int compares = labels > alabels ? alabels : labels; + + for (int i = 1; i <= compares; i++) { + int start = offset(labels - i); + int astart = arg.offset(alabels - i); + int length = name[start]; + int alength = arg.name[astart]; + for (int j = 0; j < length && j < alength; j++) { + int n = lowercase[(name[j + start + 1]) & 0xFF] - + lowercase[(arg.name[j + astart + 1]) & 0xFF]; + if (n != 0) + return (n); + } + if (length != alength) + return (length - alength); + } + return (labels - alabels); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java b/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java new file mode 100644 index 0000000..114be39 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/NameTooLongException.java @@ -0,0 +1,24 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when a name is longer than the maximum length of a DNS + * name. + * + * @author Brian Wellington + */ + +public class NameTooLongException extends WireParseException { + +public +NameTooLongException() { + super(); +} + +public +NameTooLongException(String s) { + super(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java new file mode 100644 index 0000000..47fef2f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/OPTRecord.java @@ -0,0 +1,191 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * Options - describes Extended DNS (EDNS) properties of a Message. + * No specific options are defined other than those specified in the + * header. An OPT should be generated by Resolver. + * + * EDNS is a method to extend the DNS protocol while providing backwards + * compatibility and not significantly changing the protocol. This + * implementation of EDNS is mostly complete at level 0. + * + * @see Message + * @see Resolver + * + * @author Brian Wellington + */ + +public class OPTRecord extends Record { + +private static final long serialVersionUID = -6254521894809367938L; + +private List options; + +OPTRecord() {} + +Record +getObject() { + return new OPTRecord(); +} + +/** + * Creates an OPT Record. This is normally called by SimpleResolver, but can + * also be called by a server. + * @param payloadSize The size of a packet that can be reassembled on the + * sending host. + * @param xrcode The value of the extended rcode field. This is the upper + * 16 bits of the full rcode. + * @param flags Additional message flags. + * @param version The EDNS version that this DNS implementation supports. + * This should be 0 for dnsjava. + * @param options The list of options that comprise the data field. There + * are currently no defined options. + * @see ExtendedFlags + */ +public +OPTRecord(int payloadSize, int xrcode, int version, int flags, List options) { + super(Name.root, Type.OPT, payloadSize, 0); + checkU16("payloadSize", payloadSize); + checkU8("xrcode", xrcode); + checkU8("version", version); + checkU16("flags", flags); + ttl = ((long)xrcode << 24) + ((long)version << 16) + flags; + if (options != null) { + this.options = new ArrayList(options); + } +} + +/** + * Creates an OPT Record with no data. This is normally called by + * SimpleResolver, but can also be called by a server. + * @param payloadSize The size of a packet that can be reassembled on the + * sending host. + * @param xrcode The value of the extended rcode field. This is the upper + * 16 bits of the full rcode. + * @param flags Additional message flags. + * @param version The EDNS version that this DNS implementation supports. + * This should be 0 for dnsjava. + * @see ExtendedFlags + */ +public +OPTRecord(int payloadSize, int xrcode, int version, int flags) { + this(payloadSize, xrcode, version, flags, null); +} + +/** + * Creates an OPT Record with no data. This is normally called by + * SimpleResolver, but can also be called by a server. + */ +public +OPTRecord(int payloadSize, int xrcode, int version) { + this(payloadSize, xrcode, version, 0, null); +} + +void +rrFromWire(DNSInput in) throws IOException { + if (in.remaining() > 0) + options = new ArrayList(); + while (in.remaining() > 0) { + EDNSOption option = EDNSOption.fromWire(in); + options.add(option); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for OPT"); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + if (options != null) { + sb.append(options); + sb.append(" "); + } + sb.append(" ; payload "); + sb.append(getPayloadSize()); + sb.append(", xrcode "); + sb.append(getExtendedRcode()); + sb.append(", version "); + sb.append(getVersion()); + sb.append(", flags "); + sb.append(getFlags()); + return sb.toString(); +} + +/** Returns the maximum allowed payload size. */ +public int +getPayloadSize() { + return dclass; +} + +/** + * Returns the extended Rcode + * @see Rcode + */ +public int +getExtendedRcode() { + return (int)(ttl >>> 24); +} + +/** Returns the highest supported EDNS version */ +public int +getVersion() { + return (int)((ttl >>> 16) & 0xFF); +} + +/** Returns the EDNS flags */ +public int +getFlags() { + return (int)(ttl & 0xFFFF); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + if (options == null) + return; + Iterator it = options.iterator(); + while (it.hasNext()) { + EDNSOption option = (EDNSOption) it.next(); + option.toWire(out); + } +} + +/** + * Gets all options in the OPTRecord. This returns a list of EDNSOptions. + */ +public List +getOptions() { + if (options == null) + return Collections.EMPTY_LIST; + return Collections.unmodifiableList(options); +} + +/** + * Gets all options in the OPTRecord with a specific code. This returns a list + * of EDNSOptions. + */ +public List +getOptions(int code) { + if (options == null) + return Collections.EMPTY_LIST; + List list = Collections.EMPTY_LIST; + for (Iterator it = options.iterator(); it.hasNext(); ) { + EDNSOption opt = (EDNSOption) it.next(); + if (opt.getCode() == code) { + if (list == Collections.EMPTY_LIST) + list = new ArrayList(); + list.add(opt); + } + } + return list; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java b/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java new file mode 100644 index 0000000..dadbca1 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Opcode.java @@ -0,0 +1,60 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to DNS opcodes + * + * @author Brian Wellington + */ + +public final class Opcode { + +/** A standard query */ +public static final int QUERY = 0; + +/** An inverse query (deprecated) */ +public static final int IQUERY = 1; + +/** A server status request (not used) */ +public static final int STATUS = 2; + +/** + * A message from a primary to a secondary server to initiate a zone transfer + */ +public static final int NOTIFY = 4; + +/** A dynamic update message */ +public static final int UPDATE = 5; + +private static Mnemonic opcodes = new Mnemonic("DNS Opcode", + Mnemonic.CASE_UPPER); + +static { + opcodes.setMaximum(0xF); + opcodes.setPrefix("RESERVED"); + opcodes.setNumericAllowed(true); + + opcodes.add(QUERY, "QUERY"); + opcodes.add(IQUERY, "IQUERY"); + opcodes.add(STATUS, "STATUS"); + opcodes.add(NOTIFY, "NOTIFY"); + opcodes.add(UPDATE, "UPDATE"); +} + +private +Opcode() {} + +/** Converts a numeric Opcode into a String */ +public static String +string(int i) { + return opcodes.getText(i); +} + +/** Converts a String representation of an Opcode into its numeric value */ +public static int +value(String s) { + return opcodes.getValue(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Options.java b/external/asmack/build/src/trunk/org/xbill/DNS/Options.java new file mode 100644 index 0000000..2f1dae3 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Options.java @@ -0,0 +1,123 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * Boolean options:
      + * bindttl - Print TTLs in BIND format
      + * multiline - Print records in multiline format
      + * noprintin - Don't print the class of a record if it's IN
      + * verbose - Turn on general debugging statements
      + * verbosemsg - Print all messages sent or received by SimpleResolver
      + * verbosecompression - Print messages related to name compression
      + * verbosesec - Print messages related to signature verification
      + * verbosecache - Print messages related to cache lookups
      + *
      + * Valued options:
      + * tsigfudge=n - Sets the default TSIG fudge value (in seconds)
      + * sig0validity=n - Sets the default SIG(0) validity period (in seconds)
      + * + * @author Brian Wellington + */ + +public final class Options { + +private static Map table; + +static { + try { + refresh(); + } + catch (SecurityException e) { + } +} + +private +Options() {} + +public static void +refresh() { + String s = System.getProperty("dnsjava.options"); + if (s != null) { + StringTokenizer st = new StringTokenizer(s, ","); + while (st.hasMoreTokens()) { + String token = st.nextToken(); + int index = token.indexOf('='); + if (index == -1) + set(token); + else { + String option = token.substring(0, index); + String value = token.substring(index + 1); + set(option, value); + } + } + } +} + +/** Clears all defined options */ +public static void +clear() { + table = null; +} + +/** Sets an option to "true" */ +public static void +set(String option) { + if (table == null) + table = new HashMap(); + table.put(option.toLowerCase(), "true"); +} + +/** Sets an option to the the supplied value */ +public static void +set(String option, String value) { + if (table == null) + table = new HashMap(); + table.put(option.toLowerCase(), value.toLowerCase()); +} + +/** Removes an option */ +public static void +unset(String option) { + if (table == null) + return; + table.remove(option.toLowerCase()); +} + +/** Checks if an option is defined */ +public static boolean +check(String option) { + if (table == null) + return false; + return (table.get(option.toLowerCase()) != null); +} + +/** Returns the value of an option */ +public static String +value(String option) { + if (table == null) + return null; + return ((String)table.get(option.toLowerCase())); +} + +/** + * Returns the value of an option as an integer, or -1 if not defined. + */ +public static int +intValue(String option) { + String s = value(option); + if (s != null) { + try { + int val = Integer.parseInt(s); + if (val > 0) + return (val); + } + catch (NumberFormatException e) { + } + } + return (-1); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java new file mode 100644 index 0000000..89be578 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/PTRRecord.java @@ -0,0 +1,38 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Pointer Record - maps a domain name representing an Internet Address to + * a hostname. + * + * @author Brian Wellington + */ + +public class PTRRecord extends SingleCompressedNameBase { + +private static final long serialVersionUID = -8321636610425434192L; + +PTRRecord() {} + +Record +getObject() { + return new PTRRecord(); +} + +/** + * Creates a new PTR Record with the given data + * @param target The name of the machine with this address + */ +public +PTRRecord(Name name, int dclass, long ttl, Name target) { + super(name, Type.PTR, dclass, ttl, target, "target"); +} + +/** Gets the target of the PTR Record */ +public Name +getTarget() { + return getSingleName(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java new file mode 100644 index 0000000..a407241 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/PXRecord.java @@ -0,0 +1,96 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * X.400 mail mapping record. + * + * @author Brian Wellington + */ + +public class PXRecord extends Record { + +private static final long serialVersionUID = 1811540008806660667L; + +private int preference; +private Name map822; +private Name mapX400; + +PXRecord() {} + +Record +getObject() { + return new PXRecord(); +} + +/** + * Creates an PX Record from the given data + * @param preference The preference of this mail address. + * @param map822 The RFC 822 component of the mail address. + * @param mapX400 The X.400 component of the mail address. + */ +public +PXRecord(Name name, int dclass, long ttl, int preference, + Name map822, Name mapX400) +{ + super(name, Type.PX, dclass, ttl); + + this.preference = checkU16("preference", preference); + this.map822 = checkName("map822", map822); + this.mapX400 = checkName("mapX400", mapX400); +} + +void +rrFromWire(DNSInput in) throws IOException { + preference = in.readU16(); + map822 = new Name(in); + mapX400 = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + preference = st.getUInt16(); + map822 = st.getName(origin); + mapX400 = st.getName(origin); +} + +/** Converts the PX Record to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(preference); + sb.append(" "); + sb.append(map822); + sb.append(" "); + sb.append(mapX400); + return sb.toString(); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(preference); + map822.toWire(out, null, canonical); + mapX400.toWire(out, null, canonical); +} + +/** Gets the preference of the route. */ +public int +getPreference() { + return preference; +} + +/** Gets the RFC 822 component of the mail address. */ +public Name +getMap822() { + return map822; +} + +/** Gets the X.400 component of the mail address. */ +public Name +getMapX400() { + return mapX400; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java new file mode 100644 index 0000000..7aa066c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/RPRecord.java @@ -0,0 +1,82 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Responsible Person Record - lists the mail address of a responsible person + * and a domain where TXT records are available. + * + * @author Tom Scola + * @author Brian Wellington + */ + +public class RPRecord extends Record { + +private static final long serialVersionUID = 8124584364211337460L; + +private Name mailbox; +private Name textDomain; + +RPRecord() {} + +Record +getObject() { + return new RPRecord(); +} + +/** + * Creates an RP Record from the given data + * @param mailbox The responsible person + * @param textDomain The address where TXT records can be found + */ +public +RPRecord(Name name, int dclass, long ttl, Name mailbox, Name textDomain) { + super(name, Type.RP, dclass, ttl); + + this.mailbox = checkName("mailbox", mailbox); + this.textDomain = checkName("textDomain", textDomain); +} + +void +rrFromWire(DNSInput in) throws IOException { + mailbox = new Name(in); + textDomain = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + mailbox = st.getName(origin); + textDomain = st.getName(origin); +} + +/** Converts the RP Record to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(mailbox); + sb.append(" "); + sb.append(textDomain); + return sb.toString(); +} + +/** Gets the mailbox address of the RP Record */ +public Name +getMailbox() { + return mailbox; +} + +/** Gets the text domain info of the RP Record */ +public Name +getTextDomain() { + return textDomain; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + mailbox.toWire(out, null, canonical); + textDomain.toWire(out, null, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java new file mode 100644 index 0000000..c092839 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/RRSIGRecord.java @@ -0,0 +1,50 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * Recource Record Signature - An RRSIG provides the digital signature of an + * RRset, so that the data can be authenticated by a DNSSEC-capable resolver. + * The signature is generated by a key contained in a DNSKEY Record. + * @see RRset + * @see DNSSEC + * @see KEYRecord + * + * @author Brian Wellington + */ + +public class RRSIGRecord extends SIGBase { + +private static final long serialVersionUID = -2609150673537226317L; + +RRSIGRecord() {} + +Record +getObject() { + return new RRSIGRecord(); +} + +/** + * Creates an RRSIG Record from the given data + * @param covered The RRset type covered by this signature + * @param alg The cryptographic algorithm of the key that generated the + * signature + * @param origttl The original TTL of the RRset + * @param expire The time at which the signature expires + * @param timeSigned The time at which this signature was generated + * @param footprint The footprint/key id of the signing key. + * @param signer The owner of the signing key + * @param signature Binary data representing the signature + */ +public +RRSIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl, + Date expire, Date timeSigned, int footprint, Name signer, + byte [] signature) +{ + super(name, Type.RRSIG, dclass, ttl, covered, alg, origttl, expire, + timeSigned, footprint, signer, signature); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java b/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java new file mode 100644 index 0000000..fa1a6ad --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/RRset.java @@ -0,0 +1,258 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.Serializable; +import java.util.*; + +/** + * A set of Records with the same name, type, and class. Also included + * are all RRSIG records signing the data records. + * @see Record + * @see RRSIGRecord + * + * @author Brian Wellington + */ + +public class RRset implements Serializable { + +private static final long serialVersionUID = -3270249290171239695L; + +/* + * rrs contains both normal and RRSIG records, with the RRSIG records + * at the end. + */ +private List rrs; +private short nsigs; +private short position; + +/** Creates an empty RRset */ +public +RRset() { + rrs = new ArrayList(1); + nsigs = 0; + position = 0; +} + +/** Creates an RRset and sets its contents to the specified record */ +public +RRset(Record record) { + this(); + safeAddRR(record); +} + +/** Creates an RRset with the contents of an existing RRset */ +public +RRset(RRset rrset) { + synchronized (rrset) { + rrs = (List) ((ArrayList)rrset.rrs).clone(); + nsigs = rrset.nsigs; + position = rrset.position; + } +} + +private void +safeAddRR(Record r) { + if (!(r instanceof RRSIGRecord)) { + if (nsigs == 0) + rrs.add(r); + else + rrs.add(rrs.size() - nsigs, r); + } else { + rrs.add(r); + nsigs++; + } +} + +/** Adds a Record to an RRset */ +public synchronized void +addRR(Record r) { + if (rrs.size() == 0) { + safeAddRR(r); + return; + } + Record first = first(); + if (!r.sameRRset(first)) + throw new IllegalArgumentException("record does not match " + + "rrset"); + + if (r.getTTL() != first.getTTL()) { + if (r.getTTL() > first.getTTL()) { + r = r.cloneRecord(); + r.setTTL(first.getTTL()); + } else { + for (int i = 0; i < rrs.size(); i++) { + Record tmp = (Record) rrs.get(i); + tmp = tmp.cloneRecord(); + tmp.setTTL(r.getTTL()); + rrs.set(i, tmp); + } + } + } + + if (!rrs.contains(r)) + safeAddRR(r); +} + +/** Deletes a Record from an RRset */ +public synchronized void +deleteRR(Record r) { + if (rrs.remove(r) && (r instanceof RRSIGRecord)) + nsigs--; +} + +/** Deletes all Records from an RRset */ +public synchronized void +clear() { + rrs.clear(); + position = 0; + nsigs = 0; +} + +private synchronized Iterator +iterator(boolean data, boolean cycle) { + int size, start, total; + + total = rrs.size(); + + if (data) + size = total - nsigs; + else + size = nsigs; + if (size == 0) + return Collections.EMPTY_LIST.iterator(); + + if (data) { + if (!cycle) + start = 0; + else { + if (position >= size) + position = 0; + start = position++; + } + } else { + start = total - nsigs; + } + + List list = new ArrayList(size); + if (data) { + list.addAll(rrs.subList(start, size)); + if (start != 0) + list.addAll(rrs.subList(0, start)); + } else { + list.addAll(rrs.subList(start, total)); + } + + return list.iterator(); +} + +/** + * Returns an Iterator listing all (data) records. + * @param cycle If true, cycle through the records so that each Iterator will + * start with a different record. + */ +public synchronized Iterator +rrs(boolean cycle) { + return iterator(true, cycle); +} + +/** + * Returns an Iterator listing all (data) records. This cycles through + * the records, so each Iterator will start with a different record. + */ +public synchronized Iterator +rrs() { + return iterator(true, true); +} + +/** Returns an Iterator listing all signature records */ +public synchronized Iterator +sigs() { + return iterator(false, false); +} + +/** Returns the number of (data) records */ +public synchronized int +size() { + return rrs.size() - nsigs; +} + +/** + * Returns the name of the records + * @see Name + */ +public Name +getName() { + return first().getName(); +} + +/** + * Returns the type of the records + * @see Type + */ +public int +getType() { + return first().getRRsetType(); +} + +/** + * Returns the class of the records + * @see DClass + */ +public int +getDClass() { + return first().getDClass(); +} + +/** Returns the ttl of the records */ +public synchronized long +getTTL() { + return first().getTTL(); +} + +/** + * Returns the first record + * @throws IllegalStateException if the rrset is empty + */ +public synchronized Record +first() { + if (rrs.size() == 0) + throw new IllegalStateException("rrset is empty"); + return (Record) rrs.get(0); +} + +private String +iteratorToString(Iterator it) { + StringBuffer sb = new StringBuffer(); + while (it.hasNext()) { + Record rr = (Record) it.next(); + sb.append("["); + sb.append(rr.rdataToString()); + sb.append("]"); + if (it.hasNext()) + sb.append(" "); + } + return sb.toString(); +} + +/** Converts the RRset to a String */ +public String +toString() { + if (rrs == null) + return ("{empty}"); + StringBuffer sb = new StringBuffer(); + sb.append("{ "); + sb.append(getName() + " "); + sb.append(getTTL() + " "); + sb.append(DClass.string(getDClass()) + " "); + sb.append(Type.string(getType()) + " "); + sb.append(iteratorToString(iterator(true, false))); + if (nsigs > 0) { + sb.append(" sigs: "); + sb.append(iteratorToString(iterator(false, false))); + } + sb.append(" }"); + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java new file mode 100644 index 0000000..549731e --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/RTRecord.java @@ -0,0 +1,48 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Route Through Record - lists a route preference and intermediate host. + * + * @author Brian Wellington + */ + +public class RTRecord extends U16NameBase { + +private static final long serialVersionUID = -3206215651648278098L; + +RTRecord() {} + +Record +getObject() { + return new RTRecord(); +} + +/** + * Creates an RT Record from the given data + * @param preference The preference of the route. Smaller numbers indicate + * more preferred routes. + * @param intermediateHost The domain name of the host to use as a router. + */ +public +RTRecord(Name name, int dclass, long ttl, int preference, + Name intermediateHost) +{ + super(name, Type.RT, dclass, ttl, preference, "preference", + intermediateHost, "intermediateHost"); +} + +/** Gets the preference of the route. */ +public int +getPreference() { + return getU16Field(); +} + +/** Gets the host to use as a router. */ +public Name +getIntermediateHost() { + return getNameField(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java b/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java new file mode 100644 index 0000000..7f0dd1f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Rcode.java @@ -0,0 +1,123 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to DNS rcodes (error values) + * + * @author Brian Wellington + */ + +public final class Rcode { + +private static Mnemonic rcodes = new Mnemonic("DNS Rcode", + Mnemonic.CASE_UPPER); + +private static Mnemonic tsigrcodes = new Mnemonic("TSIG rcode", + Mnemonic.CASE_UPPER); + +/** No error */ +public static final int NOERROR = 0; + +/** Format error */ +public static final int FORMERR = 1; + +/** Server failure */ +public static final int SERVFAIL = 2; + +/** The name does not exist */ +public static final int NXDOMAIN = 3; + +/** The operation requested is not implemented */ +public static final int NOTIMP = 4; + +/** Deprecated synonym for NOTIMP. */ +public static final int NOTIMPL = 4; + +/** The operation was refused by the server */ +public static final int REFUSED = 5; + +/** The name exists */ +public static final int YXDOMAIN = 6; + +/** The RRset (name, type) exists */ +public static final int YXRRSET = 7; + +/** The RRset (name, type) does not exist */ +public static final int NXRRSET = 8; + +/** The requestor is not authorized to perform this operation */ +public static final int NOTAUTH = 9; + +/** The zone specified is not a zone */ +public static final int NOTZONE = 10; + +/* EDNS extended rcodes */ +/** Unsupported EDNS level */ +public static final int BADVERS = 16; + +/* TSIG/TKEY only rcodes */ +/** The signature is invalid (TSIG/TKEY extended error) */ +public static final int BADSIG = 16; + +/** The key is invalid (TSIG/TKEY extended error) */ +public static final int BADKEY = 17; + +/** The time is out of range (TSIG/TKEY extended error) */ +public static final int BADTIME = 18; + +/** The mode is invalid (TKEY extended error) */ +public static final int BADMODE = 19; + +static { + rcodes.setMaximum(0xFFF); + rcodes.setPrefix("RESERVED"); + rcodes.setNumericAllowed(true); + + rcodes.add(NOERROR, "NOERROR"); + rcodes.add(FORMERR, "FORMERR"); + rcodes.add(SERVFAIL, "SERVFAIL"); + rcodes.add(NXDOMAIN, "NXDOMAIN"); + rcodes.add(NOTIMP, "NOTIMP"); + rcodes.addAlias(NOTIMP, "NOTIMPL"); + rcodes.add(REFUSED, "REFUSED"); + rcodes.add(YXDOMAIN, "YXDOMAIN"); + rcodes.add(YXRRSET, "YXRRSET"); + rcodes.add(NXRRSET, "NXRRSET"); + rcodes.add(NOTAUTH, "NOTAUTH"); + rcodes.add(NOTZONE, "NOTZONE"); + rcodes.add(BADVERS, "BADVERS"); + + tsigrcodes.setMaximum(0xFFFF); + tsigrcodes.setPrefix("RESERVED"); + tsigrcodes.setNumericAllowed(true); + tsigrcodes.addAll(rcodes); + + tsigrcodes.add(BADSIG, "BADSIG"); + tsigrcodes.add(BADKEY, "BADKEY"); + tsigrcodes.add(BADTIME, "BADTIME"); + tsigrcodes.add(BADMODE, "BADMODE"); +} + +private +Rcode() {} + +/** Converts a numeric Rcode into a String */ +public static String +string(int i) { + return rcodes.getText(i); +} + +/** Converts a numeric TSIG extended Rcode into a String */ +public static String +TSIGstring(int i) { + return tsigrcodes.getText(i); +} + +/** Converts a String representation of an Rcode into its numeric value */ +public static int +value(String s) { + return rcodes.getValue(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/Record.java new file mode 100644 index 0000000..8da7015 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Record.java @@ -0,0 +1,736 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.text.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * A generic DNS resource record. The specific record types extend this class. + * A record contains a name, type, class, ttl, and rdata. + * + * @author Brian Wellington + */ + +public abstract class Record implements Cloneable, Comparable, Serializable { + +private static final long serialVersionUID = 2694906050116005466L; + +protected Name name; +protected int type, dclass; +protected long ttl; + +private static final DecimalFormat byteFormat = new DecimalFormat(); + +static { + byteFormat.setMinimumIntegerDigits(3); +} + +protected +Record() {} + +Record(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + this.name = name; + this.type = type; + this.dclass = dclass; + this.ttl = ttl; +} + +/** + * Creates an empty record of the correct type; must be overriden + */ +abstract Record +getObject(); + +private static final Record +getEmptyRecord(Name name, int type, int dclass, long ttl, boolean hasData) { + Record proto, rec; + + if (hasData) { + proto = Type.getProto(type); + if (proto != null) + rec = proto.getObject(); + else + rec = new UNKRecord(); + } else + rec = new EmptyRecord(); + rec.name = name; + rec.type = type; + rec.dclass = dclass; + rec.ttl = ttl; + return rec; +} + +/** + * Converts the type-specific RR to wire format - must be overriden + */ +abstract void +rrFromWire(DNSInput in) throws IOException; + +private static Record +newRecord(Name name, int type, int dclass, long ttl, int length, DNSInput in) +throws IOException +{ + Record rec; + rec = getEmptyRecord(name, type, dclass, ttl, in != null); + if (in != null) { + if (in.remaining() < length) + throw new WireParseException("truncated record"); + in.setActive(length); + + rec.rrFromWire(in); + + if (in.remaining() > 0) + throw new WireParseException("invalid record length"); + in.clearActive(); + } + return rec; +} + +/** + * Creates a new record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param length The length of the record's data. + * @param data The rdata of the record, in uncompressed DNS wire format. Only + * the first length bytes are used. + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl, int length, byte [] data) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + DNSInput in; + if (data != null) + in = new DNSInput(data); + else + in = null; + try { + return newRecord(name, type, dclass, ttl, length, in); + } + catch (IOException e) { + return null; + } +} + +/** + * Creates a new record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param data The complete rdata of the record, in uncompressed DNS wire + * format. + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl, byte [] data) { + return newRecord(name, type, dclass, ttl, data.length, data); +} + +/** + * Creates a new empty record, with the given parameters. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @return An object of a subclass of Record + */ +public static Record +newRecord(Name name, int type, int dclass, long ttl) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + return getEmptyRecord(name, type, dclass, ttl, false); +} + +/** + * Creates a new empty record, with the given parameters. This method is + * designed to create records that will be added to the QUERY section + * of a message. + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @return An object of a subclass of Record + */ +public static Record +newRecord(Name name, int type, int dclass) { + return newRecord(name, type, dclass, 0); +} + +static Record +fromWire(DNSInput in, int section, boolean isUpdate) throws IOException { + int type, dclass; + long ttl; + int length; + Name name; + Record rec; + + name = new Name(in); + type = in.readU16(); + dclass = in.readU16(); + + if (section == Section.QUESTION) + return newRecord(name, type, dclass); + + ttl = in.readU32(); + length = in.readU16(); + if (length == 0 && isUpdate && + (section == Section.PREREQ || section == Section.UPDATE)) + return newRecord(name, type, dclass, ttl); + rec = newRecord(name, type, dclass, ttl, length, in); + return rec; +} + +static Record +fromWire(DNSInput in, int section) throws IOException { + return fromWire(in, section, false); +} + +/** + * Builds a Record from DNS uncompressed wire format. + */ +public static Record +fromWire(byte [] b, int section) throws IOException { + return fromWire(new DNSInput(b), section, false); +} + +void +toWire(DNSOutput out, int section, Compression c) { + name.toWire(out, c); + out.writeU16(type); + out.writeU16(dclass); + if (section == Section.QUESTION) + return; + out.writeU32(ttl); + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, c, false); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); +} + +/** + * Converts a Record into DNS uncompressed wire format. + */ +public byte [] +toWire(int section) { + DNSOutput out = new DNSOutput(); + toWire(out, section, null); + return out.toByteArray(); +} + +private void +toWireCanonical(DNSOutput out, boolean noTTL) { + name.toWireCanonical(out); + out.writeU16(type); + out.writeU16(dclass); + if (noTTL) { + out.writeU32(0); + } else { + out.writeU32(ttl); + } + int lengthPosition = out.current(); + out.writeU16(0); /* until we know better */ + rrToWire(out, null, true); + int rrlength = out.current() - lengthPosition - 2; + out.writeU16At(rrlength, lengthPosition); +} + +/* + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase), optionally ignoring the TTL. + */ +private byte [] +toWireCanonical(boolean noTTL) { + DNSOutput out = new DNSOutput(); + toWireCanonical(out, noTTL); + return out.toByteArray(); +} + +/** + * Converts a Record into canonical DNS uncompressed wire format (all names are + * converted to lowercase). + */ +public byte [] +toWireCanonical() { + return toWireCanonical(false); +} + +/** + * Converts the rdata in a Record into canonical DNS uncompressed wire format + * (all names are converted to lowercase). + */ +public byte [] +rdataToWireCanonical() { + DNSOutput out = new DNSOutput(); + rrToWire(out, null, true); + return out.toByteArray(); +} + +/** + * Converts the type-specific RR to text format - must be overriden + */ +abstract String rrToString(); + +/** + * Converts the rdata portion of a Record into a String representation + */ +public String +rdataToString() { + return rrToString(); +} + +/** + * Converts a Record into a String representation + */ +public String +toString() { + StringBuffer sb = new StringBuffer(); + sb.append(name); + if (sb.length() < 8) + sb.append("\t"); + if (sb.length() < 16) + sb.append("\t"); + sb.append("\t"); + if (Options.check("BINDTTL")) + sb.append(TTL.format(ttl)); + else + sb.append(ttl); + sb.append("\t"); + if (dclass != DClass.IN || !Options.check("noPrintIN")) { + sb.append(DClass.string(dclass)); + sb.append("\t"); + } + sb.append(Type.string(type)); + String rdata = rrToString(); + if (!rdata.equals("")) { + sb.append("\t"); + sb.append(rdata); + } + return sb.toString(); +} + +/** + * Converts the text format of an RR to the internal format - must be overriden + */ +abstract void +rdataFromString(Tokenizer st, Name origin) throws IOException; + +/** + * Converts a String into a byte array. + */ +protected static byte [] +byteArrayFromString(String s) throws TextParseException { + byte [] array = s.getBytes(); + boolean escaped = false; + boolean hasEscapes = false; + + for (int i = 0; i < array.length; i++) { + if (array[i] == '\\') { + hasEscapes = true; + break; + } + } + if (!hasEscapes) { + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + return array; + } + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int digits = 0; + int intval = 0; + for (int i = 0; i < array.length; i++) { + byte b = array[i]; + if (escaped) { + if (b >= '0' && b <= '9' && digits < 3) { + digits++; + intval *= 10; + intval += (b - '0'); + if (intval > 255) + throw new TextParseException + ("bad escape"); + if (digits < 3) + continue; + b = (byte) intval; + } + else if (digits > 0 && digits < 3) + throw new TextParseException("bad escape"); + os.write(b); + escaped = false; + } + else if (array[i] == '\\') { + escaped = true; + digits = 0; + intval = 0; + } + else + os.write(array[i]); + } + if (digits > 0 && digits < 3) + throw new TextParseException("bad escape"); + array = os.toByteArray(); + if (array.length > 255) { + throw new TextParseException("text string too long"); + } + + return os.toByteArray(); +} + +/** + * Converts a byte array into a String. + */ +protected static String +byteArrayToString(byte [] array, boolean quote) { + StringBuffer sb = new StringBuffer(); + if (quote) + sb.append('"'); + for (int i = 0; i < array.length; i++) { + int b = array[i] & 0xFF; + if (b < 0x20 || b >= 0x7f) { + sb.append('\\'); + sb.append(byteFormat.format(b)); + } else if (b == '"' || b == '\\') { + sb.append('\\'); + sb.append((char)b); + } else + sb.append((char)b); + } + if (quote) + sb.append('"'); + return sb.toString(); +} + +/** + * Converts a byte array into the unknown RR format. + */ +protected static String +unknownToString(byte [] data) { + StringBuffer sb = new StringBuffer(); + sb.append("\\# "); + sb.append(data.length); + sb.append(" "); + sb.append(base16.toString(data)); + return sb.toString(); +} + +/** + * Builds a new Record from its textual representation + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param st A tokenizer containing the textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * @return The new record + * @throws IOException The text format was invalid. + */ +public static Record +fromString(Name name, int type, int dclass, long ttl, Tokenizer st, Name origin) +throws IOException +{ + Record rec; + + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Type.check(type); + DClass.check(dclass); + TTL.check(ttl); + + Tokenizer.Token t = st.get(); + if (t.type == Tokenizer.IDENTIFIER && t.value.equals("\\#")) { + int length = st.getUInt16(); + byte [] data = st.getHex(); + if (data == null) { + data = new byte[0]; + } + if (length != data.length) + throw st.exception("invalid unknown RR encoding: " + + "length mismatch"); + DNSInput in = new DNSInput(data); + return newRecord(name, type, dclass, ttl, length, in); + } + st.unget(); + rec = getEmptyRecord(name, type, dclass, ttl, true); + rec.rdataFromString(st, origin); + t = st.get(); + if (t.type != Tokenizer.EOL && t.type != Tokenizer.EOF) { + throw st.exception("unexpected tokens at end of record"); + } + return rec; +} + +/** + * Builds a new Record from its textual representation + * @param name The owner name of the record. + * @param type The record's type. + * @param dclass The record's class. + * @param ttl The record's time to live. + * @param s The textual representation of the rdata. + * @param origin The default origin to be appended to relative domain names. + * @return The new record + * @throws IOException The text format was invalid. + */ +public static Record +fromString(Name name, int type, int dclass, long ttl, String s, Name origin) +throws IOException +{ + return fromString(name, type, dclass, ttl, new Tokenizer(s), origin); +} + +/** + * Returns the record's name + * @see Name + */ +public Name +getName() { + return name; +} + +/** + * Returns the record's type + * @see Type + */ +public int +getType() { + return type; +} + +/** + * Returns the type of RRset that this record would belong to. For all types + * except RRSIG, this is equivalent to getType(). + * @return The type of record, if not RRSIG. If the type is RRSIG, + * the type covered is returned. + * @see Type + * @see RRset + * @see SIGRecord + */ +public int +getRRsetType() { + if (type == Type.RRSIG) { + RRSIGRecord sig = (RRSIGRecord) this; + return sig.getTypeCovered(); + } + return type; +} + +/** + * Returns the record's class + */ +public int +getDClass() { + return dclass; +} + +/** + * Returns the record's TTL + */ +public long +getTTL() { + return ttl; +} + +/** + * Converts the type-specific RR to wire format - must be overriden + */ +abstract void +rrToWire(DNSOutput out, Compression c, boolean canonical); + +/** + * Determines if two Records could be part of the same RRset. + * This compares the name, type, and class of the Records; the ttl and + * rdata are not compared. + */ +public boolean +sameRRset(Record rec) { + return (getRRsetType() == rec.getRRsetType() && + dclass == rec.dclass && + name.equals(rec.name)); +} + +/** + * Determines if two Records are identical. This compares the name, type, + * class, and rdata (with names canonicalized). The TTLs are not compared. + * @param arg The record to compare to + * @return true if the records are equal, false otherwise. + */ +public boolean +equals(Object arg) { + if (arg == null || !(arg instanceof Record)) + return false; + Record r = (Record) arg; + if (type != r.type || dclass != r.dclass || !name.equals(r.name)) + return false; + byte [] array1 = rdataToWireCanonical(); + byte [] array2 = r.rdataToWireCanonical(); + return Arrays.equals(array1, array2); +} + +/** + * Generates a hash code based on the Record's data. + */ +public int +hashCode() { + byte [] array = toWireCanonical(true); + int code = 0; + for (int i = 0; i < array.length; i++) + code += ((code << 3) + (array[i] & 0xFF)); + return code; +} + +Record +cloneRecord() { + try { + return (Record) clone(); + } + catch (CloneNotSupportedException e) { + throw new IllegalStateException(); + } +} + +/** + * Creates a new record identical to the current record, but with a different + * name. This is most useful for replacing the name of a wildcard record. + */ +public Record +withName(Name name) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + Record rec = cloneRecord(); + rec.name = name; + return rec; +} + +/** + * Creates a new record identical to the current record, but with a different + * class and ttl. This is most useful for dynamic update. + */ +Record +withDClass(int dclass, long ttl) { + Record rec = cloneRecord(); + rec.dclass = dclass; + rec.ttl = ttl; + return rec; +} + +/* Sets the TTL to the specified value. This is intentionally not public. */ +void +setTTL(long ttl) { + this.ttl = ttl; +} + +/** + * Compares this Record to another Object. + * @param o The Object to be compared. + * @return The value 0 if the argument is a record equivalent to this record; + * a value less than 0 if the argument is less than this record in the + * canonical ordering, and a value greater than 0 if the argument is greater + * than this record in the canonical ordering. The canonical ordering + * is defined to compare by name, class, type, and rdata. + * @throws ClassCastException if the argument is not a Record. + */ +public int +compareTo(Object o) { + Record arg = (Record) o; + + if (this == arg) + return (0); + + int n = name.compareTo(arg.name); + if (n != 0) + return (n); + n = dclass - arg.dclass; + if (n != 0) + return (n); + n = type - arg.type; + if (n != 0) + return (n); + byte [] rdata1 = rdataToWireCanonical(); + byte [] rdata2 = arg.rdataToWireCanonical(); + for (int i = 0; i < rdata1.length && i < rdata2.length; i++) { + n = (rdata1[i] & 0xFF) - (rdata2[i] & 0xFF); + if (n != 0) + return (n); + } + return (rdata1.length - rdata2.length); +} + +/** + * Returns the name for which additional data processing should be done + * for this record. This can be used both for building responses and + * parsing responses. + * @return The name to used for additional data processing, or null if this + * record type does not require additional data processing. + */ +public Name +getAdditionalName() { + return null; +} + +/* Checks that an int contains an unsigned 8 bit value */ +static int +checkU8(String field, int val) { + if (val < 0 || val > 0xFF) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 8 " + + "bit value"); + return val; +} + +/* Checks that an int contains an unsigned 16 bit value */ +static int +checkU16(String field, int val) { + if (val < 0 || val > 0xFFFF) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 16 " + + "bit value"); + return val; +} + +/* Checks that a long contains an unsigned 32 bit value */ +static long +checkU32(String field, long val) { + if (val < 0 || val > 0xFFFFFFFFL) + throw new IllegalArgumentException("\"" + field + "\" " + val + + " must be an unsigned 32 " + + "bit value"); + return val; +} + +/* Checks that a name is absolute */ +static Name +checkName(String field, Name name) { + if (!name.isAbsolute()) + throw new RelativeNameException(name); + return name; +} + +static byte [] +checkByteArrayLength(String field, byte [] array, int maxLength) { + if (array.length > 0xFFFF) + throw new IllegalArgumentException("\"" + field + "\" array " + + "must have no more than " + + maxLength + " elements"); + byte [] out = new byte[array.length]; + System.arraycopy(array, 0, out, 0, array.length); + return out; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java b/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java new file mode 100644 index 0000000..869fd39 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/RelativeNameException.java @@ -0,0 +1,24 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when a relative name is passed as an argument to + * a method requiring an absolute name. + * + * @author Brian Wellington + */ + +public class RelativeNameException extends IllegalArgumentException { + +public +RelativeNameException(Name name) { + super("'" + name + "' is not an absolute name"); +} + +public +RelativeNameException(String s) { + super(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java new file mode 100644 index 0000000..3087cdb --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolveThread.java @@ -0,0 +1,45 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * A special-purpose thread used by Resolvers (both SimpleResolver and + * ExtendedResolver) to perform asynchronous queries. + * + * @author Brian Wellington + */ + +class ResolveThread extends Thread { + +private Message query; +private Object id; +private ResolverListener listener; +private Resolver res; + +/** Creates a new ResolveThread */ +public +ResolveThread(Resolver res, Message query, Object id, + ResolverListener listener) +{ + this.res = res; + this.query = query; + this.id = id; + this.listener = listener; +} + + +/** + * Performs the query, and executes the callback. + */ +public void +run() { + try { + Message response = res.send(query); + listener.receiveMessage(id, response); + } + catch (Exception e) { + listener.handleException(id, e); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java new file mode 100644 index 0000000..7d28d40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Resolver.java @@ -0,0 +1,95 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * Interface describing a resolver. + * + * @author Brian Wellington + */ + +public interface Resolver { + +/** + * Sets the port to communicate with on the server + * @param port The port to send messages to + */ +void setPort(int port); + +/** + * Sets whether TCP connections will be sent by default + * @param flag Indicates whether TCP connections are made + */ +void setTCP(boolean flag); + +/** + * Sets whether truncated responses will be ignored. If not, a truncated + * response over UDP will cause a retransmission over TCP. + * @param flag Indicates whether truncated responses should be ignored. + */ +void setIgnoreTruncation(boolean flag); + +/** + * Sets the EDNS version used on outgoing messages. + * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no + * EDNS. + * @throws IllegalArgumentException An invalid level was indicated. + */ +void setEDNS(int level); + +/** + * Sets the EDNS information on outgoing messages. + * @param level The EDNS level to use. 0 indicates EDNS0 and -1 indicates no + * EDNS. + * @param payloadSize The maximum DNS packet size that this host is capable + * of receiving over UDP. If 0 is specified, the default (1280) is used. + * @param flags EDNS extended flags to be set in the OPT record. + * @param options EDNS options to be set in the OPT record, specified as a + * List of OPTRecord.Option elements. + * @throws IllegalArgumentException An invalid field was specified. + * @see OPTRecord + */ +void setEDNS(int level, int payloadSize, int flags, List options); + +/** + * Specifies the TSIG key that messages will be signed with + * @param key The key + */ +void setTSIGKey(TSIG key); + +/** + * Sets the amount of time to wait for a response before giving up. + * @param secs The number of seconds to wait. + * @param msecs The number of milliseconds to wait. + */ +void setTimeout(int secs, int msecs); + +/** + * Sets the amount of time to wait for a response before giving up. + * @param secs The number of seconds to wait. + */ +void setTimeout(int secs); + +/** + * Sends a message and waits for a response. + * @param query The query to send. + * @return The response + * @throws IOException An error occurred while sending or receiving. + */ +Message send(Message query) throws IOException; + +/** + * Asynchronously sends a message registering a listener to receive a callback + * on success or exception. Multiple asynchronous lookups can be performed + * in parallel. Since the callback may be invoked before the function returns, + * external synchronization is necessary. + * @param query The query to send + * @param listener The object containing the callbacks. + * @return An identifier, which is also a parameter in the callback + */ +Object sendAsync(final Message query, final ResolverListener listener); + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java new file mode 100644 index 0000000..7b09daf --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverConfig.java @@ -0,0 +1,509 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +/** + * A class that tries to locate name servers and the search path to + * be appended to unqualified names. + * + * The following are attempted, in order, until one succeeds. + *
        + *
      • The properties 'dns.server' and 'dns.search' (comma delimited lists) + * are checked. The servers can either be IP addresses or hostnames + * (which are resolved using Java's built in DNS support). + *
      • The sun.net.dns.ResolverConfiguration class is queried. + *
      • On Unix, /etc/resolv.conf is parsed. + *
      • On Windows, ipconfig/winipcfg is called and its output parsed. This + * may fail for non-English versions on Windows. + *
      • "localhost" is used as the nameserver, and the search path is empty. + *
      + * + * These routines will be called internally when creating Resolvers/Lookups + * without explicitly specifying server names, and can also be called + * directly if desired. + * + * @author Brian Wellington + * @author Yannick Meudal + * @author Arnt Gulbrandsen + */ + +public class ResolverConfig { + +private String [] servers = null; +private Name [] searchlist = null; +private int ndots = -1; + +private static ResolverConfig currentConfig; + +static { + refresh(); +} + +public +ResolverConfig() { + if (findProperty()) + return; + if (findSunJVM()) + return; + if (servers == null || searchlist == null) { + String OS = System.getProperty("os.name"); + String vendor = System.getProperty("java.vendor"); + if (OS.indexOf("Windows") != -1) { + if (OS.indexOf("95") != -1 || + OS.indexOf("98") != -1 || + OS.indexOf("ME") != -1) + find95(); + else + findNT(); + } else if (OS.indexOf("NetWare") != -1) { + findNetware(); + } else if (vendor.indexOf("Android") != -1) { + findAndroid(); + } else { + findUnix(); + } + } +} + +private void +addServer(String server, List list) { + if (list.contains(server)) + return; + if (Options.check("verbose")) + System.out.println("adding server " + server); + list.add(server); +} + +private void +addSearch(String search, List list) { + Name name; + if (Options.check("verbose")) + System.out.println("adding search " + search); + try { + name = Name.fromString(search, Name.root); + } + catch (TextParseException e) { + return; + } + if (list.contains(name)) + return; + list.add(name); +} + +private int +parseNdots(String token) { + token = token.substring(6); + try { + int ndots = Integer.parseInt(token); + if (ndots >= 0) { + if (Options.check("verbose")) + System.out.println("setting ndots " + token); + return ndots; + } + } + catch (NumberFormatException e) { + } + return -1; +} + +private void +configureFromLists(List lserver, List lsearch) { + if (servers == null && lserver.size() > 0) + servers = (String []) lserver.toArray(new String[0]); + if (searchlist == null && lsearch.size() > 0) + searchlist = (Name []) lsearch.toArray(new Name[0]); +} + +private void +configureNdots(int lndots) { + if (ndots < 0 && lndots > 0) + ndots = lndots; +} + +/** + * Looks in the system properties to find servers and a search path. + * Servers are defined by dns.server=server1,server2... + * The search path is defined by dns.search=domain1,domain2... + */ +private boolean +findProperty() { + String prop; + List lserver = new ArrayList(0); + List lsearch = new ArrayList(0); + StringTokenizer st; + + prop = System.getProperty("dns.server"); + if (prop != null) { + st = new StringTokenizer(prop, ","); + while (st.hasMoreTokens()) + addServer(st.nextToken(), lserver); + } + + prop = System.getProperty("dns.search"); + if (prop != null) { + st = new StringTokenizer(prop, ","); + while (st.hasMoreTokens()) + addSearch(st.nextToken(), lsearch); + } + configureFromLists(lserver, lsearch); + return (servers != null && searchlist != null); +} + +/** + * Uses the undocumented Sun DNS implementation to determine the configuration. + * This doesn't work or even compile with all JVMs (gcj, for example). + */ +private boolean +findSunJVM() { + List lserver = new ArrayList(0); + List lserver_tmp; + List lsearch = new ArrayList(0); + List lsearch_tmp; + + try { + Class [] noClasses = new Class[0]; + Object [] noObjects = new Object[0]; + String resConfName = "sun.net.dns.ResolverConfiguration"; + Class resConfClass = Class.forName(resConfName); + Object resConf; + + // ResolverConfiguration resConf = ResolverConfiguration.open(); + Method open = resConfClass.getDeclaredMethod("open", noClasses); + resConf = open.invoke(null, noObjects); + + // lserver_tmp = resConf.nameservers(); + Method nameservers = resConfClass.getMethod("nameservers", + noClasses); + lserver_tmp = (List) nameservers.invoke(resConf, noObjects); + + // lsearch_tmp = resConf.searchlist(); + Method searchlist = resConfClass.getMethod("searchlist", + noClasses); + lsearch_tmp = (List) searchlist.invoke(resConf, noObjects); + } + catch (Exception e) { + return false; + } + + if (lserver_tmp.size() == 0) + return false; + + if (lserver_tmp.size() > 0) { + Iterator it = lserver_tmp.iterator(); + while (it.hasNext()) + addServer((String) it.next(), lserver); + } + + if (lsearch_tmp.size() > 0) { + Iterator it = lsearch_tmp.iterator(); + while (it.hasNext()) + addSearch((String) it.next(), lsearch); + } + configureFromLists(lserver, lsearch); + return true; +} + +/** + * Looks in /etc/resolv.conf to find servers and a search path. + * "nameserver" lines specify servers. "domain" and "search" lines + * define the search path. + */ +private void +findResolvConf(String file) { + InputStream in = null; + try { + in = new FileInputStream(file); + } + catch (FileNotFoundException e) { + return; + } + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + List lserver = new ArrayList(0); + List lsearch = new ArrayList(0); + int lndots = -1; + try { + String line; + while ((line = br.readLine()) != null) { + if (line.startsWith("nameserver")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip nameserver */ + addServer(st.nextToken(), lserver); + } + else if (line.startsWith("domain")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip domain */ + if (!st.hasMoreTokens()) + continue; + if (lsearch.isEmpty()) + addSearch(st.nextToken(), lsearch); + } + else if (line.startsWith("search")) { + if (!lsearch.isEmpty()) + lsearch.clear(); + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip search */ + while (st.hasMoreTokens()) + addSearch(st.nextToken(), lsearch); + } + else if(line.startsWith("options")) { + StringTokenizer st = new StringTokenizer(line); + st.nextToken(); /* skip options */ + while (st.hasMoreTokens()) { + String token = st.nextToken(); + if (token.startsWith("ndots:")) { + lndots = parseNdots(token); + } + } + } + } + br.close(); + } + catch (IOException e) { + } + + configureFromLists(lserver, lsearch); + configureNdots(lndots); +} + +private void +findUnix() { + findResolvConf("/etc/resolv.conf"); +} + +private void +findNetware() { + findResolvConf("sys:/etc/resolv.cfg"); +} + +/** + * Parses the output of winipcfg or ipconfig. + */ +private void +findWin(InputStream in, Locale locale) { + String packageName = ResolverConfig.class.getPackage().getName(); + String resPackageName = packageName + ".windows.DNSServer"; + ResourceBundle res; + if (locale != null) + res = ResourceBundle.getBundle(resPackageName, locale); + else + res = ResourceBundle.getBundle(resPackageName); + + String host_name = res.getString("host_name"); + String primary_dns_suffix = res.getString("primary_dns_suffix"); + String dns_suffix = res.getString("dns_suffix"); + String dns_servers = res.getString("dns_servers"); + + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + try { + List lserver = new ArrayList(); + List lsearch = new ArrayList(); + String line = null; + boolean readingServers = false; + boolean readingSearches = false; + while ((line = br.readLine()) != null) { + StringTokenizer st = new StringTokenizer(line); + if (!st.hasMoreTokens()) { + readingServers = false; + readingSearches = false; + continue; + } + String s = st.nextToken(); + if (line.indexOf(":") != -1) { + readingServers = false; + readingSearches = false; + } + + if (line.indexOf(host_name) != -1) { + while (st.hasMoreTokens()) + s = st.nextToken(); + Name name; + try { + name = Name.fromString(s, null); + } + catch (TextParseException e) { + continue; + } + if (name.labels() == 1) + continue; + addSearch(s, lsearch); + } else if (line.indexOf(primary_dns_suffix) != -1) { + while (st.hasMoreTokens()) + s = st.nextToken(); + if (s.equals(":")) + continue; + addSearch(s, lsearch); + readingSearches = true; + } else if (readingSearches || + line.indexOf(dns_suffix) != -1) + { + while (st.hasMoreTokens()) + s = st.nextToken(); + if (s.equals(":")) + continue; + addSearch(s, lsearch); + readingSearches = true; + } else if (readingServers || + line.indexOf(dns_servers) != -1) + { + while (st.hasMoreTokens()) + s = st.nextToken(); + if (s.equals(":")) + continue; + addServer(s, lserver); + readingServers = true; + } + } + + configureFromLists(lserver, lsearch); + } + catch (IOException e) { + } + return; +} + +private void +findWin(InputStream in) { + String property = "org.xbill.DNS.windows.parse.buffer"; + final int defaultBufSize = 8 * 1024; + int bufSize = Integer.getInteger(property, defaultBufSize).intValue(); + BufferedInputStream b = new BufferedInputStream(in, bufSize); + b.mark(bufSize); + findWin(b, null); + if (servers == null) { + try { + b.reset(); + } + catch (IOException e) { + return; + } + findWin(b, new Locale("", "")); + } +} + +/** + * Calls winipcfg and parses the result to find servers and a search path. + */ +private void +find95() { + String s = "winipcfg.out"; + try { + Process p; + p = Runtime.getRuntime().exec("winipcfg /all /batch " + s); + p.waitFor(); + File f = new File(s); + findWin(new FileInputStream(f)); + new File(s).delete(); + } + catch (Exception e) { + return; + } +} + +/** + * Calls ipconfig and parses the result to find servers and a search path. + */ +private void +findNT() { + try { + Process p; + p = Runtime.getRuntime().exec("ipconfig /all"); + findWin(p.getInputStream()); + p.destroy(); + } + catch (Exception e) { + return; + } +} + +/** + * Parses the output of getprop, which is the only way to get DNS + * info on Android. getprop might disappear in future releases, so + * this code comes with a use-by date. + */ +private void +findAndroid() { + // This originally looked for all lines containing .dns; but + // http://code.google.com/p/android/issues/detail?id=2207#c73 + // indicates that net.dns* should always be the active nameservers, so + // we use those. + String re1 = "^\\d+(\\.\\d+){3}$"; + String re2 = "^[0-9a-f]+(:[0-9a-f]*)+:[0-9a-f]+$"; + try { + ArrayList lserver = new ArrayList(); + ArrayList lsearch = new ArrayList(); + String line; + Process p = Runtime.getRuntime().exec("getprop"); + InputStream in = p.getInputStream(); + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + while ((line = br.readLine()) != null ) { + StringTokenizer t = new StringTokenizer(line, ":"); + String name = t.nextToken(); + if (name.indexOf( "net.dns" ) > -1) { + String v = t.nextToken(); + v = v.replaceAll("[ \\[\\]]", ""); + if ((v.matches(re1) || v.matches(re2)) && + !lserver.contains(v)) + lserver.add(v); + } + } + configureFromLists(lserver, lsearch); + } catch ( Exception e ) { + // ignore resolutely + } +} + +/** Returns all located servers */ +public String [] +servers() { + return servers; +} + +/** Returns the first located server */ +public String +server() { + if (servers == null) + return null; + return servers[0]; +} + +/** Returns all entries in the located search path */ +public Name [] +searchPath() { + return searchlist; +} + +/** + * Returns the located ndots value, or the default (1) if not configured. + * Note that ndots can only be configured in a resolv.conf file, and will only + * take effect if ResolverConfig uses resolv.conf directly (that is, if the + * JVM does not include the sun.net.dns.ResolverConfiguration class). + */ +public int +ndots() { + if (ndots < 0) + return 1; + return ndots; +} + +/** Gets the current configuration */ +public static synchronized ResolverConfig +getCurrentConfig() { + return currentConfig; +} + +/** Gets the current configuration */ +public static void +refresh() { + ResolverConfig newConfig = new ResolverConfig(); + synchronized (ResolverConfig.class) { + currentConfig = newConfig; + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java new file mode 100644 index 0000000..accf82c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ResolverListener.java @@ -0,0 +1,30 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.EventListener; + +/** + * An interface to the asynchronous resolver. + * @see Resolver + * + * @author Brian Wellington + */ + +public interface ResolverListener extends EventListener { + +/** + * The callback used by an asynchronous resolver + * @param id The identifier returned by Resolver.sendAsync() + * @param m The response message as returned by the Resolver + */ +void receiveMessage(Object id, Message m); + +/** + * The callback used by an asynchronous resolver when an exception is thrown + * @param id The identifier returned by Resolver.sendAsync() + * @param e The thrown exception + */ +void handleException(Object id, Exception e); + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java b/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java new file mode 100644 index 0000000..f4293de --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ReverseMap.java @@ -0,0 +1,130 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.net.*; + +/** + * A set functions designed to deal with DNS names used in reverse mappings. + * For the IPv4 address a.b.c.d, the reverse map name is d.c.b.a.in-addr.arpa. + * For an IPv6 address, the reverse map name is ...ip6.arpa. + * + * @author Brian Wellington + */ + +public final class ReverseMap { + +private static Name inaddr4 = Name.fromConstantString("in-addr.arpa."); +private static Name inaddr6 = Name.fromConstantString("ip6.arpa."); + +/* Otherwise the class could be instantiated */ +private +ReverseMap() {} + +/** + * Creates a reverse map name corresponding to an address contained in + * an array of 4 bytes (for an IPv4 address) or 16 bytes (for an IPv6 address). + * @param addr The address from which to build a name. + * @return The name corresponding to the address in the reverse map. + */ +public static Name +fromAddress(byte [] addr) { + if (addr.length != 4 && addr.length != 16) + throw new IllegalArgumentException("array must contain " + + "4 or 16 elements"); + + StringBuffer sb = new StringBuffer(); + if (addr.length == 4) { + for (int i = addr.length - 1; i >= 0; i--) { + sb.append(addr[i] & 0xFF); + if (i > 0) + sb.append("."); + } + } else { + int [] nibbles = new int[2]; + for (int i = addr.length - 1; i >= 0; i--) { + nibbles[0] = (addr[i] & 0xFF) >> 4; + nibbles[1] = (addr[i] & 0xFF) & 0xF; + for (int j = nibbles.length - 1; j >= 0; j--) { + sb.append(Integer.toHexString(nibbles[j])); + if (i > 0 || j > 0) + sb.append("."); + } + } + } + + try { + if (addr.length == 4) + return Name.fromString(sb.toString(), inaddr4); + else + return Name.fromString(sb.toString(), inaddr6); + } + catch (TextParseException e) { + throw new IllegalStateException("name cannot be invalid"); + } +} + +/** + * Creates a reverse map name corresponding to an address contained in + * an array of 4 integers between 0 and 255 (for an IPv4 address) or 16 + * integers between 0 and 255 (for an IPv6 address). + * @param addr The address from which to build a name. + * @return The name corresponding to the address in the reverse map. + */ +public static Name +fromAddress(int [] addr) { + byte [] bytes = new byte[addr.length]; + for (int i = 0; i < addr.length; i++) { + if (addr[i] < 0 || addr[i] > 0xFF) + throw new IllegalArgumentException("array must " + + "contain values " + + "between 0 and 255"); + bytes[i] = (byte) addr[i]; + } + return fromAddress(bytes); +} + +/** + * Creates a reverse map name corresponding to an address contained in + * an InetAddress. + * @param addr The address from which to build a name. + * @return The name corresponding to the address in the reverse map. + */ +public static Name +fromAddress(InetAddress addr) { + return fromAddress(addr.getAddress()); +} + +/** + * Creates a reverse map name corresponding to an address contained in + * a String. + * @param addr The address from which to build a name. + * @return The name corresponding to the address in the reverse map. + * @throws UnknownHostException The string does not contain a valid address. + */ +public static Name +fromAddress(String addr, int family) throws UnknownHostException { + byte [] array = Address.toByteArray(addr, family); + if (array == null) + throw new UnknownHostException("Invalid IP address"); + return fromAddress(array); +} + +/** + * Creates a reverse map name corresponding to an address contained in + * a String. + * @param addr The address from which to build a name. + * @return The name corresponding to the address in the reverse map. + * @throws UnknownHostException The string does not contain a valid address. + */ +public static Name +fromAddress(String addr) throws UnknownHostException { + byte [] array = Address.toByteArray(addr, Address.IPv4); + if (array == null) + array = Address.toByteArray(addr, Address.IPv6); + if (array == null) + throw new UnknownHostException("Invalid IP address"); + return fromAddress(array); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java new file mode 100644 index 0000000..5a00e72 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIG0.java @@ -0,0 +1,79 @@ +// Copyright (c) 2001-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.security.PrivateKey; +import java.util.Date; + +/** + * Creates SIG(0) transaction signatures. + * + * @author Pasi Eronen + * @author Brian Wellington + */ + +public class SIG0 { + +/** + * The default validity period for outgoing SIG(0) signed messages. + * Can be overriden by the sig0validity option. + */ +private static final short VALIDITY = 300; + +private +SIG0() { } + +/** + * Sign a message with SIG(0). The DNS key and private key must refer to the + * same underlying cryptographic key. + * @param message The message to be signed + * @param key The DNSKEY record to use as part of signing + * @param privkey The PrivateKey to use when signing + * @param previous If this message is a response, the SIG(0) from the query + */ +public static void +signMessage(Message message, KEYRecord key, PrivateKey privkey, + SIGRecord previous) throws DNSSEC.DNSSECException +{ + + int validity = Options.intValue("sig0validity"); + if (validity < 0) + validity = VALIDITY; + + long now = System.currentTimeMillis(); + Date timeSigned = new Date(now); + Date timeExpires = new Date(now + validity * 1000); + + SIGRecord sig = DNSSEC.signMessage(message, previous, key, privkey, + timeSigned, timeExpires); + + message.addRecord(sig, Section.ADDITIONAL); +} + +/** + * Verify a message using SIG(0). + * @param message The message to be signed + * @param b An array containing the message in unparsed form. This is + * necessary since SIG(0) signs the message in wire format, and we can't + * recreate the exact wire format (with the same name compression). + * @param key The KEY record to verify the signature with. + * @param previous If this message is a response, the SIG(0) from the query + */ +public static void +verifyMessage(Message message, byte [] b, KEYRecord key, SIGRecord previous) + throws DNSSEC.DNSSECException +{ + SIGRecord sig = null; + Record [] additional = message.getSectionArray(Section.ADDITIONAL); + for (int i = 0; i < additional.length; i++) { + if (additional[i].getType() != Type.SIG) + continue; + if (((SIGRecord) additional[i]).getTypeCovered() != 0) + continue; + sig = (SIGRecord) additional[i]; + break; + } + DNSSEC.verifyMessage(message, b, sig, previous, key); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java new file mode 100644 index 0000000..6e5f12d --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIGBase.java @@ -0,0 +1,193 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * The base class for SIG/RRSIG records, which have identical formats + * + * @author Brian Wellington + */ + +abstract class SIGBase extends Record { + +private static final long serialVersionUID = -3738444391533812369L; + +protected int covered; +protected int alg, labels; +protected long origttl; +protected Date expire, timeSigned; +protected int footprint; +protected Name signer; +protected byte [] signature; + +protected +SIGBase() {} + +public +SIGBase(Name name, int type, int dclass, long ttl, int covered, int alg, + long origttl, Date expire, Date timeSigned, int footprint, Name signer, + byte [] signature) +{ + super(name, type, dclass, ttl); + Type.check(covered); + TTL.check(origttl); + this.covered = covered; + this.alg = checkU8("alg", alg); + this.labels = name.labels() - 1; + if (name.isWild()) + this.labels--; + this.origttl = origttl; + this.expire = expire; + this.timeSigned = timeSigned; + this.footprint = checkU16("footprint", footprint); + this.signer = checkName("signer", signer); + this.signature = signature; +} + +void +rrFromWire(DNSInput in) throws IOException { + covered = in.readU16(); + alg = in.readU8(); + labels = in.readU8(); + origttl = in.readU32(); + expire = new Date(1000 * in.readU32()); + timeSigned = new Date(1000 * in.readU32()); + footprint = in.readU16(); + signer = new Name(in); + signature = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String typeString = st.getString(); + covered = Type.value(typeString); + if (covered < 0) + throw st.exception("Invalid type: " + typeString); + String algString = st.getString(); + alg = DNSSEC.Algorithm.value(algString); + if (alg < 0) + throw st.exception("Invalid algorithm: " + algString); + labels = st.getUInt8(); + origttl = st.getTTL(); + expire = FormattedTime.parse(st.getString()); + timeSigned = FormattedTime.parse(st.getString()); + footprint = st.getUInt16(); + signer = st.getName(origin); + signature = st.getBase64(); +} + +/** Converts the RRSIG/SIG Record to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append (Type.string(covered)); + sb.append (" "); + sb.append (alg); + sb.append (" "); + sb.append (labels); + sb.append (" "); + sb.append (origttl); + sb.append (" "); + if (Options.check("multiline")) + sb.append ("(\n\t"); + sb.append (FormattedTime.format(expire)); + sb.append (" "); + sb.append (FormattedTime.format(timeSigned)); + sb.append (" "); + sb.append (footprint); + sb.append (" "); + sb.append (signer); + if (Options.check("multiline")) { + sb.append("\n"); + sb.append(base64.formatString(signature, 64, "\t", + true)); + } else { + sb.append (" "); + sb.append(base64.toString(signature)); + } + return sb.toString(); +} + +/** Returns the RRset type covered by this signature */ +public int +getTypeCovered() { + return covered; +} + +/** + * Returns the cryptographic algorithm of the key that generated the signature + */ +public int +getAlgorithm() { + return alg; +} + +/** + * Returns the number of labels in the signed domain name. This may be + * different than the record's domain name if the record is a wildcard + * record. + */ +public int +getLabels() { + return labels; +} + +/** Returns the original TTL of the RRset */ +public long +getOrigTTL() { + return origttl; +} + +/** Returns the time at which the signature expires */ +public Date +getExpire() { + return expire; +} + +/** Returns the time at which this signature was generated */ +public Date +getTimeSigned() { + return timeSigned; +} + +/** Returns The footprint/key id of the signing key. */ +public int +getFootprint() { + return footprint; +} + +/** Returns the owner of the signing key */ +public Name +getSigner() { + return signer; +} + +/** Returns the binary data representing the signature */ +public byte [] +getSignature() { + return signature; +} + +void +setSignature(byte [] signature) { + this.signature = signature; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(covered); + out.writeU8(alg); + out.writeU8(labels); + out.writeU32(origttl); + out.writeU32(expire.getTime() / 1000); + out.writeU32(timeSigned.getTime() / 1000); + out.writeU16(footprint); + signer.toWire(out, null, canonical); + out.writeByteArray(signature); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java new file mode 100644 index 0000000..8b6f58d --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SIGRecord.java @@ -0,0 +1,50 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * Signature - A SIG provides the digital signature of an RRset, so that + * the data can be authenticated by a DNSSEC-capable resolver. The + * signature is usually generated by a key contained in a KEYRecord + * @see RRset + * @see DNSSEC + * @see KEYRecord + * + * @author Brian Wellington + */ + +public class SIGRecord extends SIGBase { + +private static final long serialVersionUID = 4963556060953589058L; + +SIGRecord() {} + +Record +getObject() { + return new SIGRecord(); +} + +/** + * Creates an SIG Record from the given data + * @param covered The RRset type covered by this signature + * @param alg The cryptographic algorithm of the key that generated the + * signature + * @param origttl The original TTL of the RRset + * @param expire The time at which the signature expires + * @param timeSigned The time at which this signature was generated + * @param footprint The footprint/key id of the signing key. + * @param signer The owner of the signing key + * @param signature Binary data representing the signature + */ +public +SIGRecord(Name name, int dclass, long ttl, int covered, int alg, long origttl, + Date expire, Date timeSigned, int footprint, Name signer, + byte [] signature) +{ + super(name, Type.SIG, dclass, ttl, covered, alg, origttl, expire, + timeSigned, footprint, signer, signature); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java new file mode 100644 index 0000000..7f27077 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SOARecord.java @@ -0,0 +1,162 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Start of Authority - describes properties of a zone. + * + * @author Brian Wellington + */ + +public class SOARecord extends Record { + +private static final long serialVersionUID = 1049740098229303931L; + +private Name host, admin; +private long serial, refresh, retry, expire, minimum; + +SOARecord() {} + +Record +getObject() { + return new SOARecord(); +} + +/** + * Creates an SOA Record from the given data + * @param host The primary name server for the zone + * @param admin The zone administrator's address + * @param serial The zone's serial number + * @param refresh The amount of time until a secondary checks for a new serial + * number + * @param retry The amount of time between a secondary's checks for a new + * serial number + * @param expire The amount of time until a secondary expires a zone + * @param minimum The minimum TTL for records in the zone +*/ +public +SOARecord(Name name, int dclass, long ttl, Name host, Name admin, + long serial, long refresh, long retry, long expire, long minimum) +{ + super(name, Type.SOA, dclass, ttl); + this.host = checkName("host", host); + this.admin = checkName("admin", admin); + this.serial = checkU32("serial", serial); + this.refresh = checkU32("refresh", refresh); + this.retry = checkU32("retry", retry); + this.expire = checkU32("expire", expire); + this.minimum = checkU32("minimum", minimum); +} + +void +rrFromWire(DNSInput in) throws IOException { + host = new Name(in); + admin = new Name(in); + serial = in.readU32(); + refresh = in.readU32(); + retry = in.readU32(); + expire = in.readU32(); + minimum = in.readU32(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + host = st.getName(origin); + admin = st.getName(origin); + serial = st.getUInt32(); + refresh = st.getTTLLike(); + retry = st.getTTLLike(); + expire = st.getTTLLike(); + minimum = st.getTTLLike(); +} + +/** Convert to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(host); + sb.append(" "); + sb.append(admin); + if (Options.check("multiline")) { + sb.append(" (\n\t\t\t\t\t"); + sb.append(serial); + sb.append("\t; serial\n\t\t\t\t\t"); + sb.append(refresh); + sb.append("\t; refresh\n\t\t\t\t\t"); + sb.append(retry); + sb.append("\t; retry\n\t\t\t\t\t"); + sb.append(expire); + sb.append("\t; expire\n\t\t\t\t\t"); + sb.append(minimum); + sb.append(" )\t; minimum"); + } else { + sb.append(" "); + sb.append(serial); + sb.append(" "); + sb.append(refresh); + sb.append(" "); + sb.append(retry); + sb.append(" "); + sb.append(expire); + sb.append(" "); + sb.append(minimum); + } + return sb.toString(); +} + +/** Returns the primary name server */ +public Name +getHost() { + return host; +} + +/** Returns the zone administrator's address */ +public Name +getAdmin() { + return admin; +} + +/** Returns the zone's serial number */ +public long +getSerial() { + return serial; +} + +/** Returns the zone refresh interval */ +public long +getRefresh() { + return refresh; +} + +/** Returns the zone retry interval */ +public long +getRetry() { + return retry; +} + +/** Returns the time until a secondary expires a zone */ +public long +getExpire() { + return expire; +} + +/** Returns the minimum TTL for records in the zone */ +public long +getMinimum() { + return minimum; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + host.toWire(out, c, canonical); + admin.toWire(out, c, canonical); + out.writeU32(serial); + out.writeU32(refresh); + out.writeU32(retry); + out.writeU32(expire); + out.writeU32(minimum); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java new file mode 100644 index 0000000..a286220 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SPFRecord.java @@ -0,0 +1,44 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * Sender Policy Framework (RFC 4408, experimental) + * + * @author Brian Wellington + */ + +public class SPFRecord extends TXTBase { + +private static final long serialVersionUID = -2100754352801658722L; + +SPFRecord() {} + +Record +getObject() { + return new SPFRecord(); +} + +/** + * Creates a SPF Record from the given data + * @param strings The text strings + * @throws IllegalArgumentException One of the strings has invalid escapes + */ +public +SPFRecord(Name name, int dclass, long ttl, List strings) { + super(name, Type.SPF, dclass, ttl, strings); +} + +/** + * Creates a SPF Record from the given data + * @param string One text string + * @throws IllegalArgumentException The string has invalid escapes + */ +public +SPFRecord(Name name, int dclass, long ttl, String string) { + super(name, Type.SPF, dclass, ttl, string); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java new file mode 100644 index 0000000..c0635fb --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SRVRecord.java @@ -0,0 +1,114 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Server Selection Record - finds hosts running services in a domain. An + * SRV record will normally be named _<service>._<protocol>.domain + * - examples would be _sips._tcp.example.org (for the secure SIP protocol) and + * _http._tcp.example.com (if HTTP used SRV records) + * + * @author Brian Wellington + */ + +public class SRVRecord extends Record { + +private static final long serialVersionUID = -3886460132387522052L; + +private int priority, weight, port; +private Name target; + +SRVRecord() {} + +Record +getObject() { + return new SRVRecord(); +} + +/** + * Creates an SRV Record from the given data + * @param priority The priority of this SRV. Records with lower priority + * are preferred. + * @param weight The weight, used to select between records at the same + * priority. + * @param port The TCP/UDP port that the service uses + * @param target The host running the service + */ +public +SRVRecord(Name name, int dclass, long ttl, int priority, + int weight, int port, Name target) +{ + super(name, Type.SRV, dclass, ttl); + this.priority = checkU16("priority", priority); + this.weight = checkU16("weight", weight); + this.port = checkU16("port", port); + this.target = checkName("target", target); +} + +void +rrFromWire(DNSInput in) throws IOException { + priority = in.readU16(); + weight = in.readU16(); + port = in.readU16(); + target = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + priority = st.getUInt16(); + weight = st.getUInt16(); + port = st.getUInt16(); + target = st.getName(origin); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(priority + " "); + sb.append(weight + " "); + sb.append(port + " "); + sb.append(target); + return sb.toString(); +} + +/** Returns the priority */ +public int +getPriority() { + return priority; +} + +/** Returns the weight */ +public int +getWeight() { + return weight; +} + +/** Returns the port that the service runs on */ +public int +getPort() { + return port; +} + +/** Returns the host running that the service */ +public Name +getTarget() { + return target; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(priority); + out.writeU16(weight); + out.writeU16(port); + target.toWire(out, null, canonical); +} + +public Name +getAdditionalName() { + return target; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java new file mode 100644 index 0000000..079741e --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SSHFPRecord.java @@ -0,0 +1,108 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import org.xbill.DNS.utils.*; + +/** + * SSH Fingerprint - stores the fingerprint of an SSH host key. + * + * @author Brian Wellington + */ + +public class SSHFPRecord extends Record { + +private static final long serialVersionUID = -8104701402654687025L; + +public static class Algorithm { + private Algorithm() {} + + public static final int RSA = 1; + public static final int DSS = 2; +} + +public static class Digest { + private Digest() {} + + public static final int SHA1 = 1; +} + +private int alg; +private int digestType; +private byte [] fingerprint; + +SSHFPRecord() {} + +Record +getObject() { + return new SSHFPRecord(); +} + +/** + * Creates an SSHFP Record from the given data. + * @param alg The public key's algorithm. + * @param digestType The public key's digest type. + * @param fingerprint The public key's fingerprint. + */ +public +SSHFPRecord(Name name, int dclass, long ttl, int alg, int digestType, + byte [] fingerprint) +{ + super(name, Type.SSHFP, dclass, ttl); + this.alg = checkU8("alg", alg); + this.digestType = checkU8("digestType", digestType); + this.fingerprint = fingerprint; +} + +void +rrFromWire(DNSInput in) throws IOException { + alg = in.readU8(); + digestType = in.readU8(); + fingerprint = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + alg = st.getUInt8(); + digestType = st.getUInt8(); + fingerprint = st.getHex(true); +} + +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(alg); + sb.append(" "); + sb.append(digestType); + sb.append(" "); + sb.append(base16.toString(fingerprint)); + return sb.toString(); +} + +/** Returns the public key's algorithm. */ +public int +getAlgorithm() { + return alg; +} + +/** Returns the public key's digest type. */ +public int +getDigestType() { + return digestType; +} + +/** Returns the fingerprint */ +public byte [] +getFingerPrint() { + return fingerprint; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU8(alg); + out.writeU8(digestType); + out.writeByteArray(fingerprint); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Section.java b/external/asmack/build/src/trunk/org/xbill/DNS/Section.java new file mode 100644 index 0000000..e0c8caa --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Section.java @@ -0,0 +1,92 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Constants and functions relating to DNS message sections + * + * @author Brian Wellington + */ + +public final class Section { + +/** The question (first) section */ +public static final int QUESTION = 0; + +/** The answer (second) section */ +public static final int ANSWER = 1; + +/** The authority (third) section */ +public static final int AUTHORITY = 2; + +/** The additional (fourth) section */ +public static final int ADDITIONAL = 3; + +/* Aliases for dynamic update */ +/** The zone (first) section of a dynamic update message */ +public static final int ZONE = 0; + +/** The prerequisite (second) section of a dynamic update message */ +public static final int PREREQ = 1; + +/** The update (third) section of a dynamic update message */ +public static final int UPDATE = 2; + +private static Mnemonic sections = new Mnemonic("Message Section", + Mnemonic.CASE_LOWER); +private static String [] longSections = new String[4]; +private static String [] updateSections = new String[4]; + +static { + sections.setMaximum(3); + sections.setNumericAllowed(true); + + sections.add(QUESTION, "qd"); + sections.add(ANSWER, "an"); + sections.add(AUTHORITY, "au"); + sections.add(ADDITIONAL, "ad"); + + longSections[QUESTION] = "QUESTIONS"; + longSections[ANSWER] = "ANSWERS"; + longSections[AUTHORITY] = "AUTHORITY RECORDS"; + longSections[ADDITIONAL] = "ADDITIONAL RECORDS"; + + updateSections[ZONE] = "ZONE"; + updateSections[PREREQ] = "PREREQUISITES"; + updateSections[UPDATE] = "UPDATE RECORDS"; + updateSections[ADDITIONAL] = "ADDITIONAL RECORDS"; +} + +private +Section() {} + +/** Converts a numeric Section into an abbreviation String */ +public static String +string(int i) { + return sections.getText(i); +} + +/** Converts a numeric Section into a full description String */ +public static String +longString(int i) { + sections.check(i); + return longSections[i]; +} + +/** + * Converts a numeric Section into a full description String for an update + * Message. + */ +public static String +updString(int i) { + sections.check(i); + return updateSections[i]; +} + +/** Converts a String representation of a Section into its numeric value */ +public static int +value(String s) { + return sections.getValue(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java b/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java new file mode 100644 index 0000000..3a146c6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Serial.java @@ -0,0 +1,61 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Helper functions for doing serial arithmetic. These should be used when + * setting/checking SOA serial numbers. SOA serial number arithmetic is + * defined in RFC 1982. + * + * @author Brian Wellington + */ + +public final class Serial { + +private static final long MAX32 = 0xFFFFFFFFL; + +private +Serial() { +} + +/** + * Compares two numbers using serial arithmetic. The numbers are assumed + * to be 32 bit unsigned integers stored in longs. + * @param serial1 The first integer + * @param serial2 The second integer + * @return 0 if the 2 numbers are equal, a positive number if serial1 is greater + * than serial2, and a negative number if serial2 is greater than serial1. + * @throws IllegalArgumentException serial1 or serial2 is out of range + */ +public static int +compare(long serial1, long serial2) { + if (serial1 < 0 || serial1 > MAX32) + throw new IllegalArgumentException(serial1 + " out of range"); + if (serial2 < 0 || serial2 > MAX32) + throw new IllegalArgumentException(serial2 + " out of range"); + long diff = serial1 - serial2; + if (diff >= MAX32) + diff -= (MAX32 + 1); + else if (diff < -MAX32) + diff += (MAX32 + 1); + return (int)diff; +} + +/** + * Increments a serial number. The number is assumed to be a 32 bit unsigned + * integer stored in a long. This basically adds 1 and resets the value to + * 0 if it is 2^32. + * @param serial The serial number + * @return The incremented serial number + * @throws IllegalArgumentException serial is out of range + */ +public static long +increment(long serial) { + if (serial < 0 || serial > MAX32) + throw new IllegalArgumentException(serial + " out of range"); + if (serial == MAX32) + return 0; + return serial + 1; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java b/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java new file mode 100644 index 0000000..05d9f32 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SetResponse.java @@ -0,0 +1,202 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * The Response from a query to Cache.lookupRecords() or Zone.findRecords() + * @see Cache + * @see Zone + * + * @author Brian Wellington + */ + +public class SetResponse { + +/** + * The Cache contains no information about the requested name/type + */ +static final int UNKNOWN = 0; + +/** + * The Zone does not contain the requested name, or the Cache has + * determined that the name does not exist. + */ +static final int NXDOMAIN = 1; + +/** + * The Zone contains the name, but no data of the requested type, + * or the Cache has determined that the name exists and has no data + * of the requested type. + */ +static final int NXRRSET = 2; + +/** + * A delegation enclosing the requested name was found. + */ +static final int DELEGATION = 3; + +/** + * The Cache/Zone found a CNAME when looking for the name. + * @see CNAMERecord + */ +static final int CNAME = 4; + +/** + * The Cache/Zone found a DNAME when looking for the name. + * @see DNAMERecord + */ +static final int DNAME = 5; + +/** + * The Cache/Zone has successfully answered the question for the + * requested name/type/class. + */ +static final int SUCCESSFUL = 6; + +private static final SetResponse unknown = new SetResponse(UNKNOWN); +private static final SetResponse nxdomain = new SetResponse(NXDOMAIN); +private static final SetResponse nxrrset = new SetResponse(NXRRSET); + +private int type; +private Object data; + +private +SetResponse() {} + +SetResponse(int type, RRset rrset) { + if (type < 0 || type > 6) + throw new IllegalArgumentException("invalid type"); + this.type = type; + this.data = rrset; +} + +SetResponse(int type) { + if (type < 0 || type > 6) + throw new IllegalArgumentException("invalid type"); + this.type = type; + this.data = null; +} + +static SetResponse +ofType(int type) { + switch (type) { + case UNKNOWN: + return unknown; + case NXDOMAIN: + return nxdomain; + case NXRRSET: + return nxrrset; + case DELEGATION: + case CNAME: + case DNAME: + case SUCCESSFUL: + SetResponse sr = new SetResponse(); + sr.type = type; + sr.data = null; + return sr; + default: + throw new IllegalArgumentException("invalid type"); + } +} + +void +addRRset(RRset rrset) { + if (data == null) + data = new ArrayList(); + List l = (List) data; + l.add(rrset); +} + +/** Is the answer to the query unknown? */ +public boolean +isUnknown() { + return (type == UNKNOWN); +} + +/** Is the answer to the query that the name does not exist? */ +public boolean +isNXDOMAIN() { + return (type == NXDOMAIN); +} + +/** Is the answer to the query that the name exists, but the type does not? */ +public boolean +isNXRRSET() { + return (type == NXRRSET); +} + +/** Is the result of the lookup that the name is below a delegation? */ +public boolean +isDelegation() { + return (type == DELEGATION); +} + +/** Is the result of the lookup a CNAME? */ +public boolean +isCNAME() { + return (type == CNAME); +} + +/** Is the result of the lookup a DNAME? */ +public boolean +isDNAME() { + return (type == DNAME); +} + +/** Was the query successful? */ +public boolean +isSuccessful() { + return (type == SUCCESSFUL); +} + +/** If the query was successful, return the answers */ +public RRset [] +answers() { + if (type != SUCCESSFUL) + return null; + List l = (List) data; + return (RRset []) l.toArray(new RRset[l.size()]); +} + +/** + * If the query encountered a CNAME, return it. + */ +public CNAMERecord +getCNAME() { + return (CNAMERecord)((RRset)data).first(); +} + +/** + * If the query encountered a DNAME, return it. + */ +public DNAMERecord +getDNAME() { + return (DNAMERecord)((RRset)data).first(); +} + +/** + * If the query hit a delegation point, return the NS set. + */ +public RRset +getNS() { + return (RRset)data; +} + +/** Prints the value of the SetResponse */ +public String +toString() { + switch (type) { + case UNKNOWN: return "unknown"; + case NXDOMAIN: return "NXDOMAIN"; + case NXRRSET: return "NXRRSET"; + case DELEGATION: return "delegation: " + data; + case CNAME: return "CNAME: " + data; + case DNAME: return "DNAME: " + data; + case SUCCESSFUL: return "successful"; + default: throw new IllegalStateException(); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java b/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java new file mode 100644 index 0000000..7436133 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SimpleResolver.java @@ -0,0 +1,351 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * An implementation of Resolver that sends one query to one server. + * SimpleResolver handles TCP retries, transaction security (TSIG), and + * EDNS 0. + * @see Resolver + * @see TSIG + * @see OPTRecord + * + * @author Brian Wellington + */ + + +public class SimpleResolver implements Resolver { + +/** The default port to send queries to */ +public static final int DEFAULT_PORT = 53; + +/** The default EDNS payload size */ +public static final int DEFAULT_EDNS_PAYLOADSIZE = 1280; + +private InetSocketAddress address; +private InetSocketAddress localAddress; +private boolean useTCP, ignoreTruncation; +private OPTRecord queryOPT; +private TSIG tsig; +private long timeoutValue = 10 * 1000; + +private static final short DEFAULT_UDPSIZE = 512; + +private static String defaultResolver = "localhost"; +private static int uniqueID = 0; + +/** + * Creates a SimpleResolver that will query the specified host + * @exception UnknownHostException Failure occurred while finding the host + */ +public +SimpleResolver(String hostname) throws UnknownHostException { + if (hostname == null) { + hostname = ResolverConfig.getCurrentConfig().server(); + if (hostname == null) + hostname = defaultResolver; + } + InetAddress addr; + if (hostname.equals("0")) + addr = InetAddress.getLocalHost(); + else + addr = InetAddress.getByName(hostname); + address = new InetSocketAddress(addr, DEFAULT_PORT); +} + +/** + * Creates a SimpleResolver. The host to query is either found by using + * ResolverConfig, or the default host is used. + * @see ResolverConfig + * @exception UnknownHostException Failure occurred while finding the host + */ +public +SimpleResolver() throws UnknownHostException { + this(null); +} + +/** + * Gets the destination address associated with this SimpleResolver. + * Messages sent using this SimpleResolver will be sent to this address. + * @return The destination address associated with this SimpleResolver. + */ +InetSocketAddress +getAddress() { + return address; +} + +/** Sets the default host (initially localhost) to query */ +public static void +setDefaultResolver(String hostname) { + defaultResolver = hostname; +} + +public void +setPort(int port) { + address = new InetSocketAddress(address.getAddress(), port); +} + +/** + * Sets the address of the server to communicate with. + * @param addr The address of the DNS server + */ +public void +setAddress(InetSocketAddress addr) { + address = addr; +} + +/** + * Sets the address of the server to communicate with (on the default + * DNS port) + * @param addr The address of the DNS server + */ +public void +setAddress(InetAddress addr) { + address = new InetSocketAddress(addr, address.getPort()); +} + +/** + * Sets the local address to bind to when sending messages. + * @param addr The local address to send messages from. + */ +public void +setLocalAddress(InetSocketAddress addr) { + localAddress = addr; +} + +/** + * Sets the local address to bind to when sending messages. A random port + * will be used. + * @param addr The local address to send messages from. + */ +public void +setLocalAddress(InetAddress addr) { + localAddress = new InetSocketAddress(addr, 0); +} + +public void +setTCP(boolean flag) { + this.useTCP = flag; +} + +public void +setIgnoreTruncation(boolean flag) { + this.ignoreTruncation = flag; +} + +public void +setEDNS(int level, int payloadSize, int flags, List options) { + if (level != 0 && level != -1) + throw new IllegalArgumentException("invalid EDNS level - " + + "must be 0 or -1"); + if (payloadSize == 0) + payloadSize = DEFAULT_EDNS_PAYLOADSIZE; + queryOPT = new OPTRecord(payloadSize, 0, level, flags, options); +} + +public void +setEDNS(int level) { + setEDNS(level, 0, 0, null); +} + +public void +setTSIGKey(TSIG key) { + tsig = key; +} + +TSIG +getTSIGKey() { + return tsig; +} + +public void +setTimeout(int secs, int msecs) { + timeoutValue = (long)secs * 1000 + msecs; +} + +public void +setTimeout(int secs) { + setTimeout(secs, 0); +} + +long +getTimeout() { + return timeoutValue; +} + +private Message +parseMessage(byte [] b) throws WireParseException { + try { + return (new Message(b)); + } + catch (IOException e) { + if (Options.check("verbose")) + e.printStackTrace(); + if (!(e instanceof WireParseException)) + e = new WireParseException("Error parsing message"); + throw (WireParseException) e; + } +} + +private void +verifyTSIG(Message query, Message response, byte [] b, TSIG tsig) { + if (tsig == null) + return; + int error = tsig.verify(response, b, query.getTSIG()); + if (Options.check("verbose")) + System.err.println("TSIG verify: " + Rcode.TSIGstring(error)); +} + +private void +applyEDNS(Message query) { + if (queryOPT == null || query.getOPT() != null) + return; + query.addRecord(queryOPT, Section.ADDITIONAL); +} + +private int +maxUDPSize(Message query) { + OPTRecord opt = query.getOPT(); + if (opt == null) + return DEFAULT_UDPSIZE; + else + return opt.getPayloadSize(); +} + +/** + * Sends a message to a single server and waits for a response. No checking + * is done to ensure that the response is associated with the query. + * @param query The query to send. + * @return The response. + * @throws IOException An error occurred while sending or receiving. + */ +public Message +send(Message query) throws IOException { + if (Options.check("verbose")) + System.err.println("Sending to " + + address.getAddress().getHostAddress() + + ":" + address.getPort()); + + if (query.getHeader().getOpcode() == Opcode.QUERY) { + Record question = query.getQuestion(); + if (question != null && question.getType() == Type.AXFR) + return sendAXFR(query); + } + + query = (Message) query.clone(); + applyEDNS(query); + if (tsig != null) + tsig.apply(query, null); + + byte [] out = query.toWire(Message.MAXLENGTH); + int udpSize = maxUDPSize(query); + boolean tcp = false; + long endTime = System.currentTimeMillis() + timeoutValue; + do { + byte [] in; + + if (useTCP || out.length > udpSize) + tcp = true; + if (tcp) + in = TCPClient.sendrecv(localAddress, address, out, + endTime); + else + in = UDPClient.sendrecv(localAddress, address, out, + udpSize, endTime); + + /* + * Check that the response is long enough. + */ + if (in.length < Header.LENGTH) { + throw new WireParseException("invalid DNS header - " + + "too short"); + } + /* + * Check that the response ID matches the query ID. We want + * to check this before actually parsing the message, so that + * if there's a malformed response that's not ours, it + * doesn't confuse us. + */ + int id = ((in[0] & 0xFF) << 8) + (in[1] & 0xFF); + int qid = query.getHeader().getID(); + if (id != qid) { + String error = "invalid message id: expected " + qid + + "; got id " + id; + if (tcp) { + throw new WireParseException(error); + } else { + if (Options.check("verbose")) { + System.err.println(error); + } + continue; + } + } + Message response = parseMessage(in); + verifyTSIG(query, response, in, tsig); + if (!tcp && !ignoreTruncation && + response.getHeader().getFlag(Flags.TC)) + { + tcp = true; + continue; + } + return response; + } while (true); +} + +/** + * Asynchronously sends a message to a single server, registering a listener + * to receive a callback on success or exception. Multiple asynchronous + * lookups can be performed in parallel. Since the callback may be invoked + * before the function returns, external synchronization is necessary. + * @param query The query to send + * @param listener The object containing the callbacks. + * @return An identifier, which is also a parameter in the callback + */ +public Object +sendAsync(final Message query, final ResolverListener listener) { + final Object id; + synchronized (this) { + id = new Integer(uniqueID++); + } + Record question = query.getQuestion(); + String qname; + if (question != null) + qname = question.getName().toString(); + else + qname = "(none)"; + String name = this.getClass() + ": " + qname; + Thread thread = new ResolveThread(this, query, id, listener); + thread.setName(name); + thread.setDaemon(true); + thread.start(); + return id; +} + +private Message +sendAXFR(Message query) throws IOException { + Name qname = query.getQuestion().getName(); + ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(qname, address, tsig); + xfrin.setTimeout((int)(getTimeout() / 1000)); + xfrin.setLocalAddress(localAddress); + try { + xfrin.run(); + } + catch (ZoneTransferException e) { + throw new WireParseException(e.getMessage()); + } + List records = xfrin.getAXFR(); + Message response = new Message(query.getHeader().getID()); + response.getHeader().setFlag(Flags.AA); + response.getHeader().setFlag(Flags.QR); + response.addRecord(query.getQuestion(), Section.QUESTION); + Iterator it = records.iterator(); + while (it.hasNext()) + response.addRecord((Record)it.next(), Section.ANSWER); + return response; +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java new file mode 100644 index 0000000..790ca1f --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SingleCompressedNameBase.java @@ -0,0 +1,31 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Implements common functionality for the many record types whose format + * is a single compressed name. + * + * @author Brian Wellington + */ + +abstract class SingleCompressedNameBase extends SingleNameBase { + +private static final long serialVersionUID = -236435396815460677L; + +protected +SingleCompressedNameBase() {} + +protected +SingleCompressedNameBase(Name name, int type, int dclass, long ttl, + Name singleName, String description) +{ + super(name, type, dclass, ttl, singleName, description); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + singleName.toWire(out, c, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java new file mode 100644 index 0000000..6d6c041 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/SingleNameBase.java @@ -0,0 +1,61 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Implements common functionality for the many record types whose format + * is a single name. + * + * @author Brian Wellington + */ + +abstract class SingleNameBase extends Record { + +private static final long serialVersionUID = -18595042501413L; + +protected Name singleName; + +protected +SingleNameBase() {} + +protected +SingleNameBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); +} + +protected +SingleNameBase(Name name, int type, int dclass, long ttl, Name singleName, + String description) +{ + super(name, type, dclass, ttl); + this.singleName = checkName(description, singleName); +} + +void +rrFromWire(DNSInput in) throws IOException { + singleName = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + singleName = st.getName(origin); +} + +String +rrToString() { + return singleName.toString(); +} + +protected Name +getSingleName() { + return singleName; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + singleName.toWire(out, null, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java b/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java new file mode 100644 index 0000000..1f17d72 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TCPClient.java @@ -0,0 +1,132 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import java.nio.*; +import java.nio.channels.*; + +final class TCPClient extends Client { + +public +TCPClient(long endTime) throws IOException { + super(SocketChannel.open(), endTime); +} + +void +bind(SocketAddress addr) throws IOException { + SocketChannel channel = (SocketChannel) key.channel(); + channel.socket().bind(addr); +} + +void +connect(SocketAddress addr) throws IOException { + SocketChannel channel = (SocketChannel) key.channel(); + if (channel.connect(addr)) + return; + key.interestOps(SelectionKey.OP_CONNECT); + try { + while (!channel.finishConnect()) { + if (!key.isConnectable()) + blockUntil(key, endTime); + } + } + finally { + if (key.isValid()) + key.interestOps(0); + } +} + +void +send(byte [] data) throws IOException { + SocketChannel channel = (SocketChannel) key.channel(); + verboseLog("TCP write", data); + byte [] lengthArray = new byte[2]; + lengthArray[0] = (byte)(data.length >>> 8); + lengthArray[1] = (byte)(data.length & 0xFF); + ByteBuffer [] buffers = new ByteBuffer[2]; + buffers[0] = ByteBuffer.wrap(lengthArray); + buffers[1] = ByteBuffer.wrap(data); + int nsent = 0; + key.interestOps(SelectionKey.OP_WRITE); + try { + while (nsent < data.length + 2) { + if (key.isWritable()) { + long n = channel.write(buffers); + if (n < 0) + throw new EOFException(); + nsent += (int) n; + if (nsent < data.length + 2 && + System.currentTimeMillis() > endTime) + throw new SocketTimeoutException(); + } else + blockUntil(key, endTime); + } + } + finally { + if (key.isValid()) + key.interestOps(0); + } +} + +private byte [] +_recv(int length) throws IOException { + SocketChannel channel = (SocketChannel) key.channel(); + int nrecvd = 0; + byte [] data = new byte[length]; + ByteBuffer buffer = ByteBuffer.wrap(data); + key.interestOps(SelectionKey.OP_READ); + try { + while (nrecvd < length) { + if (key.isReadable()) { + long n = channel.read(buffer); + if (n < 0) + throw new EOFException(); + nrecvd += (int) n; + if (nrecvd < length && + System.currentTimeMillis() > endTime) + throw new SocketTimeoutException(); + } else + blockUntil(key, endTime); + } + } + finally { + if (key.isValid()) + key.interestOps(0); + } + return data; +} + +byte [] +recv() throws IOException { + byte [] buf = _recv(2); + int length = ((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF); + byte [] data = _recv(length); + verboseLog("TCP read", data); + return data; +} + +static byte [] +sendrecv(SocketAddress local, SocketAddress remote, byte [] data, long endTime) +throws IOException +{ + TCPClient client = new TCPClient(endTime); + try { + if (local != null) + client.bind(local); + client.connect(remote); + client.send(data); + return client.recv(); + } + finally { + client.cleanup(); + } +} + +static byte [] +sendrecv(SocketAddress addr, byte [] data, long endTime) throws IOException { + return sendrecv(null, addr, data, endTime); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java new file mode 100644 index 0000000..4dcbb5c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TKEYRecord.java @@ -0,0 +1,225 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * Transaction Key - used to compute and/or securely transport a shared + * secret to be used with TSIG. + * @see TSIG + * + * @author Brian Wellington + */ + +public class TKEYRecord extends Record { + +private static final long serialVersionUID = 8828458121926391756L; + +private Name alg; +private Date timeInception; +private Date timeExpire; +private int mode, error; +private byte [] key; +private byte [] other; + +/** The key is assigned by the server (unimplemented) */ +public static final int SERVERASSIGNED = 1; + +/** The key is computed using a Diffie-Hellman key exchange */ +public static final int DIFFIEHELLMAN = 2; + +/** The key is computed using GSS_API (unimplemented) */ +public static final int GSSAPI = 3; + +/** The key is assigned by the resolver (unimplemented) */ +public static final int RESOLVERASSIGNED = 4; + +/** The key should be deleted */ +public static final int DELETE = 5; + +TKEYRecord() {} + +Record +getObject() { + return new TKEYRecord(); +} + +/** + * Creates a TKEY Record from the given data. + * @param alg The shared key's algorithm + * @param timeInception The beginning of the validity period of the shared + * secret or keying material + * @param timeExpire The end of the validity period of the shared + * secret or keying material + * @param mode The mode of key agreement + * @param error The extended error field. Should be 0 in queries + * @param key The shared secret + * @param other The other data field. Currently unused + * responses. + */ +public +TKEYRecord(Name name, int dclass, long ttl, Name alg, + Date timeInception, Date timeExpire, int mode, int error, + byte [] key, byte other[]) +{ + super(name, Type.TKEY, dclass, ttl); + this.alg = checkName("alg", alg); + this.timeInception = timeInception; + this.timeExpire = timeExpire; + this.mode = checkU16("mode", mode); + this.error = checkU16("error", error); + this.key = key; + this.other = other; +} + +void +rrFromWire(DNSInput in) throws IOException { + alg = new Name(in); + timeInception = new Date(1000 * in.readU32()); + timeExpire = new Date(1000 * in.readU32()); + mode = in.readU16(); + error = in.readU16(); + + int keylen = in.readU16(); + if (keylen > 0) + key = in.readByteArray(keylen); + else + key = null; + + int otherlen = in.readU16(); + if (otherlen > 0) + other = in.readByteArray(otherlen); + else + other = null; +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for TKEY"); +} + +protected String +modeString() { + switch (mode) { + case SERVERASSIGNED: return "SERVERASSIGNED"; + case DIFFIEHELLMAN: return "DIFFIEHELLMAN"; + case GSSAPI: return "GSSAPI"; + case RESOLVERASSIGNED: return "RESOLVERASSIGNED"; + case DELETE: return "DELETE"; + default: return Integer.toString(mode); + } +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(alg); + sb.append(" "); + if (Options.check("multiline")) + sb.append("(\n\t"); + sb.append(FormattedTime.format(timeInception)); + sb.append(" "); + sb.append(FormattedTime.format(timeExpire)); + sb.append(" "); + sb.append(modeString()); + sb.append(" "); + sb.append(Rcode.TSIGstring(error)); + if (Options.check("multiline")) { + sb.append("\n"); + if (key != null) { + sb.append(base64.formatString(key, 64, "\t", false)); + sb.append("\n"); + } + if (other != null) + sb.append(base64.formatString(other, 64, "\t", false)); + sb.append(" )"); + } else { + sb.append(" "); + if (key != null) { + sb.append(base64.toString(key)); + sb.append(" "); + } + if (other != null) + sb.append(base64.toString(other)); + } + return sb.toString(); +} + +/** Returns the shared key's algorithm */ +public Name +getAlgorithm() { + return alg; +} + +/** + * Returns the beginning of the validity period of the shared secret or + * keying material + */ +public Date +getTimeInception() { + return timeInception; +} + +/** + * Returns the end of the validity period of the shared secret or + * keying material + */ +public Date +getTimeExpire() { + return timeExpire; +} + +/** Returns the key agreement mode */ +public int +getMode() { + return mode; +} + +/** Returns the extended error */ +public int +getError() { + return error; +} + +/** Returns the shared secret or keying material */ +public byte [] +getKey() { + return key; +} + +/** Returns the other data */ +public byte [] +getOther() { + return other; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + alg.toWire(out, null, canonical); + + out.writeU32(timeInception.getTime() / 1000); + out.writeU32(timeExpire.getTime() / 1000); + + out.writeU16(mode); + out.writeU16(error); + + if (key != null) { + out.writeU16(key.length); + out.writeByteArray(key); + } + else + out.writeU16(0); + + if (other != null) { + out.writeU16(other.length); + out.writeByteArray(other); + } + else + out.writeU16(0); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java b/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java new file mode 100644 index 0000000..d9e6972 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TSIG.java @@ -0,0 +1,592 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * Transaction signature handling. This class generates and verifies + * TSIG records on messages, which provide transaction security. + * @see TSIGRecord + * + * @author Brian Wellington + */ + +public class TSIG { + +private static final String HMAC_MD5_STR = "HMAC-MD5.SIG-ALG.REG.INT."; +private static final String HMAC_SHA1_STR = "hmac-sha1."; +private static final String HMAC_SHA224_STR = "hmac-sha224."; +private static final String HMAC_SHA256_STR = "hmac-sha256."; +private static final String HMAC_SHA384_STR = "hmac-sha384."; +private static final String HMAC_SHA512_STR = "hmac-sha512."; + +/** The domain name representing the HMAC-MD5 algorithm. */ +public static final Name HMAC_MD5 = Name.fromConstantString(HMAC_MD5_STR); + +/** The domain name representing the HMAC-MD5 algorithm (deprecated). */ +public static final Name HMAC = HMAC_MD5; + +/** The domain name representing the HMAC-SHA1 algorithm. */ +public static final Name HMAC_SHA1 = Name.fromConstantString(HMAC_SHA1_STR); + +/** + * The domain name representing the HMAC-SHA224 algorithm. + * Note that SHA224 is not supported by Java out-of-the-box, this requires use + * of a third party provider like BouncyCastle.org. + */ +public static final Name HMAC_SHA224 = Name.fromConstantString(HMAC_SHA224_STR); + +/** The domain name representing the HMAC-SHA256 algorithm. */ +public static final Name HMAC_SHA256 = Name.fromConstantString(HMAC_SHA256_STR); + +/** The domain name representing the HMAC-SHA384 algorithm. */ +public static final Name HMAC_SHA384 = Name.fromConstantString(HMAC_SHA384_STR); + +/** The domain name representing the HMAC-SHA512 algorithm. */ +public static final Name HMAC_SHA512 = Name.fromConstantString(HMAC_SHA512_STR); + +/** + * The default fudge value for outgoing packets. Can be overriden by the + * tsigfudge option. + */ +public static final short FUDGE = 300; + +private Name name, alg; +private String digest; +private int digestBlockLength; +private byte [] key; + +private void +getDigest() { + if (alg.equals(HMAC_MD5)) { + digest = "md5"; + digestBlockLength = 64; + } else if (alg.equals(HMAC_SHA1)) { + digest = "sha-1"; + digestBlockLength = 64; + } else if (alg.equals(HMAC_SHA224)) { + digest = "sha-224"; + digestBlockLength = 64; + } else if (alg.equals(HMAC_SHA256)) { + digest = "sha-256"; + digestBlockLength = 64; + } else if (alg.equals(HMAC_SHA512)) { + digest = "sha-512"; + digestBlockLength = 128; + } else if (alg.equals(HMAC_SHA384)) { + digest = "sha-384"; + digestBlockLength = 128; + } else + throw new IllegalArgumentException("Invalid algorithm"); +} + +/** + * Creates a new TSIG key, which can be used to sign or verify a message. + * @param algorithm The algorithm of the shared key. + * @param name The name of the shared key. + * @param key The shared key's data. + */ +public +TSIG(Name algorithm, Name name, byte [] key) { + this.name = name; + this.alg = algorithm; + this.key = key; + getDigest(); +} + +/** + * Creates a new TSIG key with the hmac-md5 algorithm, which can be used to + * sign or verify a message. + * @param name The name of the shared key. + * @param key The shared key's data. + */ +public +TSIG(Name name, byte [] key) { + this(HMAC_MD5, name, key); +} + +/** + * Creates a new TSIG object, which can be used to sign or verify a message. + * @param name The name of the shared key. + * @param key The shared key's data represented as a base64 encoded string. + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ +public +TSIG(Name algorithm, String name, String key) { + this.key = base64.fromString(key); + if (this.key == null) + throw new IllegalArgumentException("Invalid TSIG key string"); + try { + this.name = Name.fromString(name, Name.root); + } + catch (TextParseException e) { + throw new IllegalArgumentException("Invalid TSIG key name"); + } + this.alg = algorithm; + getDigest(); +} + +/** + * Creates a new TSIG object, which can be used to sign or verify a message. + * @param name The name of the shared key. + * @param algorithm The algorithm of the shared key. The legal values are + * "hmac-md5", "hmac-sha1", "hmac-sha224", "hmac-sha256", "hmac-sha384", and + * "hmac-sha512". + * @param key The shared key's data represented as a base64 encoded string. + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ +public +TSIG(String algorithm, String name, String key) { + this(HMAC_MD5, name, key); + if (algorithm.equalsIgnoreCase("hmac-md5")) + this.alg = HMAC_MD5; + else if (algorithm.equalsIgnoreCase("hmac-sha1")) + this.alg = HMAC_SHA1; + else if (algorithm.equalsIgnoreCase("hmac-sha224")) + this.alg = HMAC_SHA224; + else if (algorithm.equalsIgnoreCase("hmac-sha256")) + this.alg = HMAC_SHA256; + else if (algorithm.equalsIgnoreCase("hmac-sha384")) + this.alg = HMAC_SHA384; + else if (algorithm.equalsIgnoreCase("hmac-sha512")) + this.alg = HMAC_SHA512; + else + throw new IllegalArgumentException("Invalid TSIG algorithm"); + getDigest(); +} + +/** + * Creates a new TSIG object with the hmac-md5 algorithm, which can be used to + * sign or verify a message. + * @param name The name of the shared key + * @param key The shared key's data, represented as a base64 encoded string. + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ +public +TSIG(String name, String key) { + this(HMAC_MD5, name, key); +} + +/** + * Creates a new TSIG object, which can be used to sign or verify a message. + * @param str The TSIG key, in the form name:secret, name/secret, + * alg:name:secret, or alg/name/secret. If an algorithm is specified, it must + * be "hmac-md5", "hmac-sha1", or "hmac-sha256". + * @throws IllegalArgumentException The string does not contain both a name + * and secret. + * @throws IllegalArgumentException The key name is an invalid name + * @throws IllegalArgumentException The key data is improperly encoded + */ +static public TSIG +fromString(String str) { + String [] parts = str.split("[:/]", 3); + if (parts.length < 2) + throw new IllegalArgumentException("Invalid TSIG key " + + "specification"); + if (parts.length == 3) { + try { + return new TSIG(parts[0], parts[1], parts[2]); + } catch (IllegalArgumentException e) { + parts = str.split("[:/]", 2); + } + } + return new TSIG(HMAC_MD5, parts[0], parts[1]); +} + +/** + * Generates a TSIG record with a specific error for a message that has + * been rendered. + * @param m The message + * @param b The rendered message + * @param error The error + * @param old If this message is a response, the TSIG from the request + * @return The TSIG record to be added to the message + */ +public TSIGRecord +generate(Message m, byte [] b, int error, TSIGRecord old) { + Date timeSigned; + if (error != Rcode.BADTIME) + timeSigned = new Date(); + else + timeSigned = old.getTimeSigned(); + int fudge; + HMAC hmac = null; + if (error == Rcode.NOERROR || error == Rcode.BADTIME) + hmac = new HMAC(digest, digestBlockLength, key); + + fudge = Options.intValue("tsigfudge"); + if (fudge < 0 || fudge > 0x7FFF) + fudge = FUDGE; + + if (old != null) { + DNSOutput out = new DNSOutput(); + out.writeU16(old.getSignature().length); + if (hmac != null) { + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + } + } + + /* Digest the message */ + if (hmac != null) + hmac.update(b); + + DNSOutput out = new DNSOutput(); + name.toWireCanonical(out); + out.writeU16(DClass.ANY); /* class */ + out.writeU32(0); /* ttl */ + alg.toWireCanonical(out); + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + out.writeU16(error); + out.writeU16(0); /* No other data */ + + if (hmac != null) + hmac.update(out.toByteArray()); + + byte [] signature; + if (hmac != null) + signature = hmac.sign(); + else + signature = new byte[0]; + + byte [] other = null; + if (error == Rcode.BADTIME) { + out = new DNSOutput(); + time = new Date().getTime() / 1000; + timeHigh = (int) (time >> 32); + timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + other = out.toByteArray(); + } + + return (new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge, + signature, m.getHeader().getID(), error, other)); +} + +/** + * Generates a TSIG record with a specific error for a message and adds it + * to the message. + * @param m The message + * @param error The error + * @param old If this message is a response, the TSIG from the request + */ +public void +apply(Message m, int error, TSIGRecord old) { + Record r = generate(m, m.toWire(), error, old); + m.addRecord(r, Section.ADDITIONAL); + m.tsigState = Message.TSIG_SIGNED; +} + +/** + * Generates a TSIG record for a message and adds it to the message + * @param m The message + * @param old If this message is a response, the TSIG from the request + */ +public void +apply(Message m, TSIGRecord old) { + apply(m, Rcode.NOERROR, old); +} + +/** + * Generates a TSIG record for a message and adds it to the message + * @param m The message + * @param old If this message is a response, the TSIG from the request + */ +public void +applyStream(Message m, TSIGRecord old, boolean first) { + if (first) { + apply(m, old); + return; + } + Date timeSigned = new Date(); + int fudge; + HMAC hmac = new HMAC(digest, digestBlockLength, key); + + fudge = Options.intValue("tsigfudge"); + if (fudge < 0 || fudge > 0x7FFF) + fudge = FUDGE; + + DNSOutput out = new DNSOutput(); + out.writeU16(old.getSignature().length); + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + + /* Digest the message */ + hmac.update(m.toWire()); + + out = new DNSOutput(); + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + hmac.update(out.toByteArray()); + + byte [] signature = hmac.sign(); + byte [] other = null; + + Record r = new TSIGRecord(name, DClass.ANY, 0, alg, timeSigned, fudge, + signature, m.getHeader().getID(), + Rcode.NOERROR, other); + m.addRecord(r, Section.ADDITIONAL); + m.tsigState = Message.TSIG_SIGNED; +} + +/** + * Verifies a TSIG record on an incoming message. Since this is only called + * in the context where a TSIG is expected to be present, it is an error + * if one is not present. After calling this routine, Message.isVerified() may + * be called on this message. + * @param m The message + * @param b An array containing the message in unparsed form. This is + * necessary since TSIG signs the message in wire format, and we can't + * recreate the exact wire format (with the same name compression). + * @param length The length of the message in the array. + * @param old If this message is a response, the TSIG from the request + * @return The result of the verification (as an Rcode) + * @see Rcode + */ +public byte +verify(Message m, byte [] b, int length, TSIGRecord old) { + m.tsigState = Message.TSIG_FAILED; + TSIGRecord tsig = m.getTSIG(); + HMAC hmac = new HMAC(digest, digestBlockLength, key); + if (tsig == null) + return Rcode.FORMERR; + + if (!tsig.getName().equals(name) || !tsig.getAlgorithm().equals(alg)) { + if (Options.check("verbose")) + System.err.println("BADKEY failure"); + return Rcode.BADKEY; + } + long now = System.currentTimeMillis(); + long then = tsig.getTimeSigned().getTime(); + long fudge = tsig.getFudge(); + if (Math.abs(now - then) > fudge * 1000) { + if (Options.check("verbose")) + System.err.println("BADTIME failure"); + return Rcode.BADTIME; + } + + if (old != null && tsig.getError() != Rcode.BADKEY && + tsig.getError() != Rcode.BADSIG) + { + DNSOutput out = new DNSOutput(); + out.writeU16(old.getSignature().length); + hmac.update(out.toByteArray()); + hmac.update(old.getSignature()); + } + m.getHeader().decCount(Section.ADDITIONAL); + byte [] header = m.getHeader().toWire(); + m.getHeader().incCount(Section.ADDITIONAL); + hmac.update(header); + + int len = m.tsigstart - header.length; + hmac.update(b, header.length, len); + + DNSOutput out = new DNSOutput(); + tsig.getName().toWireCanonical(out); + out.writeU16(tsig.dclass); + out.writeU32(tsig.ttl); + tsig.getAlgorithm().toWireCanonical(out); + long time = tsig.getTimeSigned().getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(tsig.getFudge()); + out.writeU16(tsig.getError()); + if (tsig.getOther() != null) { + out.writeU16(tsig.getOther().length); + out.writeByteArray(tsig.getOther()); + } else { + out.writeU16(0); + } + + hmac.update(out.toByteArray()); + + byte [] signature = tsig.getSignature(); + int digestLength = hmac.digestLength(); + int minDigestLength = digest.equals("md5") ? 10 : digestLength / 2; + + if (signature.length > digestLength) { + if (Options.check("verbose")) + System.err.println("BADSIG: signature too long"); + return Rcode.BADSIG; + } else if (signature.length < minDigestLength) { + if (Options.check("verbose")) + System.err.println("BADSIG: signature too short"); + return Rcode.BADSIG; + } else if (!hmac.verify(signature, true)) { + if (Options.check("verbose")) + System.err.println("BADSIG: signature verification"); + return Rcode.BADSIG; + } + + m.tsigState = Message.TSIG_VERIFIED; + return Rcode.NOERROR; +} + +/** + * Verifies a TSIG record on an incoming message. Since this is only called + * in the context where a TSIG is expected to be present, it is an error + * if one is not present. After calling this routine, Message.isVerified() may + * be called on this message. + * @param m The message + * @param b The message in unparsed form. This is necessary since TSIG + * signs the message in wire format, and we can't recreate the exact wire + * format (with the same name compression). + * @param old If this message is a response, the TSIG from the request + * @return The result of the verification (as an Rcode) + * @see Rcode + */ +public int +verify(Message m, byte [] b, TSIGRecord old) { + return verify(m, b, b.length, old); +} + +/** + * Returns the maximum length of a TSIG record generated by this key. + * @see TSIGRecord + */ +public int +recordLength() { + return (name.length() + 10 + + alg.length() + + 8 + // time signed, fudge + 18 + // 2 byte MAC length, 16 byte MAC + 4 + // original id, error + 8); // 2 byte error length, 6 byte max error field. +} + +public static class StreamVerifier { + /** + * A helper class for verifying multiple message responses. + */ + + private TSIG key; + private HMAC verifier; + private int nresponses; + private int lastsigned; + private TSIGRecord lastTSIG; + + /** Creates an object to verify a multiple message response */ + public + StreamVerifier(TSIG tsig, TSIGRecord old) { + key = tsig; + verifier = new HMAC(key.digest, key.digestBlockLength, key.key); + nresponses = 0; + lastTSIG = old; + } + + /** + * Verifies a TSIG record on an incoming message that is part of a + * multiple message response. + * TSIG records must be present on the first and last messages, and + * at least every 100 records in between. + * After calling this routine, Message.isVerified() may be called on + * this message. + * @param m The message + * @param b The message in unparsed form + * @return The result of the verification (as an Rcode) + * @see Rcode + */ + public int + verify(Message m, byte [] b) { + TSIGRecord tsig = m.getTSIG(); + + nresponses++; + + if (nresponses == 1) { + int result = key.verify(m, b, lastTSIG); + if (result == Rcode.NOERROR) { + byte [] signature = tsig.getSignature(); + DNSOutput out = new DNSOutput(); + out.writeU16(signature.length); + verifier.update(out.toByteArray()); + verifier.update(signature); + } + lastTSIG = tsig; + return result; + } + + if (tsig != null) + m.getHeader().decCount(Section.ADDITIONAL); + byte [] header = m.getHeader().toWire(); + if (tsig != null) + m.getHeader().incCount(Section.ADDITIONAL); + verifier.update(header); + + int len; + if (tsig == null) + len = b.length - header.length; + else + len = m.tsigstart - header.length; + verifier.update(b, header.length, len); + + if (tsig != null) { + lastsigned = nresponses; + lastTSIG = tsig; + } + else { + boolean required = (nresponses - lastsigned >= 100); + if (required) { + m.tsigState = Message.TSIG_FAILED; + return Rcode.FORMERR; + } else { + m.tsigState = Message.TSIG_INTERMEDIATE; + return Rcode.NOERROR; + } + } + + if (!tsig.getName().equals(key.name) || + !tsig.getAlgorithm().equals(key.alg)) + { + if (Options.check("verbose")) + System.err.println("BADKEY failure"); + m.tsigState = Message.TSIG_FAILED; + return Rcode.BADKEY; + } + + DNSOutput out = new DNSOutput(); + long time = tsig.getTimeSigned().getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(tsig.getFudge()); + verifier.update(out.toByteArray()); + + if (verifier.verify(tsig.getSignature()) == false) { + if (Options.check("verbose")) + System.err.println("BADSIG failure"); + m.tsigState = Message.TSIG_FAILED; + return Rcode.BADSIG; + } + + verifier.clear(); + out = new DNSOutput(); + out.writeU16(tsig.getSignature().length); + verifier.update(out.toByteArray()); + verifier.update(tsig.getSignature()); + + m.tsigState = Message.TSIG_VERIFIED; + return Rcode.NOERROR; + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java new file mode 100644 index 0000000..c7ce9ed --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TSIGRecord.java @@ -0,0 +1,220 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; +import org.xbill.DNS.utils.*; + +/** + * Transaction Signature - this record is automatically generated by the + * resolver. TSIG records provide transaction security between the + * sender and receiver of a message, using a shared key. + * @see Resolver + * @see TSIG + * + * @author Brian Wellington + */ + +public class TSIGRecord extends Record { + +private static final long serialVersionUID = -88820909016649306L; + +private Name alg; +private Date timeSigned; +private int fudge; +private byte [] signature; +private int originalID; +private int error; +private byte [] other; + +TSIGRecord() {} + +Record +getObject() { + return new TSIGRecord(); +} + +/** + * Creates a TSIG Record from the given data. This is normally called by + * the TSIG class + * @param alg The shared key's algorithm + * @param timeSigned The time that this record was generated + * @param fudge The fudge factor for time - if the time that the message is + * received is not in the range [now - fudge, now + fudge], the signature + * fails + * @param signature The signature + * @param originalID The message ID at the time of its generation + * @param error The extended error field. Should be 0 in queries. + * @param other The other data field. Currently used only in BADTIME + * responses. + * @see TSIG + */ +public +TSIGRecord(Name name, int dclass, long ttl, Name alg, Date timeSigned, + int fudge, byte [] signature, int originalID, int error, + byte other[]) +{ + super(name, Type.TSIG, dclass, ttl); + this.alg = checkName("alg", alg); + this.timeSigned = timeSigned; + this.fudge = checkU16("fudge", fudge); + this.signature = signature; + this.originalID = checkU16("originalID", originalID); + this.error = checkU16("error", error); + this.other = other; +} + +void +rrFromWire(DNSInput in) throws IOException { + alg = new Name(in); + + long timeHigh = in.readU16(); + long timeLow = in.readU32(); + long time = (timeHigh << 32) + timeLow; + timeSigned = new Date(time * 1000); + fudge = in.readU16(); + + int sigLen = in.readU16(); + signature = in.readByteArray(sigLen); + + originalID = in.readU16(); + error = in.readU16(); + + int otherLen = in.readU16(); + if (otherLen > 0) + other = in.readByteArray(otherLen); + else + other = null; +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("no text format defined for TSIG"); +} + +/** Converts rdata to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(alg); + sb.append(" "); + if (Options.check("multiline")) + sb.append("(\n\t"); + + sb.append (timeSigned.getTime() / 1000); + sb.append (" "); + sb.append (fudge); + sb.append (" "); + sb.append (signature.length); + if (Options.check("multiline")) { + sb.append ("\n"); + sb.append (base64.formatString(signature, 64, "\t", false)); + } else { + sb.append (" "); + sb.append (base64.toString(signature)); + } + sb.append (" "); + sb.append (Rcode.TSIGstring(error)); + sb.append (" "); + if (other == null) + sb.append (0); + else { + sb.append (other.length); + if (Options.check("multiline")) + sb.append("\n\n\n\t"); + else + sb.append(" "); + if (error == Rcode.BADTIME) { + if (other.length != 6) { + sb.append(""); + } else { + long time = ((long)(other[0] & 0xFF) << 40) + + ((long)(other[1] & 0xFF) << 32) + + ((other[2] & 0xFF) << 24) + + ((other[3] & 0xFF) << 16) + + ((other[4] & 0xFF) << 8) + + ((other[5] & 0xFF) ); + sb.append(""); + } + } else { + sb.append("<"); + sb.append(base64.toString(other)); + sb.append(">"); + } + } + if (Options.check("multiline")) + sb.append(" )"); + return sb.toString(); +} + +/** Returns the shared key's algorithm */ +public Name +getAlgorithm() { + return alg; +} + +/** Returns the time that this record was generated */ +public Date +getTimeSigned() { + return timeSigned; +} + +/** Returns the time fudge factor */ +public int +getFudge() { + return fudge; +} + +/** Returns the signature */ +public byte [] +getSignature() { + return signature; +} + +/** Returns the original message ID */ +public int +getOriginalID() { + return originalID; +} + +/** Returns the extended error */ +public int +getError() { + return error; +} + +/** Returns the other data */ +public byte [] +getOther() { + return other; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + alg.toWire(out, null, canonical); + + long time = timeSigned.getTime() / 1000; + int timeHigh = (int) (time >> 32); + long timeLow = (time & 0xFFFFFFFFL); + out.writeU16(timeHigh); + out.writeU32(timeLow); + out.writeU16(fudge); + + out.writeU16(signature.length); + out.writeByteArray(signature); + + out.writeU16(originalID); + out.writeU16(error); + + if (other != null) { + out.writeU16(other.length); + out.writeByteArray(other); + } + else + out.writeU16(0); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java b/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java new file mode 100644 index 0000000..01bf416 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TTL.java @@ -0,0 +1,113 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Routines for parsing BIND-style TTL values. These values consist of + * numbers followed by 1 letter units of time (W - week, D - day, H - hour, + * M - minute, S - second). + * + * @author Brian Wellington + */ + +public final class TTL { + +public static final long MAX_VALUE = 0x7FFFFFFFL; + +private +TTL() {} + +static void +check(long i) { + if (i < 0 || i > MAX_VALUE) + throw new InvalidTTLException(i); +} + +/** + * Parses a TTL-like value, which can either be expressed as a number or a + * BIND-style string with numbers and units. + * @param s The string representing the numeric value. + * @param clamp Whether to clamp values in the range [MAX_VALUE + 1, 2^32 -1] + * to MAX_VALUE. This should be donw for TTLs, but not other values which + * can be expressed in this format. + * @return The value as a number of seconds + * @throws NumberFormatException The string was not in a valid TTL format. + */ +public static long +parse(String s, boolean clamp) { + if (s == null || s.length() == 0 || !Character.isDigit(s.charAt(0))) + throw new NumberFormatException(); + long value = 0; + long ttl = 0; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + long oldvalue = value; + if (Character.isDigit(c)) { + value = (value * 10) + Character.getNumericValue(c); + if (value < oldvalue) + throw new NumberFormatException(); + } else { + switch (Character.toUpperCase(c)) { + case 'W': value *= 7; + case 'D': value *= 24; + case 'H': value *= 60; + case 'M': value *= 60; + case 'S': break; + default: throw new NumberFormatException(); + } + ttl += value; + value = 0; + if (ttl > 0xFFFFFFFFL) + throw new NumberFormatException(); + } + } + if (ttl == 0) + ttl = value; + + if (ttl > 0xFFFFFFFFL) + throw new NumberFormatException(); + else if (ttl > MAX_VALUE && clamp) + ttl = MAX_VALUE; + return ttl; +} + +/** + * Parses a TTL, which can either be expressed as a number or a BIND-style + * string with numbers and units. + * @param s The string representing the TTL + * @return The TTL as a number of seconds + * @throws NumberFormatException The string was not in a valid TTL format. + */ +public static long +parseTTL(String s) { + return parse(s, true); +} + +public static String +format(long ttl) { + TTL.check(ttl); + StringBuffer sb = new StringBuffer(); + long secs, mins, hours, days, weeks; + secs = ttl % 60; + ttl /= 60; + mins = ttl % 60; + ttl /= 60; + hours = ttl % 24; + ttl /= 24; + days = ttl % 7; + ttl /= 7; + weeks = ttl; + if (weeks > 0) + sb.append(weeks + "W"); + if (days > 0) + sb.append(days + "D"); + if (hours > 0) + sb.append(hours + "H"); + if (mins > 0) + sb.append(mins + "M"); + if (secs > 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) + sb.append(secs + "S"); + return sb.toString(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java new file mode 100644 index 0000000..fd99bfb --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TXTBase.java @@ -0,0 +1,123 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * Implements common functionality for the many record types whose format + * is a list of strings. + * + * @author Brian Wellington + */ + +abstract class TXTBase extends Record { + +private static final long serialVersionUID = -4319510507246305931L; + +protected List strings; + +protected +TXTBase() {} + +protected +TXTBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); +} + +protected +TXTBase(Name name, int type, int dclass, long ttl, List strings) { + super(name, type, dclass, ttl); + if (strings == null) + throw new IllegalArgumentException("strings must not be null"); + this.strings = new ArrayList(strings.size()); + Iterator it = strings.iterator(); + try { + while (it.hasNext()) { + String s = (String) it.next(); + this.strings.add(byteArrayFromString(s)); + } + } + catch (TextParseException e) { + throw new IllegalArgumentException(e.getMessage()); + } +} + +protected +TXTBase(Name name, int type, int dclass, long ttl, String string) { + this(name, type, dclass, ttl, Collections.singletonList(string)); +} + +void +rrFromWire(DNSInput in) throws IOException { + strings = new ArrayList(2); + while (in.remaining() > 0) { + byte [] b = in.readCountedString(); + strings.add(b); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + strings = new ArrayList(2); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) + break; + try { + strings.add(byteArrayFromString(t.value)); + } + catch (TextParseException e) { + throw st.exception(e.getMessage()); + } + + } + st.unget(); +} + +/** converts to a String */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + Iterator it = strings.iterator(); + while (it.hasNext()) { + byte [] array = (byte []) it.next(); + sb.append(byteArrayToString(array, true)); + if (it.hasNext()) + sb.append(" "); + } + return sb.toString(); +} + +/** + * Returns the text strings + * @return A list of Strings corresponding to the text strings. + */ +public List +getStrings() { + List list = new ArrayList(strings.size()); + for (int i = 0; i < strings.size(); i++) + list.add(byteArrayToString((byte []) strings.get(i), false)); + return list; +} + +/** + * Returns the text strings + * @return A list of byte arrays corresponding to the text strings. + */ +public List +getStringsAsByteArrays() { + return strings; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + Iterator it = strings.iterator(); + while (it.hasNext()) { + byte [] b = (byte []) it.next(); + out.writeCountedString(b); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java new file mode 100644 index 0000000..ea5de04 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TXTRecord.java @@ -0,0 +1,44 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.*; + +/** + * Text - stores text strings + * + * @author Brian Wellington + */ + +public class TXTRecord extends TXTBase { + +private static final long serialVersionUID = -5780785764284221342L; + +TXTRecord() {} + +Record +getObject() { + return new TXTRecord(); +} + +/** + * Creates a TXT Record from the given data + * @param strings The text strings + * @throws IllegalArgumentException One of the strings has invalid escapes + */ +public +TXTRecord(Name name, int dclass, long ttl, List strings) { + super(name, Type.TXT, dclass, ttl, strings); +} + +/** + * Creates a TXT Record from the given data + * @param string One text string + * @throws IllegalArgumentException The string has invalid escapes + */ +public +TXTRecord(Name name, int dclass, long ttl, String string) { + super(name, Type.TXT, dclass, ttl, string); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java b/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java new file mode 100644 index 0000000..3b9a425 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TextParseException.java @@ -0,0 +1,25 @@ +// Copyright (c) 2002-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * An exception thrown when unable to parse text. + * + * @author Brian Wellington + */ + +public class TextParseException extends IOException { + +public +TextParseException() { + super(); +} + +public +TextParseException(String s) { + super(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java b/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java new file mode 100644 index 0000000..bc637ab --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Tokenizer.java @@ -0,0 +1,713 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) +// +// Copyright (C) 2003-2004 Nominum, Inc. +// +// Permission to use, copy, modify, and distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +// OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; + +import org.xbill.DNS.utils.*; + +/** + * Tokenizer is used to parse DNS records and zones from text format, + * + * @author Brian Wellington + * @author Bob Halley + */ + +public class Tokenizer { + +private static String delim = " \t\n;()\""; +private static String quotes = "\""; + +/** End of file */ +public static final int EOF = 0; + +/** End of line */ +public static final int EOL = 1; + +/** Whitespace; only returned when wantWhitespace is set */ +public static final int WHITESPACE = 2; + +/** An identifier (unquoted string) */ +public static final int IDENTIFIER = 3; + +/** A quoted string */ +public static final int QUOTED_STRING = 4; + +/** A comment; only returned when wantComment is set */ +public static final int COMMENT = 5; + +private PushbackInputStream is; +private boolean ungottenToken; +private int multiline; +private boolean quoting; +private String delimiters; +private Token current; +private StringBuffer sb; +private boolean wantClose; + +private String filename; +private int line; + +public static class Token { + /** The type of token. */ + public int type; + + /** The value of the token, or null for tokens without values. */ + public String value; + + private + Token() { + type = -1; + value = null; + } + + private Token + set(int type, StringBuffer value) { + if (type < 0) + throw new IllegalArgumentException(); + this.type = type; + this.value = value == null ? null : value.toString(); + return this; + } + + /** + * Converts the token to a string containing a representation useful + * for debugging. + */ + public String + toString() { + switch (type) { + case EOF: + return ""; + case EOL: + return ""; + case WHITESPACE: + return ""; + case IDENTIFIER: + return ""; + case QUOTED_STRING: + return ""; + case COMMENT: + return ""; + default: + return ""; + } + } + + /** Indicates whether this token contains a string. */ + public boolean + isString() { + return (type == IDENTIFIER || type == QUOTED_STRING); + } + + /** Indicates whether this token contains an EOL or EOF. */ + public boolean + isEOL() { + return (type == EOL || type == EOF); + } +} + +static class TokenizerException extends TextParseException { + String message; + + public + TokenizerException(String filename, int line, String message) { + super(filename + ":" + line + ": " + message); + this.message = message; + } + + public String + getBaseMessage() { + return message; + } +} + +/** + * Creates a Tokenizer from an arbitrary input stream. + * @param is The InputStream to tokenize. + */ +public +Tokenizer(InputStream is) { + if (!(is instanceof BufferedInputStream)) + is = new BufferedInputStream(is); + this.is = new PushbackInputStream(is, 2); + ungottenToken = false; + multiline = 0; + quoting = false; + delimiters = delim; + current = new Token(); + sb = new StringBuffer(); + filename = ""; + line = 1; +} + +/** + * Creates a Tokenizer from a string. + * @param s The String to tokenize. + */ +public +Tokenizer(String s) { + this(new ByteArrayInputStream(s.getBytes())); +} + +/** + * Creates a Tokenizer from a file. + * @param f The File to tokenize. + */ +public +Tokenizer(File f) throws FileNotFoundException { + this(new FileInputStream(f)); + wantClose = true; + filename = f.getName(); +} + +private int +getChar() throws IOException { + int c = is.read(); + if (c == '\r') { + int next = is.read(); + if (next != '\n') + is.unread(next); + c = '\n'; + } + if (c == '\n') + line++; + return c; +} + +private void +ungetChar(int c) throws IOException { + if (c == -1) + return; + is.unread(c); + if (c == '\n') + line--; +} + +private int +skipWhitespace() throws IOException { + int skipped = 0; + while (true) { + int c = getChar(); + if (c != ' ' && c != '\t') { + if (!(c == '\n' && multiline > 0)) { + ungetChar(c); + return skipped; + } + } + skipped++; + } +} + +private void +checkUnbalancedParens() throws TextParseException { + if (multiline > 0) + throw exception("unbalanced parentheses"); +} + +/** + * Gets the next token from a tokenizer. + * @param wantWhitespace If true, leading whitespace will be returned as a + * token. + * @param wantComment If true, comments are returned as tokens. + * @return The next token in the stream. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public Token +get(boolean wantWhitespace, boolean wantComment) throws IOException { + int type; + int c; + + if (ungottenToken) { + ungottenToken = false; + if (current.type == WHITESPACE) { + if (wantWhitespace) + return current; + } else if (current.type == COMMENT) { + if (wantComment) + return current; + } else { + if (current.type == EOL) + line++; + return current; + } + } + int skipped = skipWhitespace(); + if (skipped > 0 && wantWhitespace) + return current.set(WHITESPACE, null); + type = IDENTIFIER; + sb.setLength(0); + while (true) { + c = getChar(); + if (c == -1 || delimiters.indexOf(c) != -1) { + if (c == -1) { + if (quoting) + throw exception("EOF in " + + "quoted string"); + else if (sb.length() == 0) + return current.set(EOF, null); + else + return current.set(type, sb); + } + if (sb.length() == 0 && type != QUOTED_STRING) { + if (c == '(') { + multiline++; + skipWhitespace(); + continue; + } else if (c == ')') { + if (multiline <= 0) + throw exception("invalid " + + "close " + + "parenthesis"); + multiline--; + skipWhitespace(); + continue; + } else if (c == '"') { + if (!quoting) { + quoting = true; + delimiters = quotes; + type = QUOTED_STRING; + } else { + quoting = false; + delimiters = delim; + skipWhitespace(); + } + continue; + } else if (c == '\n') { + return current.set(EOL, null); + } else if (c == ';') { + while (true) { + c = getChar(); + if (c == '\n' || c == -1) + break; + sb.append((char)c); + } + if (wantComment) { + ungetChar(c); + return current.set(COMMENT, sb); + } else if (c == -1 && + type != QUOTED_STRING) + { + checkUnbalancedParens(); + return current.set(EOF, null); + } else if (multiline > 0) { + skipWhitespace(); + sb.setLength(0); + continue; + } else + return current.set(EOL, null); + } else + throw new IllegalStateException(); + } else + ungetChar(c); + break; + } else if (c == '\\') { + c = getChar(); + if (c == -1) + throw exception("unterminated escape sequence"); + sb.append('\\'); + } else if (quoting && c == '\n') { + throw exception("newline in quoted string"); + } + sb.append((char)c); + } + if (sb.length() == 0 && type != QUOTED_STRING) { + checkUnbalancedParens(); + return current.set(EOF, null); + } + return current.set(type, sb); +} + +/** + * Gets the next token from a tokenizer, ignoring whitespace and comments. + * @return The next token in the stream. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public Token +get() throws IOException { + return get(false, false); +} + +/** + * Returns a token to the stream, so that it will be returned by the next call + * to get(). + * @throws IllegalStateException There are already ungotten tokens. + */ +public void +unget() { + if (ungottenToken) + throw new IllegalStateException + ("Cannot unget multiple tokens"); + if (current.type == EOL) + line--; + ungottenToken = true; +} + +/** + * Gets the next token from a tokenizer and converts it to a string. + * @return The next token in the stream, as a string. + * @throws TextParseException The input was invalid or not a string. + * @throws IOException An I/O error occurred. + */ +public String +getString() throws IOException { + Token next = get(); + if (!next.isString()) { + throw exception("expected a string"); + } + return next.value; +} + +private String +_getIdentifier(String expected) throws IOException { + Token next = get(); + if (next.type != IDENTIFIER) + throw exception("expected " + expected); + return next.value; +} + +/** + * Gets the next token from a tokenizer, ensures it is an unquoted string, + * and converts it to a string. + * @return The next token in the stream, as a string. + * @throws TextParseException The input was invalid or not an unquoted string. + * @throws IOException An I/O error occurred. + */ +public String +getIdentifier() throws IOException { + return _getIdentifier("an identifier"); +} + +/** + * Gets the next token from a tokenizer and converts it to a long. + * @return The next token in the stream, as a long. + * @throws TextParseException The input was invalid or not a long. + * @throws IOException An I/O error occurred. + */ +public long +getLong() throws IOException { + String next = _getIdentifier("an integer"); + if (!Character.isDigit(next.charAt(0))) + throw exception("expected an integer"); + try { + return Long.parseLong(next); + } catch (NumberFormatException e) { + throw exception("expected an integer"); + } +} + +/** + * Gets the next token from a tokenizer and converts it to an unsigned 32 bit + * integer. + * @return The next token in the stream, as an unsigned 32 bit integer. + * @throws TextParseException The input was invalid or not an unsigned 32 + * bit integer. + * @throws IOException An I/O error occurred. + */ +public long +getUInt32() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFFFFFFFL) + throw exception("expected an 32 bit unsigned integer"); + return l; +} + +/** + * Gets the next token from a tokenizer and converts it to an unsigned 16 bit + * integer. + * @return The next token in the stream, as an unsigned 16 bit integer. + * @throws TextParseException The input was invalid or not an unsigned 16 + * bit integer. + * @throws IOException An I/O error occurred. + */ +public int +getUInt16() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFFFL) + throw exception("expected an 16 bit unsigned integer"); + return (int) l; +} + +/** + * Gets the next token from a tokenizer and converts it to an unsigned 8 bit + * integer. + * @return The next token in the stream, as an unsigned 8 bit integer. + * @throws TextParseException The input was invalid or not an unsigned 8 + * bit integer. + * @throws IOException An I/O error occurred. + */ +public int +getUInt8() throws IOException { + long l = getLong(); + if (l < 0 || l > 0xFFL) + throw exception("expected an 8 bit unsigned integer"); + return (int) l; +} + +/** + * Gets the next token from a tokenizer and parses it as a TTL. + * @return The next token in the stream, as an unsigned 32 bit integer. + * @throws TextParseException The input was not valid. + * @throws IOException An I/O error occurred. + * @see TTL + */ +public long +getTTL() throws IOException { + String next = _getIdentifier("a TTL value"); + try { + return TTL.parseTTL(next); + } + catch (NumberFormatException e) { + throw exception("expected a TTL value"); + } +} + +/** + * Gets the next token from a tokenizer and parses it as if it were a TTL. + * @return The next token in the stream, as an unsigned 32 bit integer. + * @throws TextParseException The input was not valid. + * @throws IOException An I/O error occurred. + * @see TTL + */ +public long +getTTLLike() throws IOException { + String next = _getIdentifier("a TTL-like value"); + try { + return TTL.parse(next, false); + } + catch (NumberFormatException e) { + throw exception("expected a TTL-like value"); + } +} + +/** + * Gets the next token from a tokenizer and converts it to a name. + * @param origin The origin to append to relative names. + * @return The next token in the stream, as a name. + * @throws TextParseException The input was invalid or not a valid name. + * @throws IOException An I/O error occurred. + * @throws RelativeNameException The parsed name was relative, even with the + * origin. + * @see Name + */ +public Name +getName(Name origin) throws IOException { + String next = _getIdentifier("a name"); + try { + Name name = Name.fromString(next, origin); + if (!name.isAbsolute()) + throw new RelativeNameException(name); + return name; + } + catch (TextParseException e) { + throw exception(e.getMessage()); + } +} + +/** + * Gets the next token from a tokenizer and converts it to an IP Address. + * @param family The address family. + * @return The next token in the stream, as an InetAddress + * @throws TextParseException The input was invalid or not a valid address. + * @throws IOException An I/O error occurred. + * @see Address + */ +public InetAddress +getAddress(int family) throws IOException { + String next = _getIdentifier("an address"); + try { + return Address.getByAddress(next, family); + } + catch (UnknownHostException e) { + throw exception(e.getMessage()); + } +} + +/** + * Gets the next token from a tokenizer, which must be an EOL or EOF. + * @throws TextParseException The input was invalid or not an EOL or EOF token. + * @throws IOException An I/O error occurred. + */ +public void +getEOL() throws IOException { + Token next = get(); + if (next.type != EOL && next.type != EOF) { + throw exception("expected EOL or EOF"); + } +} + +/** + * Returns a concatenation of the remaining strings from a Tokenizer. + */ +private String +remainingStrings() throws IOException { + StringBuffer buffer = null; + while (true) { + Tokenizer.Token t = get(); + if (!t.isString()) + break; + if (buffer == null) + buffer = new StringBuffer(); + buffer.append(t.value); + } + unget(); + if (buffer == null) + return null; + return buffer.toString(); +} + +/** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the base64 encoded data to a byte array. + * @param required If true, an exception will be thrown if no strings remain; + * otherwise null be be returned. + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getBase64(boolean required) throws IOException { + String s = remainingStrings(); + if (s == null) { + if (required) + throw exception("expected base64 encoded string"); + else + return null; + } + byte [] array = base64.fromString(s); + if (array == null) + throw exception("invalid base64 encoding"); + return array; +} + +/** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the base64 encoded data to a byte array. + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getBase64() throws IOException { + return getBase64(false); +} + +/** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the hex encoded data to a byte array. + * @param required If true, an exception will be thrown if no strings remain; + * otherwise null be be returned. + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getHex(boolean required) throws IOException { + String s = remainingStrings(); + if (s == null) { + if (required) + throw exception("expected hex encoded string"); + else + return null; + } + byte [] array = base16.fromString(s); + if (array == null) + throw exception("invalid hex encoding"); + return array; +} + +/** + * Gets the remaining string tokens until an EOL/EOF is seen, concatenates + * them together, and converts the hex encoded data to a byte array. + * @return The byte array containing the decoded strings, or null if there + * were no strings to decode. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getHex() throws IOException { + return getHex(false); +} + +/** + * Gets the next token from a tokenizer and decodes it as hex. + * @return The byte array containing the decoded string. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getHexString() throws IOException { + String next = _getIdentifier("a hex string"); + byte [] array = base16.fromString(next); + if (array == null) + throw exception("invalid hex encoding"); + return array; +} + +/** + * Gets the next token from a tokenizer and decodes it as base32. + * @param b32 The base32 context to decode with. + * @return The byte array containing the decoded string. + * @throws TextParseException The input was invalid. + * @throws IOException An I/O error occurred. + */ +public byte [] +getBase32String(base32 b32) throws IOException { + String next = _getIdentifier("a base32 string"); + byte [] array = b32.fromString(next); + if (array == null) + throw exception("invalid base32 encoding"); + return array; +} + +/** + * Creates an exception which includes the current state in the error message + * @param s The error message to include. + * @return The exception to be thrown + */ +public TextParseException +exception(String s) { + return new TokenizerException(filename, line, s); +} + +/** + * Closes any files opened by this tokenizer. + */ +public void +close() { + if (wantClose) { + try { + is.close(); + } + catch (IOException e) { + } + } +} + +protected void +finalize() { + close(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Type.java b/external/asmack/build/src/trunk/org/xbill/DNS/Type.java new file mode 100644 index 0000000..a5d479a --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Type.java @@ -0,0 +1,356 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.util.HashMap; + +/** + * Constants and functions relating to DNS Types + * + * @author Brian Wellington + */ + +public final class Type { + +/** Address */ +public static final int A = 1; + +/** Name server */ +public static final int NS = 2; + +/** Mail destination */ +public static final int MD = 3; + +/** Mail forwarder */ +public static final int MF = 4; + +/** Canonical name (alias) */ +public static final int CNAME = 5; + +/** Start of authority */ +public static final int SOA = 6; + +/** Mailbox domain name */ +public static final int MB = 7; + +/** Mail group member */ +public static final int MG = 8; + +/** Mail rename name */ +public static final int MR = 9; + +/** Null record */ +public static final int NULL = 10; + +/** Well known services */ +public static final int WKS = 11; + +/** Domain name pointer */ +public static final int PTR = 12; + +/** Host information */ +public static final int HINFO = 13; + +/** Mailbox information */ +public static final int MINFO = 14; + +/** Mail routing information */ +public static final int MX = 15; + +/** Text strings */ +public static final int TXT = 16; + +/** Responsible person */ +public static final int RP = 17; + +/** AFS cell database */ +public static final int AFSDB = 18; + +/** X.25 calling address */ +public static final int X25 = 19; + +/** ISDN calling address */ +public static final int ISDN = 20; + +/** Router */ +public static final int RT = 21; + +/** NSAP address */ +public static final int NSAP = 22; + +/** Reverse NSAP address (deprecated) */ +public static final int NSAP_PTR = 23; + +/** Signature */ +public static final int SIG = 24; + +/** Key */ +public static final int KEY = 25; + +/** X.400 mail mapping */ +public static final int PX = 26; + +/** Geographical position (withdrawn) */ +public static final int GPOS = 27; + +/** IPv6 address */ +public static final int AAAA = 28; + +/** Location */ +public static final int LOC = 29; + +/** Next valid name in zone */ +public static final int NXT = 30; + +/** Endpoint identifier */ +public static final int EID = 31; + +/** Nimrod locator */ +public static final int NIMLOC = 32; + +/** Server selection */ +public static final int SRV = 33; + +/** ATM address */ +public static final int ATMA = 34; + +/** Naming authority pointer */ +public static final int NAPTR = 35; + +/** Key exchange */ +public static final int KX = 36; + +/** Certificate */ +public static final int CERT = 37; + +/** IPv6 address (experimental) */ +public static final int A6 = 38; + +/** Non-terminal name redirection */ +public static final int DNAME = 39; + +/** Options - contains EDNS metadata */ +public static final int OPT = 41; + +/** Address Prefix List */ +public static final int APL = 42; + +/** Delegation Signer */ +public static final int DS = 43; + +/** SSH Key Fingerprint */ +public static final int SSHFP = 44; + +/** IPSEC key */ +public static final int IPSECKEY = 45; + +/** Resource Record Signature */ +public static final int RRSIG = 46; + +/** Next Secure Name */ +public static final int NSEC = 47; + +/** DNSSEC Key */ +public static final int DNSKEY = 48; + +/** Dynamic Host Configuration Protocol (DHCP) ID */ +public static final int DHCID = 49; + +/** Next SECure, 3rd edition, RFC 5155 */ +public static final int NSEC3 = 50; + +public static final int NSEC3PARAM = 51; + +/** Sender Policy Framework (experimental) */ +public static final int SPF = 99; + +/** Transaction key - used to compute a shared secret or exchange a key */ +public static final int TKEY = 249; + +/** Transaction signature */ +public static final int TSIG = 250; + +/** Incremental zone transfer */ +public static final int IXFR = 251; + +/** Zone transfer */ +public static final int AXFR = 252; + +/** Transfer mailbox records */ +public static final int MAILB = 253; + +/** Transfer mail agent records */ +public static final int MAILA = 254; + +/** Matches any type */ +public static final int ANY = 255; + +/** DNSSEC Lookaside Validation, RFC 4431 . */ +public static final int DLV = 32769; + + +private static class TypeMnemonic extends Mnemonic { + private HashMap objects; + + public + TypeMnemonic() { + super("Type", CASE_UPPER); + setPrefix("TYPE"); + objects = new HashMap(); + } + + public void + add(int val, String str, Record proto) { + super.add(val, str); + objects.put(Mnemonic.toInteger(val), proto); + } + + public void + check(int val) { + Type.check(val); + } + + public Record + getProto(int val) { + check(val); + return (Record) objects.get(toInteger(val)); + } +} + +private static TypeMnemonic types = new TypeMnemonic(); + +static { + types.add(A, "A", new ARecord()); + types.add(NS, "NS", new NSRecord()); + types.add(MD, "MD", new MDRecord()); + types.add(MF, "MF", new MFRecord()); + types.add(CNAME, "CNAME", new CNAMERecord()); + types.add(SOA, "SOA", new SOARecord()); + types.add(MB, "MB", new MBRecord()); + types.add(MG, "MG", new MGRecord()); + types.add(MR, "MR", new MRRecord()); + types.add(NULL, "NULL", new NULLRecord()); + types.add(WKS, "WKS", new WKSRecord()); + types.add(PTR, "PTR", new PTRRecord()); + types.add(HINFO, "HINFO", new HINFORecord()); + types.add(MINFO, "MINFO", new MINFORecord()); + types.add(MX, "MX", new MXRecord()); + types.add(TXT, "TXT", new TXTRecord()); + types.add(RP, "RP", new RPRecord()); + types.add(AFSDB, "AFSDB", new AFSDBRecord()); + types.add(X25, "X25", new X25Record()); + types.add(ISDN, "ISDN", new ISDNRecord()); + types.add(RT, "RT", new RTRecord()); + types.add(NSAP, "NSAP", new NSAPRecord()); + types.add(NSAP_PTR, "NSAP-PTR", new NSAP_PTRRecord()); + types.add(SIG, "SIG", new SIGRecord()); + types.add(KEY, "KEY", new KEYRecord()); + types.add(PX, "PX", new PXRecord()); + types.add(GPOS, "GPOS", new GPOSRecord()); + types.add(AAAA, "AAAA", new AAAARecord()); + types.add(LOC, "LOC", new LOCRecord()); + types.add(NXT, "NXT", new NXTRecord()); + types.add(EID, "EID"); + types.add(NIMLOC, "NIMLOC"); + types.add(SRV, "SRV", new SRVRecord()); + types.add(ATMA, "ATMA"); + types.add(NAPTR, "NAPTR", new NAPTRRecord()); + types.add(KX, "KX", new KXRecord()); + types.add(CERT, "CERT", new CERTRecord()); + types.add(A6, "A6", new A6Record()); + types.add(DNAME, "DNAME", new DNAMERecord()); + types.add(OPT, "OPT", new OPTRecord()); + types.add(APL, "APL", new APLRecord()); + types.add(DS, "DS", new DSRecord()); + types.add(SSHFP, "SSHFP", new SSHFPRecord()); + types.add(IPSECKEY, "IPSECKEY", new IPSECKEYRecord()); + types.add(RRSIG, "RRSIG", new RRSIGRecord()); + types.add(NSEC, "NSEC", new NSECRecord()); + types.add(DNSKEY, "DNSKEY", new DNSKEYRecord()); + types.add(DHCID, "DHCID", new DHCIDRecord()); + types.add(NSEC3, "NSEC3", new NSEC3Record()); + types.add(NSEC3PARAM, "NSEC3PARAM", new NSEC3PARAMRecord()); + types.add(SPF, "SPF", new SPFRecord()); + types.add(TKEY, "TKEY", new TKEYRecord()); + types.add(TSIG, "TSIG", new TSIGRecord()); + types.add(IXFR, "IXFR"); + types.add(AXFR, "AXFR"); + types.add(MAILB, "MAILB"); + types.add(MAILA, "MAILA"); + types.add(ANY, "ANY"); + types.add(DLV, "DLV", new DLVRecord()); +} + +private +Type() { +} + +/** + * Checks that a numeric Type is valid. + * @throws InvalidTypeException The type is out of range. + */ +public static void +check(int val) { + if (val < 0 || val > 0xFFFF) + throw new InvalidTypeException(val); +} + +/** + * Converts a numeric Type into a String + * @param val The type value. + * @return The canonical string representation of the type + * @throws InvalidTypeException The type is out of range. + */ +public static String +string(int val) { + return types.getText(val); +} + +/** + * Converts a String representation of an Type into its numeric value. + * @param s The string representation of the type + * @param numberok Whether a number will be accepted or not. + * @return The type code, or -1 on error. + */ +public static int +value(String s, boolean numberok) { + int val = types.getValue(s); + if (val == -1 && numberok) { + val = types.getValue("TYPE" + s); + } + return val; +} + +/** + * Converts a String representation of an Type into its numeric value + * @return The type code, or -1 on error. + */ +public static int +value(String s) { + return value(s, false); +} + +static Record +getProto(int val) { + return types.getProto(val); +} + +/** Is this type valid for a record (a non-meta type)? */ +public static boolean +isRR(int type) { + switch (type) { + case OPT: + case TKEY: + case TSIG: + case IXFR: + case AXFR: + case MAILB: + case MAILA: + case ANY: + return false; + default: + return true; + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java b/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java new file mode 100644 index 0000000..628cc35 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/TypeBitmap.java @@ -0,0 +1,147 @@ +// Copyright (c) 2004-2009 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * Routines for deal with the lists of types found in NSEC/NSEC3 records. + * + * @author Brian Wellington + */ + +import java.io.*; +import java.util.*; + +final class TypeBitmap implements Serializable { + +private static final long serialVersionUID = -125354057735389003L; + +private TreeSet types; + +private +TypeBitmap() { + types = new TreeSet(); +} + +public +TypeBitmap(int [] array) { + this(); + for (int i = 0; i < array.length; i++) { + Type.check(array[i]); + types.add(new Integer(array[i])); + } +} + +public +TypeBitmap(DNSInput in) throws WireParseException { + this(); + int lastbase = -1; + while (in.remaining() > 0) { + if (in.remaining() < 2) + throw new WireParseException + ("invalid bitmap descriptor"); + int mapbase = in.readU8(); + if (mapbase < lastbase) + throw new WireParseException("invalid ordering"); + int maplength = in.readU8(); + if (maplength > in.remaining()) + throw new WireParseException("invalid bitmap"); + for (int i = 0; i < maplength; i++) { + int current = in.readU8(); + if (current == 0) + continue; + for (int j = 0; j < 8; j++) { + if ((current & (1 << (7 - j))) == 0) + continue; + int typecode = mapbase * 256 + + i * 8 + j; + types.add(Mnemonic.toInteger(typecode)); + } + } + } +} + +public +TypeBitmap(Tokenizer st) throws IOException { + this(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) + break; + int typecode = Type.value(t.value); + if (typecode < 0) { + throw st.exception("Invalid type: " + t.value); + } + types.add(Mnemonic.toInteger(typecode)); + } + st.unget(); +} + +public int [] +toArray() { + int [] array = new int[types.size()]; + int n = 0; + for (Iterator it = types.iterator(); it.hasNext(); ) + array[n++] = ((Integer)it.next()).intValue(); + return array; +} + +public String +toString() { + StringBuffer sb = new StringBuffer(); + for (Iterator it = types.iterator(); it.hasNext(); ) { + int t = ((Integer)it.next()).intValue(); + sb.append(Type.string(t)); + if (it.hasNext()) + sb.append(' '); + } + return sb.toString(); +} + +private static void +mapToWire(DNSOutput out, TreeSet map, int mapbase) { + int arraymax = (((Integer)map.last()).intValue()) & 0xFF; + int arraylength = (arraymax / 8) + 1; + int [] array = new int[arraylength]; + out.writeU8(mapbase); + out.writeU8(arraylength); + for (Iterator it = map.iterator(); it.hasNext(); ) { + int typecode = ((Integer)it.next()).intValue(); + array[(typecode & 0xFF) / 8] |= (1 << ( 7 - typecode % 8)); + } + for (int j = 0; j < arraylength; j++) + out.writeU8(array[j]); +} + +public void +toWire(DNSOutput out) { + if (types.size() == 0) + return; + + int mapbase = -1; + TreeSet map = new TreeSet(); + + for (Iterator it = types.iterator(); it.hasNext(); ) { + int t = ((Integer)it.next()).intValue(); + int base = t >> 8; + if (base != mapbase) { + if (map.size() > 0) { + mapToWire(out, map, mapbase); + map.clear(); + } + mapbase = base; + } + map.add(new Integer(t)); + } + mapToWire(out, map, mapbase); +} + +public boolean +empty() { + return types.isEmpty(); +} + +public boolean +contains(int typecode) { + return types.contains(Mnemonic.toInteger(typecode)); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java b/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java new file mode 100644 index 0000000..df3c836 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/U16NameBase.java @@ -0,0 +1,75 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * Implements common functionality for the many record types whose format + * is an unsigned 16 bit integer followed by a name. + * + * @author Brian Wellington + */ + +abstract class U16NameBase extends Record { + +private static final long serialVersionUID = -8315884183112502995L; + +protected int u16Field; +protected Name nameField; + +protected +U16NameBase() {} + +protected +U16NameBase(Name name, int type, int dclass, long ttl) { + super(name, type, dclass, ttl); +} + +protected +U16NameBase(Name name, int type, int dclass, long ttl, int u16Field, + String u16Description, Name nameField, String nameDescription) +{ + super(name, type, dclass, ttl); + this.u16Field = checkU16(u16Description, u16Field); + this.nameField = checkName(nameDescription, nameField); +} + +void +rrFromWire(DNSInput in) throws IOException { + u16Field = in.readU16(); + nameField = new Name(in); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + u16Field = st.getUInt16(); + nameField = st.getName(origin); +} + +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(u16Field); + sb.append(" "); + sb.append(nameField); + return sb.toString(); +} + +protected int +getU16Field() { + return u16Field; +} + +protected Name +getNameField() { + return nameField; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeU16(u16Field); + nameField.toWire(out, null, canonical); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java b/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java new file mode 100644 index 0000000..e752ce4 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/UDPClient.java @@ -0,0 +1,164 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import java.security.SecureRandom; +import java.nio.*; +import java.nio.channels.*; + +final class UDPClient extends Client { + +private static final int EPHEMERAL_START = 1024; +private static final int EPHEMERAL_STOP = 65535; +private static final int EPHEMERAL_RANGE = EPHEMERAL_STOP - EPHEMERAL_START; + +private static SecureRandom prng = new SecureRandom(); +private static volatile boolean prng_initializing = true; + +/* + * On some platforms (Windows), the SecureRandom module initialization involves + * a call to InetAddress.getLocalHost(), which can end up here if using a + * dnsjava name service provider. + * + * This can cause problems in multiple ways. + * - If the SecureRandom seed generation process calls into here, and this + * module attempts to seed the local SecureRandom object, the thread hangs. + * - If something else calls InetAddress.getLocalHost(), and that causes this + * module to seed the local SecureRandom object, the thread hangs. + * + * To avoid both of these, check at initialization time to see if InetAddress + * is in the call chain. If so, initialize the SecureRandom object in a new + * thread, and disable port randomization until it completes. + */ +static { + new Thread(new Runnable() { + public void run() { + int n = prng.nextInt(); + prng_initializing = false; + }}).start(); +} + +private boolean bound = false; + +public +UDPClient(long endTime) throws IOException { + super(DatagramChannel.open(), endTime); +} + +private void +bind_random(InetSocketAddress addr) throws IOException +{ + if (prng_initializing) { + try { + Thread.sleep(2); + } + catch (InterruptedException e) { + } + if (prng_initializing) + return; + } + + DatagramChannel channel = (DatagramChannel) key.channel(); + InetSocketAddress temp; + + for (int i = 0; i < 1024; i++) { + try { + int port = prng.nextInt(EPHEMERAL_RANGE) + + EPHEMERAL_START; + if (addr != null) + temp = new InetSocketAddress(addr.getAddress(), + port); + else + temp = new InetSocketAddress(port); + channel.socket().bind(temp); + bound = true; + return; + } + catch (SocketException e) { + } + } +} + +void +bind(SocketAddress addr) throws IOException { + if (addr == null || + (addr instanceof InetSocketAddress && + ((InetSocketAddress)addr).getPort() == 0)) + { + bind_random((InetSocketAddress) addr); + if (bound) + return; + } + + if (addr != null) { + DatagramChannel channel = (DatagramChannel) key.channel(); + channel.socket().bind(addr); + bound = true; + } +} + +void +connect(SocketAddress addr) throws IOException { + if (!bound) + bind(null); + DatagramChannel channel = (DatagramChannel) key.channel(); + channel.connect(addr); +} + +void +send(byte [] data) throws IOException { + DatagramChannel channel = (DatagramChannel) key.channel(); + verboseLog("UDP write", data); + channel.write(ByteBuffer.wrap(data)); +} + +byte [] +recv(int max) throws IOException { + DatagramChannel channel = (DatagramChannel) key.channel(); + byte [] temp = new byte[max]; + key.interestOps(SelectionKey.OP_READ); + try { + while (!key.isReadable()) + blockUntil(key, endTime); + } + finally { + if (key.isValid()) + key.interestOps(0); + } + long ret = channel.read(ByteBuffer.wrap(temp)); + if (ret <= 0) + throw new EOFException(); + int len = (int) ret; + byte [] data = new byte[len]; + System.arraycopy(temp, 0, data, 0, len); + verboseLog("UDP read", data); + return data; +} + +static byte [] +sendrecv(SocketAddress local, SocketAddress remote, byte [] data, int max, + long endTime) +throws IOException +{ + UDPClient client = new UDPClient(endTime); + try { + client.bind(local); + client.connect(remote); + client.send(data); + return client.recv(max); + } + finally { + client.cleanup(); + } +} + +static byte [] +sendrecv(SocketAddress addr, byte [] data, int max, long endTime) +throws IOException +{ + return sendrecv(null, addr, data, max, endTime); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java new file mode 100644 index 0000000..91c9697 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/UNKRecord.java @@ -0,0 +1,54 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * A class implementing Records of unknown and/or unimplemented types. This + * class can only be initialized using static Record initializers. + * + * @author Brian Wellington + */ + +public class UNKRecord extends Record { + +private static final long serialVersionUID = -4193583311594626915L; + +private byte [] data; + +UNKRecord() {} + +Record +getObject() { + return new UNKRecord(); +} + +void +rrFromWire(DNSInput in) throws IOException { + data = in.readByteArray(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + throw st.exception("invalid unknown RR encoding"); +} + +/** Converts this Record to the String "unknown format" */ +String +rrToString() { + return unknownToString(data); +} + +/** Returns the contents of this record. */ +public byte [] +getData() { + return data; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(data); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Update.java b/external/asmack/build/src/trunk/org/xbill/DNS/Update.java new file mode 100644 index 0000000..02a920b --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Update.java @@ -0,0 +1,300 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A helper class for constructing dynamic DNS (DDNS) update messages. + * + * @author Brian Wellington + */ + +public class Update extends Message { + +private Name origin; +private int dclass; + +/** + * Creates an update message. + * @param zone The name of the zone being updated. + * @param dclass The class of the zone being updated. + */ +public +Update(Name zone, int dclass) { + super(); + if (!zone.isAbsolute()) + throw new RelativeNameException(zone); + DClass.check(dclass); + getHeader().setOpcode(Opcode.UPDATE); + Record soa = Record.newRecord(zone, Type.SOA, DClass.IN); + addRecord(soa, Section.QUESTION); + this.origin = zone; + this.dclass = dclass; +} + +/** + * Creates an update message. The class is assumed to be IN. + * @param zone The name of the zone being updated. + */ +public +Update(Name zone) { + this(zone, DClass.IN); +} + +private void +newPrereq(Record rec) { + addRecord(rec, Section.PREREQ); +} + +private void +newUpdate(Record rec) { + addRecord(rec, Section.UPDATE); +} + +/** + * Inserts a prerequisite that the specified name exists; that is, there + * exist records with the given name in the zone. + */ +public void +present(Name name) { + newPrereq(Record.newRecord(name, Type.ANY, DClass.ANY, 0)); +} + +/** + * Inserts a prerequisite that the specified rrset exists; that is, there + * exist records with the given name and type in the zone. + */ +public void +present(Name name, int type) { + newPrereq(Record.newRecord(name, type, DClass.ANY, 0)); +} + +/** + * Parses a record from the string, and inserts a prerequisite that the + * record exists. Due to the way value-dependent prequisites work, the + * condition that must be met is that the set of all records with the same + * and type in the update message must be identical to the set of all records + * with that name and type on the server. + * @throws IOException The record could not be parsed. + */ +public void +present(Name name, int type, String record) throws IOException { + newPrereq(Record.fromString(name, type, dclass, 0, record, origin)); +} + +/** + * Parses a record from the tokenizer, and inserts a prerequisite that the + * record exists. Due to the way value-dependent prequisites work, the + * condition that must be met is that the set of all records with the same + * and type in the update message must be identical to the set of all records + * with that name and type on the server. + * @throws IOException The record could not be parsed. + */ +public void +present(Name name, int type, Tokenizer tokenizer) throws IOException { + newPrereq(Record.fromString(name, type, dclass, 0, tokenizer, origin)); +} + +/** + * Inserts a prerequisite that the specified record exists. Due to the way + * value-dependent prequisites work, the condition that must be met is that + * the set of all records with the same and type in the update message must + * be identical to the set of all records with that name and type on the server. + */ +public void +present(Record record) { + newPrereq(record); +} + +/** + * Inserts a prerequisite that the specified name does not exist; that is, + * there are no records with the given name in the zone. + */ +public void +absent(Name name) { + newPrereq(Record.newRecord(name, Type.ANY, DClass.NONE, 0)); +} + +/** + * Inserts a prerequisite that the specified rrset does not exist; that is, + * there are no records with the given name and type in the zone. + */ +public void +absent(Name name, int type) { + newPrereq(Record.newRecord(name, type, DClass.NONE, 0)); +} + +/** + * Parses a record from the string, and indicates that the record + * should be inserted into the zone. + * @throws IOException The record could not be parsed. + */ +public void +add(Name name, int type, long ttl, String record) throws IOException { + newUpdate(Record.fromString(name, type, dclass, ttl, record, origin)); +} + +/** + * Parses a record from the tokenizer, and indicates that the record + * should be inserted into the zone. + * @throws IOException The record could not be parsed. + */ +public void +add(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException { + newUpdate(Record.fromString(name, type, dclass, ttl, tokenizer, + origin)); +} + +/** + * Indicates that the record should be inserted into the zone. + */ +public void +add(Record record) { + newUpdate(record); +} + +/** + * Indicates that the records should be inserted into the zone. + */ +public void +add(Record [] records) { + for (int i = 0; i < records.length; i++) + add(records[i]); +} + +/** + * Indicates that all of the records in the rrset should be inserted into the + * zone. + */ +public void +add(RRset rrset) { + for (Iterator it = rrset.rrs(); it.hasNext(); ) + add((Record) it.next()); +} + +/** + * Indicates that all records with the given name should be deleted from + * the zone. + */ +public void +delete(Name name) { + newUpdate(Record.newRecord(name, Type.ANY, DClass.ANY, 0)); +} + +/** + * Indicates that all records with the given name and type should be deleted + * from the zone. + */ +public void +delete(Name name, int type) { + newUpdate(Record.newRecord(name, type, DClass.ANY, 0)); +} + +/** + * Parses a record from the string, and indicates that the record + * should be deleted from the zone. + * @throws IOException The record could not be parsed. + */ +public void +delete(Name name, int type, String record) throws IOException { + newUpdate(Record.fromString(name, type, DClass.NONE, 0, record, + origin)); +} + +/** + * Parses a record from the tokenizer, and indicates that the record + * should be deleted from the zone. + * @throws IOException The record could not be parsed. + */ +public void +delete(Name name, int type, Tokenizer tokenizer) throws IOException { + newUpdate(Record.fromString(name, type, DClass.NONE, 0, tokenizer, + origin)); +} + +/** + * Indicates that the specified record should be deleted from the zone. + */ +public void +delete(Record record) { + newUpdate(record.withDClass(DClass.NONE, 0)); +} + +/** + * Indicates that the records should be deleted from the zone. + */ +public void +delete(Record [] records) { + for (int i = 0; i < records.length; i++) + delete(records[i]); +} + +/** + * Indicates that all of the records in the rrset should be deleted from the + * zone. + */ +public void +delete(RRset rrset) { + for (Iterator it = rrset.rrs(); it.hasNext(); ) + delete((Record) it.next()); +} + +/** + * Parses a record from the string, and indicates that the record + * should be inserted into the zone replacing any other records with the + * same name and type. + * @throws IOException The record could not be parsed. + */ +public void +replace(Name name, int type, long ttl, String record) throws IOException { + delete(name, type); + add(name, type, ttl, record); +} + +/** + * Parses a record from the tokenizer, and indicates that the record + * should be inserted into the zone replacing any other records with the + * same name and type. + * @throws IOException The record could not be parsed. + */ +public void +replace(Name name, int type, long ttl, Tokenizer tokenizer) throws IOException +{ + delete(name, type); + add(name, type, ttl, tokenizer); +} + +/** + * Indicates that the record should be inserted into the zone replacing any + * other records with the same name and type. + */ +public void +replace(Record record) { + delete(record.getName(), record.getType()); + add(record); +} + +/** + * Indicates that the records should be inserted into the zone replacing any + * other records with the same name and type as each one. + */ +public void +replace(Record [] records) { + for (int i = 0; i < records.length; i++) + replace(records[i]); +} + +/** + * Indicates that all of the records in the rrset should be inserted into the + * zone replacing any other records with the same name and type. + */ +public void +replace(RRset rrset) { + delete(rrset.getName(), rrset.getType()); + for (Iterator it = rrset.rrs(); it.hasNext(); ) + add((Record) it.next()); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java b/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java new file mode 100644 index 0000000..10b61be --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/WKSRecord.java @@ -0,0 +1,719 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.net.*; +import java.io.*; +import java.util.*; + +/** + * Well Known Services - Lists services offered by this host. + * + * @author Brian Wellington + */ + +public class WKSRecord extends Record { + +private static final long serialVersionUID = -9104259763909119805L; + +public static class Protocol { + /** + * IP protocol identifiers. This is basically copied out of RFC 1010. + */ + + private Protocol() {} + + /** Internet Control Message */ + public static final int ICMP = 1; + + /** Internet Group Management */ + public static final int IGMP = 2; + + /** Gateway-to-Gateway */ + public static final int GGP = 3; + + /** Stream */ + public static final int ST = 5; + + /** Transmission Control */ + public static final int TCP = 6; + + /** UCL */ + public static final int UCL = 7; + + /** Exterior Gateway Protocol */ + public static final int EGP = 8; + + /** any private interior gateway */ + public static final int IGP = 9; + + /** BBN RCC Monitoring */ + public static final int BBN_RCC_MON = 10; + + /** Network Voice Protocol */ + public static final int NVP_II = 11; + + /** PUP */ + public static final int PUP = 12; + + /** ARGUS */ + public static final int ARGUS = 13; + + /** EMCON */ + public static final int EMCON = 14; + + /** Cross Net Debugger */ + public static final int XNET = 15; + + /** Chaos */ + public static final int CHAOS = 16; + + /** User Datagram */ + public static final int UDP = 17; + + /** Multiplexing */ + public static final int MUX = 18; + + /** DCN Measurement Subsystems */ + public static final int DCN_MEAS = 19; + + /** Host Monitoring */ + public static final int HMP = 20; + + /** Packet Radio Measurement */ + public static final int PRM = 21; + + /** XEROX NS IDP */ + public static final int XNS_IDP = 22; + + /** Trunk-1 */ + public static final int TRUNK_1 = 23; + + /** Trunk-2 */ + public static final int TRUNK_2 = 24; + + /** Leaf-1 */ + public static final int LEAF_1 = 25; + + /** Leaf-2 */ + public static final int LEAF_2 = 26; + + /** Reliable Data Protocol */ + public static final int RDP = 27; + + /** Internet Reliable Transaction */ + public static final int IRTP = 28; + + /** ISO Transport Protocol Class 4 */ + public static final int ISO_TP4 = 29; + + /** Bulk Data Transfer Protocol */ + public static final int NETBLT = 30; + + /** MFE Network Services Protocol */ + public static final int MFE_NSP = 31; + + /** MERIT Internodal Protocol */ + public static final int MERIT_INP = 32; + + /** Sequential Exchange Protocol */ + public static final int SEP = 33; + + /** CFTP */ + public static final int CFTP = 62; + + /** SATNET and Backroom EXPAK */ + public static final int SAT_EXPAK = 64; + + /** MIT Subnet Support */ + public static final int MIT_SUBNET = 65; + + /** MIT Remote Virtual Disk Protocol */ + public static final int RVD = 66; + + /** Internet Pluribus Packet Core */ + public static final int IPPC = 67; + + /** SATNET Monitoring */ + public static final int SAT_MON = 69; + + /** Internet Packet Core Utility */ + public static final int IPCV = 71; + + /** Backroom SATNET Monitoring */ + public static final int BR_SAT_MON = 76; + + /** WIDEBAND Monitoring */ + public static final int WB_MON = 78; + + /** WIDEBAND EXPAK */ + public static final int WB_EXPAK = 79; + + private static Mnemonic protocols = new Mnemonic("IP protocol", + Mnemonic.CASE_LOWER); + + static { + protocols.setMaximum(0xFF); + protocols.setNumericAllowed(true); + + protocols.add(ICMP, "icmp"); + protocols.add(IGMP, "igmp"); + protocols.add(GGP, "ggp"); + protocols.add(ST, "st"); + protocols.add(TCP, "tcp"); + protocols.add(UCL, "ucl"); + protocols.add(EGP, "egp"); + protocols.add(IGP, "igp"); + protocols.add(BBN_RCC_MON, "bbn-rcc-mon"); + protocols.add(NVP_II, "nvp-ii"); + protocols.add(PUP, "pup"); + protocols.add(ARGUS, "argus"); + protocols.add(EMCON, "emcon"); + protocols.add(XNET, "xnet"); + protocols.add(CHAOS, "chaos"); + protocols.add(UDP, "udp"); + protocols.add(MUX, "mux"); + protocols.add(DCN_MEAS, "dcn-meas"); + protocols.add(HMP, "hmp"); + protocols.add(PRM, "prm"); + protocols.add(XNS_IDP, "xns-idp"); + protocols.add(TRUNK_1, "trunk-1"); + protocols.add(TRUNK_2, "trunk-2"); + protocols.add(LEAF_1, "leaf-1"); + protocols.add(LEAF_2, "leaf-2"); + protocols.add(RDP, "rdp"); + protocols.add(IRTP, "irtp"); + protocols.add(ISO_TP4, "iso-tp4"); + protocols.add(NETBLT, "netblt"); + protocols.add(MFE_NSP, "mfe-nsp"); + protocols.add(MERIT_INP, "merit-inp"); + protocols.add(SEP, "sep"); + protocols.add(CFTP, "cftp"); + protocols.add(SAT_EXPAK, "sat-expak"); + protocols.add(MIT_SUBNET, "mit-subnet"); + protocols.add(RVD, "rvd"); + protocols.add(IPPC, "ippc"); + protocols.add(SAT_MON, "sat-mon"); + protocols.add(IPCV, "ipcv"); + protocols.add(BR_SAT_MON, "br-sat-mon"); + protocols.add(WB_MON, "wb-mon"); + protocols.add(WB_EXPAK, "wb-expak"); + } + + /** + * Converts an IP protocol value into its textual representation + */ + public static String + string(int type) { + return protocols.getText(type); + } + + /** + * Converts a textual representation of an IP protocol into its + * numeric code. Integers in the range 0..255 are also accepted. + * @param s The textual representation of the protocol + * @return The protocol code, or -1 on error. + */ + public static int + value(String s) { + return protocols.getValue(s); + } +} + +public static class Service { + /** + * TCP/UDP services. This is basically copied out of RFC 1010, + * with MIT-ML-DEV removed, as it is not unique, and the description + * of SWIFT-RVF fixed. + */ + + private Service() {} + + /** Remote Job Entry */ + public static final int RJE = 5; + + /** Echo */ + public static final int ECHO = 7; + + /** Discard */ + public static final int DISCARD = 9; + + /** Active Users */ + public static final int USERS = 11; + + /** Daytime */ + public static final int DAYTIME = 13; + + /** Quote of the Day */ + public static final int QUOTE = 17; + + /** Character Generator */ + public static final int CHARGEN = 19; + + /** File Transfer [Default Data] */ + public static final int FTP_DATA = 20; + + /** File Transfer [Control] */ + public static final int FTP = 21; + + /** Telnet */ + public static final int TELNET = 23; + + /** Simple Mail Transfer */ + public static final int SMTP = 25; + + /** NSW User System FE */ + public static final int NSW_FE = 27; + + /** MSG ICP */ + public static final int MSG_ICP = 29; + + /** MSG Authentication */ + public static final int MSG_AUTH = 31; + + /** Display Support Protocol */ + public static final int DSP = 33; + + /** Time */ + public static final int TIME = 37; + + /** Resource Location Protocol */ + public static final int RLP = 39; + + /** Graphics */ + public static final int GRAPHICS = 41; + + /** Host Name Server */ + public static final int NAMESERVER = 42; + + /** Who Is */ + public static final int NICNAME = 43; + + /** MPM FLAGS Protocol */ + public static final int MPM_FLAGS = 44; + + /** Message Processing Module [recv] */ + public static final int MPM = 45; + + /** MPM [default send] */ + public static final int MPM_SND = 46; + + /** NI FTP */ + public static final int NI_FTP = 47; + + /** Login Host Protocol */ + public static final int LOGIN = 49; + + /** IMP Logical Address Maintenance */ + public static final int LA_MAINT = 51; + + /** Domain Name Server */ + public static final int DOMAIN = 53; + + /** ISI Graphics Language */ + public static final int ISI_GL = 55; + + /** NI MAIL */ + public static final int NI_MAIL = 61; + + /** VIA Systems - FTP */ + public static final int VIA_FTP = 63; + + /** TACACS-Database Service */ + public static final int TACACS_DS = 65; + + /** Bootstrap Protocol Server */ + public static final int BOOTPS = 67; + + /** Bootstrap Protocol Client */ + public static final int BOOTPC = 68; + + /** Trivial File Transfer */ + public static final int TFTP = 69; + + /** Remote Job Service */ + public static final int NETRJS_1 = 71; + + /** Remote Job Service */ + public static final int NETRJS_2 = 72; + + /** Remote Job Service */ + public static final int NETRJS_3 = 73; + + /** Remote Job Service */ + public static final int NETRJS_4 = 74; + + /** Finger */ + public static final int FINGER = 79; + + /** HOSTS2 Name Server */ + public static final int HOSTS2_NS = 81; + + /** SU/MIT Telnet Gateway */ + public static final int SU_MIT_TG = 89; + + /** MIT Dover Spooler */ + public static final int MIT_DOV = 91; + + /** Device Control Protocol */ + public static final int DCP = 93; + + /** SUPDUP */ + public static final int SUPDUP = 95; + + /** Swift Remote Virtual File Protocol */ + public static final int SWIFT_RVF = 97; + + /** TAC News */ + public static final int TACNEWS = 98; + + /** Metagram Relay */ + public static final int METAGRAM = 99; + + /** NIC Host Name Server */ + public static final int HOSTNAME = 101; + + /** ISO-TSAP */ + public static final int ISO_TSAP = 102; + + /** X400 */ + public static final int X400 = 103; + + /** X400-SND */ + public static final int X400_SND = 104; + + /** Mailbox Name Nameserver */ + public static final int CSNET_NS = 105; + + /** Remote Telnet Service */ + public static final int RTELNET = 107; + + /** Post Office Protocol - Version 2 */ + public static final int POP_2 = 109; + + /** SUN Remote Procedure Call */ + public static final int SUNRPC = 111; + + /** Authentication Service */ + public static final int AUTH = 113; + + /** Simple File Transfer Protocol */ + public static final int SFTP = 115; + + /** UUCP Path Service */ + public static final int UUCP_PATH = 117; + + /** Network News Transfer Protocol */ + public static final int NNTP = 119; + + /** HYDRA Expedited Remote Procedure */ + public static final int ERPC = 121; + + /** Network Time Protocol */ + public static final int NTP = 123; + + /** Locus PC-Interface Net Map Server */ + public static final int LOCUS_MAP = 125; + + /** Locus PC-Interface Conn Server */ + public static final int LOCUS_CON = 127; + + /** Password Generator Protocol */ + public static final int PWDGEN = 129; + + /** CISCO FNATIVE */ + public static final int CISCO_FNA = 130; + + /** CISCO TNATIVE */ + public static final int CISCO_TNA = 131; + + /** CISCO SYSMAINT */ + public static final int CISCO_SYS = 132; + + /** Statistics Service */ + public static final int STATSRV = 133; + + /** INGRES-NET Service */ + public static final int INGRES_NET = 134; + + /** Location Service */ + public static final int LOC_SRV = 135; + + /** PROFILE Naming System */ + public static final int PROFILE = 136; + + /** NETBIOS Name Service */ + public static final int NETBIOS_NS = 137; + + /** NETBIOS Datagram Service */ + public static final int NETBIOS_DGM = 138; + + /** NETBIOS Session Service */ + public static final int NETBIOS_SSN = 139; + + /** EMFIS Data Service */ + public static final int EMFIS_DATA = 140; + + /** EMFIS Control Service */ + public static final int EMFIS_CNTL = 141; + + /** Britton-Lee IDM */ + public static final int BL_IDM = 142; + + /** Survey Measurement */ + public static final int SUR_MEAS = 243; + + /** LINK */ + public static final int LINK = 245; + + private static Mnemonic services = new Mnemonic("TCP/UDP service", + Mnemonic.CASE_LOWER); + + static { + services.setMaximum(0xFFFF); + services.setNumericAllowed(true); + + services.add(RJE, "rje"); + services.add(ECHO, "echo"); + services.add(DISCARD, "discard"); + services.add(USERS, "users"); + services.add(DAYTIME, "daytime"); + services.add(QUOTE, "quote"); + services.add(CHARGEN, "chargen"); + services.add(FTP_DATA, "ftp-data"); + services.add(FTP, "ftp"); + services.add(TELNET, "telnet"); + services.add(SMTP, "smtp"); + services.add(NSW_FE, "nsw-fe"); + services.add(MSG_ICP, "msg-icp"); + services.add(MSG_AUTH, "msg-auth"); + services.add(DSP, "dsp"); + services.add(TIME, "time"); + services.add(RLP, "rlp"); + services.add(GRAPHICS, "graphics"); + services.add(NAMESERVER, "nameserver"); + services.add(NICNAME, "nicname"); + services.add(MPM_FLAGS, "mpm-flags"); + services.add(MPM, "mpm"); + services.add(MPM_SND, "mpm-snd"); + services.add(NI_FTP, "ni-ftp"); + services.add(LOGIN, "login"); + services.add(LA_MAINT, "la-maint"); + services.add(DOMAIN, "domain"); + services.add(ISI_GL, "isi-gl"); + services.add(NI_MAIL, "ni-mail"); + services.add(VIA_FTP, "via-ftp"); + services.add(TACACS_DS, "tacacs-ds"); + services.add(BOOTPS, "bootps"); + services.add(BOOTPC, "bootpc"); + services.add(TFTP, "tftp"); + services.add(NETRJS_1, "netrjs-1"); + services.add(NETRJS_2, "netrjs-2"); + services.add(NETRJS_3, "netrjs-3"); + services.add(NETRJS_4, "netrjs-4"); + services.add(FINGER, "finger"); + services.add(HOSTS2_NS, "hosts2-ns"); + services.add(SU_MIT_TG, "su-mit-tg"); + services.add(MIT_DOV, "mit-dov"); + services.add(DCP, "dcp"); + services.add(SUPDUP, "supdup"); + services.add(SWIFT_RVF, "swift-rvf"); + services.add(TACNEWS, "tacnews"); + services.add(METAGRAM, "metagram"); + services.add(HOSTNAME, "hostname"); + services.add(ISO_TSAP, "iso-tsap"); + services.add(X400, "x400"); + services.add(X400_SND, "x400-snd"); + services.add(CSNET_NS, "csnet-ns"); + services.add(RTELNET, "rtelnet"); + services.add(POP_2, "pop-2"); + services.add(SUNRPC, "sunrpc"); + services.add(AUTH, "auth"); + services.add(SFTP, "sftp"); + services.add(UUCP_PATH, "uucp-path"); + services.add(NNTP, "nntp"); + services.add(ERPC, "erpc"); + services.add(NTP, "ntp"); + services.add(LOCUS_MAP, "locus-map"); + services.add(LOCUS_CON, "locus-con"); + services.add(PWDGEN, "pwdgen"); + services.add(CISCO_FNA, "cisco-fna"); + services.add(CISCO_TNA, "cisco-tna"); + services.add(CISCO_SYS, "cisco-sys"); + services.add(STATSRV, "statsrv"); + services.add(INGRES_NET, "ingres-net"); + services.add(LOC_SRV, "loc-srv"); + services.add(PROFILE, "profile"); + services.add(NETBIOS_NS, "netbios-ns"); + services.add(NETBIOS_DGM, "netbios-dgm"); + services.add(NETBIOS_SSN, "netbios-ssn"); + services.add(EMFIS_DATA, "emfis-data"); + services.add(EMFIS_CNTL, "emfis-cntl"); + services.add(BL_IDM, "bl-idm"); + services.add(SUR_MEAS, "sur-meas"); + services.add(LINK, "link"); + } + + /** + * Converts a TCP/UDP service port number into its textual + * representation. + */ + public static String + string(int type) { + return services.getText(type); + } + + /** + * Converts a textual representation of a TCP/UDP service into its + * port number. Integers in the range 0..65535 are also accepted. + * @param s The textual representation of the service. + * @return The port number, or -1 on error. + */ + public static int + value(String s) { + return services.getValue(s); + } +} +private byte [] address; +private int protocol; +private int [] services; + +WKSRecord() {} + +Record +getObject() { + return new WKSRecord(); +} + +/** + * Creates a WKS Record from the given data + * @param address The IP address + * @param protocol The IP protocol number + * @param services An array of supported services, represented by port number. + */ +public +WKSRecord(Name name, int dclass, long ttl, InetAddress address, int protocol, + int [] services) +{ + super(name, Type.WKS, dclass, ttl); + if (Address.familyOf(address) != Address.IPv4) + throw new IllegalArgumentException("invalid IPv4 address"); + this.address = address.getAddress(); + this.protocol = checkU8("protocol", protocol); + for (int i = 0; i < services.length; i++) { + checkU16("service", services[i]); + } + this.services = new int[services.length]; + System.arraycopy(services, 0, this.services, 0, services.length); + Arrays.sort(this.services); +} + +void +rrFromWire(DNSInput in) throws IOException { + address = in.readByteArray(4); + protocol = in.readU8(); + byte [] array = in.readByteArray(); + List list = new ArrayList(); + for (int i = 0; i < array.length; i++) { + for (int j = 0; j < 8; j++) { + int octet = array[i] & 0xFF; + if ((octet & (1 << (7 - j))) != 0) { + list.add(new Integer(i * 8 + j)); + } + } + } + services = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + services[i] = ((Integer) list.get(i)).intValue(); + } +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String s = st.getString(); + address = Address.toByteArray(s, Address.IPv4); + if (address == null) + throw st.exception("invalid address"); + + s = st.getString(); + protocol = Protocol.value(s); + if (protocol < 0) { + throw st.exception("Invalid IP protocol: " + s); + } + + List list = new ArrayList(); + while (true) { + Tokenizer.Token t = st.get(); + if (!t.isString()) + break; + int service = Service.value(t.value); + if (service < 0) { + throw st.exception("Invalid TCP/UDP service: " + + t.value); + } + list.add(new Integer(service)); + } + st.unget(); + services = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + services[i] = ((Integer) list.get(i)).intValue(); + } +} + +/** + * Converts rdata to a String + */ +String +rrToString() { + StringBuffer sb = new StringBuffer(); + sb.append(Address.toDottedQuad(address)); + sb.append(" "); + sb.append(protocol); + for (int i = 0; i < services.length; i++) { + sb.append(" " + services[i]); + } + return sb.toString(); +} + +/** + * Returns the IP address. + */ +public InetAddress +getAddress() { + try { + return InetAddress.getByAddress(address); + } catch (UnknownHostException e) { + return null; + } +} + +/** + * Returns the IP protocol. + */ +public int +getProtocol() { + return protocol; +} + +/** + * Returns the services provided by the host on the specified address. + */ +public int [] +getServices() { + return services; +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeByteArray(address); + out.writeU8(protocol); + int highestPort = services[services.length - 1]; + byte [] array = new byte[highestPort / 8 + 1]; + for (int i = 0; i < services.length; i++) { + int port = services[i]; + array[port / 8] |= (1 << (7 - port % 8)); + } + out.writeByteArray(array); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java b/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java new file mode 100644 index 0000000..2842731 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/WireParseException.java @@ -0,0 +1,31 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * An exception thrown when a DNS message is invalid. + * + * @author Brian Wellington + */ + +public class WireParseException extends IOException { + +public +WireParseException() { + super(); +} + +public +WireParseException(String s) { + super(s); +} + +public +WireParseException(String s, Throwable cause) { + super(s); + initCause(cause); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java b/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java new file mode 100644 index 0000000..1349a1e --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/X25Record.java @@ -0,0 +1,86 @@ +// Copyright (c) 2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; + +/** + * X25 - identifies the PSDN (Public Switched Data Network) address in the + * X.121 numbering plan associated with a name. + * + * @author Brian Wellington + */ + +public class X25Record extends Record { + +private static final long serialVersionUID = 4267576252335579764L; + +private byte [] address; + +X25Record() {} + +Record +getObject() { + return new X25Record(); +} + +private static final byte [] +checkAndConvertAddress(String address) { + int length = address.length(); + byte [] out = new byte [length]; + for (int i = 0; i < length; i++) { + char c = address.charAt(i); + if (!Character.isDigit(c)) + return null; + out[i] = (byte) c; + } + return out; +} + +/** + * Creates an X25 Record from the given data + * @param address The X.25 PSDN address. + * @throws IllegalArgumentException The address is not a valid PSDN address. + */ +public +X25Record(Name name, int dclass, long ttl, String address) { + super(name, Type.X25, dclass, ttl); + this.address = checkAndConvertAddress(address); + if (this.address == null) { + throw new IllegalArgumentException("invalid PSDN address " + + address); + } +} + +void +rrFromWire(DNSInput in) throws IOException { + address = in.readCountedString(); +} + +void +rdataFromString(Tokenizer st, Name origin) throws IOException { + String addr = st.getString(); + this.address = checkAndConvertAddress(addr); + if (this.address == null) + throw st.exception("invalid PSDN address " + addr); +} + +/** + * Returns the X.25 PSDN address. + */ +public String +getAddress() { + return byteArrayToString(address, false); +} + +void +rrToWire(DNSOutput out, Compression c, boolean canonical) { + out.writeCountedString(address); +} + +String +rrToString() { + return byteArrayToString(address, true); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java b/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java new file mode 100644 index 0000000..866be77 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/Zone.java @@ -0,0 +1,559 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +import java.io.*; +import java.util.*; + +/** + * A DNS Zone. This encapsulates all data related to a Zone, and provides + * convenient lookup methods. + * + * @author Brian Wellington + */ + +public class Zone implements Serializable { + +private static final long serialVersionUID = -9220510891189510942L; + +/** A primary zone */ +public static final int PRIMARY = 1; + +/** A secondary zone */ +public static final int SECONDARY = 2; + +private Map data; +private Name origin; +private Object originNode; +private int dclass = DClass.IN; +private RRset NS; +private SOARecord SOA; +private boolean hasWild; + +class ZoneIterator implements Iterator { + private Iterator zentries; + private RRset [] current; + private int count; + private boolean wantLastSOA; + + ZoneIterator(boolean axfr) { + synchronized (Zone.this) { + zentries = data.entrySet().iterator(); + } + wantLastSOA = axfr; + RRset [] sets = allRRsets(originNode); + current = new RRset[sets.length]; + for (int i = 0, j = 2; i < sets.length; i++) { + int type = sets[i].getType(); + if (type == Type.SOA) + current[0] = sets[i]; + else if (type == Type.NS) + current[1] = sets[i]; + else + current[j++] = sets[i]; + } + } + + public boolean + hasNext() { + return (current != null || wantLastSOA); + } + + public Object + next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (current == null) { + wantLastSOA = false; + return oneRRset(originNode, Type.SOA); + } + Object set = current[count++]; + if (count == current.length) { + current = null; + while (zentries.hasNext()) { + Map.Entry entry = (Map.Entry) zentries.next(); + if (entry.getKey().equals(origin)) + continue; + RRset [] sets = allRRsets(entry.getValue()); + if (sets.length == 0) + continue; + current = sets; + count = 0; + break; + } + } + return set; + } + + public void + remove() { + throw new UnsupportedOperationException(); + } +} + +private void +validate() throws IOException { + originNode = exactName(origin); + if (originNode == null) + throw new IOException(origin + ": no data specified"); + + RRset rrset = oneRRset(originNode, Type.SOA); + if (rrset == null || rrset.size() != 1) + throw new IOException(origin + + ": exactly 1 SOA must be specified"); + Iterator it = rrset.rrs(); + SOA = (SOARecord) it.next(); + + NS = oneRRset(originNode, Type.NS); + if (NS == null) + throw new IOException(origin + ": no NS set specified"); +} + +private final void +maybeAddRecord(Record record) throws IOException { + int rtype = record.getType(); + Name name = record.getName(); + + if (rtype == Type.SOA && !name.equals(origin)) { + throw new IOException("SOA owner " + name + + " does not match zone origin " + + origin); + } + if (name.subdomain(origin)) + addRecord(record); +} + +/** + * Creates a Zone from the records in the specified master file. + * @param zone The name of the zone. + * @param file The master file to read from. + * @see Master + */ +public +Zone(Name zone, String file) throws IOException { + data = new TreeMap(); + + if (zone == null) + throw new IllegalArgumentException("no zone name specified"); + Master m = new Master(file, zone); + Record record; + + origin = zone; + while ((record = m.nextRecord()) != null) + maybeAddRecord(record); + validate(); +} + +/** + * Creates a Zone from an array of records. + * @param zone The name of the zone. + * @param records The records to add to the zone. + * @see Master + */ +public +Zone(Name zone, Record [] records) throws IOException { + data = new TreeMap(); + + if (zone == null) + throw new IllegalArgumentException("no zone name specified"); + origin = zone; + for (int i = 0; i < records.length; i++) + maybeAddRecord(records[i]); + validate(); +} + +private void +fromXFR(ZoneTransferIn xfrin) throws IOException, ZoneTransferException { + data = new TreeMap(); + + origin = xfrin.getName(); + List records = xfrin.run(); + for (Iterator it = records.iterator(); it.hasNext(); ) { + Record record = (Record) it.next(); + maybeAddRecord(record); + } + if (!xfrin.isAXFR()) + throw new IllegalArgumentException("zones can only be " + + "created from AXFRs"); + validate(); +} + +/** + * Creates a Zone by doing the specified zone transfer. + * @param xfrin The incoming zone transfer to execute. + * @see ZoneTransferIn + */ +public +Zone(ZoneTransferIn xfrin) throws IOException, ZoneTransferException { + fromXFR(xfrin); +} + +/** + * Creates a Zone by performing a zone transfer to the specified host. + * @see ZoneTransferIn + */ +public +Zone(Name zone, int dclass, String remote) +throws IOException, ZoneTransferException +{ + ZoneTransferIn xfrin = ZoneTransferIn.newAXFR(zone, remote, null); + xfrin.setDClass(dclass); + fromXFR(xfrin); +} + +/** Returns the Zone's origin */ +public Name +getOrigin() { + return origin; +} + +/** Returns the Zone origin's NS records */ +public RRset +getNS() { + return NS; +} + +/** Returns the Zone's SOA record */ +public SOARecord +getSOA() { + return SOA; +} + +/** Returns the Zone's class */ +public int +getDClass() { + return dclass; +} + +private synchronized Object +exactName(Name name) { + return data.get(name); +} + +private synchronized RRset [] +allRRsets(Object types) { + if (types instanceof List) { + List typelist = (List) types; + return (RRset []) typelist.toArray(new RRset[typelist.size()]); + } else { + RRset set = (RRset) types; + return new RRset [] {set}; + } +} + +private synchronized RRset +oneRRset(Object types, int type) { + if (type == Type.ANY) + throw new IllegalArgumentException("oneRRset(ANY)"); + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + RRset set = (RRset) list.get(i); + if (set.getType() == type) + return set; + } + } else { + RRset set = (RRset) types; + if (set.getType() == type) + return set; + } + return null; +} + +private synchronized RRset +findRRset(Name name, int type) { + Object types = exactName(name); + if (types == null) + return null; + return oneRRset(types, type); +} + +private synchronized void +addRRset(Name name, RRset rrset) { + if (!hasWild && name.isWild()) + hasWild = true; + Object types = data.get(name); + if (types == null) { + data.put(name, rrset); + return; + } + int rtype = rrset.getType(); + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + RRset set = (RRset) list.get(i); + if (set.getType() == rtype) { + list.set(i, rrset); + return; + } + } + list.add(rrset); + } else { + RRset set = (RRset) types; + if (set.getType() == rtype) + data.put(name, rrset); + else { + LinkedList list = new LinkedList(); + list.add(set); + list.add(rrset); + data.put(name, list); + } + } +} + +private synchronized void +removeRRset(Name name, int type) { + Object types = data.get(name); + if (types == null) { + return; + } + if (types instanceof List) { + List list = (List) types; + for (int i = 0; i < list.size(); i++) { + RRset set = (RRset) list.get(i); + if (set.getType() == type) { + list.remove(i); + if (list.size() == 0) + data.remove(name); + return; + } + } + } else { + RRset set = (RRset) types; + if (set.getType() != type) + return; + data.remove(name); + } +} + +private synchronized SetResponse +lookup(Name name, int type) { + int labels; + int olabels; + int tlabels; + RRset rrset; + Name tname; + Object types; + SetResponse sr; + + if (!name.subdomain(origin)) + return SetResponse.ofType(SetResponse.NXDOMAIN); + + labels = name.labels(); + olabels = origin.labels(); + + for (tlabels = olabels; tlabels <= labels; tlabels++) { + boolean isOrigin = (tlabels == olabels); + boolean isExact = (tlabels == labels); + + if (isOrigin) + tname = origin; + else if (isExact) + tname = name; + else + tname = new Name(name, labels - tlabels); + + types = exactName(tname); + if (types == null) + continue; + + /* If this is a delegation, return that. */ + if (!isOrigin) { + RRset ns = oneRRset(types, Type.NS); + if (ns != null) + return new SetResponse(SetResponse.DELEGATION, + ns); + } + + /* If this is an ANY lookup, return everything. */ + if (isExact && type == Type.ANY) { + sr = new SetResponse(SetResponse.SUCCESSFUL); + RRset [] sets = allRRsets(types); + for (int i = 0; i < sets.length; i++) + sr.addRRset(sets[i]); + return sr; + } + + /* + * If this is the name, look for the actual type or a CNAME. + * Otherwise, look for a DNAME. + */ + if (isExact) { + rrset = oneRRset(types, type); + if (rrset != null) { + sr = new SetResponse(SetResponse.SUCCESSFUL); + sr.addRRset(rrset); + return sr; + } + rrset = oneRRset(types, Type.CNAME); + if (rrset != null) + return new SetResponse(SetResponse.CNAME, + rrset); + } else { + rrset = oneRRset(types, Type.DNAME); + if (rrset != null) + return new SetResponse(SetResponse.DNAME, + rrset); + } + + /* We found the name, but not the type. */ + if (isExact) + return SetResponse.ofType(SetResponse.NXRRSET); + } + + if (hasWild) { + for (int i = 0; i < labels - olabels; i++) { + tname = name.wild(i + 1); + + types = exactName(tname); + if (types == null) + continue; + + rrset = oneRRset(types, type); + if (rrset != null) { + sr = new SetResponse(SetResponse.SUCCESSFUL); + sr.addRRset(rrset); + return sr; + } + } + } + + return SetResponse.ofType(SetResponse.NXDOMAIN); +} + +/** + * Looks up Records in the Zone. This follows CNAMEs and wildcards. + * @param name The name to look up + * @param type The type to look up + * @return A SetResponse object + * @see SetResponse + */ +public SetResponse +findRecords(Name name, int type) { + return lookup(name, type); +} + +/** + * Looks up Records in the zone, finding exact matches only. + * @param name The name to look up + * @param type The type to look up + * @return The matching RRset + * @see RRset + */ +public RRset +findExactMatch(Name name, int type) { + Object types = exactName(name); + if (types == null) + return null; + return oneRRset(types, type); +} + +/** + * Adds an RRset to the Zone + * @param rrset The RRset to be added + * @see RRset + */ +public void +addRRset(RRset rrset) { + Name name = rrset.getName(); + addRRset(name, rrset); +} + +/** + * Adds a Record to the Zone + * @param r The record to be added + * @see Record + */ +public void +addRecord(Record r) { + Name name = r.getName(); + int rtype = r.getRRsetType(); + synchronized (this) { + RRset rrset = findRRset(name, rtype); + if (rrset == null) { + rrset = new RRset(r); + addRRset(name, rrset); + } else { + rrset.addRR(r); + } + } +} + +/** + * Removes a record from the Zone + * @param r The record to be removed + * @see Record + */ +public void +removeRecord(Record r) { + Name name = r.getName(); + int rtype = r.getRRsetType(); + synchronized (this) { + RRset rrset = findRRset(name, rtype); + if (rrset == null) + return; + if (rrset.size() == 1 && rrset.first().equals(r)) + removeRRset(name, rtype); + else + rrset.deleteRR(r); + } +} + +/** + * Returns an Iterator over the RRsets in the zone. + */ +public Iterator +iterator() { + return new ZoneIterator(false); +} + +/** + * Returns an Iterator over the RRsets in the zone that can be used to + * construct an AXFR response. This is identical to {@link #iterator} except + * that the SOA is returned at the end as well as the beginning. + */ +public Iterator +AXFR() { + return new ZoneIterator(true); +} + +private void +nodeToString(StringBuffer sb, Object node) { + RRset [] sets = allRRsets(node); + for (int i = 0; i < sets.length; i++) { + RRset rrset = sets[i]; + Iterator it = rrset.rrs(); + while (it.hasNext()) + sb.append(it.next() + "\n"); + it = rrset.sigs(); + while (it.hasNext()) + sb.append(it.next() + "\n"); + } +} + +/** + * Returns the contents of the Zone in master file format. + */ +public synchronized String +toMasterFile() { + Iterator zentries = data.entrySet().iterator(); + StringBuffer sb = new StringBuffer(); + nodeToString(sb, originNode); + while (zentries.hasNext()) { + Map.Entry entry = (Map.Entry) zentries.next(); + if (!origin.equals(entry.getKey())) + nodeToString(sb, entry.getValue()); + } + return sb.toString(); +} + +/** + * Returns the contents of the Zone as a string (in master file format). + */ +public String +toString() { + return toMasterFile(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java new file mode 100644 index 0000000..3ba487b --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferException.java @@ -0,0 +1,23 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS; + +/** + * An exception thrown when a zone transfer fails. + * + * @author Brian Wellington + */ + +public class ZoneTransferException extends Exception { + +public +ZoneTransferException() { + super(); +} + +public +ZoneTransferException(String s) { + super(s); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java new file mode 100644 index 0000000..8a19992 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/ZoneTransferIn.java @@ -0,0 +1,680 @@ +// Copyright (c) 2003-2004 Brian Wellington (bwelling@xbill.org) +// Parts of this are derived from lib/dns/xfrin.c from BIND 9; its copyright +// notice follows. + +/* + * Copyright (C) 1999-2001 Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING + * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION + * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +package org.xbill.DNS; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** + * An incoming DNS Zone Transfer. To use this class, first initialize an + * object, then call the run() method. If run() doesn't throw an exception + * the result will either be an IXFR-style response, an AXFR-style response, + * or an indication that the zone is up to date. + * + * @author Brian Wellington + */ + +public class ZoneTransferIn { + +private static final int INITIALSOA = 0; +private static final int FIRSTDATA = 1; +private static final int IXFR_DELSOA = 2; +private static final int IXFR_DEL = 3; +private static final int IXFR_ADDSOA = 4; +private static final int IXFR_ADD = 5; +private static final int AXFR = 6; +private static final int END = 7; + +private Name zname; +private int qtype; +private int dclass; +private long ixfr_serial; +private boolean want_fallback; +private ZoneTransferHandler handler; + +private SocketAddress localAddress; +private SocketAddress address; +private TCPClient client; +private TSIG tsig; +private TSIG.StreamVerifier verifier; +private long timeout = 900 * 1000; + +private int state; +private long end_serial; +private long current_serial; +private Record initialsoa; + +private int rtype; + +public static class Delta { + /** + * All changes between two versions of a zone in an IXFR response. + */ + + /** The starting serial number of this delta. */ + public long start; + + /** The ending serial number of this delta. */ + public long end; + + /** A list of records added between the start and end versions */ + public List adds; + + /** A list of records deleted between the start and end versions */ + public List deletes; + + private + Delta() { + adds = new ArrayList(); + deletes = new ArrayList(); + } +} + +public static interface ZoneTransferHandler { + /** + * Handles a Zone Transfer. + */ + + /** + * Called when an AXFR transfer begins. + */ + public void startAXFR() throws ZoneTransferException; + + /** + * Called when an IXFR transfer begins. + */ + public void startIXFR() throws ZoneTransferException; + + /** + * Called when a series of IXFR deletions begins. + * @param soa The starting SOA. + */ + public void startIXFRDeletes(Record soa) throws ZoneTransferException; + + /** + * Called when a series of IXFR adds begins. + * @param soa The starting SOA. + */ + public void startIXFRAdds(Record soa) throws ZoneTransferException; + + /** + * Called for each content record in an AXFR. + * @param r The DNS record. + */ + public void handleRecord(Record r) throws ZoneTransferException; +}; + +private static class BasicHandler implements ZoneTransferHandler { + private List axfr; + private List ixfr; + + public void startAXFR() { + axfr = new ArrayList(); + } + + public void startIXFR() { + ixfr = new ArrayList(); + } + + public void startIXFRDeletes(Record soa) { + Delta delta = new Delta(); + delta.deletes.add(soa); + delta.start = getSOASerial(soa); + ixfr.add(delta); + } + + public void startIXFRAdds(Record soa) { + Delta delta = (Delta) ixfr.get(ixfr.size() - 1); + delta.adds.add(soa); + delta.end = getSOASerial(soa); + } + + public void handleRecord(Record r) { + List list; + if (ixfr != null) { + Delta delta = (Delta) ixfr.get(ixfr.size() - 1); + if (delta.adds.size() > 0) + list = delta.adds; + else + list = delta.deletes; + } else + list = axfr; + list.add(r); + } +}; + +private +ZoneTransferIn() {} + +private +ZoneTransferIn(Name zone, int xfrtype, long serial, boolean fallback, + SocketAddress address, TSIG key) +{ + this.address = address; + this.tsig = key; + if (zone.isAbsolute()) + zname = zone; + else { + try { + zname = Name.concatenate(zone, Name.root); + } + catch (NameTooLongException e) { + throw new IllegalArgumentException("ZoneTransferIn: " + + "name too long"); + } + } + qtype = xfrtype; + dclass = DClass.IN; + ixfr_serial = serial; + want_fallback = fallback; + state = INITIALSOA; +} + +/** + * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). + * @param zone The zone to transfer. + * @param address The host/port from which to transfer the zone. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newAXFR(Name zone, SocketAddress address, TSIG key) { + return new ZoneTransferIn(zone, Type.AXFR, 0, false, address, key); +} + +/** + * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). + * @param zone The zone to transfer. + * @param host The host from which to transfer the zone. + * @param port The port to connect to on the server, or 0 for the default. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newAXFR(Name zone, String host, int port, TSIG key) +throws UnknownHostException +{ + if (port == 0) + port = SimpleResolver.DEFAULT_PORT; + return newAXFR(zone, new InetSocketAddress(host, port), key); +} + +/** + * Instantiates a ZoneTransferIn object to do an AXFR (full zone transfer). + * @param zone The zone to transfer. + * @param host The host from which to transfer the zone. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newAXFR(Name zone, String host, TSIG key) +throws UnknownHostException +{ + return newAXFR(zone, host, 0, key); +} + +/** + * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone + * transfer). + * @param zone The zone to transfer. + * @param serial The existing serial number. + * @param fallback If true, fall back to AXFR if IXFR is not supported. + * @param address The host/port from which to transfer the zone. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newIXFR(Name zone, long serial, boolean fallback, SocketAddress address, + TSIG key) +{ + return new ZoneTransferIn(zone, Type.IXFR, serial, fallback, address, + key); +} + +/** + * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone + * transfer). + * @param zone The zone to transfer. + * @param serial The existing serial number. + * @param fallback If true, fall back to AXFR if IXFR is not supported. + * @param host The host from which to transfer the zone. + * @param port The port to connect to on the server, or 0 for the default. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newIXFR(Name zone, long serial, boolean fallback, String host, int port, + TSIG key) +throws UnknownHostException +{ + if (port == 0) + port = SimpleResolver.DEFAULT_PORT; + return newIXFR(zone, serial, fallback, + new InetSocketAddress(host, port), key); +} + +/** + * Instantiates a ZoneTransferIn object to do an IXFR (incremental zone + * transfer). + * @param zone The zone to transfer. + * @param serial The existing serial number. + * @param fallback If true, fall back to AXFR if IXFR is not supported. + * @param host The host from which to transfer the zone. + * @param key The TSIG key used to authenticate the transfer, or null. + * @return The ZoneTransferIn object. + * @throws UnknownHostException The host does not exist. + */ +public static ZoneTransferIn +newIXFR(Name zone, long serial, boolean fallback, String host, TSIG key) +throws UnknownHostException +{ + return newIXFR(zone, serial, fallback, host, 0, key); +} + +/** + * Gets the name of the zone being transferred. + */ +public Name +getName() { + return zname; +} + +/** + * Gets the type of zone transfer (either AXFR or IXFR). + */ +public int +getType() { + return qtype; +} + +/** + * Sets a timeout on this zone transfer. The default is 900 seconds (15 + * minutes). + * @param secs The maximum amount of time that this zone transfer can take. + */ +public void +setTimeout(int secs) { + if (secs < 0) + throw new IllegalArgumentException("timeout cannot be " + + "negative"); + timeout = 1000L * secs; +} + +/** + * Sets an alternate DNS class for this zone transfer. + * @param dclass The class to use instead of class IN. + */ +public void +setDClass(int dclass) { + DClass.check(dclass); + this.dclass = dclass; +} + +/** + * Sets the local address to bind to when sending messages. + * @param addr The local address to send messages from. + */ +public void +setLocalAddress(SocketAddress addr) { + this.localAddress = addr; +} + +private void +openConnection() throws IOException { + long endTime = System.currentTimeMillis() + timeout; + client = new TCPClient(endTime); + if (localAddress != null) + client.bind(localAddress); + client.connect(address); +} + +private void +sendQuery() throws IOException { + Record question = Record.newRecord(zname, qtype, dclass); + + Message query = new Message(); + query.getHeader().setOpcode(Opcode.QUERY); + query.addRecord(question, Section.QUESTION); + if (qtype == Type.IXFR) { + Record soa = new SOARecord(zname, dclass, 0, Name.root, + Name.root, ixfr_serial, + 0, 0, 0, 0); + query.addRecord(soa, Section.AUTHORITY); + } + if (tsig != null) { + tsig.apply(query, null); + verifier = new TSIG.StreamVerifier(tsig, query.getTSIG()); + } + byte [] out = query.toWire(Message.MAXLENGTH); + client.send(out); +} + +private static long +getSOASerial(Record rec) { + SOARecord soa = (SOARecord) rec; + return soa.getSerial(); +} + +private void +logxfr(String s) { + if (Options.check("verbose")) + System.out.println(zname + ": " + s); +} + +private void +fail(String s) throws ZoneTransferException { + throw new ZoneTransferException(s); +} + +private void +fallback() throws ZoneTransferException { + if (!want_fallback) + fail("server doesn't support IXFR"); + + logxfr("falling back to AXFR"); + qtype = Type.AXFR; + state = INITIALSOA; +} + +private void +parseRR(Record rec) throws ZoneTransferException { + int type = rec.getType(); + Delta delta; + + switch (state) { + case INITIALSOA: + if (type != Type.SOA) + fail("missing initial SOA"); + initialsoa = rec; + // Remember the serial number in the initial SOA; we need it + // to recognize the end of an IXFR. + end_serial = getSOASerial(rec); + if (qtype == Type.IXFR && + Serial.compare(end_serial, ixfr_serial) <= 0) + { + logxfr("up to date"); + state = END; + break; + } + state = FIRSTDATA; + break; + + case FIRSTDATA: + // If the transfer begins with 1 SOA, it's an AXFR. + // If it begins with 2 SOAs, it's an IXFR. + if (qtype == Type.IXFR && type == Type.SOA && + getSOASerial(rec) == ixfr_serial) + { + rtype = Type.IXFR; + handler.startIXFR(); + logxfr("got incremental response"); + state = IXFR_DELSOA; + } else { + rtype = Type.AXFR; + handler.startAXFR(); + handler.handleRecord(initialsoa); + logxfr("got nonincremental response"); + state = AXFR; + } + parseRR(rec); // Restart... + return; + + case IXFR_DELSOA: + handler.startIXFRDeletes(rec); + state = IXFR_DEL; + break; + + case IXFR_DEL: + if (type == Type.SOA) { + current_serial = getSOASerial(rec); + state = IXFR_ADDSOA; + parseRR(rec); // Restart... + return; + } + handler.handleRecord(rec); + break; + + case IXFR_ADDSOA: + handler.startIXFRAdds(rec); + state = IXFR_ADD; + break; + + case IXFR_ADD: + if (type == Type.SOA) { + long soa_serial = getSOASerial(rec); + if (soa_serial == end_serial) { + state = END; + break; + } else if (soa_serial != current_serial) { + fail("IXFR out of sync: expected serial " + + current_serial + " , got " + soa_serial); + } else { + state = IXFR_DELSOA; + parseRR(rec); // Restart... + return; + } + } + handler.handleRecord(rec); + break; + + case AXFR: + // Old BINDs sent cross class A records for non IN classes. + if (type == Type.A && rec.getDClass() != dclass) + break; + handler.handleRecord(rec); + if (type == Type.SOA) { + state = END; + } + break; + + case END: + fail("extra data"); + break; + + default: + fail("invalid state"); + break; + } +} + +private void +closeConnection() { + try { + if (client != null) + client.cleanup(); + } + catch (IOException e) { + } +} + +private Message +parseMessage(byte [] b) throws WireParseException { + try { + return new Message(b); + } + catch (IOException e) { + if (e instanceof WireParseException) + throw (WireParseException) e; + throw new WireParseException("Error parsing message"); + } +} + +private void +doxfr() throws IOException, ZoneTransferException { + sendQuery(); + while (state != END) { + byte [] in = client.recv(); + Message response = parseMessage(in); + if (response.getHeader().getRcode() == Rcode.NOERROR && + verifier != null) + { + TSIGRecord tsigrec = response.getTSIG(); + + int error = verifier.verify(response, in); + if (error != Rcode.NOERROR) + fail("TSIG failure"); + } + + Record [] answers = response.getSectionArray(Section.ANSWER); + + if (state == INITIALSOA) { + int rcode = response.getRcode(); + if (rcode != Rcode.NOERROR) { + if (qtype == Type.IXFR && + rcode == Rcode.NOTIMP) + { + fallback(); + doxfr(); + return; + } + fail(Rcode.string(rcode)); + } + + Record question = response.getQuestion(); + if (question != null && question.getType() != qtype) { + fail("invalid question section"); + } + + if (answers.length == 0 && qtype == Type.IXFR) { + fallback(); + doxfr(); + return; + } + } + + for (int i = 0; i < answers.length; i++) { + parseRR(answers[i]); + } + + if (state == END && verifier != null && + !response.isVerified()) + fail("last message must be signed"); + } +} + +/** + * Does the zone transfer. + * @param handler The callback object that handles the zone transfer data. + * @throws IOException The zone transfer failed to due an IO problem. + * @throws ZoneTransferException The zone transfer failed to due a problem + * with the zone transfer itself. + */ +public void +run(ZoneTransferHandler handler) throws IOException, ZoneTransferException { + this.handler = handler; + try { + openConnection(); + doxfr(); + } + finally { + closeConnection(); + } +} + +/** + * Does the zone transfer. + * @return A list, which is either an AXFR-style response (List of Records), + * and IXFR-style response (List of Deltas), or null, which indicates that + * an IXFR was performed and the zone is up to date. + * @throws IOException The zone transfer failed to due an IO problem. + * @throws ZoneTransferException The zone transfer failed to due a problem + * with the zone transfer itself. + */ +public List +run() throws IOException, ZoneTransferException { + BasicHandler handler = new BasicHandler(); + run(handler); + if (handler.axfr != null) + return handler.axfr; + return handler.ixfr; +} + +private BasicHandler +getBasicHandler() throws IllegalArgumentException { + if (handler instanceof BasicHandler) + return (BasicHandler) handler; + throw new IllegalArgumentException("ZoneTransferIn used callback " + + "interface"); +} + +/** + * Returns true if the response is an AXFR-style response (List of Records). + * This will be true if either an IXFR was performed, an IXFR was performed + * and the server provided a full zone transfer, or an IXFR failed and + * fallback to AXFR occurred. + */ +public boolean +isAXFR() { + return (rtype == Type.AXFR); +} + +/** + * Gets the AXFR-style response. + * @throws IllegalArgumentException The transfer used the callback interface, + * so the response was not stored. + */ +public List +getAXFR() { + BasicHandler handler = getBasicHandler(); + return handler.axfr; +} + +/** + * Returns true if the response is an IXFR-style response (List of Deltas). + * This will be true only if an IXFR was performed and the server provided + * an incremental zone transfer. + */ +public boolean +isIXFR() { + return (rtype == Type.IXFR); +} + +/** + * Gets the IXFR-style response. + * @throws IllegalArgumentException The transfer used the callback interface, + * so the response was not stored. + */ +public List +getIXFR() { + BasicHandler handler = getBasicHandler(); + return handler.ixfr; +} + +/** + * Returns true if the response indicates that the zone is up to date. + * This will be true only if an IXFR was performed. + * @throws IllegalArgumentException The transfer used the callback interface, + * so the response was not stored. + */ +public boolean +isCurrent() { + BasicHandler handler = getBasicHandler(); + return (handler.axfr == null && handler.ixfr == null); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java b/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java new file mode 100644 index 0000000..14d8adb --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/spi/DNSJavaNameService.java @@ -0,0 +1,176 @@ +// Copyright (c) 2005 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.spi; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.StringTokenizer; + +import org.xbill.DNS.AAAARecord; +import org.xbill.DNS.ARecord; +import org.xbill.DNS.ExtendedResolver; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Name; +import org.xbill.DNS.PTRRecord; +import org.xbill.DNS.Record; +import org.xbill.DNS.Resolver; +import org.xbill.DNS.ReverseMap; +import org.xbill.DNS.TextParseException; +import org.xbill.DNS.Type; + +/** + * This class implements a Name Service Provider, which Java can use + * (starting with version 1.4), to perform DNS resolutions instead of using + * the standard calls. + *

      + * This Name Service Provider uses dnsjava. + *

      + * To use this provider, you must set the following system property: + * sun.net.spi.nameservice.provider.1=dns,dnsjava + * + * @author Brian Wellington + * @author Paul Cowan (pwc21@yahoo.com) + */ + +public class DNSJavaNameService implements InvocationHandler { + +private static final String nsProperty = "sun.net.spi.nameservice.nameservers"; +private static final String domainProperty = "sun.net.spi.nameservice.domain"; +private static final String v6Property = "java.net.preferIPv6Addresses"; + +private boolean preferV6 = false; + +/** + * Creates a DNSJavaNameService instance. + *

      + * Uses the + * sun.net.spi.nameservice.nameservers, + * sun.net.spi.nameservice.domain, and + * java.net.preferIPv6Addresses properties for configuration. + */ +protected +DNSJavaNameService() { + String nameServers = System.getProperty(nsProperty); + String domain = System.getProperty(domainProperty); + String v6 = System.getProperty(v6Property); + + if (nameServers != null) { + StringTokenizer st = new StringTokenizer(nameServers, ","); + String [] servers = new String[st.countTokens()]; + int n = 0; + while (st.hasMoreTokens()) + servers[n++] = st.nextToken(); + try { + Resolver res = new ExtendedResolver(servers); + Lookup.setDefaultResolver(res); + } + catch (UnknownHostException e) { + System.err.println("DNSJavaNameService: invalid " + + nsProperty); + } + } + + if (domain != null) { + try { + Lookup.setDefaultSearchPath(new String[] {domain}); + } + catch (TextParseException e) { + System.err.println("DNSJavaNameService: invalid " + + domainProperty); + } + } + + if (v6 != null && v6.equalsIgnoreCase("true")) + preferV6 = true; +} + + +public Object +invoke(Object proxy, Method method, Object[] args) throws Throwable { + try { + if (method.getName().equals("getHostByAddr")) { + return this.getHostByAddr((byte[]) args[0]); + } else if (method.getName().equals("lookupAllHostAddr")) { + InetAddress[] addresses; + addresses = this.lookupAllHostAddr((String) args[0]); + Class returnType = method.getReturnType(); + if (returnType.equals(InetAddress[].class)) { + // method for Java >= 1.6 + return addresses; + } else if (returnType.equals(byte[][].class)) { + // method for Java <= 1.5 + int naddrs = addresses.length; + byte [][] byteAddresses = new byte[naddrs][]; + byte [] addr; + for (int i = 0; i < naddrs; i++) { + addr = addresses[i].getAddress(); + byteAddresses[i] = addr; + } + return byteAddresses; + } + } + } catch (Throwable e) { + System.err.println("DNSJavaNameService: Unexpected error."); + e.printStackTrace(); + throw e; + } + throw new IllegalArgumentException( + "Unknown function name or arguments."); +} + +/** + * Performs a forward DNS lookup for the host name. + * @param host The host name to resolve. + * @return All the ip addresses found for the host name. + */ +public InetAddress [] +lookupAllHostAddr(String host) throws UnknownHostException { + Name name = null; + + try { + name = new Name(host); + } + catch (TextParseException e) { + throw new UnknownHostException(host); + } + + Record [] records = null; + if (preferV6) + records = new Lookup(name, Type.AAAA).run(); + if (records == null) + records = new Lookup(name, Type.A).run(); + if (records == null && !preferV6) + records = new Lookup(name, Type.AAAA).run(); + if (records == null) + throw new UnknownHostException(host); + + InetAddress[] array = new InetAddress[records.length]; + for (int i = 0; i < records.length; i++) { + Record record = records[i]; + if (records[i] instanceof ARecord) { + ARecord a = (ARecord) records[i]; + array[i] = a.getAddress(); + } else { + AAAARecord aaaa = (AAAARecord) records[i]; + array[i] = aaaa.getAddress(); + } + } + return array; +} + +/** + * Performs a reverse DNS lookup. + * @param addr The ip address to lookup. + * @return The host name found for the ip address. + */ +public String +getHostByAddr(byte [] addr) throws UnknownHostException { + Name name = ReverseMap.fromAddress(InetAddress.getByAddress(addr)); + Record [] records = new Lookup(name, Type.PTR).run(); + if (records == null) + throw new UnknownHostException(); + return ((PTRRecord) records[0]).getTarget().toString(); +} +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor b/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor new file mode 100644 index 0000000..1ca895c --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/spi/services/sun.net.spi.nameservice.NameServiceDescriptor @@ -0,0 +1 @@ +org.xbill.DNS.spi.DNSJavaNameServiceDescriptor diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java b/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java new file mode 100644 index 0000000..85455b9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/tests/primary.java @@ -0,0 +1,59 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.tests; + +import java.util.*; +import org.xbill.DNS.*; + +public class primary { + +private static void +usage() { + System.out.println("usage: primary [-t] [-a | -i] origin file"); + System.exit(1); +} + +public static void +main(String [] args) throws Exception { + boolean time = false; + boolean axfr = false; + boolean iterator = false; + int arg = 0; + + if (args.length < 2) + usage(); + + while (args.length - arg > 2) { + if (args[0].equals("-t")) + time = true; + else if (args[0].equals("-a")) + axfr = true; + else if (args[0].equals("-i")) + iterator = true; + arg++; + } + + Name origin = Name.fromString(args[arg++], Name.root); + String file = args[arg++]; + + long start = System.currentTimeMillis(); + Zone zone = new Zone(origin, file); + long end = System.currentTimeMillis(); + if (axfr) { + Iterator it = zone.AXFR(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } else if (iterator) { + Iterator it = zone.iterator(); + while (it.hasNext()) { + System.out.println(it.next()); + } + } else { + System.out.println(zone); + } + if (time) + System.out.println("; Load time: " + (end - start) + " ms"); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java b/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java new file mode 100644 index 0000000..066c70e --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/tests/xfrin.java @@ -0,0 +1,109 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.tests; + +import java.util.*; +import org.xbill.DNS.*; + +public class xfrin { + +private static void +usage(String s) { + System.out.println("Error: " + s); + System.out.println("usage: xfrin [-i serial] [-k keyname/secret] " + + "[-s server] [-p port] [-f] zone"); + System.exit(1); +} + +public static void +main(String [] args) throws Exception { + ZoneTransferIn xfrin; + TSIG key = null; + int ixfr_serial = -1; + String server = null; + int port = SimpleResolver.DEFAULT_PORT; + boolean fallback = false; + Name zname; + + int arg = 0; + while (arg < args.length) { + if (args[arg].equals("-i")) { + ixfr_serial = Integer.parseInt(args[++arg]); + if (ixfr_serial < 0) + usage("invalid serial number"); + } else if (args[arg].equals("-k")) { + String s = args[++arg]; + int index = s.indexOf('/'); + if (index < 0) + usage("invalid key"); + key = new TSIG(s.substring(0, index), + s.substring(index+1)); + } else if (args[arg].equals("-s")) { + server = args[++arg]; + } else if (args[arg].equals("-p")) { + port = Integer.parseInt(args[++arg]); + if (port < 0 || port > 0xFFFF) + usage("invalid port"); + } else if (args[arg].equals("-f")) { + fallback = true; + } else if (args[arg].startsWith("-")) { + usage("invalid option"); + } else { + break; + } + arg++; + } + if (arg >= args.length) + usage("no zone name specified"); + zname = Name.fromString(args[arg]); + + if (server == null) { + Lookup l = new Lookup(zname, Type.NS); + Record [] ns = l.run(); + if (ns == null) { + System.out.println("failed to look up NS record: " + + l.getErrorString()); + System.exit(1); + } + server = ns[0].rdataToString(); + System.out.println("sending to server '" + server + "'"); + } + + if (ixfr_serial >= 0) + xfrin = ZoneTransferIn.newIXFR(zname, ixfr_serial, fallback, + server, port, key); + else + xfrin = ZoneTransferIn.newAXFR(zname, server, port, key); + + List response = xfrin.run(); + if (xfrin.isAXFR()) { + if (ixfr_serial >= 0) + System.out.println("AXFR-like IXFR response"); + else + System.out.println("AXFR response"); + Iterator it = response.iterator(); + while (it.hasNext()) + System.out.println(it.next()); + } else if (xfrin.isIXFR()) { + System.out.println("IXFR response"); + Iterator it = response.iterator(); + while (it.hasNext()) { + ZoneTransferIn.Delta delta; + delta = (ZoneTransferIn.Delta) it.next(); + System.out.println("delta from " + delta.start + + " to " + delta.end); + System.out.println("deletes"); + Iterator it2 = delta.deletes.iterator(); + while (it2.hasNext()) + System.out.println(it2.next()); + System.out.println("adds"); + it2 = delta.adds.iterator(); + while (it2.hasNext()) + System.out.println(it2.next()); + } + } else if (xfrin.isCurrent()) { + System.out.println("up to date"); + } +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java new file mode 100644 index 0000000..5eb5afd --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/HMAC.java @@ -0,0 +1,182 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.utils; + +import java.util.Arrays; +import java.security.*; + +/** + * An implementation of the HMAC message authentication code. + * + * @author Brian Wellington + */ + +public class HMAC { + +private MessageDigest digest; +private int blockLength; + +private byte [] ipad, opad; + +private static final byte IPAD = 0x36; +private static final byte OPAD = 0x5c; + +private void +init(byte [] key) { + int i; + + if (key.length > blockLength) { + key = digest.digest(key); + digest.reset(); + } + ipad = new byte[blockLength]; + opad = new byte[blockLength]; + for (i = 0; i < key.length; i++) { + ipad[i] = (byte) (key[i] ^ IPAD); + opad[i] = (byte) (key[i] ^ OPAD); + } + for (; i < blockLength; i++) { + ipad[i] = IPAD; + opad[i] = OPAD; + } + digest.update(ipad); +} + +/** + * Creates a new HMAC instance + * @param digest The message digest object. + * @param blockLength The block length of the message digest. + * @param key The secret key + */ +public +HMAC(MessageDigest digest, int blockLength, byte [] key) { + digest.reset(); + this.digest = digest; + this.blockLength = blockLength; + init(key); +} + +/** + * Creates a new HMAC instance + * @param digestName The name of the message digest function. + * @param blockLength The block length of the message digest. + * @param key The secret key. + */ +public +HMAC(String digestName, int blockLength, byte [] key) { + try { + digest = MessageDigest.getInstance(digestName); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("unknown digest algorithm " + + digestName); + } + this.blockLength = blockLength; + init(key); +} + +/** + * Creates a new HMAC instance + * @param digest The message digest object. + * @param key The secret key + * @deprecated won't work with digests using a padding length other than 64; + * use {@code HMAC(MessageDigest digest, int blockLength, + * byte [] key)} instead. + * @see HMAC#HMAC(MessageDigest digest, int blockLength, byte [] key) + */ +public +HMAC(MessageDigest digest, byte [] key) { + this(digest, 64, key); +} + +/** + * Creates a new HMAC instance + * @param digestName The name of the message digest function. + * @param key The secret key. + * @deprecated won't work with digests using a padding length other than 64; + * use {@code HMAC(String digestName, int blockLength, byte [] key)} + * instead + * @see HMAC#HMAC(String digestName, int blockLength, byte [] key) + */ +public +HMAC(String digestName, byte [] key) { + this(digestName, 64, key); +} + +/** + * Adds data to the current hash + * @param b The data + * @param offset The index at which to start adding to the hash + * @param length The number of bytes to hash + */ +public void +update(byte [] b, int offset, int length) { + digest.update(b, offset, length); +} + +/** + * Adds data to the current hash + * @param b The data + */ +public void +update(byte [] b) { + digest.update(b); +} + +/** + * Signs the data (computes the secure hash) + * @return An array with the signature + */ +public byte [] +sign() { + byte [] output = digest.digest(); + digest.reset(); + digest.update(opad); + return digest.digest(output); +} + +/** + * Verifies the data (computes the secure hash and compares it to the input) + * @param signature The signature to compare against + * @return true if the signature matches, false otherwise + */ +public boolean +verify(byte [] signature) { + return verify(signature, false); +} + +/** + * Verifies the data (computes the secure hash and compares it to the input) + * @param signature The signature to compare against + * @param truncation_ok If true, the signature may be truncated; only the + * number of bytes in the provided signature are compared. + * @return true if the signature matches, false otherwise + */ +public boolean +verify(byte [] signature, boolean truncation_ok) { + byte [] expected = sign(); + if (truncation_ok && signature.length < expected.length) { + byte [] truncated = new byte[signature.length]; + System.arraycopy(expected, 0, truncated, 0, truncated.length); + expected = truncated; + } + return Arrays.equals(signature, expected); +} + +/** + * Resets the HMAC object for further use + */ +public void +clear() { + digest.reset(); + digest.update(ipad); +} + +/** + * Returns the length of the digest. + */ +public int +digestLength() { + return digest.getDigestLength(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java new file mode 100644 index 0000000..58024e6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base16.java @@ -0,0 +1,73 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.utils; + +import java.io.*; + +/** + * Routines for converting between Strings of hex-encoded data and arrays of + * binary data. This is not actually used by DNS. + * + * @author Brian Wellington + */ + +public class base16 { + +private static final String Base16 = "0123456789ABCDEF"; + +private +base16() {} + +/** + * Convert binary data to a hex-encoded String + * @param b An array containing binary data + * @return A String containing the encoded data + */ +public static String +toString(byte [] b) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < b.length; i++) { + short value = (short) (b[i] & 0xFF); + byte high = (byte) (value >> 4); + byte low = (byte) (value & 0xF); + os.write(Base16.charAt(high)); + os.write(Base16.charAt(low)); + } + return new String(os.toByteArray()); +} + +/** + * Convert a hex-encoded String to binary data + * @param str A String containing the encoded data + * @return An array containing the binary data, or null if the string is invalid + */ +public static byte [] +fromString(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte [] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) { + if (!Character.isWhitespace((char)raw[i])) + bs.write(raw[i]); + } + byte [] in = bs.toByteArray(); + if (in.length % 2 != 0) { + return null; + } + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < in.length; i += 2) { + byte high = (byte) Base16.indexOf(Character.toUpperCase((char)in[i])); + byte low = (byte) Base16.indexOf(Character.toUpperCase((char)in[i+1])); + try { + ds.writeByte((high << 4) + low); + } + catch (IOException e) { + } + } + return bs.toByteArray(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java new file mode 100644 index 0000000..a2f26ea --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base32.java @@ -0,0 +1,213 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.utils; + +import java.io.*; + +/** + * Routines for converting between Strings of base32-encoded data and arrays + * of binary data. This currently supports the base32 and base32hex alphabets + * specified in RFC 4648, sections 6 and 7. + * + * @author Brian Wellington + */ + +public class base32 { + +public static class Alphabet { + private Alphabet() {} + + public static final String BASE32 = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567="; + public static final String BASE32HEX = + "0123456789ABCDEFGHIJKLMNOPQRSTUV="; +}; + +private String alphabet; +private boolean padding, lowercase; + +/** + * Creates an object that can be used to do base32 conversions. + * @param alphabet Which alphabet should be used + * @param padding Whether padding should be used + * @param lowercase Whether lowercase characters should be used. + * default parameters (The standard base32 alphabet, no padding, uppercase) + */ +public +base32(String alphabet, boolean padding, boolean lowercase) { + this.alphabet = alphabet; + this.padding = padding; + this.lowercase = lowercase; +} + +static private int +blockLenToPadding(int blocklen) { + switch (blocklen) { + case 1: + return 6; + case 2: + return 4; + case 3: + return 3; + case 4: + return 1; + case 5: + return 0; + default: + return -1; + } +} + +static private int +paddingToBlockLen(int padlen) { + switch (padlen) { + case 6: + return 1; + case 4: + return 2; + case 3: + return 3; + case 1: + return 4; + case 0: + return 5; + default : + return -1; + } +} + +/** + * Convert binary data to a base32-encoded String + * + * @param b An array containing binary data + * @return A String containing the encoded data + */ +public String +toString(byte [] b) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < (b.length + 4) / 5; i++) { + short s[] = new short[5]; + int t[] = new int[8]; + + int blocklen = 5; + for (int j = 0; j < 5; j++) { + if ((i * 5 + j) < b.length) + s[j] = (short) (b[i * 5 + j] & 0xFF); + else { + s[j] = 0; + blocklen--; + } + } + int padlen = blockLenToPadding(blocklen); + + // convert the 5 byte block into 8 characters (values 0-31). + + // upper 5 bits from first byte + t[0] = (byte) ((s[0] >> 3) & 0x1F); + // lower 3 bits from 1st byte, upper 2 bits from 2nd. + t[1] = (byte) (((s[0] & 0x07) << 2) | ((s[1] >> 6) & 0x03)); + // bits 5-1 from 2nd. + t[2] = (byte) ((s[1] >> 1) & 0x1F); + // lower 1 bit from 2nd, upper 4 from 3rd + t[3] = (byte) (((s[1] & 0x01) << 4) | ((s[2] >> 4) & 0x0F)); + // lower 4 from 3rd, upper 1 from 4th. + t[4] = (byte) (((s[2] & 0x0F) << 1) | ((s[3] >> 7) & 0x01)); + // bits 6-2 from 4th + t[5] = (byte) ((s[3] >> 2) & 0x1F); + // lower 2 from 4th, upper 3 from 5th; + t[6] = (byte) (((s[3] & 0x03) << 3) | ((s[4] >> 5) & 0x07)); + // lower 5 from 5th; + t[7] = (byte) (s[4] & 0x1F); + + // write out the actual characters. + for (int j = 0; j < t.length - padlen; j++) { + char c = alphabet.charAt(t[j]); + if (lowercase) + c = Character.toLowerCase(c); + os.write(c); + } + + // write out the padding (if any) + if (padding) { + for (int j = t.length - padlen; j < t.length; j++) + os.write('='); + } + } + + return new String(os.toByteArray()); +} + +/** + * Convert a base32-encoded String to binary data + * + * @param str A String containing the encoded data + * @return An array containing the binary data, or null if the string is invalid + */ +public byte[] +fromString(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte [] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) + { + char c = (char) raw[i]; + if (!Character.isWhitespace(c)) { + c = Character.toUpperCase(c); + bs.write((byte) c); + } + } + + if (padding) { + if (bs.size() % 8 != 0) + return null; + } else { + while (bs.size() % 8 != 0) + bs.write('='); + } + + byte [] in = bs.toByteArray(); + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < in.length / 8; i++) { + short[] s = new short[8]; + int[] t = new int[5]; + + int padlen = 8; + for (int j = 0; j < 8; j++) { + char c = (char) in[i * 8 + j]; + if (c == '=') + break; + s[j] = (short) alphabet.indexOf(in[i * 8 + j]); + if (s[j] < 0) + return null; + padlen--; + } + int blocklen = paddingToBlockLen(padlen); + if (blocklen < 0) + return null; + + // all 5 bits of 1st, high 3 (of 5) of 2nd + t[0] = (s[0] << 3) | s[1] >> 2; + // lower 2 of 2nd, all 5 of 3rd, high 1 of 4th + t[1] = ((s[1] & 0x03) << 6) | (s[2] << 1) | (s[3] >> 4); + // lower 4 of 4th, high 4 of 5th + t[2] = ((s[3] & 0x0F) << 4) | ((s[4] >> 1) & 0x0F); + // lower 1 of 5th, all 5 of 6th, high 2 of 7th + t[3] = (s[4] << 7) | (s[5] << 2) | (s[6] >> 3); + // lower 3 of 7th, all of 8th + t[4] = ((s[6] & 0x07) << 5) | s[7]; + + try { + for (int j = 0; j < blocklen; j++) + ds.writeByte((byte) (t[j] & 0xFF)); + } + catch (IOException e) { + } + } + + return bs.toByteArray(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java new file mode 100644 index 0000000..54567cf --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/base64.java @@ -0,0 +1,145 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.utils; + +import java.io.*; + +/** + * Routines for converting between Strings of base64-encoded data and arrays of + * binary data. + * + * @author Brian Wellington + */ + +public class base64 { + +private static final String Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + +private +base64() {} + +/** + * Convert binary data to a base64-encoded String + * @param b An array containing binary data + * @return A String containing the encoded data + */ +public static String +toString(byte [] b) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + + for (int i = 0; i < (b.length + 2) / 3; i++) { + short [] s = new short[3]; + short [] t = new short[4]; + for (int j = 0; j < 3; j++) { + if ((i * 3 + j) < b.length) + s[j] = (short) (b[i*3+j] & 0xFF); + else + s[j] = -1; + } + + t[0] = (short) (s[0] >> 2); + if (s[1] == -1) + t[1] = (short) (((s[0] & 0x3) << 4)); + else + t[1] = (short) (((s[0] & 0x3) << 4) + (s[1] >> 4)); + if (s[1] == -1) + t[2] = t[3] = 64; + else if (s[2] == -1) { + t[2] = (short) (((s[1] & 0xF) << 2)); + t[3] = 64; + } + else { + t[2] = (short) (((s[1] & 0xF) << 2) + (s[2] >> 6)); + t[3] = (short) (s[2] & 0x3F); + } + for (int j = 0; j < 4; j++) + os.write(Base64.charAt(t[j])); + } + return new String(os.toByteArray()); +} + +/** + * Formats data into a nicely formatted base64 encoded String + * @param b An array containing binary data + * @param lineLength The number of characters per line + * @param prefix A string prefixing the characters on each line + * @param addClose Whether to add a close parenthesis or not + * @return A String representing the formatted output + */ +public static String +formatString(byte [] b, int lineLength, String prefix, boolean addClose) { + String s = toString(b); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < s.length(); i += lineLength) { + sb.append (prefix); + if (i + lineLength >= s.length()) { + sb.append(s.substring(i)); + if (addClose) + sb.append(" )"); + } + else { + sb.append(s.substring(i, i + lineLength)); + sb.append("\n"); + } + } + return sb.toString(); +} + + +/** + * Convert a base64-encoded String to binary data + * @param str A String containing the encoded data + * @return An array containing the binary data, or null if the string is invalid + */ +public static byte [] +fromString(String str) { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte [] raw = str.getBytes(); + for (int i = 0; i < raw.length; i++) { + if (!Character.isWhitespace((char)raw[i])) + bs.write(raw[i]); + } + byte [] in = bs.toByteArray(); + if (in.length % 4 != 0) { + return null; + } + + bs.reset(); + DataOutputStream ds = new DataOutputStream(bs); + + for (int i = 0; i < (in.length + 3) / 4; i++) { + short [] s = new short[4]; + short [] t = new short[3]; + + for (int j = 0; j < 4; j++) + s[j] = (short) Base64.indexOf(in[i*4+j]); + + t[0] = (short) ((s[0] << 2) + (s[1] >> 4)); + if (s[2] == 64) { + t[1] = t[2] = (short) (-1); + if ((s[1] & 0xF) != 0) + return null; + } + else if (s[3] == 64) { + t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF); + t[2] = (short) (-1); + if ((s[2] & 0x3) != 0) + return null; + } + else { + t[1] = (short) (((s[1] << 4) + (s[2] >> 2)) & 0xFF); + t[2] = (short) (((s[2] << 6) + s[3]) & 0xFF); + } + + try { + for (int j = 0; j < 3; j++) + if (t[j] >= 0) + ds.writeByte(t[j]); + } + catch (IOException e) { + } + } + return bs.toByteArray(); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java b/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java new file mode 100644 index 0000000..1a79a40 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/utils/hexdump.java @@ -0,0 +1,56 @@ +// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org) + +package org.xbill.DNS.utils; + +/** + * A routine to produce a nice looking hex dump + * + * @author Brian Wellington + */ + +public class hexdump { + +private static final char [] hex = "0123456789ABCDEF".toCharArray(); + +/** + * Dumps a byte array into hex format. + * @param description If not null, a description of the data. + * @param b The data to be printed. + * @param offset The start of the data in the array. + * @param length The length of the data in the array. + */ +public static String +dump(String description, byte [] b, int offset, int length) { + StringBuffer sb = new StringBuffer(); + + sb.append(length + "b"); + if (description != null) + sb.append(" (" + description + ")"); + sb.append(':'); + + int prefixlen = sb.toString().length(); + prefixlen = (prefixlen + 8) & ~ 7; + sb.append('\t'); + + int perline = (80 - prefixlen) / 3; + for (int i = 0; i < length; i++) { + if (i != 0 && i % perline == 0) { + sb.append('\n'); + for (int j = 0; j < prefixlen / 8 ; j++) + sb.append('\t'); + } + int value = (int)(b[i + offset]) & 0xFF; + sb.append(hex[(value >> 4)]); + sb.append(hex[(value & 0xF)]); + sb.append(' '); + } + sb.append('\n'); + return sb.toString(); +} + +public static String +dump(String s, byte [] b) { + return dump(s, b, 0, b.length); +} + +} diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties new file mode 100644 index 0000000..25342f9 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer.properties @@ -0,0 +1,4 @@ +host_name=Host Name +primary_dns_suffix=Primary Dns Suffix +dns_suffix=DNS Suffix +dns_servers=DNS Servers diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties new file mode 100644 index 0000000..aa3f4a6 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_de.properties @@ -0,0 +1,4 @@ +host_name=Hostname +primary_dns_suffix=Prim\u00E4res DNS-Suffix +dns_suffix=DNS-Suffixsuchliste +dns_servers=DNS-Server diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties new file mode 100644 index 0000000..7c87a25 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_fr.properties @@ -0,0 +1,4 @@ +host_name=Nom de l'h\u00F4te +primary_dns_suffix=Suffixe DNS principal +dns_suffix=Suffixe DNS propre \u00E0 la connexion +dns_servers=Serveurs DNS diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties new file mode 100644 index 0000000..f873164 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_ja.properties @@ -0,0 +1,4 @@ +host_name=\u30db\u30b9\u30c8\u540d +primary_dns_suffix=\u30d7\u30e9\u30a4\u30de\u30ea DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 +dns_suffix=DNS \u30b5\u30d5\u30a3\u30c3\u30af\u30b9 +dns_servers=DNS \u30b5\u30fc\u30d0\u30fc diff --git a/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties new file mode 100644 index 0000000..eab5774 --- /dev/null +++ b/external/asmack/build/src/trunk/org/xbill/DNS/windows/DNSServer_pl.properties @@ -0,0 +1,4 @@ +host_name=Nazwa hosta +primary_dns_suffix=Sufiks podstawowej domeny DNS +dns_suffix=Sufiks DNS konkretnego po\u0142\u0105czenia +dns_servers=Serwery DNS diff --git a/external/asmack/build/src/trunk/overview.html b/external/asmack/build/src/trunk/overview.html new file mode 100644 index 0000000..a2449b1 --- /dev/null +++ b/external/asmack/build/src/trunk/overview.html @@ -0,0 +1,4 @@ +API specification for Smack, an Open Source XMPP client library. +

      +The {@link org.jivesoftware.smack.Connection} class is the main entry point for the API. + diff --git a/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java b/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java new file mode 100644 index 0000000..eff0d05 --- /dev/null +++ b/external/asmack/build/src/trunk/samples/DiscoverServiceTypes.java @@ -0,0 +1,70 @@ +//Licensed under Apache License version 2.0 +//Original license LGPL + +// %Z%%M%, %I%, %G% +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package samples; + +import java.io.IOException; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceEvent; +import org.jmdns.ServiceTypeListener; + +/** + * Sample Code for Service Type Discovery using JmDNS and a ServiceTypeListener. + *

      + * Run the main method of this class. It lists all service types known on the + * local network on System.out. + * + * @author Werner Randelshofer + * @version %I%, %G% + */ +public class DiscoverServiceTypes { + + static class SampleListener implements ServiceTypeListener { + + public void serviceTypeAdded(ServiceEvent event) { + System.out.println("Service type added: " +event.getType()); + } + } + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + /* Activate these lines to see log messages of JmDNS + Logger logger = Logger.getLogger(JmDNS.class.getName()); + ConsoleHandler handler = new ConsoleHandler(); + logger.addHandler(handler); + logger.setLevel(Level.FINER); + handler.setLevel(Level.FINER); + */ + + try { + JmDNS jmdns = JmDNS.create(); + jmdns.addServiceTypeListener(new SampleListener()); + + System.out.println("Press q and Enter, to quit"); + int b; + while ((b = System.in.read()) != -1 && (char) b != 'q'); + jmdns.close(); + System.out.println("Done"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/external/asmack/build/src/trunk/samples/DiscoverServices.java b/external/asmack/build/src/trunk/samples/DiscoverServices.java new file mode 100644 index 0000000..612f3ac --- /dev/null +++ b/external/asmack/build/src/trunk/samples/DiscoverServices.java @@ -0,0 +1,74 @@ +//Licensed under Apache License version 2.0 +//Original license LGPL + +// %Z%%M%, %I%, %G% +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package samples; + +import java.io.IOException; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceEvent; +import org.jmdns.ServiceListener; + +/** + * Sample Code for Service Discovery using JmDNS and a ServiceListener. + *

      + * Run the main method of this class. It listens for HTTP services and lists + * all changes on System.out. + * + * @author Werner Randelshofer + * @version %I%, %G% + */ +public class DiscoverServices { + + static class SampleListener implements ServiceListener { + public void serviceAdded(ServiceEvent event) { + System.out.println("Service added : " + event.getName()+"."+event.getType()); + } + public void serviceRemoved(ServiceEvent event) { + System.out.println("Service removed : " + event.getName()+"."+event.getType()); + } + public void serviceResolved(ServiceEvent event) { + System.out.println("Service resolved: " + event.getInfo()); + } + } + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + try { + /* Activate these lines to see log messages of JmDNS + Logger logger = Logger.getLogger(JmDNS.class.getName()); + ConsoleHandler handler = new ConsoleHandler(); + logger.addHandler(handler); + logger.setLevel(Level.FINER); + handler.setLevel(Level.FINER); + */ + JmDNS jmdns = JmDNS.create(); + jmdns.addServiceListener("_http._tcp.local.", new SampleListener()); + + System.out.println("Press q and Enter, to quit"); + int b; + while ((b = System.in.read()) != -1 && (char) b != 'q'); + jmdns.close(); + System.out.println("Done"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/external/asmack/build/src/trunk/samples/ListServices.java b/external/asmack/build/src/trunk/samples/ListServices.java new file mode 100644 index 0000000..5bbfba8 --- /dev/null +++ b/external/asmack/build/src/trunk/samples/ListServices.java @@ -0,0 +1,71 @@ +// %Z%%M%, %I%, %G% +// + +//Licensed under Apache License version 2.0 +//Original license LGPL + + +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package samples; + +import java.io.IOException; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceInfo; + +/** + * Sample Code for Listing Services using JmDNS. + *

      + * Run the main method of this class. This class prints a list of available HTTP + * services every 5 seconds. + * + * @author Werner Randelshofer + * @version %I%, %G% + */ +public class ListServices { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + /* Activate these lines to see log messages of JmDNS + Logger logger = Logger.getLogger(JmDNS.class.getName()); + ConsoleHandler handler = new ConsoleHandler(); + logger.addHandler(handler); + logger.setLevel(Level.FINER); + handler.setLevel(Level.FINER); + */ + + try { + JmDNS jmdns = JmDNS.create(); + while (true) { + ServiceInfo[] infos = jmdns.list("_http._tcp.local."); + for (int i=0; i < infos.length; i++) { + System.out.println(infos[i]); + } + System.out.println(); + + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + break; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/external/asmack/build/src/trunk/samples/OpenJmDNS.java b/external/asmack/build/src/trunk/samples/OpenJmDNS.java new file mode 100644 index 0000000..46cdcf0 --- /dev/null +++ b/external/asmack/build/src/trunk/samples/OpenJmDNS.java @@ -0,0 +1,51 @@ +//Licensed under Apache License version 2.0 +//Original license LGPL + +// %Z%%M%, %I%, %G% +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package samples; + +import java.io.IOException; + +import org.jmdns.JmDNS; + +/** + * Sample Code that opens JmDNS in debug mode. + *

      + * Run the main method of this class. + * + * @author Werner Randelshofer + * @version %I%, %G% + */ +public class OpenJmDNS { + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + try { + System.setProperty("jmdns.debug", "2"); + JmDNS jmdns = JmDNS.create(); + + System.out.println("Press q and Enter, to quit"); + int b; + while ((b = System.in.read()) != -1 && (char) b != 'q'); + jmdns.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/external/asmack/build/src/trunk/samples/RegisterService.java b/external/asmack/build/src/trunk/samples/RegisterService.java new file mode 100644 index 0000000..324374e --- /dev/null +++ b/external/asmack/build/src/trunk/samples/RegisterService.java @@ -0,0 +1,87 @@ +//Licensed under Apache License version 2.0 +//Original license LGPL + +// %Z%%M%, %I%, %G% +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package samples; + +import java.io.IOException; + +import org.jmdns.JmDNS; +import org.jmdns.ServiceInfo; + +/** + * Sample Code for Service Registration using JmDNS. + *

      + * To see what happens, launch the TTY browser of JmDNS using the following + * command: + *

      + * java -jar lib/jmdns.jar -bs _http._tcp local.
      + * 
      + * Then run the main method of this class. When you press 'r' and enter, + * you should see the following output on the TTY browser: + *
      + * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
      + * 
      + * + * Press 'r' and enter, + * you should see the following output on the TTY browser: + *
      + * ADD: service[foo._http._tcp.local.,192.168.2.5:1234,path=index.html]
      + * 
      + * REMOVE: foo + * + * @author Werner Randelshofer + * @version %I%, %G% + */ +public class RegisterService { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + /* Activate these lines to see log messages of JmDNS + Logger logger = Logger.getLogger(JmDNS.class.getName()); + ConsoleHandler handler = new ConsoleHandler(); + logger.addHandler(handler); + logger.setLevel(Level.FINER); + handler.setLevel(Level.FINER); + */ + + try { + System.out.println("Opening JmDNS"); + JmDNS jmdns = JmDNS.create(); + System.out.println("Opened JmDNS"); + System.out.println("\nPress r and Enter, to register HTML service 'foo'"); + int b; + while ((b = System.in.read()) != -1 && (char) b != 'r'); + ServiceInfo info = ServiceInfo.create("_http._tcp.local.", "foo", 1268, 0, 0, "path=index.html"); + jmdns.registerService(info); + + System.out.println("\nRegistered Service as "+info); + System.out.println("Press q and Enter, to quit"); + //int b; + while ((b = System.in.read()) != -1 && (char) b != 'q'); + System.out.println("Closing JmDNS"); + jmdns.close(); + System.out.println("Done"); + System.exit(0); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/external/asmack/build/src/trunk/samples/TestShutdownHook.java b/external/asmack/build/src/trunk/samples/TestShutdownHook.java new file mode 100644 index 0000000..3728aa7 --- /dev/null +++ b/external/asmack/build/src/trunk/samples/TestShutdownHook.java @@ -0,0 +1,33 @@ +//Licensed under Apache License version 2.0 +//Original license LGPL + +package samples; + +import java.io.*; +/** + * TestShutdownHook. + * + * @author Werner Randelshofer + * @version 1.0 May 24, 2004 Created. + */ +public class TestShutdownHook { + + /** Creates a new instance. */ + public static void main(String[] args) { + Runtime.getRuntime().addShutdownHook( + new Thread() { + public void run() { + System.out.println("Shutdown Hook"); + } + } + ); + try { + int b; + while ((b=System.in.read()) != -1) { + System.out.print("\""+(char) b); + } + } catch (IOException e) { + } + } + +} diff --git a/external/asmack/build/src/trunk/samples/package.html b/external/asmack/build/src/trunk/samples/package.html new file mode 100644 index 0000000..190f6aa --- /dev/null +++ b/external/asmack/build/src/trunk/samples/package.html @@ -0,0 +1,59 @@ + + + + + + + + +Contains sample programs which demonstrate how to use the API of JmDNS. + + + +

      Related Documentation

      + + +For overviews, tutorials, examples, guides, and tool documentation, please see +the Readme file of JmDNS. + + + + + +