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        this(iqBuilder, childElementName != null ? new QName(childElementNamespace, childElementName) : null);
072    }
073
074    protected IQ(AbstractIqBuilder<?> iqBuilder, QName childElementQName) {
075        super(iqBuilder);
076
077        type = iqBuilder.type;
078
079        if (childElementQName != null) {
080            this.childElementQName = childElementQName;
081            this.childElementName = childElementQName.getLocalPart();
082            this.childElementNamespace = childElementQName.getNamespaceURI();
083        } else {
084            this.childElementQName = null;
085            this.childElementName = null;
086            this.childElementNamespace = null;
087        }
088    }
089
090    @Override
091    public Type getType() {
092        return type;
093    }
094
095    /**
096     * Sets the type of the IQ packet.
097     * <p>
098     * Since the type of an IQ must present, an IllegalArgmentException will be thrown when type is
099     * <code>null</code>.
100     * </p>
101     *
102     * @param type the type of the IQ packet.
103     */
104    // TODO: Mark this as deprecated once StanzaBuilder is ready and all call sites are gone.
105    public void setType(Type type) {
106        this.type = Objects.requireNonNull(type, "type must not be null");
107    }
108
109    public final QName getChildElementQName() {
110        return childElementQName;
111    }
112
113    public final String getChildElementName() {
114        return childElementName;
115    }
116
117    public final String getChildElementNamespace() {
118        return childElementNamespace;
119    }
120
121    @Override
122    public final String getElementName() {
123        return IQ_ELEMENT;
124    }
125
126    @Override
127    public final String toString() {
128            StringBuilder sb = new StringBuilder();
129            sb.append("IQ Stanza (");
130            sb.append(getChildElementName()).append(' ').append(getChildElementNamespace());
131            sb.append(") [");
132            logCommonAttributes(sb);
133            sb.append("type=").append(type).append(',');
134            sb.append(']');
135            return sb.toString();
136    }
137
138    @Override
139    public final XmlStringBuilder toXML(XmlEnvironment enclosingXmlEnvironment) {
140        XmlStringBuilder buf = new XmlStringBuilder(this, enclosingXmlEnvironment);
141        addCommonAttributes(buf);
142        if (type == null) {
143            buf.attribute("type", "get");
144        }
145        else {
146            buf.attribute("type", type.toString());
147        }
148        buf.rightAngleBracket();
149        appendInnerXml(buf);
150        buf.closeElement(IQ_ELEMENT);
151        return buf;
152    }
153
154    /**
155     * Returns the sub-element XML section of the IQ packet, or the empty String if there
156     * isn't one.
157     *
158     * @return the child element section of the IQ XML.
159     */
160    // TODO: This method should not be part of the public API as it is mostly used for testing purposes, with the one
161    // exception of AdHocCommand.getRaw().
162    public final XmlStringBuilder getChildElementXML() {
163        XmlStringBuilder xml = new XmlStringBuilder();
164        appendInnerXml(xml);
165        return xml;
166    }
167
168    /**
169     * Append the sub-element XML section of the IQ stanza.
170     *
171     * @param xml the XmlStringBuilder to append to.
172     */
173    private void appendInnerXml(XmlStringBuilder xml) {
174        if (type == Type.error) {
175            // Add the error sub-packet, if there is one.
176            appendErrorIfExists(xml);
177        }
178        if (childElementName == null) {
179            return;
180        }
181
182        // Add the query section if there is one.
183        IQChildElementXmlStringBuilder iqChildElement = getIQChildElementBuilder(
184                        new IQChildElementXmlStringBuilder(getChildElementName(), getChildElementNamespace(), null, xml.getXmlEnvironment()));
185        // TOOD: Document the cases where iqChildElement is null but childElementName not. And if there are none, change
186        // the logic.
187        if (iqChildElement == null) {
188            return;
189        }
190
191        xml.append(iqChildElement);
192
193        List<XmlElement> extensionsXml = getExtensions();
194        if (iqChildElement.isEmptyElement) {
195            if (extensionsXml.isEmpty()) {
196                xml.closeEmptyElement();
197                return;
198            }
199
200            xml.rightAngleBracket();
201        }
202
203        xml.append(extensionsXml);
204        xml.closeElement(iqChildElement.element);
205    }
206
207    /**
208     * This method must be overwritten by IQ subclasses to create their child content. It is important you don't use the builder
209     * <b>to add the final end tag</b>. This will be done automatically by {@link IQChildElementXmlStringBuilder}
210     * after eventual existing {@link ExtensionElement}s have been added.
211     * <p>
212     * For example to create an IQ with a extra attribute and an additional child element
213     * </p>
214     * <pre>
215     * {@code
216     * <iq to='foo@example.org' id='123'>
217     *   <bar xmlns='example:bar' extraAttribute='blaz'>
218     *      <extraElement>elementText</extraElement>
219     *   </bar>
220     * </iq>
221     * }
222     * </pre>
223     * the body of the {@code getIQChildElementBuilder} looks like
224     * <pre>
225     * {@code
226     * // The builder 'xml' will already have the child element and the 'xmlns' attribute added
227     * // So the current builder state is "<bar xmlns='example:bar'"
228     * xml.attribute("extraAttribute", "blaz");
229     * xml.rightAngleBracket();
230     * xml.element("extraElement", "elementText");
231     * // Do not close the 'bar' attribute by calling xml.closeElement('bar')
232     * }
233     * </pre>
234     * If your IQ only contains attributes and no child elements, i.e. it can be represented as empty element, then you
235     * can mark it as such.
236     * <pre>
237     * xml.attribute(&quot;myAttribute&quot;, &quot;myAttributeValue&quot;);
238     * xml.setEmptyElement();
239     * </pre>
240     * If your IQ does not contain any attributes or child elements (besides {@link ExtensionElement}s), consider sub-classing
241     * {@link SimpleIQ} instead.
242     *
243     * @param xml a pre-created builder which already has the child element and the 'xmlns' attribute set.
244     * @return the build to create the IQ child content.
245     */
246    protected abstract IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml);
247
248    /**
249     * Convenience method to create a new empty {@link Type#result IQ.Type.result}
250     * IQ based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
251     * IQ. The new stanza will be initialized with:<ul>
252     *      <li>The sender set to the recipient of the originating IQ.
253     *      <li>The recipient set to the sender of the originating IQ.
254     *      <li>The type set to {@link Type#result IQ.Type.result}.
255     *      <li>The id set to the id of the originating IQ.
256     *      <li>No child element of the IQ element.
257     * </ul>
258     *
259     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
260     * @throws IllegalArgumentException if the IQ stanza does not have a type of
261     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
262     * @return a new {@link Type#result IQ.Type.result} IQ based on the originating IQ.
263     */
264    public static IQ createResultIQ(final IQ request) {
265        return new EmptyResultIQ(request);
266    }
267
268    /**
269     * Convenience method to create a new {@link Type#error IQ.Type.error} IQ
270     * based on a {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}
271     * IQ. The new stanza will be initialized with:<ul>
272     *      <li>The sender set to the recipient of the originating IQ.
273     *      <li>The recipient set to the sender of the originating IQ.
274     *      <li>The type set to {@link Type#error IQ.Type.error}.
275     *      <li>The id set to the id of the originating IQ.
276     *      <li>The child element contained in the associated originating IQ.
277     *      <li>The provided {@link StanzaError XMPPError}.
278     * </ul>
279     *
280     * @param request the {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set} IQ packet.
281     * @param error the error to associate with the created IQ packet.
282     * @throws IllegalArgumentException if the IQ stanza does not have a type of
283     *      {@link Type#get IQ.Type.get} or {@link Type#set IQ.Type.set}.
284     * @return a new {@link Type#error IQ.Type.error} IQ based on the originating IQ.
285     */
286    public static ErrorIQ createErrorResponse(final IQ request, final StanzaError error) {
287        return ErrorIQ.createErrorResponse(request, error);
288    }
289
290    /**
291     * Deprecated.
292     *
293     * @param request the request.
294     * @param error the error.
295     * @return an error IQ.
296     * @deprecated use {@link #createErrorResponse(IQ, StanzaError)} instead.
297     */
298    @Deprecated
299    // TODO: Remove in Smack 4.5.
300    public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Builder error) {
301        return createErrorResponse(request, error.build());
302    }
303
304    public static ErrorIQ createErrorResponse(final IQ request, final StanzaError.Condition condition) {
305        return createErrorResponse(request, StanzaError.getBuilder(condition).build());
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 enum ResponseType {
349
350        result(Type.result),
351
352        error(Type.error),
353
354        ;
355
356        final Type type;
357
358        ResponseType(Type type) {
359            this.type = type;
360        }
361
362        Type getType() {
363            return type;
364        }
365    }
366
367    public static class IQChildElementXmlStringBuilder extends XmlStringBuilder {
368        private final String element;
369
370        private boolean isEmptyElement;
371
372        public IQChildElementXmlStringBuilder(ExtensionElement extensionElement,
373                        XmlEnvironment enclosingXmlEnvironment) {
374            this(extensionElement.getElementName(), extensionElement.getNamespace(), extensionElement.getLanguage(),
375                            enclosingXmlEnvironment);
376        }
377
378        private IQChildElementXmlStringBuilder(String elementName, String xmlNs, String xmlLang,
379                        XmlEnvironment enclosingXmlEnvironment) {
380            super(elementName, xmlNs, xmlLang, enclosingXmlEnvironment);
381            this.element = elementName;
382        }
383
384        public void setEmptyElement() {
385            isEmptyElement = true;
386        }
387    }
388}