001/**
002 *
003 * Copyright 2003-2005 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 */
017package org.jivesoftware.smackx.jingle;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.logging.Logger;
022
023import org.jivesoftware.smack.SmackException;
024import org.jivesoftware.smack.XMPPConnection;
025import org.jivesoftware.smack.XMPPException;
026import org.jivesoftware.smack.packet.IQ;
027import org.jivesoftware.smackx.jingle.listeners.JingleListener;
028
029/**
030 * Basic Jingle negotiator.
031 * <p/>
032 * </p>
033 * <p/>
034 * JingleNegotiator implements some basic behavior for every Jingle negotiation.
035 * It implements a "state" pattern: each stage should process Jingle packets and
036 * act depending on the current state in the negotiation...
037 * <p/>
038 * </p>
039 *
040 * @author Alvaro Saurin
041 * @author Jeff Williams
042 */
043public abstract class JingleNegotiator {
044
045        private static final Logger LOGGER = Logger.getLogger(JingleNegotiator.class.getName());
046
047        //private XMPPConnection connection; // The connection associated
048
049    protected JingleSession session;
050
051    private final List<JingleListener> listeners = new ArrayList<JingleListener>();
052
053    private String expectedAckId;
054
055    private JingleNegotiatorState state;
056    
057    private boolean isStarted;
058    
059    /**
060     * Default constructor.
061     */
062    public JingleNegotiator() {
063        this(null);
064    }
065
066    /**
067     * Default constructor with a Connection
068     *
069     * @param session the jingle session
070     */
071    public JingleNegotiator(JingleSession session) {
072        this.session = session;
073        state = JingleNegotiatorState.PENDING;
074    }
075
076    public JingleNegotiatorState getNegotiatorState() {
077        return state;
078    }
079
080    public void setNegotiatorState(JingleNegotiatorState stateIs) {
081        
082        JingleNegotiatorState stateWas = state;
083        
084        LOGGER.fine("Negotiator state change: " + stateWas + "->" + stateIs  + "(" + this.getClass().getSimpleName() + ")");
085
086        switch (stateIs) {
087            case PENDING:
088                break;
089
090            case FAILED:
091                break;
092
093            case SUCCEEDED:
094                break;
095
096            default:
097                break;
098        }
099
100       this.state = stateIs;
101    }
102
103    public XMPPConnection getConnection() {
104        if (session != null) {
105            return session.getConnection();
106        } else {
107            return null;
108        }
109    }
110
111    /**
112      * Get the XMPP connection associated with this negotiation.
113      *
114      * @return the connection
115      */
116    public JingleSession getSession() {
117        return session;
118    }
119
120    /**
121     * Set the XMPP connection associated.
122     *
123     * @param session the jingle session
124     */
125    public void setSession(JingleSession session) {
126        this.session = session;
127    }
128
129    // Acks management
130
131    /**
132     * Add expected ID
133     *
134     * @param id
135     */
136    public void addExpectedId(String id) {
137        expectedAckId = id;
138    }
139
140    /**
141     * Check if the passed ID is the expected ID
142     *
143     * @param id
144     * @return true if is expected id
145     */
146    public boolean isExpectedId(String id) {
147        if (id != null) {
148            return id.equals(expectedAckId);
149        } else {
150            return false;
151        }
152    }
153
154    /**
155     * Remove and expected ID
156     *
157     * @param id
158     */
159    public void removeExpectedId(String id) {
160        addExpectedId((String) null);
161    }
162
163    // Listeners
164
165    /**
166     * Add a Jingle session listener to listen to incoming session requests.
167     *
168     * @param li The listener
169     * @see org.jivesoftware.smackx.jingle.listeners.JingleListener
170     */
171    public void addListener(JingleListener li) {
172        synchronized (listeners) {
173            listeners.add(li);
174        }
175    }
176
177    /**
178     * Removes a Jingle session listener.
179     *
180     * @param li The jingle session listener to be removed
181     * @see org.jivesoftware.smackx.jingle.listeners.JingleListener
182     */
183    public void removeListener(JingleListener li) {
184        synchronized (listeners) {
185            listeners.remove(li);
186        }
187    }
188
189    /**
190     * Get a copy of the listeners
191     *
192     * @return a copy of the listeners
193     */
194    protected List<JingleListener> getListenersList() {
195        ArrayList<JingleListener> result;
196
197        synchronized (listeners) {
198            result = new ArrayList<JingleListener>(listeners);
199        }
200
201        return result;
202    }
203
204    /**
205     * Dispatch an incoming packet.
206     * 
207     * The negotiators form a tree relationship that roughly matches the Jingle packet format:
208     * 
209     * JingleSession
210     *      Content Negotiator
211     *          Media Negotiator
212     *          Transport Negotiator
213     *      Content Negotiator
214     *          Media Negotiator
215     *          Transport Negotiator
216     *          
217     * <jingle>
218     *      <content>
219     *          <description>
220     *          <transport>
221     *      <content>
222     *          <description>
223     *          <transport>
224     *          
225     * This way, each segment of a Jingle packet has a corresponding negotiator that know how to deal with that
226     * part of the Jingle packet.  It also allows us to support Jingle packets of arbitraty complexity.
227     * 
228     * Each parent calls dispatchIncomingPacket for each of its children.  The children then pass back a List<> of
229     * results that will get sent when we reach the top level negotiator (JingleSession).
230     *
231     * @param iq the packet received
232     * @param id the ID of the response that will be sent
233     * @return the new packet to send (either a Jingle or an IQ error).
234     * @throws XMPPException
235     */
236    public abstract List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException;
237
238    
239    public void start() {
240        isStarted = true;
241        doStart();
242    }
243    
244    public boolean isStarted() {
245        return isStarted;
246    }
247    
248    /**
249     * Each of the negotiators has their individual behavior when they start.
250     */
251    protected abstract void doStart();
252    
253    /**
254     * Close the negotiation.
255     */
256    public void close() {
257
258    }
259}