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     * @param contents the contents.
071     * @param mi the jingle content info
072     * @param sid the sid.
073     */
074    public Jingle(final List<JingleContent> contents, final JingleContentInfo mi,
075                  final String sid) {
076        this();
077
078        if (contents != null) {
079            this.contents.addAll(contents);
080        }
081
082        setContentInfo(mi);
083        setSid(sid);
084
085        // Set null all other fields in the packet
086        initiator = null;
087        responder = null;
088        action = null;
089    }
090
091    /**
092     * Constructor with a contents.
093     *
094     * @param content a content
095     */
096    public Jingle(final JingleContent content) {
097        this();
098
099        addContent(content);
100
101        // Set null all other fields in the packet
102        initiator = null;
103        responder = null;
104
105        // Some default values for the most common situation...
106        action = JingleActionEnum.UNKNOWN;
107        this.setType(IQ.Type.set);
108    }
109
110     /**
111     * Constructor with a content info.
112     *
113     * @param info The content info
114     */
115    public Jingle(final JingleContentInfo info) {
116        this();
117
118        setContentInfo(info);
119
120        // Set null all other fields in the packet
121        initiator = null;
122        responder = null;
123
124        // Some default values for the most common situation...
125        action = JingleActionEnum.UNKNOWN;
126        this.setType(IQ.Type.set);
127    }
128
129    /**
130     * A constructor where the action can be specified.
131     *
132     * @param action The action.
133     */
134    public Jingle(final JingleActionEnum action) {
135        this(null, null, null);
136        this.action = action;
137
138        // In general, a Jingle with an action is used in a SET packet...
139        this.setType(IQ.Type.set);
140    }
141
142    /**
143     * A constructor where the session ID can be specified.
144     *
145     * @param sid The session ID related to the negotiation.
146     * @see #setSid(String)
147     */
148    public Jingle(final String sid) {
149        this(null, null, sid);
150    }
151
152    /**
153     * The default constructor.
154     */
155    public Jingle() {
156        super(NODENAME, NAMESPACE);
157    }
158
159    /**
160     * Set the session ID related to this session. The session ID is a unique
161     * identifier generated by the initiator. This should match the XML Nmtoken
162     * production so that XML character escaping is not needed for characters
163     * such as &amp;.
164     *
165     * @param sid the session ID
166     */
167    public final void setSid(final String sid) {
168        this.sid = sid;
169    }
170
171    /**
172     * Returns the session ID related to the session. The session ID is a unique
173     * identifier generated by the initiator. This should match the XML Nmtoken
174     * production so that XML character escaping is not needed for characters
175     * such as &amp;.
176     *
177     * @return Returns the session ID related to the session.
178     * @see #setSid(String)
179     */
180    public String getSid() {
181
182        return sid;
183    }
184
185    /**
186     * Jingle content info.
187     *
188     * @return the audioInfo.
189     */
190    public JingleContentInfo getContentInfo() {
191        return contentInfo;
192    }
193
194    /**
195     * Set content info.
196     *
197     * @param contentInfo the audioInfo to set.
198     */
199    public void setContentInfo(final JingleContentInfo contentInfo) {
200        this.contentInfo = contentInfo;
201    }
202
203    /**
204     * Get an iterator for the contents.
205     *
206     * @return the contents
207     */
208    public Iterator<JingleContent> getContents() {
209        synchronized (contents) {
210            return Collections.unmodifiableList(new ArrayList<>(contents)).iterator();
211        }
212    }
213
214    /**
215     * Get an iterator for the content.
216     *
217     * @return the contents
218     */
219    public List<JingleContent> getContentsList() {
220        synchronized (contents) {
221            return new ArrayList<>(contents);
222        }
223    }
224
225    /**
226     * Add a new content.
227     *
228     * @param content the content to add
229     */
230    public void addContent(final JingleContent content) {
231        if (content != null) {
232            synchronized (contents) {
233                contents.add(content);
234            }
235        }
236    }
237
238    /**
239     * Add a list of JingleContent elements.
240     *
241     * @param contentList the list of contents to add
242     */
243    public void addContents(final List<JingleContent> contentList) {
244        if (contentList != null) {
245            synchronized (contents) {
246                contents.addAll(contentList);
247            }
248        }
249    }
250
251     /**
252     * Get the action specified in the packet.
253     *
254     * @return the action
255     */
256    public JingleActionEnum getAction() {
257        return action;
258    }
259
260    /**
261     * Set the action in the packet.
262     *
263     * @param action the action to set
264     */
265    public void setAction(final JingleActionEnum action) {
266        this.action = action;
267    }
268
269    /**
270     * Get the initiator. The initiator will be the full JID of the entity that
271     * has initiated the flow (which may be different to the "from" address in
272     * the IQ)
273     *
274     * @return the initiator
275     */
276    public Jid getInitiator() {
277        return initiator;
278    }
279
280    /**
281     * Set the initiator. The initiator must be the full JID of the entity that
282     * has initiated the flow (which may be different to the "from" address in
283     * the IQ)
284     *
285     * @param initiator the initiator to set
286     */
287    public void setInitiator(final Jid initiator) {
288        this.initiator = initiator;
289    }
290
291    /**
292     * Get the responder. The responder is the full JID of the entity that has
293     * replied to the initiation (which may be different to the "to" address in
294     * the IQ).
295     *
296     * @return the responder
297     */
298    public Jid getResponder() {
299        return responder;
300    }
301
302    /**
303     * Set the responder. The responder must be the full JID of the entity that
304     * has replied to the initiation (which may be different to the "to"
305     * address in the IQ).
306     *
307     * @param resp the responder to set
308     */
309    public void setResponder(final Jid resp) {
310        responder = resp;
311    }
312
313    /**
314     * Get a hash key for the session this stanza belongs to.
315     *
316     * @param sid       The session id
317     * @param initiator The initiator
318     * @return A hash key
319     */
320    public static int getSessionHash(final String sid, final Jid initiator) {
321        final int PRIME = 31;
322        int result = 1;
323        result = PRIME * result + (initiator == null ? 0 : initiator.hashCode());
324        result = PRIME * result + (sid == null ? 0 : sid.hashCode());
325        return result;
326    }
327
328    /**
329     * Return the XML representation of the packet.
330     *
331     * @return the XML string
332     */
333    @Override
334    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder buf) {
335        if (getInitiator() != null) {
336            buf.append(" initiator=\"").append(getInitiator()).append('"');
337        }
338        if (getResponder() != null) {
339            buf.append(" responder=\"").append(getResponder()).append('"');
340        }
341        if (getAction() != null) {
342            buf.append(" action=\"").append(getAction().toString()).append('"');
343        }
344        if (getSid() != null) {
345            buf.append(" sid=\"").append(getSid()).append('"');
346        }
347        buf.append('>');
348
349        synchronized (contents) {
350            for (JingleContent content : contents) {
351                buf.append(content.toXML());
352            }
353         }
354
355        // and the same for audio jmf info
356        if (contentInfo != null) {
357            buf.append(contentInfo.toXML());
358        }
359
360        return buf;
361    }
362}