Commit bce715db authored by Matt Tucker's avatar Matt Tucker Committed by matt

Checking in XMPP packet classes from Whack.

git-svn-id: http://svn.igniterealtime.org/svn/repos/wildfire/trunk@5942 b35dd754-fafc-0310-a699-88a17e54d16e
parent a9eaeaeb
......@@ -11,7 +11,7 @@ commons-codec.jar | 1.3
dom4j.jar | 1.6.1
hsqldb.jar | 1.8.0.5
jetty.jar | Jetty 6.1.0
jetty-util.jar | Jetty 6.1.0
jetty-util.jar | Jetty 6.1.0
jasper-compiler.jar | Jetty 6.1.0 (5.5.15)
jasper-runtime.jar | Jetty 6.1.0 (5.5.15)
jaxen.jar | 1.1 beta 4 (from DOM4J 1.6.1)
......@@ -19,7 +19,7 @@ junit.jar | 3.8.1
jdic.jar | 0.9.1 (for windows only)
jstl.jar | Jakarta standard taglib 1.1.2
jmdns.jar | 1.0 RC1
jsp-api.jar | Jetty 6.1.0 (2.0)
jsp-api.jar | Jetty 6.1.0 (2.0)
jtds.jar | 1.2
jzlib.jar | 1.0.7
mail.jar | 1.4.0 (JavaMail)
......
/**
* $RCSfile$
* $Revision: 2610 $
* $Date: 2005-04-13 10:28:51 -0700 (Wed, 13 Apr 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.component;
import org.xmpp.packet.Packet;
import org.xmpp.packet.JID;
/**
* Component enhance the functionality of an XMPP server.
*
* Components are JavaBeans and will have their properties exposed as ad-hoc commands.
*
* @author Matt Tucker
*/
public interface Component {
/**
* Returns the name of this component.
*
* @return the name of this component.
*/
public String getName();
/**
* Returns the description of this component.
*
* @return the description of this component.
*/
public String getDescription();
/**
* Processes a packet sent to this Component.
*
* @param packet the packet.
* @see ComponentManager#sendPacket(Component, Packet)
*/
public void processPacket(Packet packet);
/**
* Initializes this component with a ComponentManager and the JID
* that this component is available at (e.g. <tt>service.example.com</tt>). If a
* ComponentException is thrown then the component will not be loaded.<p>
*
* The initialization code must not rely on receiving packets from the server since
* the component has not been fully initialized yet. This means that at this point the
* component must not rely on information that is obtained from the server such us
* discovered items.
*
* @param jid the XMPP address that this component is available at.
* @param componentManager the component manager.
* @throws ComponentException if an error occured while initializing the component.
*/
public void initialize(JID jid, ComponentManager componentManager) throws ComponentException;
/**
* Notification message indicating that the component will start receiving incoming
* packets. At this time the component may finish pending initialization issues that
* require information obtained from the server.<p>
*
* It is likely that most of the component will leave this method empty.
*/
public void start();
/**
* Shuts down this component. All component resources must be released as
* part of shutdown.
*/
public void shutdown();
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 2589 $
* $Date: 2005-03-21 08:39:39 -0800 (Mon, 21 Mar 2005) $
*
* Copyright 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.xmpp.component;
import org.xmpp.packet.StreamError;
/**
* Thrown when an exception occors with a Component.
*
* @author Matt Tucker
*/
public class ComponentException extends Exception {
private StreamError streamError;
public ComponentException() {
super();
}
public ComponentException(String message) {
super(message);
}
public ComponentException(String message, Throwable cause) {
super(message, cause);
}
public ComponentException(Throwable cause) {
super(cause);
}
public ComponentException(String message, StreamError streamError) {
super(message);
this.streamError = streamError;
}
public ComponentException(StreamError streamError) {
super(streamError.getCondition().toXMPP());
this.streamError = streamError;
}
public StreamError getStreamError() {
return streamError;
}
}
/**
* $RCSfile$
* $Revision: 5861 $
* $Date: 2006-10-26 14:45:51 -0700 (Thu, 26 Oct 2006) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.component;
import org.xmpp.packet.Packet;
/**
* Manages components.
*
* @see Component
* @author Matt Tucker
*/
public interface ComponentManager {
/**
* Adds a component. The {@link Component#initialize(org.xmpp.packet.JID, ComponentManager)}
* method will be called on the component. The subdomain specifies the address of
* the component on a server. For example, if the subdomain is "test" and the XMPP
* server is at "example.com", then the component's address would be "test.example.com".
*
* @param subdomain the subdomain of the component's address.
* @param component the component.
*/
public void addComponent(String subdomain, Component component) throws ComponentException;
/**
* Removes a component. The {@link Component#shutdown} method will be called on the
* component.
*
* @param subdomain the subdomain of the component's address.
*/
public void removeComponent(String subdomain) throws ComponentException;
/**
* Sends a packet to the XMPP server. The "from" value of the packet must not be null.
* An <tt>IllegalArgumentException</tt> will be thrown when the "from" value is null.<p>
*
* Components are trusted by the server and may use any value in from address. Usually
* the from address uses the component's address as the domain but this is not required.
*
* @param component the component sending the packet.
* @param packet the packet to send.
*/
public void sendPacket(Component component, Packet packet) throws ComponentException;
/**
* Returns a property value specified by name. Properties can be used by
* components to store configuration data. It is recommended that each
* component qualify property names to prevent overlap. For example a
* component that broadcasts messages to groups of users, might prepend
* all property names it uses with "broadcast.".
*
* @param name the property name.
* @return the property value.
*/
public String getProperty(String name);
/**
* Sets a property value. Properties can be used by components to
* store configuration data. It is recommended that each component
* qualify property names to prevent overlap. For example a component
* that broadcasts messages to groups of users, might prepend all
* property names it uses with "broadcast.".
*
* @param name the property name.
* @param value the property value.
*/
public void setProperty(String name, String value);
/**
* Returns the domain of the XMPP server. The domain name may be the IP address or the host
* name.
*
* @return the domain of the XMPP server.
*/
public String getServerName();
/**
* Returns true if components managed by this component manager are external
* components connected to the server over a network connection. Otherwise,
* the components are internal to the server.
*
* @return true if the managed components are external components.
*/
public boolean isExternalMode();
/**
* Returns a Log instance, which can be used by components for logging error,
* warning, info, and debug messages.
*
* @return a Log instance.
*/
public Log getLog();
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 2579 $
* $Date: 2005-02-08 13:48:59 -0800 (Tue, 08 Feb 2005) $
*
* Copyright 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.xmpp.component;
/**
* Factory to get a ComponentManager implementation. The ComponentManager implementation
* used will determined in the following way:<ul>
*
* <li>An external process can set the ComponentManager using
* {@link #setComponentManager(ComponentManager)}.
* <li>If the component manager is <tt>null</tt>, the factory will check for
* the Java system property "whack.componentManagerClass". The value of the
* property should be the fully qualified class name of a ComponentManager
* implementation (e.g. com.foo.MyComponentManager). The class must have a default
* constructor.
* </ul>
*
* @author Matt Tucker
*/
public class ComponentManagerFactory {
private static ComponentManager componentManager;
/**
* Returns a ComponentManager instance.
*
* @return a ComponentManager instance.
*/
public static synchronized ComponentManager getComponentManager() {
if (componentManager != null) {
return componentManager;
}
// ComponentManager is null so we have to try to figure out how to load
// an instance. Look for a Java property.
String className = System.getProperty("whack.componentManagerClass");
if (className != null) {
try {
Class c = Class.forName(className);
componentManager = (ComponentManager)c.newInstance();
return componentManager;
}
catch (Exception e) {
e.printStackTrace();
}
}
// Got here, so throw exception.
throw new NullPointerException("No ComponentManager implementation available.");
}
/**
* Sets the ComponentManager instance that will be used.
*
* @param manager the ComponentManager instance.
*/
public static void setComponentManager(ComponentManager manager) {
componentManager = manager;
}
}
/**
* $RCSfile$
* $Revision: 2580 $
* $Date: 2005-02-10 12:48:23 -0800 (Thu, 10 Feb 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.component;
/**
* A simple logging service for components. Four log levels are provided:<ul>
*
* <li>Error -- an error occured in the component.
* <li>Warn -- a condition occured that an administrator should be warned about.
* <li>Info -- used to send information messages, such as a version or license notice.
* <li>Debug -- used to send debugging information. Most Log implementations will
* disable debug output by default.
* </ul>
*
* Log implementations will attempt use the native logging service of the component host
* server. However, this may not be possible in some cases -- for example, when using an
* external component that is not currently connected to the server.
*
* @author Matt Tucker
*/
public interface Log {
/**
* Logs an error.
*
* @param message the error message.
*/
public void error(String message);
/**
* Logs an error.
*
* @param message the error message.
* @param throwable the Throwable that caused the error.
*/
public void error(String message, Throwable throwable);
/**
* Logs an error.
*
* @param throwable the Throwable that caused the error.
*/
public void error(Throwable throwable);
/**
* Logs a warning.
*
* @param message the warning message.
*/
public void warn(String message);
/**
* Logs a warning.
*
* @param message the warning message.
* @param throwable the Throwable that caused the error.
*/
public void warn(String message, Throwable throwable);
/**
* Logs a warning.
*
* @param throwable the Throwable that caused the error.
*/
public void warn(Throwable throwable);
/**
* Logs an info message.
*
* @param message the info message.
*/
public void info(String message);
/**
* Logs an info message.
*
* @param message the info message.
* @param throwable the Throwable that caused the info message.
*/
public void info(String message, Throwable throwable);
/**
* Logs an info message.
*
* @param throwable the Throwable that caused the info message.
*/
public void info(Throwable throwable);
/**
* Logs a debug message.
*
* @param message the debug message.
*/
public void debug(String message);
/**
* Logs a debug message.
*
* @param message the debug message.
* @param throwable the Throwable that caused the debug message.
*/
public void debug(String message, Throwable throwable);
/**
* Logs a debug message.
*
* @param throwable the Throwable the caused the debug message.
*/
public void debug(Throwable throwable);
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3516 $
* $Date: 2006-03-03 12:04:06 -0800 (Fri, 03 Mar 2006) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.forms;
import org.dom4j.Element;
import org.dom4j.QName;
import org.jivesoftware.util.FastDateFormat;
import org.xmpp.packet.PacketExtension;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Represents a form that could be use for gathering data as well as for reporting data
* returned from a search.
* <p/>
* The form could be of the following types:
* <ul>
* <li>form -> Indicates a form to fill out.</li>
* <li>submit -> The form is filled out, and this is the data that is being returned from
* the form.</li>
* <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
* <li>result -> Data results being returned from a search, or some other query.</li>
* </ul>
* <p/>
* In case the form represents a search, the report will be structured in columns and rows. Use
* {@link #addReportedField(String,String,FormField.Type)} to set the columns of the report whilst
* the report's rows can be configured using {@link #addItemFields(Map)}.
*
* @author Gaston Dombiak
*/
public class DataForm extends PacketExtension {
private static final SimpleDateFormat UTC_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HH:mm:ss");
private static final FastDateFormat FAST_UTC_FORMAT =
FastDateFormat.getInstance("yyyyMMdd'T'HH:mm:ss", TimeZone.getTimeZone("UTC"));
/**
* Element name of the packet extension.
*/
public static final String ELEMENT_NAME = "x";
/**
* Namespace of the packet extension.
*/
public static final String NAMESPACE = "jabber:x:data";
static {
UTC_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT+0"));
// Register that DataForms uses the jabber:x:data namespace
registeredExtensions.put(QName.get(ELEMENT_NAME, NAMESPACE), DataForm.class);
}
/**
* Returns the Date obtained by parsing the specified date representation. The date
* representation is expected to be in the UTC GMT+0 format.
*
* @param date date representation in the UTC GMT+0 format.
* @return the Date obtained by parsing the specified date representation.
* @throws ParseException if an error occurs while parsing the date representation.
*/
public static Date parseDate(String date) throws ParseException {
synchronized (UTC_FORMAT) {
return UTC_FORMAT.parse(date);
}
}
/**
* Returns the String representation of an Object to be used as a field value.
*
* @param object the object to encode.
* @return the String representation of an Object to be used as a field value.
*/
static String encode(Object object) {
if (object instanceof String) {
return object.toString();
}
else if (object instanceof Boolean) {
return Boolean.TRUE.equals(object) ? "1" : "0";
}
else if (object instanceof Date) {
return FAST_UTC_FORMAT.format((Date) object);
}
return object.toString();
}
public DataForm(Type type) {
super(ELEMENT_NAME, NAMESPACE);
// Set the type of the data form
element.addAttribute("type", type.toString());
}
public DataForm(Element element) {
super(element);
}
/**
* Returns the type of this data form.
*
* @return the data form type.
* @see org.xmpp.forms.DataForm.Type
*/
public DataForm.Type getType() {
String type = element.attributeValue("type");
if (type != null) {
return DataForm.Type.valueOf(type);
}
else {
return null;
}
}
/**
* Sets the description of the data. It is similar to the title on a web page or an X window.
* You can put a <title/> on either a form to fill out, or a set of data results.
*
* @param title description of the data.
*/
public void setTitle(String title) {
// Remove an existing title element.
if (element.element("title") != null) {
element.remove(element.element("title"));
}
element.addElement("title").setText(title);
}
/**
* Returns the description of the data form. It is similar to the title on a web page or an X
* window. You can put a <title/> on either a form to fill out, or a set of data results.
*
* @return description of the data.
*/
public String getTitle() {
return element.elementTextTrim("title");
}
/**
* Returns an unmodifiable list of instructions that explain how to fill out the form and
* what the form is about. The dataform could include multiple instructions since each
* instruction could not contain newlines characters.
*
* @return an unmodifiable list of instructions that explain how to fill out the form.
*/
public List<String> getInstructions() {
List<String> answer = new ArrayList<String>();
for (Iterator it = element.elementIterator("instructions"); it.hasNext();) {
answer.add(((Element) it.next()).getTextTrim());
}
return Collections.unmodifiableList(answer);
}
/**
* Adds a new instruction to the list of instructions that explain how to fill out the form
* and what the form is about. The dataform could include multiple instructions since each
* instruction could not contain newlines characters.
*
* @param instruction the new instruction that explain how to fill out the form.
*/
public void addInstruction(String instruction) {
element.addElement("instructions").setText(instruction);
}
/**
* Clears all the stored instructions in this packet extension.
*/
public void clearInstructions() {
for (Iterator it = element.elementIterator("instructions"); it.hasNext();) {
it.next();
it.remove();
}
}
/**
* Adds a new field as part of the form.
*
* @return the newly created field.
*/
public FormField addField() {
return new FormField(element.addElement("field"));
}
/**
* Returns the fields that are part of the form.
*
* @return fields that are part of the form.
*/
public List<FormField> getFields() {
List<FormField> answer = new ArrayList<FormField>();
for (Iterator it = element.elementIterator("field"); it.hasNext();) {
answer.add(new FormField((Element) it.next()));
}
return answer;
}
/**
* Returns the field whose variable matches the specified variable.
*
* @param variable the variable name of the field to search.
* @return the field whose variable matches the specified variable
*/
public FormField getField(String variable) {
for (Iterator it = element.elementIterator("field"); it.hasNext();) {
FormField formField = new FormField((Element) it.next());
if (variable.equals(formField.getVariable())) {
return formField;
}
}
return null;
}
/**
* Removes the field whose variable matches the specified variable.
*
* @param variable the variable name of the field to remove.
* @return true if the field was removed.
*/
public boolean removeField(String variable) {
for (Iterator it = element.elementIterator("field"); it.hasNext();) {
Element field = (Element) it.next();
String fieldVariable = field.attributeValue("var");
if (variable.equals(fieldVariable)) {
return element.remove(field);
}
}
return false;
}
/**
* Adds a field to the list of fields that will be returned from a search. Each field represents
* a column in the report. The order of the columns in the report will honor the sequence in
* which they were added.
*
* @param variable variable name of the new column. This value will be used in
* {@link #addItemFields} when adding reported items.
* @param label label that corresponds to the new column. Optional parameter.
* @param type indicates the type of field of the new column. Optional parameter.
*/
public void addReportedField(String variable, String label, FormField.Type type) {
Element reported = element.element("reported");
synchronized (element) {
if (reported == null) {
reported = element.element("reported");
if (reported == null) {
reported = element.addElement("reported");
}
}
}
FormField newField = new FormField(reported.addElement("field"));
newField.setVariable(variable);
newField.setType(type);
newField.setLabel(label);
}
/**
* Adds a new row of items of reported data. For each entry in the <tt>fields</tt> parameter
* a <tt>field</tt> element will be added to the <item> element. The variable of the new
* <tt>field</tt> will be the key of the entry. The new <tt>field</tt> will have several values
* if the entry's value is a {@link Collection}. Since the value is of type {@link Object} it
* is possible to include any type of object as a value. The actual value to include in the
* data form is the result of the {@link #encode(Object)} method.
*
* @param fields list of <variable,value> to be added as a new item.
*/
public void addItemFields(Map<String,Object> fields) {
Element item = element.addElement("item");
// Add a field element to the item element for each row in fields
for (String var : fields.keySet()) {
Element field = item.addElement("field");
field.addAttribute("var", var);
Object value = fields.get(var);
if (value instanceof Collection) {
// Add a value element for each entry in the collection
for (Iterator it = ((Collection) value).iterator(); it.hasNext();) {
field.addElement("value").setText(encode(it.next()));
}
}
else {
field.addElement("value").setText(encode(value));
}
}
}
public DataForm createCopy() {
return new DataForm(this.getElement().createCopy());
}
/**
* Type-safe enumeration to represent the type of the Data forms.
*/
public enum Type {
/**
* The forms-processing entity is asking the forms-submitting entity to complete a form.
*/
form,
/**
* The forms-submitting entity is submitting data to the forms-processing entity.
*/
submit,
/**
* The forms-submitting entity has cancelled submission of data to the forms-processing
* entity.
*/
cancel,
/**
* The forms-processing entity is returning data (e.g., search results) to the
* forms-submitting entity, or the data is a generic data set.
*/
result;
}
}
/**
* $RCSfile$
* $Revision: 2626 $
* $Date: 2005-06-03 22:33:07 -0700 (Fri, 03 Jun 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.forms;
import org.dom4j.Element;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Represents a field of a form. The field could be used to represent a question to complete,
* a completed question or a data returned from a search. The exact interpretation of the field
* depends on the context where the field is used.
*
* @author Gaston Dombiak
*/
public class FormField {
private Element element;
FormField(Element element) {
this.element = element;
}
/**
* Adds a default value to the question if the question is part of a form to fill out.
* Otherwise, adds an answered value to the question.
*
* @param value a default value or an answered value of the question.
*/
public void addValue(Object value) {
element.addElement("value").setText(DataForm.encode(value));
}
/**
* Removes all the values of the field.
*/
public void clearValues() {
for (Iterator it = element.elementIterator("value"); it.hasNext();) {
it.next();
it.remove();
}
}
/**
* Adds an available option to the question that the user has in order to answer
* the question.
*
* @param label a label that represents the option.
* @param value the value of the option.
*/
public void addOption(String label, String value) {
Element option = element.addElement("option");
option.addAttribute("label", label);
option.addElement("value").setText(value);
}
/**
* Returns the available options to answer for this question. The returned options cannot
* be modified but they will be updated if the underlying DOM object gets updated.
*
* @return the available options to answer for this question.
*/
public List<Option> getOptions() {
List<Option> answer = new ArrayList<Option>();
for (Iterator it = element.elementIterator("option"); it.hasNext();) {
answer.add(new Option((Element) it.next()));
}
return answer;
}
/**
* Sets an indicative of the format for the data to answer. Valid formats are:
* <p/>
* <ul>
* <li>text-single -> single line or word of text
* <li>text-private -> instead of showing the user what they typed, you show ***** to
* protect it
* <li>text-multi -> multiple lines of text entry
* <li>list-single -> given a list of choices, pick one
* <li>list-multi -> given a list of choices, pick one or more
* <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
* <li>fixed -> fixed for putting in text to show sections, or just advertise your web
* site in the middle of the form
* <li>hidden -> is not given to the user at all, but returned with the questionnaire
* <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based
* on the rules for a JID.
* <li>jid-multi -> multiple entries for JIDs
* </ul>
*
* @param type an indicative of the format for the data to answer.
*/
public void setType(Type type) {
element.addAttribute("type", type==null?null:type.toXMPP());
}
/**
* Sets the attribute that uniquely identifies the field in the context of the form. If the
* field is of type "fixed" then the variable is optional.
*
* @param var the unique identifier of the field in the context of the form.
*/
public void setVariable(String var) {
element.addAttribute("var", var);
}
/**
* Sets the label of the question which should give enough information to the user to
* fill out the form.
*
* @param label the label of the question.
*/
public void setLabel(String label) {
element.addAttribute("label", label);
}
/**
* Sets if the question must be answered in order to complete the questionnaire.
*
* @param required if the question must be answered in order to complete the questionnaire.
*/
public void setRequired(boolean required) {
// Remove an existing desc element.
if (element.element("required") != null) {
element.remove(element.element("required"));
}
if (required) {
element.addElement("required");
}
}
/**
* Sets a description that provides extra clarification about the question. This information
* could be presented to the user either in tool-tip, help button, or as a section of text
* before the question.<p>
* <p/>
* If the question is of type FIXED then the description should remain empty.
*
* @param description provides extra clarification about the question.
*/
public void setDescription(String description) {
// Remove an existing desc element.
if (element.element("desc") != null) {
element.remove(element.element("desc"));
}
element.addElement("desc").setText(description);
}
/**
* Returns true if the question must be answered in order to complete the questionnaire.
*
* @return true if the question must be answered in order to complete the questionnaire.
*/
public boolean isRequired() {
return element.element("required") != null;
}
/**
* Returns the variable name that the question is filling out.
*
* @return the variable name of the question.
*/
public String getVariable() {
return element.attributeValue("var");
}
/**
* Returns an Iterator for the default values of the question if the question is part
* of a form to fill out. Otherwise, returns an Iterator for the answered values of
* the question.
*
* @return an Iterator for the default values or answered values of the question.
*/
public List<String> getValues() {
List<String> answer = new ArrayList<String>();
for (Iterator it = element.elementIterator("value"); it.hasNext();) {
answer.add(((Element) it.next()).getTextTrim());
}
return answer;
}
/**
* Returns an indicative of the format for the data to answer. Valid formats are:
* <p/>
* <ul>
* <li>text-single -> single line or word of text
* <li>text-private -> instead of showing the user what they typed, you show ***** to
* protect it
* <li>text-multi -> multiple lines of text entry
* <li>list-single -> given a list of choices, pick one
* <li>list-multi -> given a list of choices, pick one or more
* <li>boolean -> 0 or 1, true or false, yes or no. Default value is 0
* <li>fixed -> fixed for putting in text to show sections, or just advertise your web
* site in the middle of the form
* <li>hidden -> is not given to the user at all, but returned with the questionnaire
* <li>jid-single -> Jabber ID - choosing a JID from your roster, and entering one based
* on the rules for a JID.
* <li>jid-multi -> multiple entries for JIDs
* </ul>
*
* @return format for the data to answer.
*/
public Type getType() {
String type = element.attributeValue("type");
if (type != null) {
Type.fromXMPP(type);
}
return null;
}
/**
* Returns the label of the question which should give enough information to the user to
* fill out the form.
*
* @return label of the question.
*/
public String getLabel() {
return element.attributeValue("label");
}
/**
* Returns a description that provides extra clarification about the question. This information
* could be presented to the user either in tool-tip, help button, or as a section of text
* before the question.<p>
* <p/>
* If the question is of type FIXED then the description should remain empty.
*
* @return description that provides extra clarification about the question.
*/
public String getDescription() {
return element.elementTextTrim("desc");
}
/**
* Represents the available option of a given FormField.
*
* @author Gaston Dombiak
*/
public static class Option {
private Element element;
private Option(Element element) {
this.element = element;
}
/**
* Returns the label that represents the option.
*
* @return the label that represents the option.
*/
public String getLabel() {
return element.attributeValue("label");
}
/**
* Returns the value of the option.
*
* @return the value of the option.
*/
public String getValue() {
return element.elementTextTrim("value");
}
}
/**
* Type-safe enumeration to represent the field type of the Data forms.<p>
*
* Implementation note: XMPP error conditions use "-" characters in
* their names such as "jid-multi". Because "-" characters are not valid
* identifier parts in Java, they have been converted to "_" characters in
* the enumeration names, such as <tt>jid_multi</tt>. The {@link #toXMPP()} and
* {@link #fromXMPP(String)} methods can be used to convert between the
* enumertation values and Type code strings.
*/
public enum Type {
/**
* The field enables an entity to gather or provide an either-or choice between two
* options. The allowable values are 1 for yes/true/assent and 0 for no/false/decline.
* The default value is 0.
*/
boolean_type("boolean"),
/**
* The field is intended for data description (e.g., human-readable text such as
* "section" headers) rather than data gathering or provision. The <value/> child
* SHOULD NOT contain newlines (the \n and \r characters); instead an application
* SHOULD generate multiple fixed fields, each with one <value/> child.
*/
fixed("fixed"),
/**
* The field is not shown to the entity providing information, but instead is
* returned with the form.
*/
hidden("hidden"),
/**
* The field enables an entity to gather or provide multiple Jabber IDs.
*/
jid_multi("jid-multi"),
/**
* The field enables an entity to gather or provide multiple Jabber IDs.
*/
jid_single("jid-single"),
/**
* The field enables an entity to gather or provide one or more options from
* among many.
*/
list_multi("list-multi"),
/**
* The field enables an entity to gather or provide one option from among many.
*/
list_single("list-single"),
/**
* The field enables an entity to gather or provide multiple lines of text.
*/
text_multi("text-multi"),
/**
* The field enables an entity to gather or provide a single line or word of text,
* which shall be obscured in an interface (e.g., *****).
*/
text_private("text-private"),
/**
* The field enables an entity to gather or provide a single line or word of text,
* which may be shown in an interface. This field type is the default and MUST be
* assumed if an entity receives a field type it does not understand.
*/
text_single("text-single");
/**
* Converts a String value into its Type representation.
*
* @param type the String value.
* @return the type corresponding to the String.
*/
public static Type fromXMPP(String type) {
if (type == null) {
throw new NullPointerException();
}
type = type.toLowerCase();
if (boolean_type.toXMPP().equals(type)) {
return boolean_type;
}
else if (fixed.toXMPP().equals(type)) {
return fixed;
}
else if (hidden.toXMPP().equals(type)) {
return hidden;
}
else if (jid_multi.toXMPP().equals(type)) {
return jid_multi;
}
else if (jid_single.toXMPP().equals(type)) {
return jid_single;
}
else if (list_multi.toXMPP().equals(type)) {
return list_multi;
}
else if (list_single.toXMPP().equals(type)) {
return list_single;
}
else if (text_multi.toXMPP().equals(type)) {
return text_multi;
}
else if (text_private.toXMPP().equals(type)) {
return text_private;
}
else if (text_single.toXMPP().equals(type)) {
return text_single;
}
else {
throw new IllegalArgumentException("Type invalid:" + type);
}
}
private String value;
private Type(String value) {
this.value = value;
}
/**
* Returns the Field Type as a valid Field Type code string.
*
* @return the Field Type value.
*/
public String toXMPP() {
return value;
}
}
}
/**
* $RCSfile$
* $Revision: 2591 $
* $Date: 2005-03-27 08:24:56 -0800 (Sun, 27 Mar 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.muc;
import org.dom4j.Element;
import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
/**
* DestroyRoom is a packet that when sent will ask the server to destroy a given room. The room to
* destroy must be specified in the TO attribute of the IQ packet. The server will send a presence
* unavailable together with the alternate room and reason for the destruction to all the room
* occupants before destroying the room.<p>
*
* When destroying a room it is possible to provide an alternate room which may be replacing the
* room about to be destroyed. It is also possible to provide a reason for the room destruction.
*/
public class DestroyRoom extends IQ {
/**
* Creates a new DestroyRoom with the reason for the destruction and an alternate room JID.
*
* @param alternateJID JID of the alternate room or <tt>null</tt> if none.
* @param reason reason for the destruction or <tt>null</tt> if none.
*/
public DestroyRoom(JID alternateJID, String reason) {
super();
setType(Type.set);
Element query = setChildElement("query", "http://jabber.org/protocol/muc#owner");
Element destroy = query.addElement("destroy");
if (alternateJID != null) {
destroy.addAttribute("jid", alternateJID.toString());
}
if (reason != null) {
destroy.addElement("reason").setText(reason);
}
}
}
/**
* $RCSfile$
* $Revision: 2576 $
* $Date: 2005-02-06 12:04:40 -0800 (Sun, 06 Feb 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.muc;
import org.dom4j.Element;
import org.xmpp.packet.Message;
/**
* Represents an invitation to a Multi-User Chat room from a room occupant to a user that is not
* an occupant of the room. The invitation must be <b>sent to the room</b> and it's the room
* responsibility to forward the invitation to the invitee. The <b>sender of the invitation must be
* the real full JID of the inviter</b>.<p>
*
* Code example:
* <pre>
* // Invite the someone to the room.
* Invitation invitation = new Invitation("invitee@jabber.org", "Join this excellent room");
* invitation.setTo("room@conference.jabber.org");
* invitation.setFrom("inviter@jabber.org/notebook");
*
* component.sendPacket(invitation);
* </pre>
*
* @author Gaston Dombiak
*/
public class Invitation extends Message {
/**
* Creates a new invitation.
*
* @param invitee the XMPP address of the invitee. The room will forward the invitation to this
* address.
* @param reason the reason why the invitation is being sent.
*/
public Invitation(String invitee, String reason) {
super();
Element element = addChildElement("x", "http://jabber.org/protocol/muc#user");
Element invite = element.addElement("invite");
invite.addAttribute("to", invitee);
if (reason != null && reason.length() > 0) {
invite.addElement("reason").setText(reason);
}
}
}
/**
* $RCSfile$
* $Revision: 2576 $
* $Date: 2005-02-06 12:04:40 -0800 (Sun, 06 Feb 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.muc;
import org.xmpp.packet.Presence;
/**
* Initial presence sent when joining an existing room or creating a new room. The JoinRoom presence
* indicates the posibility of the sender to speak MUC.<p>
*
* Code example:
* <pre>
* // Join an existing room or create a new one.
* JoinRoom joinRoom = new JoinRoom("john@jabber.org/notebook", "room@conference.jabber.org/nick");
*
* component.sendPacket(joinRoom);
* </pre>
*
* @author Gaston Dombiak
*/
public class JoinRoom extends Presence {
/**
* Creates a new Presence packet that could be sent to a MUC service in order to join
* an existing MUC room or create a new one.
*
* @param from the real full JID of the user that will join or create a MUC room.
* @param to a full JID where the bare JID is the MUC room address and the resource is the
* nickname of the user joining the room.
*/
public JoinRoom(String from, String to) {
super();
setFrom(from);
setTo(to);
addChildElement("x", "http://jabber.org/protocol/muc");
}
}
/**
* $RCSfile$
* $Revision: 2576 $
* $Date: 2005-02-06 12:04:40 -0800 (Sun, 06 Feb 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.muc;
import org.xmpp.packet.Presence;
/**
* Initial presence sent when joining an existing room or creating a new room. The JoinRoom presence
* indicates the posibility of the sender to speak MUC.<p>
*
* Code example:
* <pre>
* // Join an existing room or create a new one.
* JoinRoom joinRoom = new JoinRoom("john@jabber.org/notebook", "room@conference.jabber.org/nick");
*
* component.sendPacket(joinRoom);
* </pre>
*
* @author Gaston Dombiak
*/
public class LeaveRoom extends Presence {
/**
* Creates a new Presence packet that could be sent to a MUC service in order to leave the room.
*
* @param from the full JID of the user that wants to leave the room.
* @param to the room JID. That is the room address plus the nickname of the user as a resource.
*/
public LeaveRoom(String from, String to) {
super();
setFrom(from);
setTo(to);
setType(Type.unavailable);
}
}
/**
* $RCSfile$
* $Revision: 2576 $
* $Date: 2005-02-06 12:04:40 -0800 (Sun, 06 Feb 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.muc;
import org.dom4j.Element;
import org.xmpp.packet.IQ;
import java.util.Collection;
import java.util.Map;
/**
* RoomConfiguration is a packet that helps to set the configuration of MUC rooms. RoomConfiguration
* is a speacial IQ packet whose child element contains a data form. The data form holds the fields
* to set together with a list of values.<p>
*
* Code example:
* <pre>
* // Set the fields and the values.
* Map<String,Collection<String>> fields = new HashMap<String,Collection<String>>();
* // Make a non-public room
* List<String> values = new ArrayList<String>();
* values.add("0");
* fields.put("muc#roomconfig_publicroom", values);
*
* // Create a RoomConfiguration with the fields and values
* RoomConfiguration conf = new RoomConfiguration(fields);
* conf.setTo("room@conference.jabber.org");
* conf.setFrom("john@jabber.org/notebook");
*
* component.sendPacket(conf);
* </pre>
*
* @author Gaston Dombiak
*/
public class RoomConfiguration extends IQ {
/**
* Creates a new IQ packet that contains the field and values to send for setting the room
* configuration.
*
* @param fieldValues the list of fields associated with the list of values.
*/
public RoomConfiguration(Map<String,Collection<String>> fieldValues) {
super();
setType(Type.set);
Element query = setChildElement("query", "http://jabber.org/protocol/muc#owner");
Element form = query.addElement("x", "jabber:x:data");
form.addAttribute("type", "submit");
// Add static field
Element field = form.addElement("field");
field.addAttribute("var", "FORM_TYPE");
field.addElement("value").setText("http://jabber.org/protocol/muc#roomconfig");
// Add the specified fields and their corresponding values
for (String variable : fieldValues.keySet()) {
field = form.addElement("field");
field.addAttribute("var", variable);
for (String value : fieldValues.get(variable)) {
field.addElement("value").setText(value);
}
}
}
}
package org.xmpp.packet;
import org.dom4j.Element;
import org.dom4j.QName;
import java.lang.reflect.Constructor;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
/**
* 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 extended XML
* namespace.
*/
public class IQ extends Packet {
// Sequence and random number generator used for creating unique ID's.
private static int sequence = 0;
private static Random random = new Random();
/**
* Constructs a new IQ with an automatically generated ID and a type
* of {@link Type#get IQ.Type.get}.
*/
public IQ() {
this.element = docFactory.createDocument().addElement("iq");
String id = String.valueOf(random.nextInt(1000) + "-" + sequence++);
setType(Type.get);
setID(id);
}
/**
* Constructs a new IQ using the specified type. A packet ID will
* be automatically generated.
*
* @param type the IQ type.
*/
public IQ(Type type) {
this.element = docFactory.createDocument().addElement("iq");
setType(type);
String id = String.valueOf(random.nextInt(1000) + "-" + sequence++);
setID(id);
}
/**
* Constructs a new IQ using the specified type and ID.
*
* @param ID the packet ID of the IQ.
* @param type the IQ type.
*/
public IQ(Type type, String ID) {
this.element = docFactory.createDocument().addElement("iq");
setType(type);
setID(ID);
}
/**
* Constructs a new IQ using an existing Element. This is useful
* for parsing incoming IQ Elements into IQ objects.
*
* @param element the IQ Element.
*/
public IQ(Element element) {
super(element);
}
/**
* Constructs a new IQ that is a copy of an existing IQ.
*
* @param iq the iq packet.
* @see #createCopy()
*/
private IQ(IQ iq) {
Element elementCopy = iq.element.createCopy();
docFactory.createDocument().add(elementCopy);
this.element = elementCopy;
// Copy cached JIDs (for performance reasons)
this.toJID = iq.toJID;
this.fromJID = iq.fromJID;
}
/**
* Returns the type of this IQ.
*
* @return the IQ type.
* @see Type
*/
public Type getType() {
String type = element.attributeValue("type");
if (type != null) {
return Type.valueOf(type);
}
else {
return null;
}
}
/**
* Sets the type of this IQ.
*
* @param type the IQ type.
* @see Type
*/
public void setType(Type type) {
element.addAttribute("type", type==null?null:type.toString());
}
/**
* Returns the child element of this IQ. IQ packets may have a single child
* element in an extended namespace. This is a convenience method to
* avoid manipulating the underlying packet's Element instance directly.<p>
*
* An IQ child element in extended namespaces is used to extend the features
* of XMPP. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @return the child element.
*/
public Element getChildElement() {
List elements = element.elements();
if (elements.isEmpty()) {
return null;
}
else {
// Search for a child element that is in a different namespace.
for (int i=0; i<elements.size(); i++) {
Element element = (Element)elements.get(i);
String namespace = element.getNamespaceURI();
if (!namespace.equals("") && !namespace.equals("jabber:client") &&
!namespace.equals("jabber:server"))
{
return element;
}
}
return null;
}
}
/**
* Sets the child element of this IQ. IQ packets may have a single child
* element in an extended namespace. This is a convenience method to
* avoid manipulating this underlying packet's Element instance directly.<p>
*
* A sample use of this method might look like the following:
* <pre>
* IQ iq = new IQ("time_1");
* iq.setTo("mary@example.com");
* iq.setType(IQ.Type.GET);
* iq.setChildElement(docFactory.createElement("query", "jabber:iq:time"));</pre><p>
*
* An IQ child element in extended namespaces is used to extend the features
* of XMPP. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @param childElement the child element.
*/
public void setChildElement(Element childElement) {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
element.remove((Element)i.next());
}
element.add(childElement);
}
/**
* Sets the child element of this IQ by constructing a new Element with the
* given name and namespace. The newly created child element is returned.
* IQ packets may have a single child element in an extended namespace.
* This method is a convenience method to avoid manipulating the underlying
* packet's Element instance directly.<p>
*
* In some cases, additional custom sub-elements must be added to an IQ child
* element (called packet extensions). For example, when adding a data form to
* an IQ response. See {@link #addExtension(PacketExtension)}.<p>
*
* A sample use of this method might look like the following:
* <pre>
* IQ iq = new IQ("time_1");
* iq.setTo("mary@example.com");
* iq.setType(IQ.Type.GET);
* iq.setChildElement("query", "jabber:iq:time");</pre>
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return the newly created child element.
*/
public Element setChildElement(String name, String namespace) {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
element.remove((Element)i.next());
}
return element.addElement(name, namespace);
}
/**
* Adds the element contained in the PacketExtension to the child element of the IQ
* packet. IQ packets, unlike the other packet types, have a unique child element that
* holds the packet extensions. If an extension is added to an IQ packet that does
* not have a child element then an IllegalStateException will be thrown.<p>
*
* It is important that this is the first and last time the element contained in
* PacketExtension is added to another Packet. Otherwise, a runtime error will be
* thrown when trying to add the PacketExtension's element to the Packet's element.
* Future modifications to the PacketExtension will be reflected in this Packet.<p>
*
* Note: packet extensions on IQ packets are only for use in specialized situations.
* In most cases, you should only need to set the child element of the IQ.
*
* @param extension the PacketExtension whose element will be added to this Packet's element.
*/
public void addExtension(PacketExtension extension) {
Element childElement = getChildElement();
if (childElement == null) {
throw new IllegalStateException("Cannot add packet extension when child element is null");
}
// Add the extension to the child element
childElement.add(extension.getElement());
}
/**
* Returns a {@link PacketExtension} on the first element found in this packet's
* child element for the specified <tt>name</tt> and <tt>namespace</tt> or <tt>null</tt> if
* none was found. If the IQ packet does not have a child element then <tt>null</tt>
* will be returned.<p>
*
* Note: packet extensions on IQ packets are only for use in specialized situations.
* In most cases, you should only need to set the child element of the IQ.
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return a PacketExtension on the first element found in this packet for the specified
* name and namespace or <tt>null</tt> if none was found.
*/
public PacketExtension getExtension(String name, String namespace) {
Element childElement = getChildElement();
if (childElement == null) {
return null;
}
// Search for extensions in the child element
List extensions = childElement.elements(QName.get(name, namespace));
if (!extensions.isEmpty()) {
Class extensionClass = PacketExtension.getExtensionClass(name, namespace);
if (extensionClass != null) {
try {
Constructor constructor = extensionClass.getDeclaredConstructor(new Class[]{
Element.class});
return (PacketExtension) constructor.newInstance(new Object[]{
extensions.get(0)});
}
catch (Exception e) {
}
}
}
return null;
}
/**
* Deletes the first element whose element name and namespace matches the specified
* element name and namespace in this packet's child element. If the
* IQ packet does not have a child element then this method does nothing and returns
* <tt>false</tt>.<p>
*
* Notice that this method may remove any child element that matches the specified
* element name and namespace even if that element was not added to the Packet using a
* {@link PacketExtension}.<p>
*
* Note: packet extensions on IQ packets are only for use in specialized situations.
* In most cases, you should only need to set the child element of the IQ.
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return true if a child element was removed.
*/
public boolean deleteExtension(String name, String namespace) {
Element childElement = getChildElement();
if (childElement == null) {
return false;
}
// Delete extensions in the child element
List extensions = childElement.elements(QName.get(name, namespace));
if (!extensions.isEmpty()) {
childElement.remove((Element) extensions.get(0));
return true;
}
return false;
}
/**
* Returns a deep copy of this IQ.
*
* @return a deep copy of this IQ.
*/
public IQ createCopy() {
return new IQ(this);
}
/**
* Convenience method to create a new {@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.
* </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(IQ iq) {
if (!(iq.getType() == Type.get || iq.getType() == Type.set)) {
throw new IllegalArgumentException(
"IQ must be of type 'set' or 'get'. Original IQ: " + iq.toXML());
}
IQ result = new IQ(Type.result, iq.getID());
result.setFrom(iq.getTo());
result.setTo(iq.getFrom());
return result;
}
/**
* Type-safe enumeration to represent the type of the IQ packet. The types are:
*
* <ul>
* <li>IQ.Type.get -- the IQ is a request for information or requirements.
* <li>IQ.Type.set -- the IQ provides required data, sets new values, or
* replaces existing values.
* <li>IQ.Type.result -- the IQ is a response to a successful get or set request.
* <li>IQ.Type.error -- an error has occurred regarding processing or delivery of a
* previously-sent get or set.
* </ul>
*
* If {@link #get IQ.Type.get} or {@link #set IQ.Type.set} is received the response
* must be {@link #result IQ.Type.result} or {@link #error IQ.Type.error}. The id of the
* originating {@link #get IQ.Type.get} of {@link #set IQ.Type.set} IQ must be preserved
* when sending {@link #result IQ.Type.result} or {@link #error IQ.Type.error}.
*/
public enum Type {
/**
* The IQ is a request for information or requirements.
*/
get,
/**
* The IQ provides required data, sets new values, or
* replaces existing values.
*/
set,
/**
* The IQ is a response to a successful get or set request.
*/
result,
/**
* An error has occurred regarding processing or delivery of a
* previously-sent get or set.
*/
error;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3420 $
* $Date: 2006-02-08 18:28:28 -0800 (Wed, 08 Feb 2006) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.jivesoftware.stringprep.IDNA;
import org.jivesoftware.stringprep.Stringprep;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* An XMPP address (JID). A JID is made up of a node (generally a username), a domain,
* and a resource. The node and resource are optional; domain is required. In simple
* ABNF form:
*
* <ul><tt>jid = [ node "@" ] domain [ "/" resource ]</tt></ul>
*
* Some sample JID's:
* <ul>
* <li><tt>user@example.com</tt></li>
* <li><tt>user@example.com/home</tt></li>
* <li><tt>example.com</tt></li>
* </ul>
*
* Each allowable portion of a JID (node, domain, and resource) must not be more
* than 1023 bytes in length, resulting in a maximum total size (including the '@'
* and '/' separators) of 3071 bytes.
*
* @author Matt Tucker
*/
public class JID implements Comparable, Serializable {
// Stringprep operations are very expensive. Therefore, we cache node, domain and
// resource values that have already had stringprep applied so that we can check
// incoming values against the cache.
private static Map stringprepCache = Collections.synchronizedMap(new Cache(1000));
private String node;
private String domain;
private String resource;
private String cachedFullJID;
private String cachedBareJID;
/**
* Escapes the node portion of a JID according to "JID Escaping" (JEP-0106).
* Escaping replaces characters prohibited by node-prep with escape sequences,
* as follows:<p>
*
* <table border="1">
* <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
* <tr><td>&lt;space&gt;</td><td>\20</td></tr>
* <tr><td>"</td><td>\22</td></tr>
* <tr><td>&</td><td>\26</td></tr>
* <tr><td>'</td><td>\27</td></tr>
* <tr><td>/</td><td>\2f</td></tr>
* <tr><td>:</td><td>\3a</td></tr>
* <tr><td>&lt;</td><td>\3c</td></tr>
* <tr><td>&gt;</td><td>\3e</td></tr>
* <tr><td>@</td><td>\40</td></tr>
* <tr><td>\</td><td>\5c</td></tr>
* </table><p>
*
* This process is useful when the node comes from an external source that doesn't
* conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
* the &lt;space&gt; character isn't a valid part of a node, the username should
* be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
* after case-folding, etc. has been applied).<p>
*
* All node escaping and un-escaping must be performed manually at the appropriate
* time; the JID class will not escape or un-escape automatically.
*
* @param node the node.
* @return the escaped version of the node.
*/
public static String escapeNode(String node) {
if (node == null) {
return null;
}
StringBuilder buf = new StringBuilder(node.length() + 8);
for (int i=0, n=node.length(); i<n; i++) {
char c = node.charAt(i);
switch (c) {
case '"': buf.append("\\22"); break;
case '&': buf.append("\\26"); break;
case '\'': buf.append("\\27"); break;
case '/': buf.append("\\2f"); break;
case ':': buf.append("\\3a"); break;
case '<': buf.append("\\3c"); break;
case '>': buf.append("\\3e"); break;
case '@': buf.append("\\40"); break;
case '\\': buf.append("\\5c"); break;
default: {
if (Character.isWhitespace(c)) {
buf.append("\\20");
}
else {
buf.append(c);
}
}
}
}
return buf.toString();
}
/**
* Un-escapes the node portion of a JID according to "JID Escaping" (JEP-0106).<p>
* Escaping replaces characters prohibited by node-prep with escape sequences,
* as follows:<p>
*
* <table border="1">
* <tr><td><b>Unescaped Character</b></td><td><b>Encoded Sequence</b></td></tr>
* <tr><td>&lt;space&gt;</td><td>\20</td></tr>
* <tr><td>"</td><td>\22</td></tr>
* <tr><td>&</td><td>\26</td></tr>
* <tr><td>'</td><td>\27</td></tr>
* <tr><td>/</td><td>\2f</td></tr>
* <tr><td>:</td><td>\3a</td></tr>
* <tr><td>&lt;</td><td>\3c</td></tr>
* <tr><td>&gt;</td><td>\3e</td></tr>
* <tr><td>@</td><td>\40</td></tr>
* <tr><td>\</td><td>\5c</td></tr>
* </table><p>
*
* This process is useful when the node comes from an external source that doesn't
* conform to nodeprep. For example, a username in LDAP may be "Joe Smith". Because
* the &lt;space&gt; character isn't a valid part of a node, the username should
* be escaped to "Joe\20Smith" before being made into a JID (e.g. "joe\20smith@example.com"
* after case-folding, etc. has been applied).<p>
*
* All node escaping and un-escaping must be performed manually at the appropriate
* time; the JID class will not escape or un-escape automatically.
*
* @param node the escaped version of the node.
* @return the un-escaped version of the node.
*/
public static String unescapeNode(String node) {
if (node == null) {
return null;
}
char [] nodeChars = node.toCharArray();
StringBuilder buf = new StringBuilder(nodeChars.length);
for (int i=0, n=nodeChars.length; i<n; i++) {
compare: {
char c = node.charAt(i);
if (c == '\\' && i+2<n) {
char c2 = nodeChars[i+1];
char c3 = nodeChars[i+2];
if (c2 == '2') {
switch (c3) {
case '0': buf.append(' '); i+=2; break compare;
case '2': buf.append('"'); i+=2; break compare;
case '6': buf.append('&'); i+=2; break compare;
case '7': buf.append('\''); i+=2; break compare;
case 'f': buf.append('/'); i+=2; break compare;
}
}
else if (c2 == '3') {
switch (c3) {
case 'a': buf.append(':'); i+=2; break compare;
case 'c': buf.append('<'); i+=2; break compare;
case 'e': buf.append('>'); i+=2; break compare;
}
}
else if (c2 == '4') {
if (c3 == '0') {
buf.append("@");
i+=2;
break compare;
}
}
else if (c2 == '5') {
if (c3 == 'c') {
buf.append("\\");
i+=2;
break compare;
}
}
}
buf.append(c);
}
}
return buf.toString();
}
/**
* Constructs a JID from it's String representation.
*
* @param jid a valid JID.
* @throws IllegalArgumentException if the JID is not valid.
*/
public JID(String jid) {
if (jid == null) {
throw new NullPointerException("JID cannot be null");
}
String node = null;
String domain = null;
String resource = null;
int atIndex = jid.indexOf("@");
int slashIndex = jid.indexOf("/");
// Node
if (atIndex > 0) {
node = jid.substring(0, atIndex);
}
// Domain
if (atIndex + 1 > jid.length()) {
throw new IllegalArgumentException("JID with empty domain not valid");
}
if (atIndex < 0) {
if (slashIndex > 0) {
domain = jid.substring(0, slashIndex);
}
else {
domain = jid;
}
}
else {
if (slashIndex > 0) {
domain = jid.substring(atIndex + 1, slashIndex);
}
else {
domain = jid.substring(atIndex + 1);
}
}
// Resource
if (slashIndex + 1 > jid.length() || slashIndex < 0) {
resource = null;
}
else {
resource = jid.substring(slashIndex + 1);
}
init(node, domain,resource);
}
/**
* Constructs a JID given a node, domain, and resource.
*
* @param node the node.
* @param domain the domain, which must not be <tt>null</tt>.
* @param resource the resource.
* @throws IllegalArgumentException if the JID is not valid.
*/
public JID(String node, String domain, String resource) {
if (domain == null) {
throw new NullPointerException("Domain cannot be null");
}
init(node, domain, resource);
}
/**
* Constructs a new JID, bypassing all stringprep profiles. This
* is useful for constructing a JID object when it's already known
* that the String representation is well-formed.
*
* @param jid the JID.
* @param fake an extra param to create a different method signature.
* The value <tt>null</tt> should be passed in as this argument.
*/
JID(String jid, Object fake) {
fake = null; // Workaround IDE warnings for unused param.
if (jid == null) {
throw new NullPointerException("JID cannot be null");
}
int atIndex = jid.indexOf("@");
int slashIndex = jid.indexOf("/");
// Node
if (atIndex > 0) {
node = jid.substring(0, atIndex);
}
// Domain
if (atIndex + 1 > jid.length()) {
throw new IllegalArgumentException("JID with empty domain not valid");
}
if (atIndex < 0) {
if (slashIndex > 0) {
domain = jid.substring(0, slashIndex);
}
else {
domain = jid;
}
}
else {
if (slashIndex > 0) {
domain = jid.substring(atIndex + 1, slashIndex);
}
else {
domain = jid.substring(atIndex + 1);
}
}
// Resource
if (slashIndex + 1 > jid.length() || slashIndex < 0) {
resource = null;
}
else {
resource = jid.substring(slashIndex + 1);
}
// Cache the bare and full JID String representation
updateCache();
}
/**
* Transforms the JID parts using the appropriate Stringprep profiles, then
* validates them. If they are fully valid, the field values are saved, otherwise
* an IllegalArgumentException is thrown.
*
* @param node the node.
* @param domain the domain.
* @param resource the resource.
*/
private void init(String node, String domain, String resource) {
// Set node and resource to null if they are the empty string.
if (node != null && node.equals("")) {
node = null;
}
if (resource != null && resource.equals("")) {
resource = null;
}
// Stringprep (node prep, resourceprep, etc).
try {
if (!stringprepCache.containsKey(node)) {
this.node = Stringprep.nodeprep(node);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (node != null && node.length()*2 > 1023) {
throw new IllegalArgumentException("Node cannot be larger than 1023 bytes. " +
"Size is " + (node.length() * 2) + " bytes.");
}
stringprepCache.put(this.node, null);
}
else {
this.node = node;
}
// XMPP specifies that domains should be run through IDNA and
// that they should be run through nameprep before doing any
// comparisons. We always run the domain through nameprep to
// make comparisons easier later.
if (!stringprepCache.containsKey(domain)) {
this.domain = Stringprep.nameprep(IDNA.toASCII(domain), false);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (domain.length()*2 > 1023) {
throw new IllegalArgumentException("Domain cannot be larger than 1023 bytes. " +
"Size is " + (domain.length() * 2) + " bytes.");
}
stringprepCache.put(this.domain, null);
}
else {
this.domain = domain;
}
if (!stringprepCache.containsKey(resource)) {
this.resource = Stringprep.resourceprep(resource);
// Validate field is not greater than 1023 bytes. UTF-8 characters use two bytes.
if (resource != null && resource.length()*2 > 1023) {
throw new IllegalArgumentException("Resource cannot be larger than 1023 bytes. " +
"Size is " + (resource.length() * 2) + " bytes.");
}
stringprepCache.put(this.resource, null);
}
else {
this.resource = resource;
}
// Cache the bare and full JID String representation
updateCache();
}
catch (Exception e) {
StringBuilder buf = new StringBuilder();
if (node != null) {
buf.append(node).append("@");
}
buf.append(domain);
if (resource != null) {
buf.append("/").append(resource);
}
throw new IllegalArgumentException("Illegal JID: " + buf.toString(), e);
}
}
private void updateCache() {
// Cache the bare JID
StringBuilder buf = new StringBuilder(40);
if (node != null) {
buf.append(node).append("@");
}
buf.append(domain);
cachedBareJID = buf.toString();
// Cache the full JID
if (resource != null) {
buf.append("/").append(resource);
cachedFullJID = buf.toString();
}
else {
cachedFullJID = cachedBareJID;
}
}
/**
* Returns the node, or <tt>null</tt> if this JID does not contain node information.
*
* @return the node.
*/
public String getNode() {
return node;
}
/**
* Returns the domain.
*
* @return the domain.
*/
public String getDomain() {
return domain;
}
/**
* Returns the resource, or <tt>null</tt> if this JID does not contain resource information.
*
* @return the resource.
*/
public String getResource() {
return resource;
}
/**
* Returns the String representation of the bare JID, which is the JID with
* resource information removed.
*
* @return the bare JID.
*/
public String toBareJID() {
return cachedBareJID;
}
/**
* Returns a String representation of the JID.
*
* @return a String representation of the JID.
*/
public String toString() {
return cachedFullJID;
}
public int hashCode() {
return toString().hashCode();
}
public boolean equals(Object object) {
if (!(object instanceof JID)) {
return false;
}
if (this == object) {
return true;
}
JID jid = (JID)object;
// Node. If node isn't null, compare.
if (node != null) {
if (!node.equals(jid.node)) {
return false;
}
}
// Otherwise, jid.node must be null.
else if (jid.node != null) {
return false;
}
// Compare domain, which must be null.
if (!domain.equals(jid.domain)) {
return false;
}
// Resource. If resource isn't null, compare.
if (resource != null) {
if (!resource.equals(jid.resource)) {
return false;
}
}
// Otherwise, jid.resource must be null.
else if (jid.resource != null) {
return false;
}
// Passed all checks, so equal.
return true;
}
public int compareTo(Object o) {
if (!(o instanceof JID)) {
throw new ClassCastException("Ojbect not instanceof JID: " + o);
}
JID jid = (JID)o;
// Comparison order is domain, node, resource.
int compare = domain.compareTo(jid.domain);
if (compare == 0 && node != null && jid.node != null) {
compare = node.compareTo(jid.node);
}
if (compare == 0 && resource != null && jid.resource != null) {
compare = resource.compareTo(jid.resource);
}
return compare;
}
/**
* Returns true if two JID's are equivalent. The JID components are compared using
* the following rules:<ul>
* <li>Nodes are normalized using nodeprep (case insensitive).
* <li>Domains are normalized using IDNA and then nameprep (case insensitive).
* <li>Resources are normalized using resourceprep (case sensitive).</ul>
*
* These normalization rules ensure, for example, that
* <tt>User@EXAMPLE.com/home</tt> is considered equal to <tt>user@example.com/home</tt>.
*
* @param jid1 a JID.
* @param jid2 a JID.
* @return true if the JIDs are equivalent; false otherwise.
* @throws IllegalArgumentException if either JID is not valid.
*/
public static boolean equals(String jid1, String jid2) {
return new JID(jid1).equals(new JID(jid2));
}
/**
* A simple cache class that extends LinkedHashMap. It uses an LRU policy to
* keep the cache at a maximum size.
*/
private static class Cache extends LinkedHashMap {
private int maxSize;
public Cache(int maxSize) {
super(64, .75f, true);
this.maxSize = maxSize;
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > maxSize;
}
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3160 $
* $Date: 2005-12-04 17:58:57 -0800 (Sun, 04 Dec 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.Element;
import java.util.Iterator;
/**
* Message packet.<p>
*
* A message can have one of several {@link Type Types}. For each message type,
* different message fields are typically used as follows:
*
* <p>
* <table border="1">
* <tr><td>&nbsp;</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>Error</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>
*/
public class Message extends Packet {
/**
* Constructs a new Message.
*/
public Message() {
this.element = docFactory.createDocument().addElement("message");
}
/**
* Constructs a new Message using an existing Element. This is useful
* for parsing incoming message Elements into Message objects.
*
* @param element the message Element.
*/
public Message(Element element) {
super(element);
}
/**
* Constructs a new Message that is a copy of an existing Message.
*
* @param message the message packet.
* @see #createCopy()
*/
private Message(Message message) {
Element elementCopy = message.element.createCopy();
docFactory.createDocument().add(elementCopy);
this.element = elementCopy;
// Copy cached JIDs (for performance reasons)
this.toJID = message.toJID;
this.fromJID = message.fromJID;
}
/**
* Returns the type of this message
*
* @return the message type.
* @see Type
*/
public Type getType() {
String type = element.attributeValue("type");
if (type != null) {
return Type.valueOf(type);
}
else {
return Type.normal;
}
}
/**
* Sets the type of this message.
*
* @param type the message type.
* @see Type
*/
public void setType(Type type) {
element.addAttribute("type", type==null?null:type.toString());
}
/**
* Returns the subject of this message or <tt>null</tt> if there is no subject..
*
* @return the subject.
*/
public String getSubject() {
return element.elementText("subject");
}
/**
* Sets the subject of this message.
*
* @param subject the subject.
*/
public void setSubject(String subject) {
Element subjectElement = element.element("subject");
// If subject is null, clear the subject.
if (subject == null && subjectElement != null) {
element.remove(subjectElement);
return;
}
// Do nothing if the new subject is null
if (subject == null) {
return;
}
if (subjectElement == null) {
subjectElement = element.addElement("subject");
}
subjectElement.setText(subject);
}
/**
* Returns the body of this message or <tt>null</tt> if there is no body.
*
* @return the body.
*/
public String getBody() {
return element.elementText("body");
}
/**
* Sets the body of this message.
*
* @param body the body.
*/
public void setBody(String body) {
Element bodyElement = element.element("body");
// If body is null, clear the body.
if (body == null) {
if (bodyElement != null) {
element.remove(bodyElement);
}
return;
}
// Do nothing if the new body is null
if (body == null) {
return;
}
if (bodyElement == null) {
bodyElement = element.addElement("body");
}
bodyElement.setText(body);
}
/**
* Returns the thread value of this message, an identifier that is used for
* tracking a conversation thread ("instant messaging session")
* between two entities. If the thread is not set, <tt>null</tt> will be
* returned.
*
* @return the thread value.
*/
public String getThread() {
return element.elementText("thread");
}
/**
* Sets the thread value of this message, an identifier that is used for
* tracking a conversation thread ("instant messaging session")
* between two entities.
*
* @param thread thread value.
*/
public void setThread(String thread) {
Element threadElement = element.element("thread");
// If thread is null, clear the thread.
if (thread == null) {
if (threadElement != null) {
element.remove(threadElement);
}
return;
}
if (threadElement == null) {
threadElement = element.addElement("thread");
}
threadElement.setText(thread);
}
/**
* Returns the first child element of this packet that matches the
* given name and namespace. If no matching element is found,
* <tt>null</tt> will be returned. This is a convenience method to avoid
* manipulating this underlying packet's Element instance directly.<p>
*
* Child elements in extended namespaces are used to extend the features
* of XMPP. Examples include a "user is typing" indicator and invitations to
* group chat rooms. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @param name the element name.
* @param namespace the element namespace.
* @return the first matching child element, or <tt>null</tt> if there
* is no matching child element.
*/
public Element getChildElement(String name, String namespace) {
for (Iterator i=element.elementIterator(name); i.hasNext(); ) {
Element element = (Element)i.next();
if (element.getNamespaceURI().equals(namespace)) {
return element;
}
}
return null;
}
/**
* Adds a new child element to this packet with the given name and
* namespace. The newly created Element is returned. This is a
* convenience method to avoid manipulating this underlying packet's
* Element instance directly.<p>
*
* Child elements in extended namespaces are used to extend the features
* of XMPP. Examples include a "user is typing" indicator and invitations to
* group chat rooms. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @param name the element name.
* @param namespace the element namespace.
* @return the newly created child element.
*/
public Element addChildElement(String name, String namespace) {
return element.addElement(name, namespace);
}
/**
* Returns a deep copy of this Message.
*
* @return a deep copy of this Message.
*/
public Message createCopy() {
return new Message(this);
}
/**
* Type-safe enumeration for the type of a message. The types are:
*
* <ul>
* <li>{@link #normal Message.Type.normal} -- (Default) a normal text message
* used in email like interface.
* <li>{@link #chat Message.Type.cha}t -- a typically short text message used
* in line-by-line chat interfaces.
* <li>{@link #groupchat Message.Type.groupchat} -- a chat message sent to a
* groupchat server for group chats.
* <li>{@link #headline Message.Type.headline} -- a text message to be displayed
* in scrolling marquee displays.
* <li>{@link #error Message.Type.error} -- indicates a messaging error.
* </ul>
*/
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;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3160 $
* $Date: 2005-12-04 17:58:57 -0800 (Sun, 04 Dec 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.util.List;
/**
* An XMPP packet (also referred to as a stanza). Each packet is backed by a
* DOM4J Element. A set of convenience methods allows easy manipulation of
* the Element, or the Element can be accessed directly and manipulated.<p>
*
* There are three core packet types:<ul>
* <li>{@link Message} -- used to send data between users.
* <li>{@link Presence} -- contains user presence information or is used
* to manage presence subscriptions.
* <li>{@link IQ} -- exchange information and perform queries using a
* request/response protocol.
* </ul>
*
* @author Matt Tucker
*/
public abstract class Packet {
protected static DocumentFactory docFactory = DocumentFactory.getInstance();
protected Element element;
// Cache to and from JIDs
protected JID toJID;
protected JID fromJID;
/**
* Constructs a new Packet.
*
* @param element the XML Element that contains the packet contents.
*/
public Packet(Element element) {
this.element = element;
// Apply stringprep profiles to the "to" and "from" values.
String to = element.attributeValue("to");
if (to != null) {
toJID = new JID(to);
element.addAttribute("to", toJID.toString());
}
String from = element.attributeValue("from");
if (from != null) {
fromJID = new JID(from);
element.addAttribute("from", fromJID.toString());
}
}
/**
* Constructs a new Packet with no element data. This method is used by
* extensions of this class that require a more optimized path for creating
* new packets.
*/
protected Packet() {
}
/**
* Returns the packet ID, or <tt>null</tt> if the packet does not have an ID.
* Packet ID's are optional, except for IQ packets.
*
* @return the packet ID.
*/
public String getID() {
return element.attributeValue("id");
}
/**
* Sets the packet ID. Packet ID's are optional, except for IQ packets.
*
* @param ID the packet ID.
*/
public void setID(String ID) {
element.addAttribute("id", ID);
}
/**
* Returns the XMPP address (JID) that the packet is addressed to, or <tt>null</tt>
* if the "to" attribute is not set. The XMPP protocol often makes the "to"
* attribute optional, so it does not always need to be set.
*
* @return the XMPP address (JID) that the packet is addressed to, or <tt>null</tt>
* if not set.
*/
public JID getTo() {
String to = element.attributeValue("to");
if (to == null) {
return null;
}
else {
if (toJID != null && to.equals(toJID.toString())) {
return toJID;
}
else {
// Return a new JID that bypasses stringprep profile checking.
// This improves speed and is safe as long as the user doesn't
// directly manipulate the attributes of the underlying Element
// that represent JID's.
toJID = new JID(to, null);
return toJID;
}
}
}
/**
* Sets the XMPP address (JID) that the packet is addressed to. The XMPP protocol
* often makes the "to" attribute optional, so it does not always need to be set.
*
* @param to the XMPP address (JID) that the packet is addressed to.
*/
public void setTo(String to) {
// Apply stringprep profiles to value.
if (to != null) {
toJID = new JID(to);
to = toJID.toString();
}
element.addAttribute("to", to);
}
/**
* Sets the XMPP address (JID) that the packet is address to. The XMPP protocol
* often makes the "to" attribute optional, so it does not always need to be set.
*
* @param to the XMPP address (JID) that the packet is addressed to.
*/
public void setTo(JID to) {
toJID = to;
if (to == null) {
element.addAttribute("to", null);
}
else {
element.addAttribute("to", to.toString());
}
}
/**
* Returns the XMPP address (JID) that the packet is from, or <tt>null</tt>
* if the "from" attribute is not set. The XMPP protocol often makes the "from"
* attribute optional, so it does not always need to be set.
*
* @return the XMPP address that the packet is from, or <tt>null</tt>
* if not set.
*/
public JID getFrom() {
String from = element.attributeValue("from");
if (from == null) {
return null;
}
else {
if (fromJID != null && from.equals(fromJID.toString())) {
return fromJID;
}
else {
// Return a new JID that bypasses stringprep profile checking.
// This improves speed and is safe as long as the user doesn't
// directly manipulate the attributes of the underlying Element
// that represent JID's.
fromJID = new JID(from, null);
return fromJID;
}
}
}
/**
* Sets the XMPP address (JID) that the packet comes from. The XMPP protocol
* often makes the "from" attribute optional, so it does not always need to be set.
*
* @param from the XMPP address (JID) that the packet comes from.
*/
public void setFrom(String from) {
// Apply stringprep profiles to value.
if (from != null) {
fromJID = new JID(from);
from = fromJID.toString();
}
element.addAttribute("from", from);
}
/**
* Sets the XMPP address (JID) that the packet comes from. The XMPP protocol
* often makes the "from" attribute optional, so it does not always need to be set.
*
* @param from the XMPP address (JID) that the packet comes from.
*/
public void setFrom(JID from) {
fromJID = from;
if (from == null) {
element.addAttribute("from", null);
}
else {
element.addAttribute("from", from.toString());
}
}
/**
* Adds the element contained in the PacketExtension to the element of this packet.
* It is important that this is the first and last time the element contained in
* PacketExtension is added to another Packet. Otherwise, a runtime error will be
* thrown when trying to add the PacketExtension's element to the Packet's element.
* Future modifications to the PacketExtension will be reflected in this Packet.
*
* @param extension the PacketExtension whose element will be added to this Packet's element.
*/
public void addExtension(PacketExtension extension) {
element.add(extension.getElement());
}
/**
* Returns a {@link PacketExtension} on the first element found in this packet
* for the specified <tt>name</tt> and <tt>namespace</tt> or <tt>null</tt> if
* none was found.
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return a PacketExtension on the first element found in this packet for the specified
* name and namespace or null if none was found.
*/
public PacketExtension getExtension(String name, String namespace) {
List extensions = element.elements(QName.get(name, namespace));
if (!extensions.isEmpty()) {
Class extensionClass = PacketExtension.getExtensionClass(name, namespace);
if (extensionClass != null) {
try {
Constructor constructor = extensionClass.getDeclaredConstructor(new Class[]{
Element.class});
return (PacketExtension) constructor.newInstance(new Object[]{
extensions.get(0)});
}
catch (Exception e) {
// Ignore.
}
}
}
return null;
}
/**
* Deletes the first element whose element name and namespace matches the specified
* element name and namespace.<p>
*
* Notice that this method may remove any child element that matches the specified
* element name and namespace even if that element was not added to the Packet using a
* {@link PacketExtension}.
*
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return true if a child element was removed.
*/
public boolean deleteExtension(String name, String namespace) {
List extensions = element.elements(QName.get(name, namespace));
if (!extensions.isEmpty()) {
element.remove((Element) extensions.get(0));
return true;
}
return false;
}
/**
* Returns the packet error, or <tt>null</tt> if there is no packet error.
*
* @return the packet error.
*/
public PacketError getError() {
Element error = element.element("error");
if (error != null) {
return new PacketError(error);
}
return null;
}
/**
* Sets the packet error. Calling this method will automatically set
* the packet "type" attribute to "error".
*
* @param error the packet error.
*/
public void setError(PacketError error) {
if (element == null) {
throw new NullPointerException("Error cannot be null");
}
// Force the packet type to "error".
element.addAttribute("type", "error");
// Remove an existing error packet.
if (element.element("error") != null) {
element.remove(element.element("error"));
}
// Add the error element.
element.add(error.getElement());
}
/**
* Sets the packet error using the specified condition. Calling this
* method will automatically set the packet "type" attribute to "error".
* This is a convenience method equivalent to calling:
*
* <tt>setError(new PacketError(condition));</tt>
*
* @param condition the error condition.
*/
public void setError(PacketError.Condition condition) {
setError(new PacketError(condition));
}
/**
* Creates a deep copy of this packet.
*
* @return a deep copy of this packet.
*/
public abstract Packet createCopy();
/**
* Returns the DOM4J Element that backs the packet. The element is the definitive
* representation of the packet and can be manipulated directly to change
* packet contents.
*
* @return the DOM4J Element that represents the packet.
*/
public Element getElement() {
return element;
}
/**
* Returns the textual XML representation of this packet.
*
* @return the textual XML representation of this packet.
*/
public String toXML() {
return element.asXML();
}
public String toString() {
StringWriter out = new StringWriter();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
try {
writer.write(element);
}
catch (Exception e) {
// Ignore.
}
return out.toString();
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 3513 $
* $Date: 2006-03-02 12:06:44 -0800 (Thu, 02 Mar 2006) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import java.io.StringWriter;
import java.util.Iterator;
/**
* A packet error. Errors must have a type and condition. Optionally, they
* can include explanation text.
*
* @author Matt Tucker
*/
public class PacketError {
private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-stanzas";
private static DocumentFactory docFactory = DocumentFactory.getInstance();
private Element element;
/**
* Construcs a new PacketError with the specified condition. The error
* type will be set to the default for the specified condition.
*
* @param condition the error condition.
*/
public PacketError(Condition condition) {
this.element = docFactory.createElement("error");
setCondition(condition);
setType(condition.getDefaultType());
}
/**
* Constructs a new PacketError with the specified condition and type.
*
* @param condition the error condition.
* @param type the error type.
*/
public PacketError(Condition condition, Type type) {
this.element = docFactory.createElement("error");
setCondition(condition);
setType(type);
}
/**
* Constructs a new PacketError.
*
* @param type the error type.
* @param condition the error condition.
* @param text the text description of the error.
*/
public PacketError(Condition condition, Type type, String text) {
this.element = docFactory.createElement("error");
setType(type);
setCondition(condition);
setText(text, null);
}
/**
* Constructs a new PacketError.
*
* @param type the error type.
* @param condition the error condition.
* @param text the text description of the error.
* @param lang the language code of the error description (e.g. "en").
*/
public PacketError(Condition condition, Type type, String text, String lang) {
this.element = docFactory.createElement("error");
setType(type);
setCondition(condition);
setText(text, lang);
}
/**
* Constructs a new PacketError using an existing Element. This is useful
* for parsing incoming error Elements into PacketError objects.
*
* @param element the error Element.
*/
public PacketError(Element element) {
this.element = element;
}
/**
* Returns the error type.
*
* @return the error type.
* @see Type
*/
public Type getType() {
String type = element.attributeValue("type");
if (type != null) {
return Type.fromXMPP(type);
}
else {
return null;
}
}
/**
* Sets the error type.
*
* @param type the error type.
* @see Type
*/
public void setType(Type type) {
element.addAttribute("type", type==null?null:type.toXMPP());
}
/**
* Returns the error condition.
*
* @return the error condition.
* @see Condition
*/
public Condition getCondition() {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = (Element)i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
return Condition.fromXMPP(el.getName());
}
}
// Looking for XMPP condition failed. See if a legacy error code exists,
// which can be mapped into an XMPP error condition.
String code = element.attributeValue("code");
if (code != null) {
try {
return Condition.fromLegacyCode(Integer.parseInt(code));
}
catch (Exception e) {
// Ignore -- unable to map legacy code into a valid condition
// so return null.
}
}
return null;
}
/**
* Sets the error condition.
*
* @param condition the error condition.
* @see Condition
*/
public void setCondition(Condition condition) {
if (condition == null) {
throw new NullPointerException("Condition cannot be null");
}
// Set the error code for legacy support.
element.addAttribute("code", Integer.toString(condition.getLegacyCode()));
Element conditionElement = null;
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = (Element)i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
conditionElement = el;
}
}
if (conditionElement != null) {
element.remove(conditionElement);
}
conditionElement = docFactory.createElement(condition.toXMPP(),
ERROR_NAMESPACE);
element.add(conditionElement);
}
/**
* Returns a text description of the error, or <tt>null</tt> if there
* is no text description.
*
* @return the text description of the error.
*/
public String getText() {
return element.elementText("text");
}
/**
* Sets the text description of the error.
*
* @param text the text description of the error.
*/
public void setText(String text) {
setText(text, null);
}
/**
* Sets the text description of the error. Optionally, a language code
* can be specified to indicate the language of the description.
*
* @param text the text description of the error.
* @param lang the language code of the description, or <tt>null</tt> to specify
* no language code.
*/
public void setText(String text, String lang) {
Element textElement = element.element("text");
// If text is null, clear the text.
if (text == null) {
if (textElement != null) {
element.remove(textElement);
}
return;
}
if (textElement == null) {
textElement = docFactory.createElement("text", ERROR_NAMESPACE);
if (lang != null) {
textElement.addAttribute(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"), lang);
}
element.add(textElement);
}
textElement.setText(text);
}
/**
* Returns the text description's language code, or <tt>null</tt> if there
* is no language code associated with the description text.
*
* @return the language code of the text description, if it exists.
*/
public String getTextLang() {
Element textElement = element.element("text");
if (textElement != null) {
return textElement.attributeValue(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"));
}
return null;
}
/**
* Returns the DOM4J Element that backs the error. The element is the definitive
* representation of the error and can be manipulated directly to change
* error contents.
*
* @return the DOM4J Element.
*/
public Element getElement() {
return element;
}
/**
* Returns the textual XML representation of this error.
*
* @return the textual XML representation of this error.
*/
public String toXML() {
return element.asXML();
}
public String toString() {
StringWriter out = new StringWriter();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
try {
writer.write(element);
}
catch (Exception e) {
// Ignore.
}
return out.toString();
}
/**
* Type-safe enumeration for the error condition.<p>
*
* Implementation note: XMPP error conditions use "-" characters in
* their names such as "bad-request". Because "-" characters are not valid
* identifier parts in Java, they have been converted to "_" characters in
* the enumeration names, such as <tt>bad_request</tt>. The {@link #toXMPP()} and
* {@link #fromXMPP(String)} methods can be used to convert between the
* enumertation values and XMPP error code strings.
*/
public enum Condition {
/**
* The sender has sent XML that is malformed or that cannot be processed
* (e.g., an IQ stanza that includes an unrecognized value of the 'type'
* attribute); the associated error type SHOULD be "modify".
*/
bad_request("bad-request", Type.modify, 400),
/**
* Access cannot be granted because an existing resource or session
* exists with the same name or address; the associated error type
* SHOULD be "cancel".
*/
conflict("conflict", Type.cancel, 409),
/**
* The feature requested is not implemented by the recipient or
* server and therefore cannot be processed; the associated error
* type SHOULD be "cancel".
*/
feature_not_implemented("feature-not-implemented", Type.cancel, 501),
/**
* The requesting entity does not possess the required permissions to
* perform the action; the associated error type SHOULD be "auth".
*/
forbidden("forbidden", Type.auth, 403),
/**
* The recipient or server can no longer be contacted at this address
* (the error stanza MAY contain a new address in the XML character
* data of the <gone/> element); the associated error type SHOULD be
* "modify".
*/
gone("gone", Type.modify, 302),
/**
* The server could not process the stanza because of a misconfiguration
* or an otherwise-undefined internal server error; the associated error
* type SHOULD be "wait".
*/
internal_server_error("internal-server-error", Type.wait, 500),
/**
* The addressed JID or item requested cannot be found; the associated
* error type SHOULD be "cancel".
*/
item_not_found("item-not-found", Type.cancel, 404),
/**
* The sending entity has provided or communicated an XMPP address
* (e.g., a value of the 'to' attribute) or aspect thereof (e.g.,
* a resource identifier) that does not adhere to the syntax defined
* in Addressing Scheme (Section 3); the associated error type SHOULD
* be "modify".
*/
jid_malformed("jid-malformed", Type.modify, 400),
/**
* The recipient or server understands the request but is refusing
* to process it because it does not meet criteria defined by the
* recipient or server (e.g., a local policy regarding acceptable
* words in messages); the associated error type SHOULD be "modify".
*/
not_acceptable("not-acceptable", Type.modify, 406),
/**
* The recipient or server does not allow any entity to perform
* the action; the associated error type SHOULD be "cancel".
*/
not_allowed("not-allowed", Type.cancel, 405),
/**
* The sender must provide proper credentials before being allowed
* to perform the action, or has provided improper credentials;
* the associated error type SHOULD be "auth".
*/
not_authorized("not-authorized", Type.auth, 401),
/**
* The requesting entity is not authorized to access the requested
* service because payment is required; the associated error type
* SHOULD be "auth".
*/
payment_required("payment-required", Type.auth, 402),
/**
* The intended recipient is temporarily unavailable; the associated
* error type SHOULD be "wait" (note: an application MUST NOT return
* this error if doing so would provide information about the intended
* recipient's network availability to an entity that is not authorized
* to know such information).
*/
recipient_unavailable("recipient-unavailable", Type.wait, 404),
/**
* The recipient or server is redirecting requests for this
* information to another entity, usually temporarily (the error
* stanza SHOULD contain the alternate address, which MUST be a
* valid JID, in the XML character data of the &lt;redirect/&gt; element);
* the associated error type SHOULD be "modify".
*/
redirect("redirect", Type.modify, 302),
/**
* The requesting entity is not authorized to access the requested
* service because registration is required; the associated error
* type SHOULD be "auth".
*/
registration_required("registration-required", Type.auth, 407),
/**
* A remote server or service specified as part or all of the JID
* of the intended recipient does not exist; the associated error
* type SHOULD be "cancel".
*/
remote_server_not_found("remote-server-not-found", Type.cancel, 404),
/**
* A remote server or service specified as part or all of the JID of
* the intended recipient (or required to fulfill a request) could not
* be contacted within a reasonable amount of time; the associated
* error type SHOULD be "wait".
*/
remote_server_timeout("remote-server-timeout", Type.wait, 504),
/**
* The server or recipient lacks the system resources necessary to
* service the request; the associated error type SHOULD be "wait".
*/
resource_constraint("resource-constraint", Type.wait, 500),
/**
* The server or recipient does not currently provide the requested
* service; the associated error type SHOULD be "cancel".
*/
service_unavailable("service-unavailable", Type.cancel, 503),
/**
* The requesting entity is not authorized to access the requested
* service because a subscription is required; the associated error
* type SHOULD be "auth".
*/
subscription_required("subscription-required", Type.auth, 407),
/**
* The error condition is not one of those defined by the other
* conditions in this list; any error type may be associated with
* this condition, and it SHOULD be used only in conjunction with
* an application-specific condition.<p>
*
* Implementation note: the default type for this condition is
* {@link Type#wait}, which is not specified in the XMPP protocol.
*/
undefined_condition("undefined-condition", Type.wait, 500),
/**
* The recipient or server understood the request but was not
* expecting it at this time (e.g., the request was out of order);
* the associated error type SHOULD be "wait".
*/
unexpected_request("unexpected-request", Type.wait, 400);
/**
* Converts a String value into its Condition representation.
*
* @param condition the String value.
* @return the condition corresponding to the String.
*/
public static Condition fromXMPP(String condition) {
if (condition == null) {
throw new NullPointerException();
}
condition = condition.toLowerCase();
if (bad_request.toXMPP().equals(condition)) {
return bad_request;
}
else if (conflict.toXMPP().equals(condition)) {
return conflict;
}
else if (feature_not_implemented.toXMPP().equals(condition)) {
return feature_not_implemented;
}
else if (forbidden.toXMPP().equals(condition)) {
return forbidden;
}
else if (gone.toXMPP().equals(condition)) {
return gone;
}
else if (internal_server_error.toXMPP().equals(condition)) {
return internal_server_error;
}
else if (item_not_found.toXMPP().equals(condition)) {
return item_not_found;
}
else if (jid_malformed.toXMPP().equals(condition)) {
return jid_malformed;
}
else if (not_acceptable.toXMPP().equals(condition)) {
return not_acceptable;
}
else if (not_allowed.toXMPP().equals(condition)) {
return not_allowed;
}
else if (not_authorized.toXMPP().equals(condition)) {
return not_authorized;
}
else if (payment_required.toXMPP().equals(condition)) {
return payment_required;
}
else if (recipient_unavailable.toXMPP().equals(condition)) {
return recipient_unavailable;
}
else if (redirect.toXMPP().equals(condition)) {
return redirect;
}
else if (registration_required.toXMPP().equals(condition)) {
return registration_required;
}
else if (remote_server_not_found.toXMPP().equals(condition)) {
return remote_server_not_found;
}
else if (remote_server_timeout.toXMPP().equals(condition)) {
return remote_server_timeout;
}
else if (resource_constraint.toXMPP().equals(condition)) {
return resource_constraint;
}
else if (service_unavailable.toXMPP().equals(condition)) {
return service_unavailable;
}
else if (subscription_required.toXMPP().equals(condition)) {
return subscription_required;
}
else if (undefined_condition.toXMPP().equals(condition)) {
return undefined_condition;
}
else if (unexpected_request.toXMPP().equals(condition)) {
return unexpected_request;
}
else {
throw new IllegalArgumentException("Condition invalid:" + condition);
}
}
public static Condition fromLegacyCode(int code) {
if (bad_request.getLegacyCode() == code) {
return bad_request;
}
else if (conflict.getLegacyCode() == code) {
return conflict;
}
else if (feature_not_implemented.getLegacyCode() == code) {
return feature_not_implemented;
}
else if (forbidden.getLegacyCode() == code) {
return forbidden;
}
else if (gone.getLegacyCode() == code) {
return gone;
}
else if (internal_server_error.getLegacyCode() == code) {
return internal_server_error;
}
else if (item_not_found.getLegacyCode() == code) {
return item_not_found;
}
else if (jid_malformed.getLegacyCode() == code) {
return jid_malformed;
}
else if (not_acceptable.getLegacyCode() == code) {
return not_acceptable;
}
else if (not_allowed.getLegacyCode() == code) {
return not_allowed;
}
else if (not_authorized.getLegacyCode() == code) {
return not_authorized;
}
else if (payment_required.getLegacyCode() == code) {
return payment_required;
}
else if (recipient_unavailable.getLegacyCode() == code) {
return recipient_unavailable;
}
else if (redirect.getLegacyCode() == code) {
return redirect;
}
else if (registration_required.getLegacyCode() == code) {
return registration_required;
}
else if (remote_server_not_found.getLegacyCode() == code) {
return remote_server_not_found;
}
else if (remote_server_timeout.getLegacyCode() == code) {
return remote_server_timeout;
}
else if (resource_constraint.getLegacyCode() == code) {
return resource_constraint;
}
else if (service_unavailable.getLegacyCode() == code) {
return service_unavailable;
}
else if (subscription_required.getLegacyCode() == code) {
return subscription_required;
}
else if (undefined_condition.getLegacyCode() == code) {
return undefined_condition;
}
else if (unexpected_request.getLegacyCode() == code) {
return unexpected_request;
}
else {
throw new IllegalArgumentException("Code invalid:" + code);
}
}
private String value;
private int code;
private Type defaultType;
private Condition(String value, Type defaultType, int code) {
this.value = value;
this.defaultType = defaultType;
this.code = code;
}
/**
* Returns the default {@link Type} associated with this condition. Each
* error condition has an error type that it is usually associated with.
*
* @return the default error type.
*/
public Type getDefaultType() {
return defaultType;
}
/**
* Returns the legacy error code associated with the error. Error code mappings
* are based on <a href="http://www.jabber.org/jeps/jep-0086.html">JEP-0086</a>.
* Support for legacy error codes is necessary since many "Jabber" clients
* do not understand XMPP error codes. The {@link #fromLegacyCode(int)} method
* will convert numeric error codes into Conditions.
*
* @return the legacy error code.
*/
public int getLegacyCode() {
return code;
}
/**
* Returns the error code as a valid XMPP error code string.
*
* @return the XMPP error code value.
*/
public String toXMPP() {
return value;
}
}
/**
* Error type. Valid types are:<ul>
*
* <li>{@link #cancel Error.Type.cancel} -- do not retry (the error is unrecoverable).
* <li>{@link #continue_processing Error.Type.continue_processing} -- proceed
* (the condition was only a warning). Equivalent to the XMPP error type
* "continue".
* <li>{@link #modify Error.Type.modify} -- retry after changing the data sent.
* <li>{@link #auth Eror.Type.auth} -- retry after providing credentials.
* <li>{@link #wait Error.Type.wait} -- retry after waiting (the error is temporary).
* </ul>
*
* Implementation note: one of the XMPP error types is "continue". Because "continue"
* is a reserved Java keyword, the enum name is <tt>continue_processing</tt>. The
* {@link #toXMPP()} and {@link #fromXMPP(String)} methods can be used to convert
* between the enumertation values and XMPP error type strings.
*/
public enum Type {
/**
* Do not retry (the error is unrecoverable).
*/
cancel("cancel"),
/**
* Proceed (the condition was only a warning). This represents
* the "continue" error code in XMPP; because "continue" is a
* reserved keyword in Java the enum name has been changed.
*/
continue_processing("continue"),
/**
* Retry after changing the data sent.
*/
modify("modify"),
/**
* Retry after providing credentials.
*/
auth("auth"),
/**
* Retry after waiting (the error is temporary).
*/
wait("wait");
/**
* Converts a String value into its Type representation.
*
* @param type the String value.
* @return the condition corresponding to the String.
*/
public static Type fromXMPP(String type) {
if (type == null) {
throw new NullPointerException();
}
type = type.toLowerCase();
if (cancel.toXMPP().equals(type)) {
return cancel;
}
else if (continue_processing.toXMPP().equals(type)) {
return continue_processing;
}
else if (modify.toXMPP().equals(type)) {
return modify;
}
else if (auth.toXMPP().equals(type)) {
return auth;
}
else if (wait.toXMPP().equals(type)) {
return wait;
}
else {
throw new IllegalArgumentException("Type invalid:" + type);
}
}
private String value;
private Type(String value) {
this.value = value;
}
/**
* Returns the error code as a valid XMPP error code string.
*
* @return the XMPP error code value.
*/
public String toXMPP() {
return value;
}
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 2624 $
* $Date: 2005-05-11 12:56:11 -0700 (Wed, 11 May 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.DocumentFactory;
import org.dom4j.Element;
import org.dom4j.QName;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* A packet extension represents a child element of a Packet for a given qualified name. The
* PacketExtension acts as a wrapper on a child element the same way Packet does for a whole
* element. The wrapper provides an easy way to handle the packet extension.<p>
*
* Subclasses of this class should be registered in the static variable
* <tt>registeredExtensions</tt> when loaded. The registration process associates the new subclass
* with a given qualified name (ie. element name and namespace). This information will be used by
* {@link Packet#getExtension(String, String)} for locating the corresponding PacketExtension
* subclass to return for the requested qualified name.
*
* @author Gaston Dombiak
*/
public abstract class PacketExtension {
protected static DocumentFactory docFactory = DocumentFactory.getInstance();
/**
* Subclasses of PacketExtension should register the element name and namespace that the
* subclass is using.
*/
protected static Map<QName, Class> registeredExtensions = new ConcurrentHashMap<QName, Class>();
protected Element element;
/**
* Returns the extension class to use for the specified element name and namespace. For
* instance, the DataForm class should be used for the element "x" and
* namespace "jabber:x:data".
*
* @param name the child element name.
* @param namespace the child element namespace.
* @return the extension class to use for the specified element name and namespace.
*/
public static Class getExtensionClass(String name, String namespace) {
return registeredExtensions.get(QName.get(name, namespace));
}
/**
* Constructs a new Packet extension using the specified name and namespace.
*
* @param name the child element name.
* @param namespace the child element namespace.
*/
public PacketExtension(String name, String namespace) {
this.element = docFactory.createDocument().addElement(name, namespace);
}
/**
* Constructs a new PacketExtension.
*
* @param element the XML Element that contains the packet extension contents.
*/
public PacketExtension(Element element) {
this.element = element;
}
/**
* Returns the DOM4J Element that backs the packet. The element is the definitive
* representation of the packet and can be manipulated directly to change
* packet contents.
*
* @return the DOM4J Element that represents the packet.
*/
public Element getElement() {
return element;
}
/**
* Creates a deep copy of this packet extension.
*
* @return a deep copy of this packet extension.
*/
public abstract PacketExtension createCopy();
}
/**
* $RCSfile$
* $Revision: 3160 $
* $Date: 2005-12-04 17:58:57 -0800 (Sun, 04 Dec 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.Element;
import java.util.Iterator;
/**
* Presence packet. Presence packets are used to express an entity's current
* network availability and to notify other entities of that availability.
* Presence packets are also used to negotiate and manage subscriptions to the
* presence of other entities.<p>
*
* A presence optionally has a {@link Type}.
*
* @author Matt Tucker
*/
public class Presence extends Packet {
/**
* Constructs a new Presence.
*/
public Presence() {
this.element = docFactory.createDocument().addElement("presence");
}
/**
* Constructs a new Presence with the specified type.
*
* @param type the presence type.
*/
public Presence(Presence.Type type) {
this();
setType(type);
}
/**
* Constructs a new Presence using an existing Element. This is useful
* for parsing incoming presence Elements into Presence objects.
*
* @param element the presence Element.
*/
public Presence(Element element) {
super(element);
}
/**
* Constructs a new Presence that is a copy of an existing Presence.
*
* @param presence the presence packet.
* @see #createCopy()
*/
private Presence(Presence presence) {
Element elementCopy = presence.element.createCopy();
docFactory.createDocument().add(elementCopy);
this.element = elementCopy;
// Copy cached JIDs (for performance reasons)
this.toJID = presence.toJID;
this.fromJID = presence.fromJID;
}
/**
* Returns true if the presence type is "available". This is a
* convenience method that is equivalent to:
*
* <pre>getType() == null</pre>
*
*/
public boolean isAvailable() {
return getType() == null;
}
/**
* Returns the type of this presence. If the presence is "available", the
* type will be <tt>null</tt> (in XMPP, no value for the type attribute is
* defined as available).
*
* @return the presence type or <tt>null</tt> if "available".
* @see Type
*/
public Type getType() {
String type = element.attributeValue("type");
if (type == null) {
return null;
}
else {
return Type.valueOf(type);
}
}
/**
* Sets the type of this presence.
*
* @param type the presence type.
* @see Type
*/
public void setType(Type type) {
element.addAttribute("type", type==null?null:type.toString());
}
/**
* Returns the presence "show" value, which specifies a particular availability
* status. If the &lt;show&gt; element is not present, this method will return
* <tt>null</tt>. The show value can only be set if the presence type is "avaialble".
* A <tt>null</tt> show value is used to represent "available", which is the
* default.
*
* @return the presence show value..
* @see Show
*/
public Show getShow() {
String show = element.elementText("show");
if (show == null) {
return null;
}
else {
return Show.valueOf(show);
}
}
/**
* Sets the presence "show" value, which specifies a particular availability
* status. The show value can only be set if the presence type is "available".
* A <tt>null</tt> show value is used to represent "available", which is the
* default.
*
* @param show the presence show value.
* @throws IllegalArgumentException if the presence type is not available.
* @see Show
*/
public void setShow(Show show) {
Element showElement = element.element("show");
// If show is null, clear the subject.
if (show == null) {
if (showElement != null) {
element.remove(showElement);
}
return;
}
if (showElement == null) {
if (!isAvailable()) {
throw new IllegalArgumentException("Cannot set 'show' if 'type' attribute is set.");
}
showElement = element.addElement("show");
}
showElement.setText(show.toString());
}
/**
* Returns the status of this presence packet, a natural-language description
* of availability status.
*
* @return the status.
*/
public String getStatus() {
return element.elementText("status");
}
/**
* Sets the status of this presence packet, a natural-language description
* of availability status.
*
* @param status the status.
*/
public void setStatus(String status) {
Element statusElement = element.element("status");
// If subject is null, clear the subject.
if (status == null) {
if (statusElement != null) {
element.remove(statusElement);
}
return;
}
if (statusElement == null) {
statusElement = element.addElement("status");
}
statusElement.setText(status);
}
/**
* Returns the priority. The valid priority range is -128 through 128.
* If no priority element exists in the packet, this method will return
* the default value of 0.
*
* @return the priority.
*/
public int getPriority() {
String priority = element.elementText("priority");
if (priority == null) {
return 0;
}
else {
try {
return Integer.parseInt(priority);
}
catch (Exception e) {
return 0;
}
}
}
/**
* Sets the priority. The valid priority range is -128 through 128.
*
* @param priority the priority.
* @throws IllegalArgumentException if the priority is less than -128 or greater
* than 128.
*/
public void setPriority(int priority) {
if (priority < -128 || priority > 128) {
throw new IllegalArgumentException("Priority value of " + priority +
" is outside the valid range of -128 through 128");
}
Element priorityElement = element.element("priority");
if (priorityElement == null) {
priorityElement = element.addElement("priority");
}
priorityElement.setText(Integer.toString(priority));
}
/**
* Returns the first child element of this packet that matches the
* given name and namespace. If no matching element is found,
* <tt>null</tt> will be returned. This is a convenience method to avoid
* manipulating this underlying packet's Element instance directly.<p>
*
* Child elements in extended namespaces are used to extend the features
* of XMPP. Examples include a "user is typing" indicator and invitations to
* group chat rooms. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @param name the element name.
* @param namespace the element namespace.
* @return the first matching child element, or <tt>null</tt> if there
* is no matching child element.
*/
public Element getChildElement(String name, String namespace) {
for (Iterator i=element.elementIterator(name); i.hasNext(); ) {
Element element = (Element)i.next();
if (element.getNamespaceURI().equals(namespace)) {
return element;
}
}
return null;
}
/**
* Adds a new child element to this packet with the given name and
* namespace. The newly created Element is returned. This is a
* convenience method to avoid manipulating this underlying packet's
* Element instance directly.<p>
*
* Child elements in extended namespaces are used to extend the features
* of XMPP. Examples include a "user is typing" indicator and invitations to
* group chat rooms. Although any valid XML can be included in a child element
* in an extended namespace, many common features have been standardized
* as <a href="http://www.jabber.org/jeps">Jabber Enhancement Proposals</a>
* (JEPs).
*
* @param name the element name.
* @param namespace the element namespace.
* @return the newly created child element.
*/
public Element addChildElement(String name, String namespace) {
return element.addElement(name, namespace);
}
/**
* Returns a deep copy of this Presence.
*
* @return a deep copy of this Presence.
*/
public Presence createCopy() {
return new Presence(this);
}
/**
* Represents the type of a presence packet. Note: the presence is assumed
* to be "available" when the type attribute of the packet is <tt>null</tt>.
* The valid types are:
*
* <ul>
* <li>{@link #unavailable Presence.Type.unavailable} -- signals that the
* entity is no longer available for communication.
* <li>{@link #subscribe Presence.Type.subscribe} -- the sender wishes to
* subscribe to the recipient's presence.
* <li>{@link #subscribed Presence.Type.subscribed} -- the sender has allowed
* the recipient to receive their presence.
* <li>{@link #unsubscribe Presence.Type.unsubscribe} -- the sender is
* unsubscribing from another entity's presence.
* <li>{@link #unsubscribed Presence.Type.unsubcribed} -- the subscription
* request has been denied or a previously-granted subscription has been cancelled.
* <li>{@link #probe Presence.Type.probe} -- a request for an entity's current
* presence; SHOULD be generated only by a server on behalf of a user.
* <li>{@link #error Presence.Type.error} -- an error has occurred regarding
* processing or delivery of a previously-sent presence stanza.
* </ul>
*/
public enum Type {
/**
* Typically short text message used in line-by-line chat interfaces.
*/
unavailable,
/**
* The sender wishes to subscribe to the recipient's presence.
*/
subscribe,
/**
* The sender has allowed the recipient to receive their presence.
*/
subscribed,
/**
* The sender is unsubscribing from another entity's presence.
*/
unsubscribe,
/**
* The subscription request has been denied or a previously-granted
* subscription has been cancelled.
*/
unsubscribed,
/**
* A request for an entity's current presence; SHOULD be
* generated only by a server on behalf of a user.
*/
probe,
/**
* An error has occurred regarding processing or delivery
* of a previously-sent presence stanza.
*/
error;
}
/**
* Represents the presence "show" value. Note: a <tt>null</tt> "show" value is the
* default, which means "available". Valid values are:
*
* <ul>
* <li>{@link #chat Presence.Show.chat} -- the entity or resource is actively
* interested in chatting.
* <li>{@link #away Presence.Show.away} -- the entity or resource is
* temporarily away.
* <li>{@link #dnd Presence.Show.dnd} -- the entity or resource is busy
* (dnd = "Do Not Disturb").
* <li>{@link #xa Presence.Show.xa} -- the entity or resource is away for an
* extended period (xa = "eXtended Away").
* </ul>
*/
public enum Show {
/**
* The entity or resource is actively interested in chatting.
*/
chat,
/**
* The entity or resource is temporarily away.
*/
away,
/**
* The entity or resource is away for an extended period (xa = "eXtended Away").
*/
xa,
/**
* The entity or resource is busy (dnd = "Do Not Disturb").
*/
dnd;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 2624 $
* $Date: 2005-05-11 12:56:11 -0700 (Wed, 11 May 2005) $
*
* Copyright 2004 Jive Software.
*
* All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.xmpp.packet;
import org.dom4j.*;
import java.util.*;
/**
* Roster packet. The roster is a list of JIDs (typically other users) that
* the user wishes to track the presence of. Each roster item is keyed by
* JID and contains a nickname (optional), subscription type, and list of
* groups (optional).
*
* @author Matt Tucker
*/
public class Roster extends IQ {
/**
* Constructs a new Roster with an automatically generated ID and a type
* of {@link IQ.Type#get}.
*/
public Roster() {
super();
element.addElement("query", "jabber:iq:roster");
}
/**
* Constructs a new Roster using the specified type. A packet ID will
* be automatically generated.
*
* @param type the IQ type.
*/
public Roster(Type type) {
super(type);
element.addElement("query", "jabber:iq:roster");
}
/**
* Constructs a new Roster using the specified type and ID.
*
* @param type the IQ type.
* @param ID the packet ID of the IQ.
*/
public Roster(Type type, String ID) {
super(type, ID);
element.addElement("query", "jabber:iq:roster");
}
/**
* Constructs a new Roster that is a copy of an existing Roster.
*
* @param roster the roster packet.
* @see #createCopy()
*/
private Roster(Roster roster) {
Element elementCopy = roster.element.createCopy();
docFactory.createDocument().add(elementCopy);
this.element = elementCopy;
}
/**
* Constructs a new Roster using an existing Element. This is useful
* for parsing incoming roster Elements into Roster objects.
*
* @param element the Roster Element.
*/
public Roster(Element element) {
super(element);
}
/**
* Adds a new item to the roster. The name and groups are set to <tt>null</tt>
* If the roster packet already contains an item using the same JID, the
* information in the existing item will be overwritten with the new information.<p>
*
* The XMPP specification recommends that if the roster item is associated with another
* instant messaging user (human), that the JID be in bare form (e.g. user@domain).
* Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
*
* @param jid the JID.
* @param subscription the subscription type.
* @return the newly created item.
*/
public Item addItem(String jid, Subscription subscription) {
if (getType() == IQ.Type.get || getType() == IQ.Type.error) {
throw new IllegalStateException("IQ type must be 'result' or 'set'");
}
if (jid == null) {
throw new NullPointerException("JID cannot be null");
}
return addItem(new JID(jid), null, null, subscription, null);
}
/**
* Adds a new item to the roster. The name and groups are set to <tt>null</tt>
* If the roster packet already contains an item using the same JID, the
* information in the existing item will be overwritten with the new information.<p>
*
* The XMPP specification recommends that if the roster item is associated with another
* instant messaging user (human), that the JID be in bare form (e.g. user@domain).
* Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
*
* @param jid the JID.
* @param subscription the subscription type.
* @return the newly created item.
*/
public Item addItem(JID jid, Subscription subscription) {
if (getType() != IQ.Type.result && getType() != IQ.Type.set) {
throw new IllegalStateException("IQ type must be 'result' or 'set'");
}
if (jid == null) {
throw new NullPointerException("JID cannot be null");
}
return addItem(jid, null, null, subscription, null);
}
/**
* Adds a new item to the roster. If the roster packet already contains an item
* using the same JID, the information in the existing item will be overwritten
* with the new information.<p>
*
* The XMPP specification recommends that if the roster item is associated with another
* instant messaging user (human), that the JID be in bare form (e.g. user@domain).
* Use the {@link JID#toBareJID() toBareJID()} method for a bare JID.
*
* @param jid the JID.
* @param name the nickname.
* @param ask the ask type.
* @param subscription the subscription type.
* @param groups a Collection of groups.
* @return the newly created item.
*/
public Item addItem(JID jid, String name, Ask ask, Subscription subscription,
Collection<String> groups)
{
if (jid == null) {
throw new NullPointerException("JID cannot be null");
}
if (subscription == null) {
throw new NullPointerException("Subscription cannot be null");
}
Element query = element.element(new QName("query", Namespace.get("jabber:iq:roster")));
if (query == null) {
query = element.addElement("query", "jabber:iq:roster");
}
Element item = null;
for (Iterator i=query.elementIterator("item"); i.hasNext(); ) {
Element el = (Element)i.next();
if (el.attributeValue("jid").equals(jid.toString())) {
item = el;
}
}
if (item == null) {
item = query.addElement("item");
}
item.addAttribute("jid", jid.toBareJID());
item.addAttribute("name", name);
if (ask != null) {
item.addAttribute("ask", ask.toString());
}
item.addAttribute("subscription", subscription.toString());
// Erase existing groups in case the item previously existed.
for (Iterator i=item.elementIterator("group"); i.hasNext(); ) {
item.remove((Element)i.next());
}
// Add in groups.
if (groups != null) {
for (String group : groups) {
item.addElement("group").setText(group);
}
}
return new Item(jid, name, ask, subscription, groups);
}
/**
* Removes an item from this roster.
*
* @param jid the JID of the item to remove.
*/
public void removeItem(JID jid) {
Element query = element.element(new QName("query", Namespace.get("jabber:iq:roster")));
if (query != null) {
for (Iterator i=query.elementIterator("item"); i.hasNext(); ) {
Element item = (Element)i.next();
if (item.attributeValue("jid").equals(jid.toString())) {
query.remove(item);
return;
}
}
}
}
/**
* Returns an unmodifiable copy of the {@link Item Items} in the roster packet.
*
* @return an unmodifable copy of the {@link Item Items} in the roster packet.
*/
public Collection<Item> getItems() {
Collection<Item> items = new ArrayList<Item>();
Element query = element.element(new QName("query", Namespace.get("jabber:iq:roster")));
if (query != null) {
for (Iterator i=query.elementIterator("item"); i.hasNext(); ) {
Element item = (Element)i.next();
String jid = item.attributeValue("jid");
String name = item.attributeValue("name");
String ask = item.attributeValue("ask");
String subscription = item.attributeValue("subscription");
Collection<String> groups = new ArrayList<String>();
for (Iterator j=item.elementIterator("group"); j.hasNext(); ) {
Element group = (Element)j.next();
groups.add(group.getTextTrim());
}
Ask askStatus = ask == null ? null : Ask.valueOf(ask);
Subscription subStatus = subscription == null ?
null : Subscription.valueOf(subscription);
items.add(new Item(new JID(jid), name, askStatus, subStatus, groups));
}
}
return Collections.unmodifiableCollection(items);
}
/**
* Returns a deep copy of this Roster.
*
* @return a deep copy of this Roster.
*/
public Roster createCopy() {
return new Roster(this);
}
/**
* Item in a roster, which represents an individual contact. Each contact
* has a JID, an optional nickname, a subscription type, and can belong to
* one ore more groups.
*/
public static class Item {
private JID jid;
private String name;
private Ask ask;
private Subscription subscription;
private Collection<String> groups;
/**
* Constructs a new roster item.
*
* @param jid the JID.
* @param name the nickname.
* @param ask the ask state.
* @param subscription the subscription state.
* @param groups the item groups.
*/
private Item(JID jid, String name, Ask ask, Subscription subscription,
Collection<String> groups) {
this.jid = jid;
this.name = name;
this.ask = ask;
this.subscription = subscription;
this.groups = groups;
}
/**
* Returns the JID associated with this item. The JID is the "key" in the
* list of items that make up a roster. There can only be a single item per
* JID in a roster.
*
* @return the JID associated with this item.
*/
public JID getJID() {
return jid;
}
/**
* Returns the nickname associated with this item. If no nickname exists,
* <tt>null</tt> is returned.
*
* @return the nickname, or <tt>null</tt> if it doesn't exist.
*/
public String getName() {
return name;
}
/**
* Returns the ask state of this item.
*
* @return the ask state of this item.
*/
public Ask getAsk() {
return ask;
}
/**
* Returns the subscription state of this item.
*
* @return the subscription state of this item.
*/
public Subscription getSubscription() {
return subscription;
}
/**
* Returns a Collection of the groups defined in this item. If
* no groups are defined, an empty Collection is returned.
*
* @return the groups in this item.
*/
public Collection<String> getGroups() {
if (groups == null) {
return Collections.emptyList();
}
return groups;
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("<item ");
buf.append("jid=\"").append(jid).append("\"");
if (name != null) {
buf.append(" name=\"").append(name).append("\"");
}
buf.append(" subscrption=\"").append(subscription).append("\"");
if (groups == null || groups.isEmpty()) {
buf.append("/>");
}
else {
buf.append(">\n");
for (String group : groups) {
buf.append(" <group>").append(group).append("</group>\n");
}
buf.append("</item>");
}
return buf.toString();
}
}
/**
* Type-safe enumeration for the roster subscription type. Valid subcription types:
*
* <ul>
* <li>{@link #none Roster.Subscription.none} -- the user does not have a
* subscription to the contact's presence information, and the contact
* does not have a subscription to the user's presence information.
* <li>{@link #to Roster.Subscription.to} -- the user has a subscription to
* the contact's presence information, but the contact does not have a
* subscription to the user's presence information.
* <li>{@link #from Roster.Subscription.from} -- the contact has a subscription
* to the user's presence information, but the user does not have a
* subscription to the contact's presence information.
* <li>{@link #both Roster.Subscription.both} -- both the user and the contact
* have subscriptions to each other's presence information.
* <li>{@link #remove Roster.Subscription.remove} -- the user is removing a
* contact from his or her roster.
* </ul>
*/
public enum Subscription {
/**
* The user does not have a subscription to the contact's presence information,
* and the contact does not have a subscription to the user's presence information.
*/
none,
/**
* The user has a subscription to the contact's presence information, but the
* contact does not have a subscription to the user's presence information.
*/
to,
/**
* The contact has a subscription to the user's presence information, but the
* user does not have a subscription to the contact's presence information.
*/
from,
/**
* Both the user and the contact have subscriptions to each other's presence
* information.
*/
both,
/**
* The user is removing a contact from his or her roster. The user's server will
* 1) automatically cancel any existing presence subscription between the user and the
* contact, 2) remove the roster item from the user's roster and inform all of the user's
* available resources that have requested the roster of the roster item removal, 3) inform
* the resource that initiated the removal of success and 4) send unavailable presence from
* all of the user's available resources to the contact.
*/
remove;
}
/**
* Type-safe enumeration for the roster ask type. Valid ask types:
*
* <ul>
* <li>{@link #subscribe Roster.Ask.subscribe} -- the roster item has been asked
* for permission to subscribe to their presence but no response has been received.
* <li>{@link #unsubscribe Roster.Ask.unsubscribe} -- the roster owner has asked
* to the roster item to unsubscribe from it's presence but has not received
* confirmation.
* </ul>
*/
public enum Ask {
/**
* The roster item has been asked for permission to subscribe to their presence
* but no response has been received.
*/
subscribe,
/**
* The roster owner has asked to the roster item to unsubscribe from it's
* presence but has not received confirmation.
*/
unsubscribe;
}
}
\ No newline at end of file
/**
* $RCSfile$
* $Revision: 2599 $
* $Date: 2005-04-03 14:09:41 -0700 (Sun, 03 Apr 2005) $
*
* Copyright 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.xmpp.packet;
import org.dom4j.*;
import org.dom4j.io.XMLWriter;
import org.dom4j.io.OutputFormat;
import java.util.Iterator;
import java.io.StringWriter;
/**
* A stream error. Stream errors have a condition and they
* can optionally include explanation text.
*
* @author Matt Tucker
*/
public class StreamError {
private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-streams";
private static DocumentFactory docFactory = DocumentFactory.getInstance();
private Element element;
/**
* Construcs a new StreamError with the specified condition.
*
* @param condition the error condition.
*/
public StreamError(Condition condition) {
this.element = docFactory.createElement(docFactory.createQName("error", "stream",
"http://etherx.jabber.org/streams"));
setCondition(condition);
}
/**
* Constructs a new StreamError with the specified condition and error text.
*
* @param condition the error condition.
* @param text the text description of the error.
*/
public StreamError(Condition condition, String text) {
this.element = docFactory.createElement(docFactory.createQName("error", "stream",
"http://etherx.jabber.org/streams"));
setCondition(condition);
setText(text, null);
}
/**
* Constructs a new StreamError with the specified condition and error text.
*
* @param condition the error condition.
* @param text the text description of the error.
* @param language the language code of the error description (e.g. "en").
*/
public StreamError(Condition condition, String text, String language) {
this.element = docFactory.createElement(docFactory.createQName("error", "stream",
"http://etherx.jabber.org/streams"));
setCondition(condition);
setText(text, language);
}
/**
* Constructs a new StreamError using an existing Element. This is useful
* for parsing incoming error Elements into StreamError objects.
*
* @param element the stream error Element.
*/
public StreamError(Element element) {
this.element = element;
}
/**
* Returns the error condition.
*
* @return the error condition.
* @see Condition
*/
public Condition getCondition() {
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = (Element)i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
return Condition.fromXMPP(el.getName());
}
}
return null;
}
/**
* Sets the error condition.
*
* @param condition the error condition.
* @see Condition
*/
public void setCondition(Condition condition) {
if (condition == null) {
throw new NullPointerException("Condition cannot be null");
}
Element conditionElement = null;
for (Iterator i=element.elementIterator(); i.hasNext(); ) {
Element el = (Element)i.next();
if (el.getNamespaceURI().equals(ERROR_NAMESPACE) &&
!el.getName().equals("text"))
{
conditionElement = el;
}
}
if (conditionElement != null) {
element.remove(conditionElement);
}
conditionElement = docFactory.createElement(condition.toXMPP(), ERROR_NAMESPACE);
element.add(conditionElement);
}
/**
* Returns a text description of the error, or <tt>null</tt> if there
* is no text description.
*
* @return the text description of the error.
*/
public String getText() {
return element.elementText("text");
}
/**
* Sets the text description of the error.
*
* @param text the text description of the error.
*/
public void setText(String text) {
setText(text, null);
}
/**
* Sets the text description of the error. Optionally, a language code
* can be specified to indicate the language of the description.
*
* @param text the text description of the error.
* @param language the language code of the description, or <tt>null</tt> to specify
* no language code.
*/
public void setText(String text, String language) {
Element textElement = element.element("text");
// If text is null, clear the text.
if (text == null) {
if (textElement != null) {
element.remove(textElement);
}
return;
}
if (textElement == null) {
textElement = docFactory.createElement("text", ERROR_NAMESPACE);
if (language != null) {
textElement.addAttribute(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"), language);
}
element.add(textElement);
}
textElement.setText(text);
}
/**
* Returns the text description's language code, or <tt>null</tt> if there
* is no language code associated with the description text.
*
* @return the language code of the text description, if it exists.
*/
public String getTextLanguage() {
Element textElement = element.element("text");
if (textElement != null) {
return textElement.attributeValue(QName.get("lang", "xml",
"http://www.w3.org/XML/1998/namespace"));
}
return null;
}
/**
* Returns the DOM4J Element that backs the error. The element is the definitive
* representation of the error and can be manipulated directly to change
* error contents.
*
* @return the DOM4J Element.
*/
public Element getElement() {
return element;
}
/**
* Returns the textual XML representation of this stream error.
*
* @return the textual XML representation of this stream error.
*/
public String toXML() {
return element.asXML();
}
public String toString() {
StringWriter out = new StringWriter();
XMLWriter writer = new XMLWriter(out, OutputFormat.createPrettyPrint());
try {
writer.write(element);
}
catch (Exception e) { }
return out.toString();
}
/**
* Type-safe enumeration for the error condition.<p>
*
* Implementation note: XMPP error conditions use "-" characters in
* their names such as "bad-request". Because "-" characters are not valid
* identifier parts in Java, they have been converted to "_" characters in
* the enumeration names, such as <tt>bad_request</tt>. The {@link #toXMPP()} and
* {@link #fromXMPP(String)} methods can be used to convert between the
* enumertation values and XMPP error code strings.
*/
public enum Condition {
/**
* The entity has sent XML that cannot be processed; this error MAY be used
* instead of the more specific XML-related errors, such as &lt;bad-namespace-prefix/&gt;,
* &lt;invalid-xml/&gt;, &lt;restricted-xml/&gt;, &lt;unsupported-encoding/&gt;, and
* &lt;xml-not-well-formed/&gt;, although the more specific errors are preferred.
*/
bad_format("bad-format"),
/**
* The entity has sent a namespace prefix that is unsupported, or has sent no
* namespace prefix on an element that requires such a prefix.
*/
bad_namespace_prefix("bad-namespace-prefix"),
/**
* The server is closing the active stream for this entity because a new stream
* has been initiated that conflicts with the existing stream.
*/
conflict("conflict"),
/**
* The entity has not generated any traffic over the stream for some period of
* time (configurable according to a local service policy).
*/
connection_timeout("connection-timeout"),
/**
* The value of the 'to' attribute provided by the initiating entity in the
* stream header corresponds to a hostname that is no longer hosted by the server.
*/
host_gone("host-gone"),
/**
* 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.
*/
host_unknown("host-unknown"),
/**
* A stanza sent between two servers lacks a 'to' or 'from' attribute
* (or the attribute has no value).
*/
improper_addressing("improper-addressing"),
/**
* The server has experienced a misconfiguration or an otherwise-undefined
* internal error that prevents it from servicing the stream.
*/
internal_server_error("internal-server-error"),
/**
* The JID or hostname provided in a 'from' address does not match an authorized
* JID or validated domain negotiated between servers via SASL or dialback, or
* between a client and a server via authentication and resource binding.
*/
invalid_from("invalid-from"),
/**
* The stream ID or dialback ID is invalid or does not match an ID previously provided.
*/
invalid_id("invalid-id"),
/**
* the streams namespace name is something other than "http://etherx.jabber.org/streams"
* or the dialback namespace name is something other than "jabber:server:dialback".
*/
invalid_namespace("invalid-namespace"),
/**
* The entity has sent invalid XML over the stream to a server that performs validation.
*/
invalid_xml("invalid-xml"),
/**
* The entity has attempted to send data before the stream has been authenticated,
* or otherwise is not authorized to perform an action related to stream
* negotiation; the receiving entity MUST NOT process the offending stanza before
* sending the stream error.
*/
not_authorized("not-authorized"),
/**
* The entity has violated some local service policy; the server MAY choose to
* specify the policy in the <text/> element or an application-specific condition
* element.
*/
policy_violation("policy-violation"),
/**
* The server is unable to properly connect to a remote entity that is required for
* authentication or authorization.
*/
remote_connection_failed("remote-connection-failed"),
/**
* The server lacks the system resources necessary to service the stream.
*/
resource_constraint("resource-constraint"),
/**
* The entity has attempted to send restricted XML features such as a comment,
* processing instruction, DTD, entity reference, or unescaped character.
*/
restricted_xml("restricted-xml"),
/**
* The server will not provide service to the initiating entity but is redirecting
* traffic to another host; the server SHOULD specify the alternate hostname or IP
* address (which MUST be a valid domain identifier) as the XML character data of the
* &lt;see-other-host/&gt; element.
*/
see_other_host("see-other-host"),
/**
* The server is being shut down and all active streams are being closed.
*/
system_shutdown("system-shutdown"),
/**
* The error condition is not one of those defined by the other conditions in this
* list; this error condition SHOULD be used only in conjunction with an
* application-specific condition.
*/
undefined_condition("undefined-condition"),
/**
* The initiating entity has encoded the stream in an encoding that is not
* supported by the server.
*/
unsupported_encoding("unsupported-encoding"),
/**
* The initiating entity has sent a first-level child of the stream that is
* not supported by the server.
*/
unsupported_stanza_type("unsupported-stanza-type"),
/**
* the value of the 'version' attribute provided by the initiating entity in the
* stream header specifies a version of XMPP that is not supported by the server;
* the server MAY specify the version(s) it supports in the &lt;text/&gt; element.
*/
unsupported_version("unsupported-version"),
/**
* The initiating entity has sent XML that is not well-formed.
*/
xml_not_well_formed("xml-not-well-formed");
/**
* Converts a String value into its Condition representation.
*
* @param condition the String value.
* @return the condition corresponding to the String.
*/
public static Condition fromXMPP(String condition) {
if (condition == null) {
throw new NullPointerException();
}
condition = condition.toLowerCase();
if (bad_format.toXMPP().equals(condition)) {
return bad_format;
}
else if (bad_namespace_prefix.toXMPP().equals(condition)) {
return bad_namespace_prefix;
}
else if (conflict.toXMPP().equals(condition)) {
return conflict;
}
else if (connection_timeout.toXMPP().equals(condition)) {
return connection_timeout;
}
else if (host_gone.toXMPP().equals(condition)) {
return host_gone;
}
else if (host_unknown.toXMPP().equals(condition)) {
return host_unknown;
}
else if (improper_addressing.toXMPP().equals(condition)) {
return improper_addressing;
}
else if (internal_server_error.toXMPP().equals(condition)) {
return internal_server_error;
}
else if (invalid_from.toXMPP().equals(condition)) {
return invalid_from;
}
else if (invalid_id.toXMPP().equals(condition)) {
return invalid_id;
}
else if (invalid_namespace.toXMPP().equals(condition)) {
return invalid_namespace;
}
else if (invalid_xml.toXMPP().equals(condition)) {
return invalid_xml;
}
else if (not_authorized.toXMPP().equals(condition)) {
return not_authorized;
}
else if (policy_violation.toXMPP().equals(condition)) {
return policy_violation;
}
else if (remote_connection_failed.toXMPP().equals(condition)) {
return remote_connection_failed;
}
else if (resource_constraint.toXMPP().equals(condition)) {
return resource_constraint;
}
else if (restricted_xml.toXMPP().equals(condition)) {
return restricted_xml;
}
else if (see_other_host.toXMPP().equals(condition)) {
return see_other_host;
}
else if (system_shutdown.toXMPP().equals(condition)) {
return system_shutdown;
}
else if (undefined_condition.toXMPP().equals(condition)) {
return undefined_condition;
}
else if (unsupported_encoding.toXMPP().equals(condition)) {
return unsupported_encoding;
}
else if (unsupported_stanza_type.toXMPP().equals(condition)) {
return unsupported_stanza_type;
}
else if (unsupported_version.toXMPP().equals(condition)) {
return unsupported_version;
}
else if (xml_not_well_formed.toXMPP().equals(condition)) {
return xml_not_well_formed;
}
else {
throw new IllegalArgumentException("Condition invalid:" + condition);
}
}
private String value;
private Condition(String value) {
this.value = value;
}
/**
* Returns the error code as a valid XMPP error code string.
*
* @return the XMPP error code value.
*/
public String toXMPP() {
return value;
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment