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.smackx.jingleold.packet;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.Iterator;
023import java.util.List;
024
025import org.jivesoftware.smack.packet.IQ;
026
027import org.jivesoftware.smackx.jingleold.JingleActionEnum;
028
029import org.jxmpp.jid.Jid;
030
031/**
032 * An Jingle sub-packet, which is used by XMPP clients to exchange info like
033 * descriptions and transports. The following link summarizes the
034 * requirements of Jingle IM: <a
035 * href="http://www.xmpp.org/extensions/jep-0166.html">Valid tags</a>.
036 *
037 * Warning: this is an non-standard protocol documented by <a
038 * href="http://www.xmpp.org/extensions/jep-0166.html">XEP-166</a>. Because this is
039 * a non-standard protocol, it is subject to change.
040 *
041 * @author Alvaro Saurin
042 */
043public class Jingle extends IQ {
044
045    // static
046
047    public static final String NAMESPACE = "urn:xmpp:tmp:jingle";
048
049    public static final String NODENAME = "jingle";
050
051    // non-static
052
053    private String sid; // The session id
054
055    private JingleActionEnum action; // The action associated to the Jingle
056
057    private Jid initiator; // The initiator as a "user@host/resource"
058
059    private Jid responder; // The responder
060
061    // Sub-elements of a Jingle object.
062
063    private final List<JingleContent> contents = new ArrayList<>();
064
065    private JingleContentInfo contentInfo;
066
067    /**
068     * A constructor where the main components can be initialized.
069     */
070    public Jingle(final List<JingleContent> contents, final JingleContentInfo mi,
071                  final String sid) {
072        this();
073
074        if (contents != null) {
075            this.contents.addAll(contents);
076        }
077
078        setContentInfo(mi);
079        setSid(sid);
080
081        // Set null all other fields in the packet
082        initiator = null;
083        responder = null;
084        action = null;
085    }
086
087    /**
088     * Constructor with a contents.
089     *
090     * @param content a content
091     */
092    public Jingle(final JingleContent content) {
093        this();
094
095        addContent(content);
096
097        // Set null all other fields in the packet
098        initiator = null;
099        responder = null;
100
101        // Some default values for the most common situation...
102        action = JingleActionEnum.UNKNOWN;
103        this.setType(IQ.Type.set);
104    }
105
106     /**
107     * Constructor with a content info.
108     *
109     * @param info The content info
110     */
111    public Jingle(final JingleContentInfo info) {
112        this();
113
114        setContentInfo(info);
115
116        // Set null all other fields in the packet
117        initiator = null;
118        responder = null;
119
120        // Some default values for the most common situation...
121        action = JingleActionEnum.UNKNOWN;
122        this.setType(IQ.Type.set);
123    }
124
125    /**
126     * A constructor where the action can be specified.
127     *
128     * @param action The action.
129     */
130    public Jingle(final JingleActionEnum action) {
131        this(null, null, null);
132        this.action = action;
133
134        // In general, a Jingle with an action is used in a SET packet...
135        this.setType(IQ.Type.set);
136    }
137
138    /**
139     * A constructor where the session ID can be specified.
140     *
141     * @param sid The session ID related to the negotiation.
142     * @see #setSid(String)
143     */
144    public Jingle(final String sid) {
145        this(null, null, sid);
146    }
147
148    /**
149     * The default constructor.
150     */
151    public Jingle() {
152        super(NODENAME, NAMESPACE);
153    }
154
155    /**
156     * Set the session ID related to this session. The session ID is a unique
157     * identifier generated by the initiator. This should match the XML Nmtoken
158     * production so that XML character escaping is not needed for characters
159     * such as &amp;.
160     *
161     * @param sid the session ID
162     */
163    public final void setSid(final String sid) {
164        this.sid = sid;
165    }
166
167    /**
168     * Returns the session ID related to the session. The session ID is a unique
169     * identifier generated by the initiator. This should match the XML Nmtoken
170     * production so that XML character escaping is not needed for characters
171     * such as &amp;.
172     *
173     * @return Returns the session ID related to the session.
174     * @see #setSid(String)
175     */
176    public String getSid() {
177
178        return sid;
179    }
180
181    /**
182     * Returns the XML element name of the extension sub-packet root element.
183     * Always returns "jingle"
184     *
185     * @return the XML element name of the stanza extension.
186     */
187    public static String getElementName() {
188        return NODENAME;
189    }
190
191    /**
192     * Returns the XML namespace of the extension sub-packet root element.
193     *
194     * @return the XML namespace of the stanza extension.
195     */
196    public static String getNamespace() {
197        return NAMESPACE;
198    }
199
200    /**
201     * Jingle content info.
202     *
203     * @return the audioInfo.
204     */
205    public JingleContentInfo getContentInfo() {
206        return contentInfo;
207    }
208
209    /**
210     * Set content info.
211     *
212     * @param contentInfo the audioInfo to set.
213     */
214    public void setContentInfo(final JingleContentInfo contentInfo) {
215        this.contentInfo = contentInfo;
216    }
217
218    /**
219     * Get an iterator for the contents.
220     *
221     * @return the contents
222     */
223    public Iterator<JingleContent> getContents() {
224        synchronized (contents) {
225            return Collections.unmodifiableList(new ArrayList<>(contents)).iterator();
226        }
227    }
228
229    /**
230     * Get an iterator for the content.
231     *
232     * @return the contents
233     */
234    public List<JingleContent> getContentsList() {
235        synchronized (contents) {
236            return new ArrayList<>(contents);
237        }
238    }
239
240    /**
241     * Add a new content.
242     *
243     * @param content the content to add
244     */
245    public void addContent(final JingleContent content) {
246        if (content != null) {
247            synchronized (contents) {
248                contents.add(content);
249            }
250        }
251    }
252
253    /**
254     * Add a list of JingleContent elements.
255     *
256     * @param contentList the list of contents to add
257     */
258    public void addContents(final List<JingleContent> contentList) {
259        if (contentList != null) {
260            synchronized (contents) {
261                contents.addAll(contentList);
262            }
263        }
264    }
265
266     /**
267     * Get the action specified in the packet.
268     *
269     * @return the action
270     */
271    public JingleActionEnum getAction() {
272        return action;
273    }
274
275    /**
276     * Set the action in the packet.
277     *
278     * @param action the action to set
279     */
280    public void setAction(final JingleActionEnum action) {
281        this.action = action;
282    }
283
284    /**
285     * Get the initiator. The initiator will be the full JID of the entity that
286     * has initiated the flow (which may be different to the "from" address in
287     * the IQ)
288     *
289     * @return the initiator
290     */
291    public Jid getInitiator() {
292        return initiator;
293    }
294
295    /**
296     * Set the initiator. The initiator must be the full JID of the entity that
297     * has initiated the flow (which may be different to the "from" address in
298     * the IQ)
299     *
300     * @param initiator the initiator to set
301     */
302    public void setInitiator(final Jid initiator) {
303        this.initiator = initiator;
304    }
305
306    /**
307     * Get the responder. The responder is the full JID of the entity that has
308     * replied to the initiation (which may be different to the "to" address in
309     * the IQ).
310     *
311     * @return the responder
312     */
313    public Jid getResponder() {
314        return responder;
315    }
316
317    /**
318     * Set the responder. The responder must be the full JID of the entity that
319     * has replied to the initiation (which may be different to the "to"
320     * address in the IQ).
321     *
322     * @param resp the responder to set
323     */
324    public void setResponder(final Jid resp) {
325        responder = resp;
326    }
327
328    /**
329     * Get a hash key for the session this stanza belongs to.
330     *
331     * @param sid       The session id
332     * @param initiator The initiator
333     * @return A hash key
334     */
335    public static int getSessionHash(final String sid, final Jid initiator) {
336        final int PRIME = 31;
337        int result = 1;
338        result = PRIME * result + (initiator == null ? 0 : initiator.hashCode());
339        result = PRIME * result + (sid == null ? 0 : sid.hashCode());
340        return result;
341    }
342
343    /**
344     * Return the XML representation of the packet.
345     *
346     * @return the XML string
347     */
348    @Override
349    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
350        if (getInitiator() != null) {
351            buf.append(" initiator=\"").append(getInitiator()).append('"');
352        }
353        if (getResponder() != null) {
354            buf.append(" responder=\"").append(getResponder()).append('"');
355        }
356        if (getAction() != null) {
357            buf.append(" action=\"").append(getAction().toString()).append('"');
358        }
359        if (getSid() != null) {
360            buf.append(" sid=\"").append(getSid()).append('"');
361        }
362        buf.append('>');
363
364        synchronized (contents) {
365            for (JingleContent content : contents) {
366                buf.append(content.toXML(null));
367            }
368         }
369
370        // and the same for audio jmf info
371        if (contentInfo != null) {
372            buf.append(contentInfo.toXML(null));
373        }
374
375        return buf;
376    }
377}