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