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 XmlStringBuilder toXML() {
117        XmlStringBuilder buf = new XmlStringBuilder();
118        buf.halfOpenElement(IQ_ELEMENT);
119        addCommonAttributes(buf);
120        if (type == null) {
121            buf.attribute("type", "get");
122        }
123        else {
124            buf.attribute("type", type.toString());
125        }
126        buf.rightAngleBracket();
127        buf.append(getChildElementXML());
128        buf.closeElement(IQ_ELEMENT);
129        return buf;
130    }
131
132    /**
133     * Returns the sub-element XML section of the IQ packet, or the empty String if there
134     * isn't one.
135     *
136     * @return the child element section of the IQ XML.
137     */
138    public final XmlStringBuilder getChildElementXML() {
139        XmlStringBuilder xml = new XmlStringBuilder();
140        if (type == Type.error) {
141            // Add the error sub-packet, if there is one.
142            appendErrorIfExists(xml);
143        }
144        else if (childElementName != null) {
145            // Add the query section if there is one.
146            IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this));
147            if (iqChildElement != null) {
148                xml.append(iqChildElement);
149                XmlStringBuilder extensionsXml = getExtensionsXML();
150                if (iqChildElement.isEmptyElement) {
151                    if (extensionsXml.length() == 0) {
152                         xml.closeEmptyElement();
153                         return xml;
154                    } else {
155                        xml.rightAngleBracket();
156                    }
157                }
158                xml.append(extensionsXml);
159                xml.closeElement(iqChildElement.element);
160            }
161        }
162        return xml;
163    }
164
165    /**
166     * This method must be overwritten by IQ subclasses to create their child content. It is important that the builder
167     * <b>does not include the final end element</b>. This will be done automatically by IQChildelementXmlStringBuilder
168     * after eventual existing stanza(/packet) extensions have been added.
169     * <p>
170     * For example to create an IQ with a extra attribute and an additional child element
171     * </p>
172     * <pre>
173     * {@code
174     * <iq to='foo@example.org' id='123'>
175     *   <bar xmlns='example:bar' extraAttribute='blaz'>
176     *      <extraElement>elementText</extraElement>
177     *   </bar>
178     * </iq>
179     * }
180     * </pre>
181     * the body of the {@code getIQChildElementBuilder} looks like
182     * <pre>
183     * {@code
184     * // The builder 'xml' will already have the child element and the 'xmlns' attribute added
185     * // So the current builder state is "<bar xmlns='example:bar'"
186     * xml.attribute("extraAttribute", "blaz");
187     * xml.rightAngleBracket();
188     * xml.element("extraElement", "elementText");
189     * // Do not close the 'bar' attribute by calling xml.closeElement('bar')
190     * }
191     * </pre>
192     * If your IQ only contains attributes and no child elements, i.e. it can be represented as empty element, then you
193     * can mark it as such.
194     * <pre>
195     * xml.attribute(&quot;myAttribute&quot;, &quot;myAttributeValue&quot;);
196     * xml.setEmptyElement();
197     * </pre>
198     * If your IQ does not contain any attributes or child elements (besides stanza(/packet) extensions), consider sub-classing
199     * {@link SimpleIQ} instead.
200     * 
201     * @param xml a pre-created builder which already has the child element and the 'xmlns' attribute set.
202     * @return the build to create the IQ child content.
203     */
204    protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml);
205
206    /**
207     * Convenience method to create a new empty {@link Type#result IQ.Type.result}
208     * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
209     * IQ. The new stanza(/packet) will be initialized with:<ul>
210     *      <li>The sender set to the recipient of the originating IQ.
211     *      <li>The recipient set to the sender of the originating IQ.
212     *      <li>The type set to {@link Type#result IQ.Type.result}.
213     *      <li>The id set to the id of the originating IQ.
214     *      <li>No child element of the IQ element.
215     * </ul>
216     *
217     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
218     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
219     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
220     * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ.
221     */
222    public static IQ createResultIQ(final IQ request) {
223        return new EmptyResultIQ(request);
224    }
225
226    /**
227     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
228     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
229     * IQ. The new stanza(/packet) will be initialized with:<ul>
230     *      <li>The sender set to the recipient of the originating IQ.
231     *      <li>The recipient set to the sender of the originating IQ.
232     *      <li>The type set to {@link Type#error IQ.Type.error}.
233     *      <li>The id set to the id of the originating IQ.
234     *      <li>The child element contained in the associated originating IQ.
235     *      <li>The provided {@link XMPPError XMPPError}.
236     * </ul>
237     *
238     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
239     * @param error the error to associate with the created IQ packet.
240     * @throws IllegalArgumentException if the IQ stanza(/packet) does not have a type of
241     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
242     * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
243     */
244    public static ErrorIQ createErrorResponse(final IQ request, final XMPPError error) {
245        if (!(request.getType() == Type.get || request.getType() == Type.set)) {
246            throw new IllegalArgumentException(
247                    "IQ must be of type 'set' or 'get'. Original IQ: " + request.toXML());
248        }
249        final ErrorIQ result = new ErrorIQ(error);
250        result.setStanzaId(request.getStanzaId());
251        result.setFrom(request.getTo());
252        result.setTo(request.getFrom());
253        return result;
254    }
255
256    /**
257     * A enum to represent the type of the IQ stanza.
258     */
259    public enum Type {
260
261        /**
262         * The IQ stanza requests information, inquires about what data is needed in order to complete further operations, etc.
263         */
264        get,
265
266        /**
267         * The IQ stanza provides data that is needed for an operation to be completed, sets new values, replaces existing values, etc.
268         */
269        set,
270
271        /**
272         * The IQ stanza is a response to a successful get or set request.
273         */
274        result,
275
276        /**
277         * The IQ stanza reports an error that has occurred regarding processing or delivery of a get or set request.
278         */
279        error,
280        ;
281
282        /**
283         * Converts a String into the corresponding types. Valid String values
284         * that can be converted to types are: "get", "set", "result", and "error".
285         *
286         * @param string the String value to covert.
287         * @return the corresponding Type.
288         * @throws IllegalArgumentException when not able to parse the string parameter
289         * @throws NullPointerException if the string is null
290         */
291        public static Type fromString(String string) {
292            return Type.valueOf(string.toLowerCase(Locale.US));
293        }
294    }
295
296    public static class IQChildElementXmlStringBuilder extends XmlStringBuilder {
297        private final String element;
298
299        private boolean isEmptyElement;
300
301        private IQChildElementXmlStringBuilder(IQ iq) {
302            this(iq.getChildElementName(), iq.getChildElementNamespace());
303        }
304
305        public IQChildElementXmlStringBuilder(ExtensionElement pe) {
306            this(pe.getElementName(), pe.getNamespace());
307        }
308
309        private IQChildElementXmlStringBuilder(String element, String namespace) {
310            prelude(element, namespace);
311            this.element = element;
312        }
313
314        public void setEmptyElement() {
315            isEmptyElement = true;
316        }
317    }
318}