001: /**
002: * Copyright (C) 2004-2007 Jive Software. All rights reserved.
003: *
004: * This software is published under the terms of the GNU Public License (GPL),
005: * a copy of which is included in this distribution.
006: */package org.xmpp.packet;
007:
008: import org.dom4j.DocumentFactory;
009: import org.dom4j.Element;
010: import org.dom4j.QName;
011: import org.dom4j.io.OutputFormat;
012: import org.dom4j.io.XMLWriter;
013:
014: import java.io.StringWriter;
015: import java.util.Iterator;
016:
017: /**
018: * A stream error. Stream errors have a condition and they
019: * can optionally include explanation text.
020: *
021: * @author Matt Tucker
022: */
023: public class StreamError {
024:
025: private static final String ERROR_NAMESPACE = "urn:ietf:params:xml:ns:xmpp-streams";
026:
027: private static DocumentFactory docFactory = DocumentFactory
028: .getInstance();
029:
030: private Element element;
031:
032: /**
033: * Construcs a new StreamError with the specified condition.
034: *
035: * @param condition the error condition.
036: */
037: public StreamError(Condition condition) {
038: this .element = docFactory.createElement(docFactory.createQName(
039: "error", "stream", "http://etherx.jabber.org/streams"));
040: setCondition(condition);
041: }
042:
043: /**
044: * Constructs a new StreamError with the specified condition and error text.
045: *
046: * @param condition the error condition.
047: * @param text the text description of the error.
048: */
049: public StreamError(Condition condition, String text) {
050: this .element = docFactory.createElement(docFactory.createQName(
051: "error", "stream", "http://etherx.jabber.org/streams"));
052: setCondition(condition);
053: setText(text, null);
054: }
055:
056: /**
057: * Constructs a new StreamError with the specified condition and error text.
058: *
059: * @param condition the error condition.
060: * @param text the text description of the error.
061: * @param language the language code of the error description (e.g. "en").
062: */
063: public StreamError(Condition condition, String text, String language) {
064: this .element = docFactory.createElement(docFactory.createQName(
065: "error", "stream", "http://etherx.jabber.org/streams"));
066: setCondition(condition);
067: setText(text, language);
068: }
069:
070: /**
071: * Constructs a new StreamError using an existing Element. This is useful
072: * for parsing incoming error Elements into StreamError objects.
073: *
074: * @param element the stream error Element.
075: */
076: public StreamError(Element element) {
077: this .element = element;
078: }
079:
080: /**
081: * Returns the error condition.
082: *
083: * @return the error condition.
084: * @see Condition
085: */
086: public Condition getCondition() {
087: for (Iterator i = element.elementIterator(); i.hasNext();) {
088: Element el = (Element) i.next();
089: if (el.getNamespaceURI().equals(ERROR_NAMESPACE)
090: && !el.getName().equals("text")) {
091: return Condition.fromXMPP(el.getName());
092: }
093: }
094: return null;
095: }
096:
097: /**
098: * Sets the error condition.
099: *
100: * @param condition the error condition.
101: * @see Condition
102: */
103: public void setCondition(Condition condition) {
104: if (condition == null) {
105: throw new NullPointerException("Condition cannot be null");
106: }
107: Element conditionElement = null;
108: for (Iterator i = element.elementIterator(); i.hasNext();) {
109: Element el = (Element) i.next();
110: if (el.getNamespaceURI().equals(ERROR_NAMESPACE)
111: && !el.getName().equals("text")) {
112: conditionElement = el;
113: }
114: }
115: if (conditionElement != null) {
116: element.remove(conditionElement);
117: }
118:
119: conditionElement = docFactory.createElement(condition.toXMPP(),
120: ERROR_NAMESPACE);
121: element.add(conditionElement);
122: }
123:
124: /**
125: * Returns a text description of the error, or <tt>null</tt> if there
126: * is no text description.
127: *
128: * @return the text description of the error.
129: */
130: public String getText() {
131: return element.elementText("text");
132: }
133:
134: /**
135: * Sets the text description of the error.
136: *
137: * @param text the text description of the error.
138: */
139: public void setText(String text) {
140: setText(text, null);
141: }
142:
143: /**
144: * Sets the text description of the error. Optionally, a language code
145: * can be specified to indicate the language of the description.
146: *
147: * @param text the text description of the error.
148: * @param language the language code of the description, or <tt>null</tt> to specify
149: * no language code.
150: */
151: public void setText(String text, String language) {
152: Element textElement = element.element("text");
153: // If text is null, clear the text.
154: if (text == null) {
155: if (textElement != null) {
156: element.remove(textElement);
157: }
158: return;
159: }
160:
161: if (textElement == null) {
162: textElement = docFactory.createElement("text",
163: ERROR_NAMESPACE);
164: if (language != null) {
165: textElement.addAttribute(QName.get("lang", "xml",
166: "http://www.w3.org/XML/1998/namespace"),
167: language);
168: }
169: element.add(textElement);
170: }
171: textElement.setText(text);
172: }
173:
174: /**
175: * Returns the text description's language code, or <tt>null</tt> if there
176: * is no language code associated with the description text.
177: *
178: * @return the language code of the text description, if it exists.
179: */
180: public String getTextLanguage() {
181: Element textElement = element.element("text");
182: if (textElement != null) {
183: return textElement.attributeValue(QName.get("lang", "xml",
184: "http://www.w3.org/XML/1998/namespace"));
185: }
186: return null;
187: }
188:
189: /**
190: * Returns the DOM4J Element that backs the error. The element is the definitive
191: * representation of the error and can be manipulated directly to change
192: * error contents.
193: *
194: * @return the DOM4J Element.
195: */
196: public Element getElement() {
197: return element;
198: }
199:
200: /**
201: * Returns the textual XML representation of this stream error.
202: *
203: * @return the textual XML representation of this stream error.
204: */
205: public String toXML() {
206: return element.asXML();
207: }
208:
209: public String toString() {
210: StringWriter out = new StringWriter();
211: XMLWriter writer = new XMLWriter(out, OutputFormat
212: .createPrettyPrint());
213: try {
214: writer.write(element);
215: } catch (Exception e) {
216: }
217: return out.toString();
218: }
219:
220: /**
221: * Type-safe enumeration for the error condition.<p>
222: *
223: * Implementation note: XMPP error conditions use "-" characters in
224: * their names such as "bad-request". Because "-" characters are not valid
225: * identifier parts in Java, they have been converted to "_" characters in
226: * the enumeration names, such as <tt>bad_request</tt>. The {@link #toXMPP()} and
227: * {@link #fromXMPP(String)} methods can be used to convert between the
228: * enumertation values and XMPP error code strings.
229: */
230: public enum Condition {
231:
232: /**
233: * The entity has sent XML that cannot be processed; this error MAY be used
234: * instead of the more specific XML-related errors, such as <bad-namespace-prefix/>,
235: * <invalid-xml/>, <restricted-xml/>, <unsupported-encoding/>, and
236: * <xml-not-well-formed/>, although the more specific errors are preferred.
237: */
238: bad_format("bad-format"),
239:
240: /**
241: * The entity has sent a namespace prefix that is unsupported, or has sent no
242: * namespace prefix on an element that requires such a prefix.
243: */
244: bad_namespace_prefix("bad-namespace-prefix"),
245:
246: /**
247: * The server is closing the active stream for this entity because a new stream
248: * has been initiated that conflicts with the existing stream.
249: */
250: conflict("conflict"),
251:
252: /**
253: * The entity has not generated any traffic over the stream for some period of
254: * time (configurable according to a local service policy).
255: */
256: connection_timeout("connection-timeout"),
257:
258: /**
259: * The value of the 'to' attribute provided by the initiating entity in the
260: * stream header corresponds to a hostname that is no longer hosted by the server.
261: */
262: host_gone("host-gone"),
263:
264: /**
265: * The value of the 'to' attribute provided by the initiating entity in the
266: * stream header does not correspond to a hostname that is hosted by the server.
267: */
268: host_unknown("host-unknown"),
269:
270: /**
271: * A stanza sent between two servers lacks a 'to' or 'from' attribute
272: * (or the attribute has no value).
273: */
274: improper_addressing("improper-addressing"),
275:
276: /**
277: * The server has experienced a misconfiguration or an otherwise-undefined
278: * internal error that prevents it from servicing the stream.
279: */
280: internal_server_error("internal-server-error"),
281:
282: /**
283: * The JID or hostname provided in a 'from' address does not match an authorized
284: * JID or validated domain negotiated between servers via SASL or dialback, or
285: * between a client and a server via authentication and resource binding.
286: */
287: invalid_from("invalid-from"),
288:
289: /**
290: * The stream ID or dialback ID is invalid or does not match an ID previously provided.
291: */
292: invalid_id("invalid-id"),
293:
294: /**
295: * the streams namespace name is something other than "http://etherx.jabber.org/streams"
296: * or the dialback namespace name is something other than "jabber:server:dialback".
297: */
298: invalid_namespace("invalid-namespace"),
299:
300: /**
301: * The entity has sent invalid XML over the stream to a server that performs validation.
302: */
303: invalid_xml("invalid-xml"),
304:
305: /**
306: * The entity has attempted to send data before the stream has been authenticated,
307: * or otherwise is not authorized to perform an action related to stream
308: * negotiation; the receiving entity MUST NOT process the offending stanza before
309: * sending the stream error.
310: */
311: not_authorized("not-authorized"),
312:
313: /**
314: * The entity has violated some local service policy; the server MAY choose to
315: * specify the policy in the <text/> element or an application-specific condition
316: * element.
317: */
318: policy_violation("policy-violation"),
319:
320: /**
321: * The server is unable to properly connect to a remote entity that is required for
322: * authentication or authorization.
323: */
324: remote_connection_failed("remote-connection-failed"),
325:
326: /**
327: * The server lacks the system resources necessary to service the stream.
328: */
329: resource_constraint("resource-constraint"),
330:
331: /**
332: * The entity has attempted to send restricted XML features such as a comment,
333: * processing instruction, DTD, entity reference, or unescaped character.
334: */
335: restricted_xml("restricted-xml"),
336:
337: /**
338: * The server will not provide service to the initiating entity but is redirecting
339: * traffic to another host; the server SHOULD specify the alternate hostname or IP
340: * address (which MUST be a valid domain identifier) as the XML character data of the
341: * <see-other-host/> element.
342: */
343: see_other_host("see-other-host"),
344:
345: /**
346: * The server is being shut down and all active streams are being closed.
347: */
348: system_shutdown("system-shutdown"),
349:
350: /**
351: * The error condition is not one of those defined by the other conditions in this
352: * list; this error condition SHOULD be used only in conjunction with an
353: * application-specific condition.
354: */
355: undefined_condition("undefined-condition"),
356:
357: /**
358: * The initiating entity has encoded the stream in an encoding that is not
359: * supported by the server.
360: */
361: unsupported_encoding("unsupported-encoding"),
362:
363: /**
364: * The initiating entity has sent a first-level child of the stream that is
365: * not supported by the server.
366: */
367: unsupported_stanza_type("unsupported-stanza-type"),
368:
369: /**
370: * the value of the 'version' attribute provided by the initiating entity in the
371: * stream header specifies a version of XMPP that is not supported by the server;
372: * the server MAY specify the version(s) it supports in the <text/> element.
373: */
374: unsupported_version("unsupported-version"),
375:
376: /**
377: * The initiating entity has sent XML that is not well-formed.
378: */
379: xml_not_well_formed("xml-not-well-formed");
380:
381: /**
382: * Converts a String value into its Condition representation.
383: *
384: * @param condition the String value.
385: * @return the condition corresponding to the String.
386: */
387: public static Condition fromXMPP(String condition) {
388: if (condition == null) {
389: throw new NullPointerException();
390: }
391: condition = condition.toLowerCase();
392: if (bad_format.toXMPP().equals(condition)) {
393: return bad_format;
394: } else if (bad_namespace_prefix.toXMPP().equals(condition)) {
395: return bad_namespace_prefix;
396: } else if (conflict.toXMPP().equals(condition)) {
397: return conflict;
398: } else if (connection_timeout.toXMPP().equals(condition)) {
399: return connection_timeout;
400: } else if (host_gone.toXMPP().equals(condition)) {
401: return host_gone;
402: } else if (host_unknown.toXMPP().equals(condition)) {
403: return host_unknown;
404: } else if (improper_addressing.toXMPP().equals(condition)) {
405: return improper_addressing;
406: } else if (internal_server_error.toXMPP().equals(condition)) {
407: return internal_server_error;
408: } else if (invalid_from.toXMPP().equals(condition)) {
409: return invalid_from;
410: } else if (invalid_id.toXMPP().equals(condition)) {
411: return invalid_id;
412: } else if (invalid_namespace.toXMPP().equals(condition)) {
413: return invalid_namespace;
414: } else if (invalid_xml.toXMPP().equals(condition)) {
415: return invalid_xml;
416: } else if (not_authorized.toXMPP().equals(condition)) {
417: return not_authorized;
418: } else if (policy_violation.toXMPP().equals(condition)) {
419: return policy_violation;
420: } else if (remote_connection_failed.toXMPP().equals(
421: condition)) {
422: return remote_connection_failed;
423: } else if (resource_constraint.toXMPP().equals(condition)) {
424: return resource_constraint;
425: } else if (restricted_xml.toXMPP().equals(condition)) {
426: return restricted_xml;
427: } else if (see_other_host.toXMPP().equals(condition)) {
428: return see_other_host;
429: } else if (system_shutdown.toXMPP().equals(condition)) {
430: return system_shutdown;
431: } else if (undefined_condition.toXMPP().equals(condition)) {
432: return undefined_condition;
433: } else if (unsupported_encoding.toXMPP().equals(condition)) {
434: return unsupported_encoding;
435: } else if (unsupported_stanza_type.toXMPP().equals(
436: condition)) {
437: return unsupported_stanza_type;
438: } else if (unsupported_version.toXMPP().equals(condition)) {
439: return unsupported_version;
440: } else if (xml_not_well_formed.toXMPP().equals(condition)) {
441: return xml_not_well_formed;
442: } else {
443: throw new IllegalArgumentException("Condition invalid:"
444: + condition);
445: }
446: }
447:
448: private String value;
449:
450: private Condition(String value) {
451: this .value = value;
452: }
453:
454: /**
455: * Returns the error code as a valid XMPP error code string.
456: *
457: * @return the XMPP error code value.
458: */
459: public String toXMPP() {
460: return value;
461: }
462: }
463: }
|