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