1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-09-10 18:59:41 +02:00

Added privacy list support and improved error handling from Francisco (SMACK-121, SMACK-31).

git-svn-id: http://svn.igniterealtime.org/svn/repos/smack/trunk@4603 b35dd754-fafc-0310-a699-88a17e54d16e
This commit is contained in:
Matt Tucker 2006-07-19 19:24:00 +00:00 committed by matt
parent 1716c6ed22
commit 47abf627b7
20 changed files with 2099 additions and 81 deletions

View file

@ -0,0 +1,315 @@
package org.jivesoftware.smack.packet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.Set;
/**
* A Privacy IQ Packet, is used by the {@see PrivacyListManager} and {@see PrivacyProvider} to allow
* and block communications from other users. It contains the appropriate structure to suit
* user-defined privacy lists. Different configured Privacy packages are used in the Server
* Manager communication in order to:
* <ul>
* <li>Retrieving one's privacy lists.
* <li>Adding, removing, and editing one's privacy lists.
* <li>Setting, changing, or declining active lists.
* <li>Setting, changing, or declining the default list (i.e., the list that is active by default).
* </ul>
* Privacy Items can handle different kind of blocking communications based on JID, group,
* subscription type or globally {@see PrivacyItem}
*
* @author Francisco Vives
*/
public class Privacy extends IQ {
/** declineActiveList is true when the user declines the use of the active list **/
private boolean declineActiveList=false;
/** activeName is the name associated with the active list set for the session **/
private String activeName;
/** declineDefaultList is true when the user declines the use of the default list **/
private boolean declineDefaultList=false;
/** defaultName is the name of the default list that applies to the user as a whole **/
private String defaultName;
/** itemLists holds the set of privacy items classified in lists. It is a map where the
* key is the name of the list and the value a collection with privacy items. **/
private Map itemLists = new HashMap();
/**
* Set or update a privacy list with {@link PrivacyItem}.
*
* @param listName the name of the new privacy list.
* @param listItem the {@link PrivacyItem} that rules the list.
* @return the privacy List.
*/
public List setPrivacyList(String listName, List listItem) {
// Add new list to the itemLists
this.getItemLists().put(listName, listItem);
return listItem;
}
/**
* Set the active list based on the default list.
*
* @return the active List.
*/
public List setActivePrivacyList() {
this.setActiveName(this.getDefaultName());
return (List) this.getItemLists().get(this.getActiveName());
}
/**
* Deletes an existing privacy list. If the privacy list being deleted was the default list
* then the user will end up with no default list. Therefore, the user will have to set a new
* default list.
*
* @param listName the name of the list being deleted.
*/
public void deletePrivacyList(String listName) {
// Remove the list from the cache
this.getItemLists().remove(listName);
// Check if deleted list was the default list
if (this.getDefaultName() != null && listName.equals(this.getDefaultName())) {
this.setDefaultName(null);
}
}
/**
* Returns the active privacy list or <tt>null</tt> if none was found.
*
* @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
*/
public List getActivePrivacyList() {
// Check if we have the default list
if (this.getActiveName() == null) {
return null;
} else {
return (List) this.getItemLists().get(this.getActiveName());
}
}
/**
* Returns the default privacy list or <tt>null</tt> if none was found.
*
* @return list with {@link PrivacyItem} or <tt>null</tt> if none was found.
*/
public List getDefaultPrivacyList() {
// Check if we have the default list
if (this.getDefaultName() == null) {
return null;
} else {
return (List) this.getItemLists().get(this.getDefaultName());
}
}
/**
* Returns a specific privacy list.
*
* @param listName the name of the list to get.
* @return a List with {@link PrivacyItem}
*/
public List getPrivacyList(String listName) {
return (List) this.getItemLists().get(listName);
}
/**
* Returns the privacy item in the specified order.
*
* @param order the order of the element.
* @return a List with {@link PrivacyItem}
*/
public PrivacyItem getItem(String listName, int order) {
Iterator values = getPrivacyList(listName).iterator();
PrivacyItem itemFound = null;
while (itemFound == null && values.hasNext()) {
PrivacyItem element = (PrivacyItem) values.next();
if (element.getOrder() == order) {
itemFound = element;
}
}
return itemFound;
}
/**
* Sets a given privacy list as the new user default list.
*
* @param newDefault the new default privacy list.
* @return if the default list was changed.
*/
public boolean changeDefaultList(String newDefault) {
if (this.getItemLists().containsKey(newDefault)) {
this.setDefaultName(newDefault);
return true;
} else {
return false;
}
}
/**
* Remove the list.
*
* @param listName name of the list to remove.
*/
public void deleteList(String listName) {
this.getItemLists().remove(listName);
}
/**
* Returns the name associated with the active list set for the session. Communications
* will be verified against the active list.<p>
*
* @return the name of the active list.
*/
public String getActiveName() {
return activeName;
}
/**
* Sets the name associated with the active list set for the session. Communications
* will be verified against the active list.<p>
*
* @param activeName is the name of the active list.
*/
public void setActiveName(String activeName) {
this.activeName = activeName;
}
/**
* Returns the name of the default list that applies to the user as a whole. Default list is
* processed if there is no active list set for the target session/resource to which a stanza
* is addressed, or if there are no current sessions for the user.
*
* @return the name of the default list.
*/
public String getDefaultName() {
return defaultName;
}
/**
* Sets the name of the default list that applies to the user as a whole. Default list is
* processed if there is no active list set for the target session/resource to which a stanza
* is addressed, or if there are no current sessions for the user.
*
* If there is no default list set, then all Privacy Items are processed.
*
* @param defaultName is the name of the default list.
*/
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
/**
* Returns the collection of privacy list that the user holds. A Privacy List contains a set of
* rules that define if communication with the list owner is allowed or denied.
* Users may have zero, one or more privacy items.
*
* @return a map where the key is the name of the list and the value the
* collection of privacy items.
*/
public Map getItemLists() {
return itemLists;
}
/**
* Returns whether the receiver allows or declines the use of an active list.
*
* @return the decline status of the list.
*/
public boolean isDeclineActiveList() {
return declineActiveList;
}
/**
* Sets whether the receiver allows or declines the use of an active list.
*
* @param declineActiveList indicates if the receiver declines the use of an active list.
*/
public void setDeclineActiveList(boolean declineActiveList) {
this.declineActiveList = declineActiveList;
}
/**
* Returns whether the receiver allows or declines the use of a default list.
*
* @return the decline status of the list.
*/
public boolean isDeclineDefaultList() {
return declineDefaultList;
}
/**
* Sets whether the receiver allows or declines the use of a default list.
*
* @param declineActiveList indicates if the receiver declines the use of a default list.
*/
public void setDeclineDefaultList(boolean declineDefaultList) {
this.declineDefaultList = declineDefaultList;
}
/**
* Returns all the list names the user has defined to group restrictions.
*
* @return a Set with Strings containing every list names.
*/
public Set getPrivacyListNames() {
return this.itemLists.keySet();
}
public String getChildElementXML() {
StringBuffer buf = new StringBuffer();
buf.append("<query xmlns=\"jabber:iq:privacy\">");
// Add the active tag
if (this.isDeclineActiveList()) {
buf.append("<active/>");
} else {
if (this.getActiveName() != null) {
buf.append("<active name=\"").append(this.getActiveName()).append("\"/>");
}
}
// Add the default tag
if (this.isDeclineDefaultList()) {
buf.append("<default/>");
} else {
if (this.getDefaultName() != null) {
buf.append("<default name=\"").append(this.getDefaultName()).append("\"/>");
}
}
// Add the list with their privacy items
int listNameSize = this.getItemLists().size();
Iterator listItemsPairs = this.getItemLists().entrySet().iterator();
for (int i = 0; i < listNameSize; i++) {
Map.Entry entry = (Map.Entry) listItemsPairs.next();
String listName = (String) entry.getKey();
List items = (List) entry.getValue();
// Begin the list tag
if (items.isEmpty()) {
buf.append("<list name=\"").append(listName).append("\"/>");
} else {
buf.append("<list name=\"").append(listName).append("\">");
}
for (Iterator itemIterator = items.iterator(); itemIterator.hasNext();) {
PrivacyItem item = (PrivacyItem) itemIterator.next();
// Append the item xml representation
buf.append(item.toXML());
}
// Close the list tag
if (!items.isEmpty()) {
buf.append("</list>");
}
}
// Add packet extensions, if any are defined.
buf.append(getExtensionsXML());
buf.append("</query>");
String generatedXML = buf.toString();
return generatedXML;
}
}

