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