/** * $RCSfile$ * $Revision: 2407 $ * $Date: 2004-11-02 23:37:00 +0000 (Tue, 02 Nov 2004) $ * * Copyright 2003-2007 Jive Software. * * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.packet; import org.jivesoftware.smack.packet.IQ; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * An Jingle sub-packet, which is used by XMPP clients to exchange info like * descriptions and transports.

The following link summarizes the * requirements of Jingle IM: Valid tags. *

*

Warning: this is an non-standard protocol documented by JEP-166. Because this is * a non-standard protocol, it is subject to change. * * @author Alvaro Saurin */ public class Jingle extends IQ { // static public static final String NAMESPACE = "http://jabber.org/protocol/jingle"; public static final String NODENAME = "jingle"; // non-static private String sid; // The session id private Action action; // The action associated to the Jingle private String initiator; // The initiator as a "user@host/resource" private String responder; // The responder // Sub-elements of a Jingle object. private final List descriptions = new ArrayList(); private final List transports = new ArrayList(); private JingleContentInfo contentInfo; /** * A constructor where the main components can be initialized. */ public Jingle(final List descs, final List trans, final JingleContentInfo mi, final String sid) { super(); if (descs != null) { descriptions.addAll(descs); } if (trans != null) { transports.addAll(trans); } setContentInfo(mi); setSid(sid); // Set null all other fields in the packet initiator = null; responder = null; action = null; } /** * Constructor with a description. * * @param descr a description */ public Jingle(final JingleContentDescription descr) { super(); addDescription(descr); // Set null all other fields in the packet initiator = null; responder = null; // Some default values for the most common situation... action = Jingle.Action.DESCRIPTIONINFO; this.setType(IQ.Type.SET); } /** * Constructor with a transport. * * @param trans a transport */ public Jingle(final JingleTransport trans) { super(); addTransport(trans); // Set null all other fields in the packet initiator = null; responder = null; // Some default values for the most common situation... action = Jingle.Action.TRANSPORTINFO; this.setType(IQ.Type.SET); } /** * Constructor with a content info. * * @param info The content info */ public Jingle(final JingleContentInfo info) { super(); setContentInfo(info); // Set null all other fields in the packet initiator = null; responder = null; // Some default values for the most common situation... action = Jingle.Action.DESCRIPTIONINFO; this.setType(IQ.Type.SET); } /** * A constructor where the action can be specified. * * @param action The action. */ public Jingle(final Jingle.Action action) { this(null, null, null, null); this.action = action; // In general, a Jingle with an action is used in a SET packet... this.setType(IQ.Type.SET); } /** * A constructor where the session ID can be specified. * * @param sid The session ID related to the negotiation. * @see #setSid(String) */ public Jingle(final String sid) { this(null, null, null, sid); } /** * The default constructor */ public Jingle() { super(); } /** * Set the session ID related to this session. The session ID is a unique * identifier generated by the initiator. This should match the XML Nmtoken * production so that XML character escaping is not needed for characters * such as &. * * @param sid the session ID */ public final void setSid(final String sid) { this.sid = sid; } /** * Returns the session ID related to the session. The session ID is a unique * identifier generated by the initiator. This should match the XML Nmtoken * production so that XML character escaping is not needed for characters * such as &. * * @return Returns the session ID related to the session. * @see #setSid(String) */ public String getSid() { return sid; } /** * Returns the XML element name of the extension sub-packet root element. * Always returns "jingle" * * @return the XML element name of the packet extension. */ public static String getElementName() { return NODENAME; } /** * Returns the XML namespace of the extension sub-packet root element. * According the specification the namespace is always * "http://jabber.org/protocol/jingle" * * @return the XML namespace of the packet extension. */ public static String getNamespace() { return NAMESPACE; } /** * @return the audioInfo */ public JingleContentInfo getContentInfo() { return contentInfo; } /** * @param contentInfo the audioInfo to set */ public void setContentInfo(final JingleContentInfo contentInfo) { this.contentInfo = contentInfo; } /** * Get an iterator for the content descriptions * * @return the descriptions */ public Iterator getDescriptions() { synchronized (descriptions) { return Collections.unmodifiableList(new ArrayList(descriptions)).iterator(); } } /** * Get an iterator for the content descriptions * * @return the descriptions */ public ArrayList getDescriptionsList() { synchronized (descriptions) { return new ArrayList(descriptions); } } /** * Add a new content description. * * @param desc the descriptions to add */ public void addDescription(final JingleContentDescription desc) { if (desc != null) { synchronized (descriptions) { descriptions.add(desc); } } } /** * Add a list of JingleContentDescription elements * * @param descsList the list of transports to add */ public void addDescriptions(final List descsList) { if (descsList != null) { synchronized (descriptions) { descriptions.addAll(descsList); } } } /** * Get an iterator for the transport. * * @return the transports */ public Iterator getTransports() { synchronized (transports) { return Collections.unmodifiableList(new ArrayList(transports)).iterator(); } } /** * Get the list of transports. * * @return the transports list. */ public ArrayList getTransportsList() { synchronized (transports) { return new ArrayList(transports); } } /** * Add a new TransportNegotiator element * * @param trans the transports to add */ public void addTransport(final JingleTransport trans) { if (trans != null) { synchronized (transports) { transports.add(trans); } } } /** * Add a list of TransportNegotiator elements * * @param transList the list of transports to add */ public void addTransports(final List transList) { if (transList != null) { synchronized (transports) { transports.addAll(transList); } } } /** * Get the action specified in the packet * * @return the action */ public Action getAction() { return action; } /** * Set the action in the packet * * @param action the action to set */ public void setAction(final Action action) { this.action = action; } /** * Get the initiator. The initiator will be the full JID of the entity that * has initiated the flow (which may be different to the "from" address in * the IQ) * * @return the initiator */ public String getInitiator() { return initiator; } /** * Set the initiator. The initiator must be the full JID of the entity that * has initiated the flow (which may be different to the "from" address in * the IQ) * * @param initiator the initiator to set */ public void setInitiator(final String initiator) { this.initiator = initiator; } /** * Get the responder. The responder is the full JID of the entity that has * replied to the initiation (which may be different to the "to" addresss in * the IQ). * * @return the responder */ public String getResponder() { return responder; } /** * Set the responder. The responder must be the full JID of the entity that * has replied to the initiation (which may be different to the "to" * addresss in the IQ). * * @param resp the responder to set */ public void setResponder(final String resp) { responder = resp; } /** * Get a hash key for the session this packet belongs to. * * @param sid The session id * @param initiator The initiator * @return A hash key */ public static int getSessionHash(final String sid, final String initiator) { final int PRIME = 31; int result = 1; result = PRIME * result + (initiator == null ? 0 : initiator.hashCode()); result = PRIME * result + (sid == null ? 0 : sid.hashCode()); return result; } /** * Return the XML representation of the packet. * * @return the XML string */ public String getChildElementXML() { StringBuilder buf = new StringBuilder(); buf.append("<").append(getElementName()); buf.append(" xmlns=\"").append(getNamespace()).append("\""); if (getInitiator() != null) { buf.append(" initiator=\"").append(getInitiator()).append("\""); } if (getResponder() != null) { buf.append(" responder=\"").append(getResponder()).append("\""); } if (getAction() != null) { buf.append(" action=\"").append(getAction()).append("\""); } if (getSid() != null) { buf.append(" sid=\"").append(getSid()).append("\""); } buf.append(">"); //TODO Update to accept more than one content per session (XEP-0166) buf.append(""); // Look for possible payload types, and dump them. synchronized (descriptions) { for (int i = 0; i < descriptions.size(); i++) { JingleContentDescription desc = (JingleContentDescription) descriptions .get(i); buf.append(desc.toXML()); } } // If the packet has transports, dump them. synchronized (transports) { for (int i = 0; i < transports.size(); i++) { JingleTransport trans = (JingleTransport) transports.get(i); buf.append(trans.toXML()); } } buf.append(""); // and the same for audio jmf info if (contentInfo != null) { buf.append(contentInfo.toXML()); } buf.append(""); return buf.toString(); } /** * The "action" in the jingle packet, as an enum. */ public static enum Action { CONTENTACCEPT, CONTENTADD, CONTENTDECLINE, CONTENTMODIFY, CONTENTREMOVE, DESCRIPTIONADD, DESCRIPTIONDECLINE, DESCRIPTIONINFO, DESCRIPTIONMODIFY, SESSIONACCEPT, SESSIONINFO, SESSIONINITIATE, SESSIONREDIRECT, SESSIONTERMINATE, TRANSPORTACCEPT, TRANSPORTDECLINE, TRANSPORTINFO, TRANSPORTMODIFY; private static String names[] = {"content-accept", "content-add", "content-decline", "content-modify", "content-remove", "description-accept", "description-decline", "description-info", "description-modify", "session-accept", "session-info", "session-initiate", "session-redirect", "session-terminate", "transport-accept", "transport-decline", "transport-info", "transport-modify"}; /** * Returns the String value for an Action. */ public String toString() { return names[this.ordinal()]; } /** * Returns the Action for a String value. */ public static Action getAction(String str) { for (int i = 0; i < names.length; i++) { if (names[i].equals(str)) return Action.values()[i]; } return null; } } }