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    protected final void initialzeAsResultFor(IQ request) {
219        if (!(request.getType() == Type.get || request.getType() == Type.set)) {
220            throw new IllegalArgumentException(
221                    "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
222        }
223        setStanzaId(request.getStanzaId());
224        setFrom(request.getTo());
225        setTo(request.getFrom());
226        setType(Type.result);
227    }
228
229    /**
230     * Convenience method to create a new empty {@link Type#result IQ.Type.result}
231     * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
232     * IQ. The new stanza(/packet) will be initialized with:<ul>
233     *      <li>The sender set to the recipient of the originating IQ.
234     *      <li>The recipient set to the sender of the originating IQ.
235     *      <li>The type set to {@link Type#result IQ.Type.result}.
236     *      <li>The id set to the id of the originating IQ.
237     *      <li>No child element of the IQ element.
238     * </ul>
239     *
240     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
241     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
242     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
243     * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ.
244     */
245    public static IQ createResultIQ(final IQ request) {
246        return new EmptyResultIQ(request);
247    }
248
249    /**
250     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
251     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
252     * IQ. The new stanza(/packet) will be initialized with:<ul>
253     *      <li>The sender set to the recipient of the originating IQ.
254     *      <li>The recipient set to the sender of the originating IQ.
255     *      <li>The type set to {@link Type#error IQ.Type.error}.
256     *      <li>The id set to the id of the originating IQ.
257     *      <li>The child element contained in the associated originating IQ.
258     *      <li>The provided {@link XMPPError XMPPError}.
259     * </ul>
260     *
261     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
262     * @param error the error to associate with the created IQ packet.
263     * @throws IllegalArgumentException if the IQ stanza(/packet) 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#error IQ.Type.error} IQ based on the originating IQ.
266     */
267    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Builder error) {
268        if (!(request.getType() == Type.get || request.getType() == Type.set)) {
269            throw new IllegalArgumentException(
270                    "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
271        }
272        final ErrorIQ result = new ErrorIQ(error);
273        result.setStanzaId(request.getStanzaId());
274        result.setFrom(request.getTo());
275        result.setTo(request.getFrom());
276
277        error.setStanza(result);
278
279        return result;
280    }
281
282    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError.Condition condition) {
283        return createErrorResponse(request, XMPPError.getBuilder(condition));
284    }
285
286    /**
287     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
288     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
289     * IQ. The new stanza(/packet) will be initialized with:<ul>
290     *      <li>The sender set to the recipient of the originating IQ.
291     *      <li>The recipient set to the sender of the originating IQ.
292     *      <li>The type set to {@link Type#error IQ.Type.error}.
293     *      <li>The id set to the id of the originating IQ.
294     *      <li>The child element contained in the associated originating IQ.
295     *      <li>The provided {@link XMPPError XMPPError}.
296     * </ul>
297     *
298     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
299     * @param error the error to associate with the created IQ packet.
300     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
301     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
302     * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
303     */
304    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) {
305        return createErrorResponse(request, XMPPError.getBuilder(error));
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 static class IQChildElementXmlStringBuilder extends XmlStringBuilder {
349        private final String element;
350
351        private boolean isEmptyElement;
352
353        private IQChildElementXmlStringBuilder(IQ iq) {
354            this(iq.getChildElementName(), iq.getChildElementNamespace());
355        }
356
357        public IQChildElementXmlStringBuilder(ExtensionElement pe) {
358            this(pe.getElementName(), pe.getNamespace());
359        }
360
361        private IQChildElementXmlStringBuilder(String element, String namespace) {
362            prelude(element, namespace);
363            this.element = element;
364        }
365
366        public void setEmptyElement() {
367            isEmptyElement = true;
368        }
369    }
370}