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>&lt;query xmlns="jabber:iq:auth"&gt; -- an authentication IQ.
036 *  <li>&lt;query xmlns="jabber:iq:private"&gt; -- a private storage IQ.
037 *  <li>&lt;pubsub xmlns="http://jabber.org/protocol/pubsub"&gt; -- 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(&quot;myAttribute&quot;, &quot;myAttributeValue&quot;);
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    /**
219     * @deprecated use {@link #initializeAsResultFor(IQ)} instead.
220     */
221    @Deprecated
222    protected final void initialzeAsResultFor(IQ request) {
223        initializeAsResultFor(request);
224    }
225
226    protected final void initializeAsResultFor(IQ request) {
227        if (!(request.getType() == Type.get || request.getType() == Type.set)) {
228            throw new IllegalArgumentException(
229                    "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
230        }
231        setStanzaId(request.getStanzaId());
232        setFrom(request.getTo());
233        setTo(request.getFrom());
234        setType(Type.result);
235    }
236
237    /**
238     * Convenience method to create a new empty {@link Type#result IQ.Type.result}
239     * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
240     * IQ. The new stanza(/packet) will be initialized with:<ul>
241     *      <li>The sender set to the recipient of the originating IQ.
242     *      <li>The recipient set to the sender of the originating IQ.
243     *      <li>The type set to {@link Type#result IQ.Type.result}.
244     *      <li>The id set to the id of the originating IQ.
245     *      <li>No child element of the IQ element.
246     * </ul>
247     *
248     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
249     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
250     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
251     * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ.
252     */
253    public static IQ createResultIQ(final IQ request) {
254        return new EmptyResultIQ(request);
255    }
256
257    /**
258     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
259     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
260     * IQ. The new stanza(/packet) will be initialized with:<ul>
261     *      <li>The sender set to the recipient of the originating IQ.
262     *      <li>The recipient set to the sender of the originating IQ.
263     *      <li>The type set to {@link Type#error IQ.Type.error}.
264     *      <li>The id set to the id of the originating IQ.
265     *      <li>The child element contained in the associated originating IQ.
266     *      <li>The provided {@link XMPPError XMPPError}.
267     * </ul>
268     *
269     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
270     * @param error the error to associate with the created IQ packet.
271     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
272     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
273     * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
274     */
275    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Builder error) {
276        if (!(request.getType() == Type.get || request.getType() == Type.set)) {
277            throw new IllegalArgumentException(
278                    "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
279        }
280        final ErrorIQ result = new ErrorIQ(error);
281        result.setStanzaId(request.getStanzaId());
282        result.setFrom(request.getTo());
283        result.setTo(request.getFrom());
284
285        error.setStanza(result);
286
287        return result;
288    }
289
290    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Condition condition) {
291        return createErrorResponse(request, XMPPError.getBuilder(condition));
292    }
293
294    /**
295     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
296     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
297     * IQ. The new stanza(/packet) will be initialized with:<ul>
298     *      <li>The sender set to the recipient of the originating IQ.
299     *      <li>The recipient set to the sender of the originating IQ.
300     *      <li>The type set to {@link Type#error IQ.Type.error}.
301     *      <li>The id set to the id of the originating IQ.
302     *      <li>The child element contained in the associated originating IQ.
303     *      <li>The provided {@link XMPPError XMPPError}.
304     * </ul>
305     *
306     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
307     * @param error the error to associate with the created IQ packet.
308     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
309     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
310     * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
311     */
312    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) {
313        return createErrorResponse(request, XMPPError.getBuilder(error));
314    }
315
316    /**
317     * A enum to represent the type of the IQ stanza.
318     */
319    public enum Type {
320
321        /**
322         * The IQ stanza requests information, inquires about what data is needed in order to complete further operations, etc.
323         */
324        get,
325
326        /**
327         * The IQ stanza provides data that is needed for an operation to be completed, sets new values, replaces existing values, etc.
328         */
329        set,
330
331        /**
332         * The IQ stanza is a response to a successful get or set request.
333         */
334        result,
335
336        /**
337         * The IQ stanza reports an error that has occurred regarding processing or delivery of a get or set request.
338         */
339        error,
340        ;
341
342        /**
343         * Converts a String into the corresponding types. Valid String values
344         * that can be converted to types are: "get", "set", "result", and "error".
345         *
346         * @param string the String value to covert.
347         * @return the corresponding Type.
348         * @throws IllegalArgumentException when not able to parse the string parameter
349         * @throws NullPointerException if the string is null
350         */
351        public static Type fromString(String string) {
352            return Type.valueOf(string.toLowerCase(Locale.US));
353        }
354    }
355
356    public static class IQChildElementXmlStringBuilder extends XmlStringBuilder {
357        private final String element;
358
359        private boolean isEmptyElement;
360
361        private IQChildElementXmlStringBuilder(IQ iq) {
362            this(iq.getChildElementName(), iq.getChildElementNamespace());
363        }
364
365        public IQChildElementXmlStringBuilder(ExtensionElement pe) {
366            this(pe.getElementName(), pe.getNamespace());
367        }
368
369        private IQChildElementXmlStringBuilder(String element, String namespace) {
370            prelude(element, namespace);
371            this.element = element;
372        }
373
374        public void setEmptyElement() {
375            isEmptyElement = true;
376        }
377    }
378}