001/** 002 * 003 * Copyright 2003-2007 Jive Software. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.jivesoftware.smack.packet; 019 020import java.util.Locale; 021 022import org.jivesoftware.smack.util.Objects; 023import org.jivesoftware.smack.util.XmlStringBuilder; 024 025/** 026 * The base IQ (Info/Query) packet. IQ packets are used to get and set information 027 * on the server, including authentication, roster operations, and creating 028 * accounts. Each IQ stanza(/packet) has a specific type that indicates what type of action 029 * is being taken: "get", "set", "result", or "error".<p> 030 * 031 * IQ packets can contain a single child element that exists in a specific XML 032 * namespace. The combination of the element name and namespace determines what 033 * type of IQ stanza(/packet) it is. Some example IQ subpacket snippets:<ul> 034 * 035 * <li><query xmlns="jabber:iq:auth"> -- an authentication IQ. 036 * <li><query xmlns="jabber:iq:private"> -- a private storage IQ. 037 * <li><pubsub xmlns="http://jabber.org/protocol/pubsub"> -- a pubsub IQ. 038 * </ul> 039 * 040 * @author Matt Tucker 041 */ 042public abstract class IQ extends Stanza { 043 044 // Don't name this field 'ELEMENT'. When it comes to IQ, ELEMENT is the child element! 045 public static final String IQ_ELEMENT = "iq"; 046 public static final String QUERY_ELEMENT = "query"; 047 048 private final String childElementName; 049 private final String childElementNamespace; 050 051 private Type type = Type.get; 052 053 public IQ(IQ iq) { 054 super(iq); 055 type = iq.getType(); 056 this.childElementName = iq.childElementName; 057 this.childElementNamespace = iq.childElementNamespace; 058 } 059 060 protected IQ(String childElementName) { 061 this(childElementName, null); 062 } 063 064 protected IQ(String childElementName, String childElementNamespace) { 065 this.childElementName = childElementName; 066 this.childElementNamespace = childElementNamespace; 067 } 068 069 /** 070 * Returns the type of the IQ packet. 071 * 072 * @return the type of the IQ packet. 073 */ 074 public Type getType() { 075 return type; 076 } 077 078 /** 079 * Sets the type of the IQ packet. 080 * <p> 081 * Since the type of an IQ must present, an IllegalArgmentException will be thrown when type is 082 * <code>null</code>. 083 * </p> 084 * 085 * @param type the type of the IQ packet. 086 */ 087 public void setType(Type type) { 088 this.type = Objects.requireNonNull(type, "type must not be null"); 089 } 090 091 /** 092 * Return true if this IQ is a request IQ, i.e. an IQ of type {@link Type#get} or {@link Type#set}. 093 * 094 * @return true if IQ type is 'get' or 'set', false otherwise. 095 * @since 4.1 096 */ 097 public boolean isRequestIQ() { 098 switch (type) { 099 case get: 100 case set: 101 return true; 102 default: 103 return false; 104 } 105 } 106 107 public final String getChildElementName() { 108 return childElementName; 109 } 110 111 public final String getChildElementNamespace() { 112 return childElementNamespace; 113 } 114 115 @Override 116 public final String toString() { 117 StringBuilder sb = new StringBuilder(); 118 sb.append("IQ Stanza ("); 119 sb.append(getChildElementName()).append(' ').append(getChildElementNamespace()); 120 sb.append(") ["); 121 logCommonAttributes(sb); 122 sb.append("type=").append(type).append(','); 123 sb.append(']'); 124 return sb.toString(); 125 } 126 127 @Override 128 public final XmlStringBuilder toXML() { 129 XmlStringBuilder buf = new XmlStringBuilder(); 130 buf.halfOpenElement(IQ_ELEMENT); 131 addCommonAttributes(buf); 132 if (type == null) { 133 buf.attribute("type", "get"); 134 } 135 else { 136 buf.attribute("type", type.toString()); 137 } 138 buf.rightAngleBracket(); 139 buf.append(getChildElementXML()); 140 buf.closeElement(IQ_ELEMENT); 141 return buf; 142 } 143 144 /** 145 * Returns the sub-element XML section of the IQ packet, or the empty String if there 146 * isn't one. 147 * 148 * @return the child element section of the IQ XML. 149 */ 150 public final XmlStringBuilder getChildElementXML() { 151 XmlStringBuilder xml = new XmlStringBuilder(); 152 if (type == Type.error) { 153 // Add the error sub-packet, if there is one. 154 appendErrorIfExists(xml); 155 } 156 else if (childElementName != null) { 157 // Add the query section if there is one. 158 IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this)); 159 if (iqChildElement != null) { 160 xml.append(iqChildElement); 161 XmlStringBuilder extensionsXml = getExtensionsXML(); 162 if (iqChildElement.isEmptyElement) { 163 if (extensionsXml.length() == 0) { 164 xml.closeEmptyElement(); 165 return xml; 166 } else { 167 xml.rightAngleBracket(); 168 } 169 } 170 xml.append(extensionsXml); 171 xml.closeElement(iqChildElement.element); 172 } 173 } 174 return xml; 175 } 176 177 /** 178 * This method must be overwritten by IQ subclasses to create their child content. It is important you don't use the builder 179 * <b>to add the final end tag</b>. This will be done automatically by {@link IQChildElementXmlStringBuilder} 180 * after eventual existing {@link ExtensionElement}s have been added. 181 * <p> 182 * For example to create an IQ with a extra attribute and an additional child element 183 * </p> 184 * <pre> 185 * {@code 186 * <iq to='foo@example.org' id='123'> 187 * <bar xmlns='example:bar' extraAttribute='blaz'> 188 * <extraElement>elementText</extraElement> 189 * </bar> 190 * </iq> 191 * } 192 * </pre> 193 * the body of the {@code getIQChildElementBuilder} looks like 194 * <pre> 195 * {@code 196 * // The builder 'xml' will already have the child element and the 'xmlns' attribute added 197 * // So the current builder state is "<bar xmlns='example:bar'" 198 * xml.attribute("extraAttribute", "blaz"); 199 * xml.rightAngleBracket(); 200 * xml.element("extraElement", "elementText"); 201 * // Do not close the 'bar' attribute by calling xml.closeElement('bar') 202 * } 203 * </pre> 204 * If your IQ only contains attributes and no child elements, i.e. it can be represented as empty element, then you 205 * can mark it as such. 206 * <pre> 207 * xml.attribute("myAttribute", "myAttributeValue"); 208 * xml.setEmptyElement(); 209 * </pre> 210 * If your IQ does not contain any attributes or child elements (besides {@link ExtensionElement}s), consider sub-classing 211 * {@link SimpleIQ} instead. 212 * 213 * @param xml a pre-created builder which already has the child element and the 'xmlns' attribute set. 214 * @return the build to create the IQ child content. 215 */ 216 protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml); 217 218 protected final void initialzeAsResultFor(IQ request) { 219 if (!(request.getType() == Type.get || request.getType() == Type.set)) { 220 throw new IllegalArgumentException( 221 "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); 222 } 223 setStanzaId(request.getStanzaId()); 224 setFrom(request.getTo()); 225 setTo(request.getFrom()); 226 setType(Type.result); 227 } 228 229 /** 230 * Convenience method to create a new empty {@link Type#result IQ.Type.result} 231 * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} 232 * IQ. The new stanza(/packet) will be initialized with:<ul> 233 * <li>The sender set to the recipient of the originating IQ. 234 * <li>The recipient set to the sender of the originating IQ. 235 * <li>The type set to {@link Type#result IQ.Type.result}. 236 * <li>The id set to the id of the originating IQ. 237 * <li>No child element of the IQ element. 238 * </ul> 239 * 240 * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet. 241 * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of 242 * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. 243 * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ. 244 */ 245 public static IQ createResultIQ(final IQ request) { 246 return new EmptyResultIQ(request); 247 } 248 249 /** 250 * Convenience method to create a new {@link Type#error IQ.Type.error} IQ 251 * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} 252 * IQ. The new stanza(/packet) will be initialized with:<ul> 253 * <li>The sender set to the recipient of the originating IQ. 254 * <li>The recipient set to the sender of the originating IQ. 255 * <li>The type set to {@link Type#error IQ.Type.error}. 256 * <li>The id set to the id of the originating IQ. 257 * <li>The child element contained in the associated originating IQ. 258 * <li>The provided {@link XMPPError XMPPError}. 259 * </ul> 260 * 261 * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet. 262 * @param error the error to associate with the created IQ packet. 263 * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of 264 * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. 265 * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ. 266 */ 267 public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Builder error) { 268 if (!(request.getType() == Type.get || request.getType() == Type.set)) { 269 throw new IllegalArgumentException( 270 "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML()); 271 } 272 final ErrorIQ result = new ErrorIQ(error); 273 result.setStanzaId(request.getStanzaId()); 274 result.setFrom(request.getTo()); 275 result.setTo(request.getFrom()); 276 277 error.setStanza(result); 278 279 return result; 280 } 281 282 public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Condition condition) { 283 return createErrorResponse(request, XMPPError.getBuilder(condition)); 284 } 285 286 /** 287 * Convenience method to create a new {@link Type#error IQ.Type.error} IQ 288 * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} 289 * IQ. The new stanza(/packet) will be initialized with:<ul> 290 * <li>The sender set to the recipient of the originating IQ. 291 * <li>The recipient set to the sender of the originating IQ. 292 * <li>The type set to {@link Type#error IQ.Type.error}. 293 * <li>The id set to the id of the originating IQ. 294 * <li>The child element contained in the associated originating IQ. 295 * <li>The provided {@link XMPPError XMPPError}. 296 * </ul> 297 * 298 * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet. 299 * @param error the error to associate with the created IQ packet. 300 * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of 301 * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. 302 * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ. 303 */ 304 public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) { 305 return createErrorResponse(request, XMPPError.getBuilder(error)); 306 } 307 308 /** 309 * A enum to represent the type of the IQ stanza. 310 */ 311 public enum Type { 312 313 /** 314 * The IQ stanza requests information, inquires about what data is needed in order to complete further operations, etc. 315 */ 316 get, 317 318 /** 319 * The IQ stanza provides data that is needed for an operation to be completed, sets new values, replaces existing values, etc. 320 */ 321 set, 322 323 /** 324 * The IQ stanza is a response to a successful get or set request. 325 */ 326 result, 327 328 /** 329 * The IQ stanza reports an error that has occurred regarding processing or delivery of a get or set request. 330 */ 331 error, 332 ; 333 334 /** 335 * Converts a String into the corresponding types. Valid String values 336 * that can be converted to types are: "get", "set", "result", and "error". 337 * 338 * @param string the String value to covert. 339 * @return the corresponding Type. 340 * @throws IllegalArgumentException when not able to parse the string parameter 341 * @throws NullPointerException if the string is null 342 */ 343 public static Type fromString(String string) { 344 return Type.valueOf(string.toLowerCase(Locale.US)); 345 } 346 } 347 348 public static class IQChildElementXmlStringBuilder extends XmlStringBuilder { 349 private final String element; 350 351 private boolean isEmptyElement; 352 353 private IQChildElementXmlStringBuilder(IQ iq) { 354 this(iq.getChildElementName(), iq.getChildElementNamespace()); 355 } 356 357 public IQChildElementXmlStringBuilder(ExtensionElement pe) { 358 this(pe.getElementName(), pe.getNamespace()); 359 } 360 361 private IQChildElementXmlStringBuilder(String element, String namespace) { 362 prelude(element, namespace); 363 this.element = element; 364 } 365 366 public void setEmptyElement() { 367 isEmptyElement = true; 368 } 369 } 370}