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>&lt;query xmlns="jabber:iq:auth"&gt; -- an authentication IQ.
037 *  <li>&lt;query xmlns="jabber:iq:private"&gt; -- a private storage IQ.
038 *  <li>&lt;pubsub xmlns="http://jabber.org/protocol/pubsub"&gt; -- 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(&quot;myAttribute&quot;, &quot;myAttributeValue&quot;);
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}