View file

@ -0,0 +1,454 @@
package org.jivesoftware.smack.packet;
/**
* A privacy item acts a rule that when matched defines if a packet should be blocked or not.
*
* Privacy Items can handle different kind of blocking communications based on JID, group,
* subscription type or globally by:<ul>
* <li>Allowing or blocking messages.
* <li>Allowing or blocking inbound presence notifications.
* <li>Allowing or blocking outbound presence notifications.
* <li>Allowing or blocking IQ stanzas.
* <li>Allowing or blocking all communications.
* </ul>
* @author Francisco Vives
*/
public class PrivacyItem {
/** allow is the action associated with the item, it can allow or deny the communication. */
private boolean allow;
/** order is a non-negative integer that is unique among all items in the list. */
private int order;
/** rule hold the kind of communication ([jid|group|subscription]) it will allow or block and
* identifier to apply the action.
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none". */
private PrivacyRule rule;
/** blocks incoming IQ stanzas. */
private boolean filterIQ = false;
/** filterMessage blocks incoming message stanzas. */
private boolean filterMessage = false;
/** blocks incoming presence notifications. */
private boolean filterPresence_in = false;
/** blocks outgoing presence notifications. */
private boolean filterPresence_out = false;
/**
* Creates a new privacy item.
*
* @param type the type.
*/
public PrivacyItem(String type, boolean allow, int order) {
this.setRule(PrivacyRule.fromString(type));
this.setAllow(allow);
this.setOrder(order);
}
/**
* Returns the action associated with the item, it MUST be filled and will allow or deny
* the communication.
*
* @return the allow communication status.
*/
public boolean isAllow() {
return allow;
}
/**
* Sets the action associated with the item, it can allow or deny the communication.
*
* @param allow indicates if the receiver allow or deny the communication.
*/
private void setAllow(boolean allow) {
this.allow = allow;
}
/**
* Returns whether the receiver allow or deny incoming IQ stanzas or not.
*
* @return the iq filtering status.
*/
public boolean isFilterIQ() {
return filterIQ;
}
/**
* Sets whether the receiver allows or denies incoming IQ stanzas or not.
*
* @param filterIQ indicates if the receiver allows or denies incoming IQ stanzas.
*/
public void setFilterIQ(boolean filterIQ) {
this.filterIQ = filterIQ;
}
/**
* Returns whether the receiver allows or denies incoming messages or not.
*
* @return the message filtering status.
*/
public boolean isFilterMessage() {
return filterMessage;
}
/**
* Sets wheather the receiver allows or denies incoming messages or not.
*
* @param filterMessage indicates if the receiver allows or denies incoming messages or not.
*/
public void setFilterMessage(boolean filterMessage) {
this.filterMessage = filterMessage;
}
/**
* Returns whether the receiver allows or denies incoming presence or not.
*
* @return the iq filtering incoming presence status.
*/
public boolean isFilterPresence_in() {
return filterPresence_in;
}
/**
* Sets whether the receiver allows or denies incoming presence or not.
*
* @param filterPresence_in indicates if the receiver allows or denies filtering incoming presence.
*/
public void setFilterPresence_in(boolean filterPresence_in) {
this.filterPresence_in = filterPresence_in;
}
/**
* Returns whether the receiver allows or denies incoming presence or not.
*
* @return the iq filtering incoming presence status.
*/
public boolean isFilterPresence_out() {
return filterPresence_out;
}
/**
* Sets whether the receiver allows or denies outgoing presence or not.
*
* @param filterPresence_out indicates if the receiver allows or denies filtering outgoing presence
*/
public void setFilterPresence_out(boolean filterPresence_out) {
this.filterPresence_out = filterPresence_out;
}
/**
* Returns the order where the receiver is processed. List items are processed in
* ascending order.
*
* The order MUST be filled and its value MUST be a non-negative integer
* that is unique among all items in the list.
*
* @return the order number.
*/
public int getOrder() {
return order;
}
/**
* Sets the order where the receiver is processed.
*
* The order MUST be filled and its value MUST be a non-negative integer
* that is unique among all items in the list.
*
* @param order indicates the order in the list.
*/
private void setOrder(int order) {
this.order = order;
}
/**
* Sets the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @param value is the identifier to apply the action.
*/
public void setValue(String value) {
if (!(this.getRule() == null && value == null)) {
this.getRule().setValue(value);
}
}
/**
* Returns the type hold the kind of communication it will allow or block.
* It MUST be filled with one of these values: jid, group or subscription.
*
* @return the type of communication it represent.
*/
public String getType() {
if (this.getRule() == null) {
return null;
} else {
return this.getRule().getType();
}
}
/**
* Returns the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @return the identifier to apply the action.
*/
public String getValue() {
if (this.getRule() == null) {
return null;
} else {
return this.getRule().getValue();
}
}
/**
* Returns whether the receiver allows or denies every kind of communication.
*
* When filterIQ, filterMessage, filterPresence_in and filterPresence_out are not set
* the receiver will block all communications.
*
* @return the all communications status.
*/
public boolean isFilterEverything() {
return !(this.isFilterIQ() || this.isFilterMessage() || this.isFilterPresence_in()
|| this.isFilterPresence_out());
}
private PrivacyRule getRule() {
return rule;
}
private void setRule(PrivacyRule rule) {
this.rule = rule;
}
/**
* Answer an xml representation of the receiver according to the RFC 3921.
*
* @return the text xml representation.
*/
public String toXML() {
StringBuffer buf = new StringBuffer();
buf.append("<item");
if (this.isAllow()) {
buf.append(" action=\"allow\"");
} else {
buf.append(" action=\"deny\"");
}
buf.append(" order=\"").append(getOrder()).append("\"");
if (getType() != null) {
buf.append(" type=\"").append(getType()).append("\"");
}
if (getValue() != null) {
buf.append(" value=\"").append(getValue()).append("\"");
}
if (isFilterEverything()) {
buf.append("/>");
} else {
buf.append(">");
if (this.isFilterIQ()) {
buf.append("<iq/>");
}
if (this.isFilterMessage()) {
buf.append("<message/>");
}
if (this.isFilterPresence_in()) {
buf.append("<presence-in/>");
}
if (this.isFilterPresence_out()) {
buf.append("<presence-out/>");
}
buf.append("</item>");
}
return buf.toString();
}
/**
* Privacy Rule represents the kind of action to apply.
* It holds the kind of communication ([jid|group|subscription]) it will allow or block and
* identifier to apply the action.
*/
public static class PrivacyRule {
/**
* Type defines if the rule is based on JIDs, roster groups or presence subscription types.
* Available values are: [jid|group|subscription]
*/
private String type;
/**
* The value hold the element identifier to apply the action.
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*/
private String value;
/**
* JID being analyzed should have a resource match, domain match or bare JID match.
*/
public static final String GROUP = "group";
/**
* JID being analyzed should belong to a roster group of the list's owner.
*/
public static final String JID = "jid";
/**
* JID being analyzed should belong to a contact present in the owner's roster with
* the specified subscription status.
*/
public static final String SUBSCRIPTION = "subscription";
/**
* If the type is "subscription", then the 'value' attribute MUST be one of "both",
* "to", "from", or "none"
*/
public static final String SUBSCRIPTION_BOTH = "subscription";
public static final String SUBSCRIPTION_TO = "to";
public static final String SUBSCRIPTION_FROM = "from";
public static final String SUBSCRIPTION_NONE = "none";
/**
* Returns the type constant associated with the String value.
*/
protected static PrivacyRule fromString(String value) {
String type = null;
if (value == null) {
return null;
}
if (SUBSCRIPTION.equalsIgnoreCase(value)) {
type = SUBSCRIPTION;
}
else if (GROUP.equalsIgnoreCase(value)) {
type = GROUP;
}
else if (JID.equalsIgnoreCase(value)) {
type = JID;
}
// Default to available.
else {
return null;
}
PrivacyRule rule = new PrivacyRule();
rule.setType(type);
return rule;
}
/**
* Returns the type hold the kind of communication it will allow or block.
* It MUST be filled with one of these values: jid, group or subscription.
*
* @return the type of communication it represent.
*/
public String getType() {
return type;
}
/**
* Sets the action associated with the item, it can allow or deny the communication.
*
* @param allow indicates if the receiver allows or denies the communication.
*/
private void setType(String type) {
this.type = type;
}
/**
* Returns the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @return the identifier to apply the action.
*/
public String getValue() {
return value;
}
/**
* Sets the element identifier to apply the action.
*
* If the type is "jid", then the 'value' attribute MUST contain a valid Jabber ID.
* If the type is "group", then the 'value' attribute SHOULD contain the name of a group
* in the user's roster.
* If the type is "subscription", then the 'value' attribute MUST be one of "both", "to",
* "from", or "none".
*
* @param value is the identifier to apply the action.
*/
protected void setValue(String value) {
if (this.isSuscription()) {
setSuscriptionValue(value);
} else {
this.value = value;
}
}
/**
* Sets the element identifier to apply the action.
*
* The 'value' attribute MUST be one of "both", "to", "from", or "none".
*
* @param value is the identifier to apply the action.
*/
private void setSuscriptionValue(String value) {
String setValue = null;
if (value == null) {
setValue = null;
}
if (SUBSCRIPTION_BOTH.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_BOTH;
}
else if (SUBSCRIPTION_TO.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_TO;
}
else if (SUBSCRIPTION_FROM.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_FROM;
}
else if (SUBSCRIPTION_NONE.equalsIgnoreCase(value)) {
setValue = SUBSCRIPTION_NONE;
}
// Default to available.
else {
setValue = null;
}
this.value = setValue;
}
/**
* Returns if the receiver represents a subscription rule.
*
* @return if the receiver represents a subscription rule.
*/
public boolean isSuscription () {
return this.getValue() == SUBSCRIPTION;
}
}
}

