diff options
Diffstat (limited to 'src/org/jivesoftware/smack/packet')
-rw-r--r-- | src/org/jivesoftware/smack/packet/Authentication.java | 186 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Bind.java | 71 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/DefaultPacketExtension.java | 133 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/IQ.java | 244 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Message.java | 672 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Packet.java | 509 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/PacketExtension.java | 56 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Presence.java | 358 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Privacy.java | 323 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/PrivacyItem.java | 462 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Registration.java | 155 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/RosterPacket.java | 311 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/Session.java | 45 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/StreamError.java | 106 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/XMPPError.java | 453 | ||||
-rw-r--r-- | src/org/jivesoftware/smack/packet/package.html | 1 |
16 files changed, 4085 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smack/packet/Authentication.java b/src/org/jivesoftware/smack/packet/Authentication.java new file mode 100644 index 0000000..a47c079 --- /dev/null +++ b/src/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": + * <p/> + * <p><tt>setType(IQ.Type.GET);</tt> + */ + public Authentication() { + setType(IQ.Type.SET); + } + + /** + * Returns the username, or <tt>null</tt> 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 <tt>null</tt> 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 <tt>null</tt> 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 <tt>null</tt> 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("<query xmlns=\"jabber:iq:auth\">"); + if (username != null) { + if (username.equals("")) { + buf.append("<username/>"); + } + else { + buf.append("<username>").append(username).append("</username>"); + } + } + if (digest != null) { + if (digest.equals("")) { + buf.append("<digest/>"); + } + else { + buf.append("<digest>").append(digest).append("</digest>"); + } + } + if (password != null && digest == null) { + if (password.equals("")) { + buf.append("<password/>"); + } + else { + buf.append("<password>").append(StringUtils.escapeForXML(password)).append("</password>"); + } + } + if (resource != null) { + if (resource.equals("")) { + buf.append("<resource/>"); + } + else { + buf.append("<resource>").append(resource).append("</resource>"); + } + } + buf.append("</query>"); + return buf.toString(); + } +} diff --git a/src/org/jivesoftware/smack/packet/Bind.java b/src/org/jivesoftware/smack/packet/Bind.java new file mode 100644 index 0000000..07cd193 --- /dev/null +++ b/src/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.<p>
+ *
+ * For more information refer to the following
+ * <a href=http://www.xmpp.org/specs/rfc3920.html#bind>link</a>.
+ *
+ * @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("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\">");
+ if (resource != null) {
+ buf.append("<resource>").append(resource).append("</resource>");
+ }
+ if (jid != null) {
+ buf.append("<jid>").append(jid).append("</jid>");
+ }
+ buf.append("</bind>");
+ return buf.toString();
+ }
+}
diff --git a/src/org/jivesoftware/smack/packet/DefaultPacketExtension.java b/src/org/jivesoftware/smack/packet/DefaultPacketExtension.java new file mode 100644 index 0000000..6cc7934 --- /dev/null +++ b/src/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.<p> + * + * 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: + * + * <pre> + * <foo xmlns="http://bar.com"> + * <color>blue</color> + * <food>pizza</food> + * </foo></pre> + * + * 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<String,String> 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("</").append(name).append(">"); + } + buf.append("</").append(elementName).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<String> getNames() { + if (map == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashMap<String,String>(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<String,String>(); + } + map.put(name, value); + } +}
\ No newline at end of file diff --git a/src/org/jivesoftware/smack/packet/IQ.java b/src/org/jivesoftware/smack/packet/IQ.java new file mode 100644 index 0000000..8e1f7d4 --- /dev/null +++ b/src/org/jivesoftware/smack/packet/IQ.java @@ -0,0 +1,244 @@ +/** + * $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".<p> + * + * 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:<ul> + * + * <li><query xmlns="jabber:iq:auth"> -- an authentication IQ. + * <li><query xmlns="jabber:iq:private"> -- a private storage IQ. + * <li><pubsub xmlns="http://jabber.org/protocol/pubsub"> -- a pubsub IQ. + * </ul> + * + * @author Matt Tucker + */ +public abstract class IQ extends Packet { + + private Type type = Type.GET; + + public IQ() { + super(); + } + + public IQ(IQ iq) { + super(iq); + type = iq.getType(); + } + /** + * 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("<iq "); + if (getPacketID() != null) { + buf.append("id=\"" + getPacketID() + "\" "); + } + if (getTo() != null) { + buf.append("to=\"").append(StringUtils.escapeForXML(getTo())).append("\" "); + } + if (getFrom() != null) { + buf.append("from=\"").append(StringUtils.escapeForXML(getFrom())).append("\" "); + } + if (type == null) { + buf.append("type=\"get\">"); + } + 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("</iq>"); + return buf.toString(); + } + + /** + * Returns the sub-element XML section of the IQ packet, or <tt>null</tt> if there + * isn't one. Packet extensions <b>must</b> be included, if any are defined.<p> + * + * 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:<ul> + * <li>The sender set to the recipient of the originating IQ. + * <li>The recipient set to the sender of the originating IQ. + * <li>The type set to {@link Type#RESULT IQ.Type.RESULT}. + * <li>The id set to the id of the originating IQ. + * <li>No child element of the IQ element. + * </ul> + * + * @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:<ul> + * <li>The sender set to the recipient of the originating IQ. + * <li>The recipient set to the sender of the originating IQ. + * <li>The type set to {@link Type#ERROR IQ.Type.ERROR}. + * <li>The id set to the id of the originating IQ. + * <li>The child element contained in the associated originating IQ. + * <li>The provided {@link XMPPError XMPPError}. + * </ul> + * + * @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: + * + * <ul> + * <li>IQ.Type.GET + * <li>IQ.Type.SET + * <li>IQ.Type.RESULT + * <li>IQ.Type.ERROR + * </ul> + */ + 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/src/org/jivesoftware/smack/packet/Message.java b/src/org/jivesoftware/smack/packet/Message.java new file mode 100644 index 0000000..d28a9f4 --- /dev/null +++ b/src/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: + * + * <ul> + * <li>Message.Type.NORMAL -- (Default) a normal text message used in email like interface. + * <li>Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces. + * <li>Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats. + * <li>Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays. + * <li>Message.Type.ERROR -- indicates a messaging error. + * </ul> + * + * For each message type, different message fields are typically used as follows: + * <p> + * <table border="1"> + * <tr><td> </td><td colspan="5"><b>Message type</b></td></tr> + * <tr><td><i>Field</i></td><td><b>Normal</b></td><td><b>Chat</b></td><td><b>Group Chat</b></td><td><b>Headline</b></td><td><b>XMPPError</b></td></tr> + * <tr><td><i>subject</i></td> <td>SHOULD</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td><td>SHOULD NOT</td></tr> + * <tr><td><i>thread</i></td> <td>OPTIONAL</td><td>SHOULD</td><td>OPTIONAL</td><td>OPTIONAL</td><td>SHOULD NOT</td></tr> + * <tr><td><i>body</i></td> <td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD</td><td>SHOULD NOT</td></tr> + * <tr><td><i>error</i></td> <td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST NOT</td><td>MUST</td></tr> + * </table> + * + * @author Matt Tucker + */ +public class Message extends Packet { + + private Type type = Type.normal; + private String thread = null; + private String language; + + private final Set<Subject> subjects = new HashSet<Subject>(); + private final Set<Body> bodies = new HashSet<Body>(); + + /** + * 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. + * <p> + * 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<Subject> 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<String> getSubjectLanguages() { + Subject defaultSubject = getMessageSubject(null); + List<String> languages = new ArrayList<String>(); + 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. + * <p> + * 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<Body> 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<String> getBodyLanguages() { + Body defaultBody = getMessageBody(null); + List<String> languages = new ArrayList<String>(); + 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, <tt>null</tt> will be returned. + * + * @return the thread id of the message, or <tt>null</tt> 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("<message"); + if (getXmlns() != null) { + buf.append(" xmlns=\"").append(getXmlns()).append("\""); + } + if (language != null) { + buf.append(" xml:lang=\"").append(getLanguage()).append("\""); + } + if (getPacketID() != null) { + buf.append(" id=\"").append(getPacketID()).append("\""); + } + if (getTo() != null) { + buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); + } + if (getFrom() != null) { + buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); + } + if (type != Type.normal) { + buf.append(" type=\"").append(type).append("\""); + } + buf.append(">"); + // Add the subject in the default language + Subject defaultSubject = getMessageSubject(null); + if (defaultSubject != null) { + buf.append("<subject>").append(StringUtils.escapeForXML(defaultSubject.subject)).append("</subject>"); + } + // Add the subject in other languages + for (Subject subject : getSubjects()) { + // Skip the default language + if(subject.equals(defaultSubject)) + continue; + buf.append("<subject xml:lang=\"").append(subject.language).append("\">"); + buf.append(StringUtils.escapeForXML(subject.subject)); + buf.append("</subject>"); + } + // Add the body in the default language + Body defaultBody = getMessageBody(null); + if (defaultBody != null) { + buf.append("<body>").append(StringUtils.escapeForXML(defaultBody.message)).append("</body>"); + } + // Add the bodies in other languages + for (Body body : getBodies()) { + // Skip the default language + if(body.equals(defaultBody)) + continue; + buf.append("<body xml:lang=\"").append(body.getLanguage()).append("\">"); + buf.append(StringUtils.escapeForXML(body.getMessage())); + buf.append("</body>"); + } + if (thread != null) { + buf.append("<thread>").append(thread).append("</thread>"); + } + // 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("</message>"); + 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/src/org/jivesoftware/smack/packet/Packet.java b/src/org/jivesoftware/smack/packet/Packet.java new file mode 100644 index 0000000..3f1185e --- /dev/null +++ b/src/org/jivesoftware/smack/packet/Packet.java @@ -0,0 +1,509 @@ +/** + * $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 <tt>null</tt>. + */ + 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. + * <p> + * 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<PacketExtension> packetExtensions + = new CopyOnWriteArrayList<PacketExtension>(); + + private final Map<String,Object> properties = new HashMap<String, Object>(); + private XMPPError error = null; + + public Packet() { + } + + public Packet(Packet p) { + packetID = p.getPacketID(); + to = p.getTo(); + from = p.getFrom(); + xmlns = p.xmlns; + error = p.error; + + // Copy extensions + for (PacketExtension pe : p.getExtensions()) { + addExtension(pe); + } + } + + /** + * Returns the unique ID of the packet. The returned value could be <tt>null</tt> when + * ID_NOT_AVAILABLE was set as the packet's id. + * + * @return the packet's unique ID or <tt>null</tt> 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 <tt>null</tt> if + * the value is not set. The XMPP protocol often makes the "to" + * attribute optional, so it does not always need to be set.<p> + * + * 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 <tt>null</tt> 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 <tt>null</tt> if + * the value is not set. The XMPP protocol often makes the "from" + * attribute optional, so it does not always need to be set.<p> + * + * 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 <tt>null</tt> 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 <tt>null</tt> if there are + * no errors. + * + * @return the error sub-packet or <tt>null</tt> 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<PacketExtension> getExtensions() { + if (packetExtensions == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(new ArrayList<PacketExtension>(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 <tt>null</tt> 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 <tt>null</tt> 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. Does nothing if extension is null. + * + * @param extension a packet extension. + */ + public void addExtension(PacketExtension extension) { + if (extension == null) return; + packetExtensions.add(extension); + } + + /** + * Adds a collection of packet extensions to the packet. Does nothing if extensions is null. + * + * @param extensions a collection of packet extensions + */ + public void addExtensions(Collection<PacketExtension> extensions) { + if (extensions == null) return; + packetExtensions.addAll(extensions); + } + + /** + * 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 <tt>null</tt> if the + * property doesn't exist. Property values that were originally 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 <tt>null</tt> 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<String> getPropertyNames() { + if (properties == null) { + return Collections.emptySet(); + } + return Collections.unmodifiableSet(new HashSet<String>(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("<properties xmlns=\"http://www.jivesoftware.com/xmlns/xmpp/properties\">"); + // Loop through all properties and write them out. + for (String name : getPropertyNames()) { + Object value = getProperty(name); + buf.append("<property>"); + buf.append("<name>").append(StringUtils.escapeForXML(name)).append("</name>"); + buf.append("<value type=\""); + if (value instanceof Integer) { + buf.append("integer\">").append(value).append("</value>"); + } + else if (value instanceof Long) { + buf.append("long\">").append(value).append("</value>"); + } + else if (value instanceof Float) { + buf.append("float\">").append(value).append("</value>"); + } + else if (value instanceof Double) { + buf.append("double\">").append(value).append("</value>"); + } + else if (value instanceof Boolean) { + buf.append("boolean\">").append(value).append("</value>"); + } + else if (value instanceof String) { + buf.append("string\">"); + buf.append(StringUtils.escapeForXML((String)value)); + buf.append("</value>"); + } + // 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("</value>"); + } + 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("</property>"); + } + buf.append("</properties>"); + } + 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/src/org/jivesoftware/smack/packet/PacketExtension.java b/src/org/jivesoftware/smack/packet/PacketExtension.java new file mode 100644 index 0000000..d2afbf8 --- /dev/null +++ b/src/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/src/org/jivesoftware/smack/packet/Presence.java b/src/org/jivesoftware/smack/packet/Presence.java new file mode 100644 index 0000000..84fcfef --- /dev/null +++ b/src/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: + * <ul> + * <li>{@link Presence.Type#available available} -- (Default) indicates the user is available to + * receive messages. + * <li>{@link Presence.Type#unavailable unavailable} -- the user is unavailable to receive messages. + * <li>{@link Presence.Type#subscribe subscribe} -- request subscription to recipient's presence. + * <li>{@link Presence.Type#subscribed subscribed} -- grant subscription to sender's presence. + * <li>{@link Presence.Type#unsubscribe unsubscribe} -- request removal of subscription to + * sender's presence. + * <li>{@link Presence.Type#unsubscribed unsubscribed} -- grant removal of subscription to + * sender's presence. + * <li>{@link Presence.Type#error error} -- the presence packet contains an error message. + * </ul><p> + * + * A number of attributes are optional: + * <ul> + * <li>Status -- free-form text describing a user's presence (i.e., gone to lunch). + * <li>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. + * <li>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). + * </ul><p> + * + * 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 <tt>getType() == Presence.Type.available</tt>. 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 + * <tt>type == Type.available && (mode == Mode.away || mode == Mode.xa || mode == Mode.dnd)</tt>. + * + * @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 <tt>null</tt> 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 <tt>null</tt> 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 + */ + public 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("<presence"); + if(getXmlns() != null) { + buf.append(" xmlns=\"").append(getXmlns()).append("\""); + } + if (language != null) { + buf.append(" xml:lang=\"").append(getLanguage()).append("\""); + } + if (getPacketID() != null) { + buf.append(" id=\"").append(getPacketID()).append("\""); + } + if (getTo() != null) { + buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); + } + if (getFrom() != null) { + buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); + } + if (type != Type.available) { + buf.append(" type=\"").append(type).append("\""); + } + buf.append(">"); + if (status != null) { + buf.append("<status>").append(StringUtils.escapeForXML(status)).append("</status>"); + } + if (priority != Integer.MIN_VALUE) { + buf.append("<priority>").append(priority).append("</priority>"); + } + if (mode != null && mode != Mode.available) { + buf.append("<show>").append(mode).append("</show>"); + } + + 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("</presence>"); + + 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/src/org/jivesoftware/smack/packet/Privacy.java b/src/org/jivesoftware/smack/packet/Privacy.java new file mode 100644 index 0000000..a62d578 --- /dev/null +++ b/src/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:
+ * <ul>
+ * <li>Retrieving one's privacy lists.
+ * <li>Adding, removing, and editing one's privacy lists.
+ * <li>Setting, changing, or declining active lists.
+ * <li>Setting, changing, or declining the default list (i.e., the list that is active by default).
+ * </ul>
+ * 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<String, List<PrivacyItem>> itemLists = new HashMap<String, List<PrivacyItem>>();
+
+ /**
+ * 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<PrivacyItem> setPrivacyList(String listName, List<PrivacyItem> 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<PrivacyItem> 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 <tt>null</tt> if none was found.
+ *
+ * @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
+ */
+ public List<PrivacyItem> 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 <tt>null</tt> if none was found.
+ *
+ * @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
+ */
+ public List<PrivacyItem> 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<PrivacyItem> 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<PrivacyItem> 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<String, List<PrivacyItem>> 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<String> getPrivacyListNames() {
+ return this.itemLists.keySet();
+ }
+
+ public String getChildElementXML() {
+ StringBuilder buf = new StringBuilder();
+ buf.append("<query xmlns=\"jabber:iq:privacy\">");
+
+ // Add the active tag
+ if (this.isDeclineActiveList()) {
+ buf.append("<active/>");
+ } else {
+ if (this.getActiveName() != null) {
+ buf.append("<active name=\"").append(this.getActiveName()).append("\"/>");
+ }
+ }
+ // Add the default tag
+ if (this.isDeclineDefaultList()) {
+ buf.append("<default/>");
+ } else {
+ if (this.getDefaultName() != null) {
+ buf.append("<default name=\"").append(this.getDefaultName()).append("\"/>");
+ }
+ }
+
+ // Add the list with their privacy items
+ for (Map.Entry<String, List<PrivacyItem>> entry : this.getItemLists().entrySet()) {
+ String listName = entry.getKey();
+ List<PrivacyItem> items = entry.getValue();
+ // Begin the list tag
+ if (items.isEmpty()) {
+ buf.append("<list name=\"").append(listName).append("\"/>");
+ } else {
+ buf.append("<list name=\"").append(listName).append("\">");
+ }
+ for (PrivacyItem item : items) {
+ // Append the item xml representation
+ buf.append(item.toXML());
+ }
+ // Close the list tag
+ if (!items.isEmpty()) {
+ buf.append("</list>");
+ }
+ }
+
+ // Add packet extensions, if any are defined.
+ buf.append(getExtensionsXML());
+ buf.append("</query>");
+ return buf.toString();
+ }
+
+}
\ No newline at end of file diff --git a/src/org/jivesoftware/smack/packet/PrivacyItem.java b/src/org/jivesoftware/smack/packet/PrivacyItem.java new file mode 100644 index 0000000..2e144ee --- /dev/null +++ b/src/org/jivesoftware/smack/packet/PrivacyItem.java @@ -0,0 +1,462 @@ +/** + * $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.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:<ul>
+ * <li>Allowing or blocking messages.
+ * <li>Allowing or blocking inbound presence notifications.
+ * <li>Allowing or blocking outbound presence notifications.
+ * <li>Allowing or blocking IQ stanzas.
+ * <li>Allowing or blocking all communications.
+ * </ul>
+ * @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("<item");
+ if (this.isAllow()) {
+ buf.append(" action=\"allow\"");
+ } else {
+ buf.append(" action=\"deny\"");
+ }
+ buf.append(" order=\"").append(getOrder()).append("\"");
+ if (getType() != null) {
+ buf.append(" type=\"").append(getType()).append("\"");
+ }
+ if (getValue() != null) {
+ buf.append(" value=\"").append(getValue()).append("\"");
+ }
+ if (isFilterEverything()) {
+ buf.append("/>");
+ } else {
+ buf.append(">");
+ if (this.isFilterIQ()) {
+ buf.append("<iq/>");
+ }
+ if (this.isFilterMessage()) {
+ buf.append("<message/>");
+ }
+ if (this.isFilterPresence_in()) {
+ buf.append("<presence-in/>");
+ }
+ if (this.isFilterPresence_out()) {
+ buf.append("<presence-out/>");
+ }
+ buf.append("</item>");
+ }
+ 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/src/org/jivesoftware/smack/packet/Registration.java b/src/org/jivesoftware/smack/packet/Registration.java new file mode 100644 index 0000000..df22e27 --- /dev/null +++ b/src/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: + * <ul> + * <li>name -- the user's name. + * <li>first -- the user's first name. + * <li>last -- the user's last name. + * <li>email -- the user's email address. + * <li>city -- the user's city. + * <li>state -- the user's state. + * <li>zip -- the user's ZIP code. + * <li>phone -- the user's phone number. + * <li>url -- the user's website. + * <li>date -- the date the registration took place. + * <li>misc -- other miscellaneous information to associate with the account. + * <li>text -- textual information to associate with the account. + * <li>remove -- empty flag to remove account. + * </ul> + * + * @author Matt Tucker + */ +public class Registration extends IQ { + + private String instructions = null; + private Map<String, String> attributes = new HashMap<String,String>(); + private List<String> requiredFields = new ArrayList<String>(); + private boolean registered = false; + private boolean remove = false; + + /** + * Returns the registration instructions, or <tt>null</tt> 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 <tt>null</tt> 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<String, String> 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<String, String> attributes) { + this.attributes = attributes; + } + + public List<String> 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<String> getFieldNames(){ + return new ArrayList<String>(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("<query xmlns=\"jabber:iq:register\">"); + if (instructions != null && !remove) { + buf.append("<instructions>").append(instructions).append("</instructions>"); + } + 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("</").append(name).append(">"); + } + } + else if(remove){ + buf.append("</remove>"); + } + // Add packet extensions, if any are defined. + buf.append(getExtensionsXML()); + buf.append("</query>"); + return buf.toString(); + } +}
\ No newline at end of file diff --git a/src/org/jivesoftware/smack/packet/RosterPacket.java b/src/org/jivesoftware/smack/packet/RosterPacket.java new file mode 100644 index 0000000..98483c8 --- /dev/null +++ b/src/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<Item> rosterItems = new ArrayList<Item>(); + /* + * 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<Item> getRosterItems() { + synchronized (rosterItems) { + return Collections.unmodifiableList(new ArrayList<Item>(rosterItems)); + } + } + + public String getChildElementXML() { + StringBuilder buf = new StringBuilder(); + buf.append("<query xmlns=\"jabber:iq:roster\" "); + if(version!=null){ + buf.append(" ver=\""+version+"\" "); + } + buf.append(">"); + synchronized (rosterItems) { + for (Item entry : rosterItems) { + buf.append(entry.toXML()); + } + } + buf.append("</query>"); + 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<String> 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<String>(); + } + + /** + * 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<String> 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("<item jid=\"").append(user).append("\""); + if (name != null) { + buf.append(" name=\"").append(StringUtils.escapeForXML(name)).append("\""); + } + if (itemType != null) { + buf.append(" subscription=\"").append(itemType).append("\""); + } + if (itemStatus != null) { + buf.append(" ask=\"").append(itemStatus).append("\""); + } + buf.append(">"); + for (String groupName : groupNames) { + buf.append("<group>").append(StringUtils.escapeForXML(groupName)).append("</group>"); + } + buf.append("</item>"); + 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/src/org/jivesoftware/smack/packet/Session.java b/src/org/jivesoftware/smack/packet/Session.java new file mode 100644 index 0000000..fd403ae --- /dev/null +++ b/src/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.<p>
+ *
+ * If a server supports sessions, it MUST include a <i>session</i> 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.<p>
+ *
+ * For more information refer to the following
+ * <a href=http://www.xmpp.org/specs/rfc3921.html#session>link</a>.
+ *
+ * @author Gaston Dombiak
+ */
+public class Session extends IQ {
+
+ public Session() {
+ setType(IQ.Type.SET);
+ }
+
+ public String getChildElementXML() {
+ return "<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"/>";
+ }
+}
diff --git a/src/org/jivesoftware/smack/packet/StreamError.java b/src/org/jivesoftware/smack/packet/StreamError.java new file mode 100644 index 0000000..8bb4c75 --- /dev/null +++ b/src/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:<p>
+ *
+ * <table border=1>
+ * <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
+ * <tr><td> bad-format </td><td> the entity has sent XML that cannot be processed </td></tr>
+ * <tr><td> unsupported-encoding </td><td> the entity has sent a namespace prefix that is
+ * unsupported </td></tr>
+ * <tr><td> bad-namespace-prefix </td><td> Remote Server Timeout </td></tr>
+ * <tr><td> conflict </td><td> the server is closing the active stream for this entity
+ * because a new stream has been initiated that conflicts with the existing
+ * stream. </td></tr>
+ * <tr><td> connection-timeout </td><td> the entity has not generated any traffic over
+ * the stream for some period of time. </td></tr>
+ * <tr><td> host-gone </td><td> 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. </td></tr>
+ * <tr><td> host-unknown </td><td> 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. </td></tr>
+ * <tr><td> improper-addressing </td><td> a stanza sent between two servers lacks a 'to'
+ * or 'from' attribute </td></tr>
+ * <tr><td> internal-server-error </td><td> the server has experienced a
+ * misconfiguration. </td></tr>
+ * <tr><td> invalid-from </td><td> the JID or hostname provided in a 'from' address does
+ * not match an authorized JID. </td></tr>
+ * <tr><td> invalid-id </td><td> the stream ID or dialback ID is invalid or does not match
+ * an ID previously provided. </td></tr>
+ * <tr><td> invalid-namespace </td><td> the streams namespace name is invalid. </td></tr>
+ * <tr><td> invalid-xml </td><td> the entity has sent invalid XML over the stream. </td></tr>
+ * <tr><td> not-authorized </td><td> the entity has attempted to send data before the
+ * stream has been authenticated </td></tr>
+ * <tr><td> policy-violation </td><td> the entity has violated some local service
+ * policy. </td></tr>
+ * <tr><td> remote-connection-failed </td><td> Rthe server is unable to properly connect
+ * to a remote entity. </td></tr>
+ * <tr><td> resource-constraint </td><td> Rthe server lacks the system resources necessary
+ * to service the stream. </td></tr>
+ * <tr><td> restricted-xml </td><td> the entity has attempted to send restricted XML
+ * features. </td></tr>
+ * <tr><td> see-other-host </td><td> the server will not provide service to the initiating
+ * entity but is redirecting traffic to another host. </td></tr>
+ * <tr><td> system-shutdown </td><td> the server is being shut down and all active streams
+ * are being closed. </td></tr>
+ * <tr><td> undefined-condition </td><td> the error condition is not one of those defined
+ * by the other conditions in this list. </td></tr>
+ * <tr><td> unsupported-encoding </td><td> the initiating entity has encoded the stream in
+ * an encoding that is not supported. </td></tr>
+ * <tr><td> unsupported-stanza-type </td><td> the initiating entity has sent a first-level
+ * child of the stream that is not supported. </td></tr>
+ * <tr><td> unsupported-version </td><td> the value of the 'version' attribute provided by
+ * the initiating entity in the stream header specifies a version of XMPP that is not
+ * supported. </td></tr>
+ * <tr><td> xml-not-well-formed </td><td> the initiating entity has sent XML that is
+ * not well-formed. </td></tr>
+ * </table>
+ *
+ * @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/src/org/jivesoftware/smack/packet/XMPPError.java b/src/org/jivesoftware/smack/packet/XMPPError.java new file mode 100644 index 0000000..770a09c --- /dev/null +++ b/src/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:<p> + * + * <table border=1> + * <hr><td><b>Code</b></td><td><b>XMPP Error</b></td><td><b>Type</b></td></hr> + * <tr><td>500</td><td>interna-server-error</td><td>WAIT</td></tr> + * <tr><td>403</td><td>forbidden</td><td>AUTH</td></tr> + * <tr><td>400</td<td>bad-request</td><td>MODIFY</td>></tr> + * <tr><td>404</td><td>item-not-found</td><td>CANCEL</td></tr> + * <tr><td>409</td><td>conflict</td><td>CANCEL</td></tr> + * <tr><td>501</td><td>feature-not-implemented</td><td>CANCEL</td></tr> + * <tr><td>302</td><td>gone</td><td>MODIFY</td></tr> + * <tr><td>400</td><td>jid-malformed</td><td>MODIFY</td></tr> + * <tr><td>406</td><td>no-acceptable</td><td> MODIFY</td></tr> + * <tr><td>405</td><td>not-allowed</td><td>CANCEL</td></tr> + * <tr><td>401</td><td>not-authorized</td><td>AUTH</td></tr> + * <tr><td>402</td><td>payment-required</td><td>AUTH</td></tr> + * <tr><td>404</td><td>recipient-unavailable</td><td>WAIT</td></tr> + * <tr><td>302</td><td>redirect</td><td>MODIFY</td></tr> + * <tr><td>407</td><td>registration-required</td><td>AUTH</td></tr> + * <tr><td>404</td><td>remote-server-not-found</td><td>CANCEL</td></tr> + * <tr><td>504</td><td>remote-server-timeout</td><td>WAIT</td></tr> + * <tr><td>502</td><td>remote-server-error</td><td>CANCEL</td></tr> + * <tr><td>500</td><td>resource-constraint</td><td>WAIT</td></tr> + * <tr><td>503</td><td>service-unavailable</td><td>CANCEL</td></tr> + * <tr><td>407</td><td>subscription-required</td><td>AUTH</td></tr> + * <tr><td>500</td><td>undefined-condition</td><td>WAIT</td></tr> + * <tr><td>400</td><td>unexpected-condition</td><td>WAIT</td></tr> + * <tr><td>408</td><td>request-timeout</td><td>CANCEL</td></tr> + * </table> + * + * @author Matt Tucker + */ +public class XMPPError { + + private int code; + private Type type; + private String condition; + private String message; + private List<PacketExtension> 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<PacketExtension> 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("<error code=\"").append(code).append("\""); + if (type != null) { + buf.append(" type=\""); + buf.append(type.name()); + buf.append("\""); + } + buf.append(">"); + if (condition != null) { + buf.append("<").append(condition); + buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"); + } + if (message != null) { + buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">"); + buf.append(message); + buf.append("</text>"); + } + for (PacketExtension element : this.getExtensions()) { + buf.append(element.toXML()); + } + buf.append("</error>"); + 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<PacketExtension> 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 <tt>null</tt> 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 <tt>null</tt> 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<PacketExtension>(); + } + applicationExtensions.add(extension); + } + + /** + * Set the packet extension to the error. + * + * @param extension a packet extension. + */ + public synchronized void setExtension(List<PacketExtension> extension) { + applicationExtensions = extension; + } + + /** + * A class to represent the type of the Error. The types are: + * + * <ul> + * <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary) + * <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable) + * <li>XMPPError.Type.MODIFY - retry after changing the data sent + * <li>XMPPError.Type.AUTH - retry after providing credentials + * <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning) + * </ul> + */ + 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<Condition, ErrorSpecification> instances = errorSpecifications(); + + private ErrorSpecification(Condition condition, Type type, int code) { + this.code = code; + this.type = type; + this.condition = condition; + } + + private static Map<Condition, ErrorSpecification> errorSpecifications() { + Map<Condition, ErrorSpecification> instances = new HashMap<Condition, ErrorSpecification>(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/src/org/jivesoftware/smack/packet/package.html b/src/org/jivesoftware/smack/packet/package.html new file mode 100644 index 0000000..18a6405 --- /dev/null +++ b/src/org/jivesoftware/smack/packet/package.html @@ -0,0 +1 @@ +<body>XML packets that are part of the XMPP protocol.</body>
\ No newline at end of file |