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