View file

@ -20,29 +20,43 @@
package org.jivesoftware.smack.packet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* Represents a XMPP error sub-packet. Typically, a server responds to a request that has
* problems by sending the packet back and including an error packet. Each error has a code
* as well as as an optional text explanation. Typical error codes are as follows:<p>
* problems by sending the packet back and including an error packet. Each error has a code, type,
* error condition as well as as an optional text explanation. Typical errors are:<p>
*
* <table border=1>
* <tr><td><b>Code</b></td><td><b>Description</b></td></tr>
* <tr><td> 302 </td><td> Redirect </td></tr>
* <tr><td> 400 </td><td> Bad Request </td></tr>
* <tr><td> 401 </td><td> Unauthorized </td></tr>
* <tr><td> 402 </td><td> Payment Required </td></tr>
* <tr><td> 403 </td><td> Forbidden </td></tr>
* <tr><td> 404 </td><td> Not Found </td></tr>
* <tr><td> 405 </td><td> Not Allowed </td></tr>
* <tr><td> 406 </td><td> Not Acceptable </td></tr>
* <tr><td> 407 </td><td> Registration Required </td></tr>
* <tr><td> 408 </td><td> Request Timeout </td></tr>
* <tr><td> 409 </td><td> Conflict </td></tr>
* <tr><td> 500 </td><td> Internal Server XMPPError </td></tr>
* <tr><td> 501 </td><td> Not Implemented </td></tr>
* <tr><td> 502 </td><td> Remote Server Error </td></tr>
* <tr><td> 503 </td><td> Service Unavailable </td></tr>
* <tr><td> 504 </td><td> Remote Server Timeout </td></tr>
* <hr><td><b>Code</b></td><td><b>XMPP Error</b></td><td><b>Type</b></td></hr>
* <tr><td>500</td><td>interna-server-error</td><td>WAIT</td></tr>
* <tr><td>403</td><td>forbidden</td><td>AUTH</td></tr>
* <tr><td>400</td<td>bad-request</td><td>MODIFY</td>></tr>
* <tr><td>404</td><td>item-not-found</td><td>CANCEL</td></tr>
* <tr><td>409</td><td>conflict</td><td>CANCEL</td></tr>
* <tr><td>501</td><td>feature-not-implemented</td><td>CANCEL</td></tr>
* <tr><td>302</td><td>gone</td><td>MODIFY</td></tr>
* <tr><td>400</td><td>jid-malformed</td><td>MODIFY</td></tr>
* <tr><td>406</td><td>no-acceptable</td><td> MODIFY</td></tr>
* <tr><td>405</td><td>not-allowed</td><td>CANCEL</td></tr>
* <tr><td>401</td><td>not-authorized</td><td>AUTH</td></tr>
* <tr><td>402</td><td>payment-required</td><td>AUTH</td></tr>
* <tr><td>404</td><td>recipient-unavailable</td><td>WAIT</td></tr>
* <tr><td>302</td><td>redirect</td><td>MODIFY</td></tr>
* <tr><td>407</td><td>registration-required</td><td>AUTH</td></tr>
* <tr><td>404</td><td>remote-server-not-found</td><td>CANCEL</td></tr>
* <tr><td>504</td><td>remote-server-timeout</td><td>WAIT</td></tr>
* <tr><td>502</td><td>remote-server-error</td><td>CANCEL</td></tr>
* <tr><td>500</td><td>resource-constraint</td><td>WAIT</td></tr>
* <tr><td>503</td><td>service-unavailable</td><td>CANCEL</td></tr>
* <tr><td>407</td><td>subscription-required</td><td>AUTH</td></tr>
* <tr><td>500</td><td>undefined-condition</td><td>WAIT</td></tr>
* <tr><td>400</td><td>unexpected-condition</td><td>WAIT</td></tr>
* <tr><td>408</td><td>request-timeout</td><td>CANCEL</td></tr>
* </table>
*
* @author Matt Tucker
@ -50,29 +64,119 @@ package org.jivesoftware.smack.packet;
public class XMPPError {
private int code;
private Type type;
private String condition;
private String message;
private List applicationExtensions = null;
/**
* Creates a new error with the specified code and no message..
* Creates a new error with the specified condition infering the type and code.
* If the Condition is predefined, client code should be like:
* new XMPPError(XMPPError.Condition.remote_server_timeout);
* If the Condition is not predefined, invocations should be like
* new XMPPError(new XMPPError.Condition("my_own_error"));
*
* @param code the error code.
*/
public XMPPError(Condition condition) {
this.init(condition);
this.message = null;
}
/**
* Creates a new error with the specified condition and message infering the type and code.
* If the Condition is predefined, client code should be like:
* new XMPPError(XMPPError.Condition.remote_server_timeout, "Error Explanation");
* If the Condition is not predefined, invocations should be like
* new XMPPError(new XMPPError.Condition("my_own_error"), "Error Explanation");
*
* @param code the error code.
* @param message a message describing the error.
*/
public XMPPError(Condition condition, String messageText) {
this.init(condition);
this.message = messageText;
}
/**
* Creates a new error with the specified code and no message.
*
* @param code the error code.
* @Deprecated new errors should be created using the constructor XMPPError(condition)
*/
public XMPPError(int code) {
this.code = code;
this.message = null;
}
/**
* Creates a new error with the specified code and message.
* deprecated
*
* @param code the error code.
* @param message a message describing the error.
* @Deprecated new errors should be created using the constructor XMPPError(condition, message)
*/
public XMPPError(int code, String message) {
this.code = code;
this.message = message;
}
/**
* Creates a new error with the specified code, type, condition and message.
* This constructor is used when the condition is not recognized automatically by XMPPError
* i.e. there is not a defined instance of ErrorCondition or it does not applies the default
* specification.
*
* @param code the error code.
* @param type the error type.
* @param condition the error condition.
* @param message a message describing the error.
*/
public XMPPError(int code, Type type, String condition, String message, List extension) {
this.code = code;
this.type = type;
this.condition = condition;
this.message = message;
this.applicationExtensions = extension;
}
/**
* Initialize the error infering the type and code for the received condition.
*
* @param condition the error condition.
*/
private void init(Condition condition) {
// Look for the condition and its default code and type
ErrorSpecification defaultErrorSpecification = ErrorSpecification.specFor(condition);
this.condition = condition.value;
if (defaultErrorSpecification != null) {
// If there is a default error specification for the received condition,
// it get configured with the infered type and code.
this.type = defaultErrorSpecification.getType();
this.code = defaultErrorSpecification.getCode();
}
}
/**
* Returns the error condition.
*
* @return the error condition.
*/
public String getCondition() {
return condition;
}
/**
* Returns the error type.
*
* @return the error type.
*/
public Type getType() {
return type;
}
/**
* Returns the error code.
*
@ -97,21 +201,299 @@ public class XMPPError {
* @return the error as XML.
*/
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<error code=\"").append(code).append("\">");
if (message != null) {
buf.append(message);
StringBuffer buf = new StringBuffer();
buf.append("<error code=\"").append(code).append("\"");
if (type != null) {
buf.append(" type=\"");
buf.append(type.value);
buf.append("\"");
}
buf.append(">");
if (condition != null) {
buf.append("<").append(condition);
buf.append(" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>");
}
if (message != null) {
buf.append("<text xml:lang=\"en\" xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\">");
buf.append(message);
buf.append("</text>");
}
for (Iterator extensions = this.getExtensions(); extensions.hasNext();) {
PacketExtension element = (PacketExtension) extensions.next();
buf.append(element.toXML());
}
buf.append("</error>");
return buf.toString();
}
public String toString() {
StringBuilder txt = new StringBuilder();
StringBuffer txt = new StringBuffer();
txt.append("(").append(code).append(")");
if (message != null) {
txt.append(" ").append(message);
}
return txt.toString();
}
/**
* Returns an Iterator for the error extensions attached to the xmppError.
* An application MAY provide application-specific error information by including a
* properly-namespaced child in the error element.
*
* @return an Iterator for the error extensions.
*/
public synchronized Iterator getExtensions() {
if (applicationExtensions == null) {
return Collections.EMPTY_LIST.iterator();
}
return Collections.unmodifiableList(new ArrayList(applicationExtensions)).iterator();
}
/**
* Returns the first patcket extension that matches the specified element name and
* namespace, or <tt>null</tt> if it doesn't exist.
*
* @param elementName the XML element name of the packet extension.
* @param namespace the XML element namespace of the packet extension.
* @return the extension, or <tt>null</tt> if it doesn't exist.
*/
public synchronized PacketExtension getExtension(String elementName, String namespace) {
if (applicationExtensions == null || elementName == null || namespace == null) {
return null;
}
for (Iterator i=applicationExtensions.iterator(); i.hasNext(); ) {
PacketExtension ext = (PacketExtension)i.next();
if (elementName.equals(ext.getElementName()) && namespace.equals(ext.getNamespace())) {
return ext;
}
}
return null;
}
/**
* Adds a packet extension to the error.
*
* @param extension a packet extension.
*/
public synchronized void addExtension(PacketExtension extension) {
if (applicationExtensions == null) {
applicationExtensions = new ArrayList();
}
applicationExtensions.add(extension);
}
/**
* Set the packet extension to the error.
*
* @param extension a packet extension.
*/
public synchronized void setExtension(List extension) {
applicationExtensions = extension;
}
/**
* A class to represent the type of the Error. The types are:
*
* <ul>
* <li>XMPPError.Type.WAIT - retry after waiting (the error is temporary)
* <li>XMPPError.Type.CANCEL - do not retry (the error is unrecoverable)
* <li>XMPPError.Type.MODIFY - retry after changing the data sent
* <li>XMPPError.Type.AUTH - retry after providing credentials
* <li>XMPPError.Type.CONTINUE - proceed (the condition was only a warning)
* </ul>
*/
public static class Type {
public static final Type WAIT = new Type("wait");
public static final Type CANCEL = new Type("cancel");
public static final Type MODIFY = new Type("modify");
public static final Type AUTH = new Type("auth");
public static final Type CONTINUE = new Type("continue");
/**
* Converts a String into the corresponding types. Valid String values
* that can be converted to types are: "wait", "cancel", "modify", "auth" or a user defined.
*
* @param type the String value to covert.
* @return the corresponding Type.
*/
public static Type fromString(String type) {
if (type == null) {
return null;
}
type = type.toLowerCase();
if (CANCEL.toString().equals(type)) {
return CANCEL;
}
else if (CONTINUE.toString().equals(type)) {
return CONTINUE;
}
else if (WAIT.toString().equals(type)) {
return WAIT;
}
else if (MODIFY.toString().equals(type)) {
return MODIFY;
}
else if (AUTH.toString().equals(type)) {
return AUTH;
}
else {
return null;
}
}
private String value;
private Type(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
/**
* A class to represent predefined error conditions.
*/
public static class Condition {
public static final Condition interna_server_error = new Condition("internal-server-error");
public static final Condition forbidden = new Condition("forbidden");
public static final Condition bad_request = new Condition("bad-request");
public static final Condition conflict = new Condition("conflict");
public static final Condition feature_not_implemented = new Condition("feature-not-implemented");
public static final Condition gone = new Condition("gone");
public static final Condition item_not_found = new Condition("item-not-found");
public static final Condition jid_malformed = new Condition("jid-malformed");
public static final Condition no_acceptable = new Condition("not-acceptable");
public static final Condition not_allowed = new Condition("not-allowed");
public static final Condition not_authorized = new Condition("not-authorized");
public static final Condition payment_required = new Condition("payment-required");
public static final Condition recipient_unavailable = new Condition("recipient-unavailable");
public static final Condition redirect = new Condition("redirect");
public static final Condition registration_required = new Condition("registration-required");
public static final Condition remote_server_error = new Condition("remote-server-error");
public static final Condition remote_server_not_found = new Condition("remote-server-not-found");
public static final Condition remote_server_timeout = new Condition("remote-server-timeout");
public static final Condition resource_constraint = new Condition("resource-constraint");
public static final Condition service_unavailable = new Condition("service-unavailable");
public static final Condition subscription_required = new Condition("subscription-required");
public static final Condition undefined_condition = new Condition("undefined-condition");
public static final Condition unexpected_condition = new Condition("unexpected-condition");
public static final Condition request_timeout = new Condition("request-timeout");
private String value;
public Condition(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
/**
* A class to represent the error specification used to infer common usage.
*/
private static class ErrorSpecification {
private int code;
private Type type;
private Condition condition;
private static HashMap instances = errorSpecifications();
private ErrorSpecification(Condition condition, Type type, int code) {
this.code = code;
this.type = type;
this.condition = condition;
}
private static HashMap errorSpecifications() {
HashMap instances = new HashMap(22);
instances.put(Condition.interna_server_error, new ErrorSpecification(
Condition.interna_server_error, Type.WAIT, 500));
instances.put(Condition.forbidden, new ErrorSpecification(Condition.forbidden,
Type.AUTH, 403));
instances.put(Condition.bad_request, new XMPPError.ErrorSpecification(
Condition.bad_request, Type.MODIFY, 400));
instances.put(Condition.item_not_found, new XMPPError.ErrorSpecification(
Condition.item_not_found, Type.CANCEL, 404));
instances.put(Condition.conflict, new XMPPError.ErrorSpecification(
Condition.conflict, Type.CANCEL, 409));
instances.put(Condition.feature_not_implemented, new XMPPError.ErrorSpecification(
Condition.feature_not_implemented, Type.CANCEL, 501));
instances.put(Condition.gone, new XMPPError.ErrorSpecification(
Condition.gone, Type.MODIFY, 302));
instances.put(Condition.jid_malformed, new XMPPError.ErrorSpecification(
Condition.jid_malformed, Type.MODIFY, 400));
instances.put(Condition.no_acceptable, new XMPPError.ErrorSpecification(
Condition.no_acceptable, Type.MODIFY, 406));
instances.put(Condition.not_allowed, new XMPPError.ErrorSpecification(
Condition.not_allowed, Type.CANCEL, 405));
instances.put(Condition.not_authorized, new XMPPError.ErrorSpecification(
Condition.not_authorized, Type.AUTH, 401));
instances.put(Condition.payment_required, new XMPPError.ErrorSpecification(
Condition.payment_required, Type.AUTH, 402));
instances.put(Condition.recipient_unavailable, new XMPPError.ErrorSpecification(
Condition.recipient_unavailable, Type.WAIT, 404));
instances.put(Condition.redirect, new XMPPError.ErrorSpecification(
Condition.redirect, Type.MODIFY, 302));
instances.put(Condition.registration_required, new XMPPError.ErrorSpecification(
Condition.registration_required, Type.AUTH, 407));
instances.put(Condition.remote_server_not_found, new XMPPError.ErrorSpecification(
Condition.remote_server_not_found, Type.CANCEL, 404));
instances.put(Condition.remote_server_timeout, new XMPPError.ErrorSpecification(
Condition.remote_server_timeout, Type.WAIT, 504));
instances.put(Condition.remote_server_error, new XMPPError.ErrorSpecification(
Condition.remote_server_error, Type.CANCEL, 502));
instances.put(Condition.resource_constraint, new XMPPError.ErrorSpecification(
Condition.resource_constraint, Type.WAIT, 500));
instances.put(Condition.service_unavailable, new XMPPError.ErrorSpecification(
Condition.service_unavailable, Type.CANCEL, 503));
instances.put(Condition.subscription_required, new XMPPError.ErrorSpecification(
Condition.subscription_required, Type.AUTH, 407));
instances.put(Condition.undefined_condition, new XMPPError.ErrorSpecification(
Condition.undefined_condition, Type.WAIT, 500));
instances.put(Condition.unexpected_condition, new XMPPError.ErrorSpecification(
Condition.unexpected_condition, Type.WAIT, 400));
instances.put(Condition.request_timeout, new XMPPError.ErrorSpecification(
Condition.request_timeout, Type.CANCEL, 408));
return instances;
}
protected static ErrorSpecification specFor(Condition condition) {
return (ErrorSpecification) instances.get(condition);
}
/**
* Returns the error condition.
*
* @return the error condition.
*/
protected Condition getCondition() {
return condition;
}
/**
* Returns the error type.
*
* @return the error type.
*/
protected Type getType() {
return type;
}
/**
* Returns the error code.
*
* @return the error code.
*/
protected int getCode() {
return code;
}
}
}