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