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 this(iqBuilder, childElementName != null ? new QName(childElementNamespace, childElementName) : null); 072 } 073 074 protected IQ(AbstractIqBuilder<?> iqBuilder, QName childElementQName) { 075 super(iqBuilder); 076 077 type = iqBuilder.type; 078 079 if (childElementQName != null) { 080 this.childElementQName = childElementQName; 081 this.childElementName = childElementQName.getLocalPart(); 082 this.childElementNamespace = childElementQName.getNamespaceURI(); 083 } else { 084 this.childElementQName = null; 085 this.childElementName = null; 086 this.childElementNamespace = null; 087 } 088 } 089 090 @Override 091 public Type getType() { 092 return type; 093 } 094 095 /** 096 * Sets the type of the IQ packet. 097 * <p> 098 * Since the type of an IQ must present, an IllegalArgmentException will be thrown when type is 099 * <code>null</code>. 100 * </p> 101 * 102 * @param type the type of the IQ packet. 103 */ 104 // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone. 105 public void setType(Type type) { 106 this.type = Objects.requireNonNull(type, "type must not be null"); 107 } 108 109 public final QName getChildElementQName() { 110 return childElementQName; 111 } 112 113 public final String getChildElementName() { 114 return childElementName; 115 } 116 117 public final String getChildElementNamespace() { 118 return childElementNamespace; 119 } 120 121 @Override 122 public final String getElementName() { 123 return IQ_ELEMENT; 124 } 125 126 @Override 127 public final String toString() { 128 StringBuilder sb = new StringBuilder(); 129 sb.append("IQ Stanza ("); 130 sb.append(getChildElementName()).append(' ').append(getChildElementNamespace()); 131 sb.append(") ["); 132 logCommonAttributes(sb); 133 sb.append("type=").append(type).append(','); 134 sb.append(']'); 135 return sb.toString(); 136 } 137 138 @Override 139 public final XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) { 140 XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment); 141 addCommonAttributes(buf); 142 if (type == null) { 143 buf.attribute("type", "get"); 144 } 145 else { 146 buf.attribute("type", type.toString()); 147 } 148 buf.rightAngleBracket(); 149 appendInnerXml(buf); 150 buf.closeElement(IQ_ELEMENT); 151 return buf; 152 } 153 154 /** 155 * Returns the sub-element XML section of the IQ packet, or the empty String if there 156 * isn't one. 157 * 158 * @return the child element section of the IQ XML. 159 */ 160 // TODO: This method should not be part of the public API as it is mostly used for testing purposes, with the one 161 // exception of AdHocCommand.getRaw(). 162 public final XmlStringBuilder getChildElementXML() { 163 XmlStringBuilder xml = new XmlStringBuilder(); 164 appendInnerXml(xml); 165 return xml; 166 } 167 168 /** 169 * Append the sub-element XML section of the IQ stanza. 170 * 171 * @param xml the XmlStringBuilder to append to. 172 */ 173 private void appendInnerXml(XmlStringBuilder xml) { 174 if (type == Type.error) { 175 // Add the error sub-packet, if there is one. 176 appendErrorIfExists(xml); 177 } 178 if (childElementName == null) { 179 return; 180 } 181 182 // Add the query section if there is one. 183 IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder( 184 new IQChildElementXmlStringBuilder(getChildElementName(), getChildElementNamespace(), null, xml.getXmlEnvironment())); 185 // TOOD: Document the cases where iqChildElement is null but childElementName not. And if there are none, change 186 // the logic. 187 if (iqChildElement == null) { 188 return; 189 } 190 191 xml.append(iqChildElement); 192 193 List<XmlElement> extensionsXml = getExtensions(); 194 if (iqChildElement.isEmptyElement) { 195 if (extensionsXml.isEmpty()) { 196 xml.closeEmptyElement(); 197 return; 198 } 199 200 xml.rightAngleBracket(); 201 } 202 203 xml.append(extensionsXml); 204 xml.closeElement(iqChildElement.element); 205 } 206 207 /** 208 * This method must be overwritten by IQ subclasses to create their child content. It is important you don't use the builder 209 * <b>to add the final end tag</b>. This will be done automatically by {@link IQChildElementXmlStringBuilder} 210 * after eventual existing {@link ExtensionElement}s have been added. 211 * <p> 212 * For example to create an IQ with a extra attribute and an additional child element 213 * </p> 214 * <pre> 215 * {@code 216 * <iq to='foo@example.org' id='123'> 217 * <bar xmlns='example:bar' extraAttribute='blaz'> 218 * <extraElement>elementText</extraElement> 219 * </bar> 220 * </iq> 221 * } 222 * </pre> 223 * the body of the {@code getIQChildElementBuilder} looks like 224 * <pre> 225 * {@code 226 * // The builder 'xml' will already have the child element and the 'xmlns' attribute added 227 * // So the current builder state is "<bar xmlns='example:bar'" 228 * xml.attribute("extraAttribute", "blaz"); 229 * xml.rightAngleBracket(); 230 * xml.element("extraElement", "elementText"); 231 * // Do not close the 'bar' attribute by calling xml.closeElement('bar') 232 * } 233 * </pre> 234 * If your IQ only contains attributes and no child elements, i.e. it can be represented as empty element, then you 235 * can mark it as such. 236 * <pre> 237 * xml.attribute("myAttribute", "myAttributeValue"); 238 * xml.setEmptyElement(); 239 * </pre> 240 * If your IQ does not contain any attributes or child elements (besides {@link ExtensionElement}s), consider sub-classing 241 * {@link SimpleIQ} instead. 242 * 243 * @param xml a pre-created builder which already has the child element and the 'xmlns' attribute set. 244 * @return the build to create the IQ child content. 245 */ 246 protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml); 247 248 /** 249 * Convenience method to create a new empty {@link Type#result IQ.Type.result} 250 * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} 251 * IQ. The new stanza will be initialized with:<ul> 252 * <li>The sender set to the recipient of the originating IQ. 253 * <li>The recipient set to the sender of the originating IQ. 254 * <li>The type set to {@link Type#result IQ.Type.result}. 255 * <li>The id set to the id of the originating IQ. 256 * <li>No child element of the IQ element. 257 * </ul> 258 * 259 * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet. 260 * @throws IllegalArgumentException if the IQ stanza does not have a type of 261 * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. 262 * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ. 263 */ 264 public static IQ createResultIQ(final IQ request) { 265 return new EmptyResultIQ(request); 266 } 267 268 /** 269 * Convenience method to create a new {@link Type#error IQ.Type.error} IQ 270 * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} 271 * IQ. The new stanza will be initialized with:<ul> 272 * <li>The sender set to the recipient of the originating IQ. 273 * <li>The recipient set to the sender of the originating IQ. 274 * <li>The type set to {@link Type#error IQ.Type.error}. 275 * <li>The id set to the id of the originating IQ. 276 * <li>The child element contained in the associated originating IQ. 277 * <li>The provided {@link StanzaError XMPPError}. 278 * </ul> 279 * 280 * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet. 281 * @param error the error to associate with the created IQ packet. 282 * @throws IllegalArgumentException if the IQ stanza does not have a type of 283 * {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}. 284 * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ. 285 */ 286 public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) { 287 return ErrorIQ.createErrorResponse(request, error); 288 } 289 290 /** 291 * Deprecated. 292 * 293 * @param request the request. 294 * @param error the error. 295 * @return an error IQ. 296 * @deprecated use {@link #createErrorResponse(IQ, StanzaError)} instead. 297 */ 298 @Deprecated 299 // TODO: Remove in Smack 4.5. 300 public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Builder error) { 301 return createErrorResponse(request, error.build()); 302 } 303 304 public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Condition condition) { 305 return createErrorResponse(request, StanzaError.getBuilder(condition).build()); 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 enum ResponseType { 349 350 result(Type.result), 351 352 error(Type.error), 353 354 ; 355 356 final Type type; 357 358 ResponseType(Type type) { 359 this.type = type; 360 } 361 362 Type getType() { 363 return type; 364 } 365 } 366 367 public static class IQChildElementXmlStringBuilder extends XmlStringBuilder { 368 private final String element; 369 370 private boolean isEmptyElement; 371 372 public IQChildElementXmlStringBuilder(ExtensionElement extensionElement, 373 XmlEnvironment enclosingXmlEnvironment) { 374 this(extensionElement.getElementName(), extensionElement.getNamespace(), extensionElement.getLanguage(), 375 enclosingXmlEnvironment); 376 } 377 378 private IQChildElementXmlStringBuilder(String elementName, String xmlNs, String xmlLang, 379 XmlEnvironment enclosingXmlEnvironment) { 380 super(elementName, xmlNs, xmlLang, enclosingXmlEnvironment); 381 this.element = elementName; 382 } 383 384 public void setEmptyElement() { 385 isEmptyElement = true; 386 } 387 } 388}