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