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