001/**
002 *
003 * Copyright the original author or authors
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.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Random;
024import java.util.logging.Logger;
025
026import org.jivesoftware.smack.AbstractConnectionClosedListener;
027import org.jivesoftware.smack.ConnectionListener;
028import org.jivesoftware.smack.StanzaListener;
029import org.jivesoftware.smack.SmackException;
030import org.jivesoftware.smack.SmackException.NotConnectedException;
031import org.jivesoftware.smack.XMPPConnection;
032import org.jivesoftware.smack.XMPPException;
033import org.jivesoftware.smack.filter.StanzaFilter;
034import org.jivesoftware.smack.packet.IQ;
035import org.jivesoftware.smack.packet.Stanza;
036import org.jivesoftware.smack.packet.XMPPError;
037import org.jivesoftware.smackx.jingleold.listeners.JingleListener;
038import org.jivesoftware.smackx.jingleold.listeners.JingleMediaListener;
039import org.jivesoftware.smackx.jingleold.listeners.JingleSessionListener;
040import org.jivesoftware.smackx.jingleold.listeners.JingleTransportListener;
041import org.jivesoftware.smackx.jingleold.media.JingleMediaManager;
042import org.jivesoftware.smackx.jingleold.media.JingleMediaSession;
043import org.jivesoftware.smackx.jingleold.media.MediaNegotiator;
044import org.jivesoftware.smackx.jingleold.media.MediaReceivedListener;
045import org.jivesoftware.smackx.jingleold.media.PayloadType;
046import org.jivesoftware.smackx.jingleold.nat.JingleTransportManager;
047import org.jivesoftware.smackx.jingleold.nat.TransportCandidate;
048import org.jivesoftware.smackx.jingleold.nat.TransportNegotiator;
049import org.jivesoftware.smackx.jingleold.nat.TransportResolver;
050import org.jivesoftware.smackx.jingleold.packet.Jingle;
051import org.jivesoftware.smackx.jingleold.packet.JingleError;
052
053/**
054 * An abstract Jingle session. <p/> This class contains some basic properties of
055 * every Jingle session. However, the concrete implementation can be found in
056 * subclasses.
057 * 
058 * @author Alvaro Saurin
059 * @author Jeff Williams
060 */
061public class JingleSession extends JingleNegotiator implements MediaReceivedListener {
062
063        private static final Logger LOGGER = Logger.getLogger(JingleSession.class.getName());
064
065        // static
066    private static final HashMap<XMPPConnection, JingleSession> sessions = new HashMap<XMPPConnection, JingleSession>();
067
068    private static final Random randomGenerator = new Random();
069
070    // non-static
071
072    private String initiator; // Who started the communication
073
074    private String responder; // The other endpoint
075
076    private String sid; // A unique id that identifies this session
077
078    ConnectionListener connectionListener;
079    
080    StanzaListener packetListener;
081
082    StanzaFilter packetFilter;
083
084    protected List<JingleMediaManager> jingleMediaManagers = null;
085
086    private JingleSessionState sessionState;
087
088    private List<ContentNegotiator> contentNegotiators;
089
090    private XMPPConnection connection;
091
092    private String sessionInitPacketID;
093    
094    private Map<String, JingleMediaSession> mediaSessionMap;
095
096    /**
097     * Full featured JingleSession constructor
098     * 
099     * @param conn
100     *            the XMPPConnection which is used
101     * @param initiator
102     *            the initiator JID
103     * @param responder
104     *            the responder JID
105     * @param sessionid
106     *            the session ID
107     * @param jingleMediaManagers
108     *            the jingleMediaManager
109     */
110    public JingleSession(XMPPConnection conn, String initiator, String responder, String sessionid,
111            List<JingleMediaManager> jingleMediaManagers) {
112        super();
113
114        this.initiator = initiator;
115        this.responder = responder;
116        this.sid = sessionid;
117        this.jingleMediaManagers = jingleMediaManagers;
118        this.setSession(this);
119        this.connection = conn;
120
121        // Initially, we don't known the session state.
122        setSessionState(JingleSessionStateUnknown.getInstance());
123        
124        contentNegotiators = new ArrayList<ContentNegotiator>();
125        mediaSessionMap = new HashMap<String, JingleMediaSession>();
126
127        // Add the session to the list and register the listeneres
128        registerInstance();
129        installConnectionListeners(conn);
130    }
131
132    /**
133     * JingleSession constructor (for an outgoing Jingle session)
134     * 
135     * @param conn
136     *            Connection
137     * @param initiator
138     *            the initiator JID
139     * @param responder
140     *            the responder JID
141     * @param jingleMediaManagers
142     *            the jingleMediaManager
143     */
144    public JingleSession(XMPPConnection conn, JingleSessionRequest request, String initiator, String responder,
145            List<JingleMediaManager> jingleMediaManagers) {
146        this(conn, initiator, responder, generateSessionId(), jingleMediaManagers);
147        //sessionRequest = request; // unused
148    }
149
150    /**
151     * Get the session initiator
152     * 
153     * @return the initiator
154     */
155    public String getInitiator() {
156        return initiator;
157    }
158
159    public XMPPConnection getConnection() {
160        return connection;
161    }
162
163    /**
164     * Set the session initiator
165     * 
166     * @param initiator
167     *            the initiator to set
168     */
169    public void setInitiator(String initiator) {
170        this.initiator = initiator;
171    }
172
173    /**
174     * Get the Media Manager of this Jingle Session
175     * 
176     * @return the JingleMediaManagers
177     */
178    public List<JingleMediaManager> getMediaManagers() {
179        return jingleMediaManagers;
180    }
181
182    /**
183     * Set the Media Manager of this Jingle Session
184     * 
185     * @param jingleMediaManagers
186     */
187    public void setMediaManagers(List<JingleMediaManager> jingleMediaManagers) {
188        this.jingleMediaManagers = jingleMediaManagers;
189    }
190
191    /**
192     * Get the session responder
193     * 
194     * @return the responder
195     */
196    public String getResponder() {
197        return responder;
198    }
199
200    /**
201     * Set the session responder.
202     * 
203     * @param responder
204     *            the receptor to set
205     */
206    public void setResponder(String responder) {
207        this.responder = responder;
208    }
209
210    /**
211     * Get the session ID
212     * 
213     * @return the sid
214     */
215    public String getSid() {
216        return sid;
217    }
218
219    /**
220     * Set the session ID
221     * 
222     * @param sessionId
223     *            the sid to set
224     */
225    protected void setSid(String sessionId) {
226        sid = sessionId;
227    }
228
229    /**
230     * Generate a unique session ID.
231     */
232    protected static String generateSessionId() {
233        return String.valueOf(Math.abs(randomGenerator.nextLong()));
234    }
235
236    /**
237     * Validate the state changes.
238     */
239
240    public void setSessionState(JingleSessionState stateIs) {
241
242        LOGGER.fine("Session state change: " + sessionState + "->" + stateIs);
243        stateIs.enter();
244        sessionState = stateIs;
245    }
246
247    public JingleSessionState getSessionState() {
248        return sessionState;
249    }
250
251    /**
252     * Return true if all of the media managers have finished
253     */
254    public boolean isFullyEstablished() {
255        boolean result = true;
256        for (ContentNegotiator contentNegotiator : contentNegotiators) {
257            if (!contentNegotiator.isFullyEstablished())
258                result = false;
259        }
260        return result;
261    }
262
263    // ----------------------------------------------------------------------------------------------------------
264    // Receive section
265    // ----------------------------------------------------------------------------------------------------------
266
267    /**
268     * Process and respond to an incoming packet. <p/> This method is called
269     * from the stanza(/packet) listener dispatcher when a new stanza(/packet) has arrived. The
270     * method is responsible for recognizing the stanza(/packet) type and, depending on
271     * the current state, delivering it to the right event handler and wait for
272     * a response. The response will be another Jingle stanza(/packet) that will be sent
273     * to the other end point.
274     * 
275     * @param iq
276     *            the stanza(/packet) received
277     * @throws XMPPException
278     * @throws SmackException 
279     */
280    public synchronized void receivePacketAndRespond(IQ iq) throws XMPPException, SmackException {
281        List<IQ> responses = new ArrayList<IQ>();
282
283        String responseId = null;
284
285        LOGGER.fine("Packet: " + iq.toXML());
286
287        try {
288
289            // Dispatch the packet to the JingleNegotiators and get back a list of the results.
290            responses.addAll(dispatchIncomingPacket(iq, null));
291
292            if (iq != null) {
293                responseId = iq.getStanzaId();
294
295                // Send the IQ to each of the content negotiators for further processing.
296                // Each content negotiator may pass back a list of JingleContent for addition to the response packet.
297
298                for (ContentNegotiator contentNegotiator : contentNegotiators) {
299                        // If at this point the content negotiator isn't started, it's because we sent a session-init jingle
300                        // packet from startOutgoing() and we're waiting for the other side to let us know they're ready
301                        // to take jingle packets.  (This packet might be a session-terminate, but that will get handled
302                        // later.
303                        if (!contentNegotiator.isStarted()) {
304                                contentNegotiator.start();
305                        }
306                    responses.addAll(contentNegotiator.dispatchIncomingPacket(iq, responseId));
307                }
308
309            }
310            // Acknowledge the IQ reception
311            // Not anymore.  The state machine generates an appropriate response IQ that
312            // gets sent back at the end of this routine.
313            //sendAck(iq);
314
315        } catch (JingleException e) {
316            // Send an error message, if present
317            JingleError error = e.getError();
318            if (error != null) {
319                responses.add(createJingleError(iq, error));
320            }
321
322            // Notify the session end and close everything...
323            triggerSessionClosedOnError(e);
324        }
325
326        //        // If the response is anything other than a RESULT then send it now.
327        //        if ((response != null) && (!response.getType().equals(IQ.Type.result))) {
328        //            getConnection().sendStanza(response);
329        //        }
330
331        // Loop through all of the responses and send them.
332        for (IQ response : responses) {
333            sendStanza(response);
334        }
335    }
336
337    /**
338     * Dispatch an incoming packet. The method is responsible for recognizing
339     * the stanza(/packet) type and, depending on the current state, delivering the
340     * stanza(/packet) to the right event handler and wait for a response.
341     * 
342     * @param iq
343     *            the stanza(/packet) received
344     * @return the new Jingle stanza(/packet) to send.
345     * @throws XMPPException
346     * @throws SmackException 
347     */
348    public List<IQ> dispatchIncomingPacket(IQ iq, String id) throws XMPPException, SmackException {
349        List<IQ> responses = new ArrayList<IQ>();
350        IQ response = null;
351
352        if (iq != null) {
353            if (iq.getType().equals(IQ.Type.error)) {
354                // Process errors
355                // TODO getState().eventError(iq);
356            } else if (iq.getType().equals(IQ.Type.result)) {
357                // Process ACKs
358                if (isExpectedId(iq.getStanzaId())) {
359
360                    // The other side provisionally accepted our session-initiate.
361                    // Kick off some negotiators.
362                    if (iq.getStanzaId().equals(sessionInitPacketID)) {
363                        startNegotiators();
364                    }
365                    removeExpectedId(iq.getStanzaId());
366                }
367            } else if (iq instanceof Jingle) {
368                // It is not an error: it is a Jingle packet...
369                Jingle jin = (Jingle) iq;
370                JingleActionEnum action = jin.getAction();
371
372                // Depending on the state we're in we'll get different processing actions.
373                // (See Design Patterns AKA GoF State behavioral pattern.)
374                response = getSessionState().processJingle(this, jin, action);
375            }
376        }
377
378        if (response != null) {
379            // Save the packet id, for recognizing ACKs...
380            addExpectedId(response.getStanzaId());
381            responses.add(response);
382        }
383
384        return responses;
385    }
386
387    /**
388     * Add a new content negotiator on behalf of a <content> section received.
389     */
390    public void addContentNegotiator(ContentNegotiator inContentNegotiator) {
391        contentNegotiators.add(inContentNegotiator);
392    }
393
394  
395
396     // ----------------------------------------------------------------------------------------------------------
397    // Send section
398    // ----------------------------------------------------------------------------------------------------------
399
400    public void sendStanza(IQ iq) throws NotConnectedException {
401
402        if (iq instanceof Jingle) {
403
404            sendFormattedJingle((Jingle) iq);
405
406        } else {
407
408            getConnection().sendStanza(iq);
409        }
410    }
411
412    /**
413     * Complete and send a packet. Complete all the null fields in a Jingle
414     * reponse, using the session information we have.
415     * 
416     * @param jout
417     *            the Jingle stanza(/packet) we want to complete and send
418     * @throws NotConnectedException 
419     */
420    public Jingle sendFormattedJingle(Jingle jout) throws NotConnectedException {
421        return sendFormattedJingle(null, jout);
422    }
423
424    /**
425     * Complete and send a packet. Complete all the null fields in a Jingle
426     * reponse, using the session information we have or some info from the
427     * incoming packet.
428     * 
429     * @param iq
430     *            The Jingle stanza(/packet) we are responing to
431     * @param jout
432     *            the Jingle stanza(/packet) we want to complete and send
433     * @throws NotConnectedException 
434     */
435    public Jingle sendFormattedJingle(IQ iq, Jingle jout) throws NotConnectedException {
436        if (jout != null) {
437            if (jout.getInitiator() == null) {
438                jout.setInitiator(getInitiator());
439            }
440
441            if (jout.getResponder() == null) {
442                jout.setResponder(getResponder());
443            }
444
445            if (jout.getSid() == null) {
446                jout.setSid(getSid());
447            }
448
449            String me = getConnection().getUser();
450            String other = getResponder().equals(me) ? getInitiator() : getResponder();
451
452            if (jout.getTo() == null) {
453                if (iq != null) {
454                    jout.setTo(iq.getFrom());
455                } else {
456                    jout.setTo(other);
457                }
458            }
459
460            if (jout.getFrom() == null) {
461                if (iq != null) {
462                    jout.setFrom(iq.getTo());
463                } else {
464                    jout.setFrom(me);
465                }
466            }
467
468            // The the packet.
469            if ((getConnection() != null) && (getConnection().isConnected()))
470                getConnection().sendStanza(jout);
471        }
472        return jout;
473    }
474
475    /**
476     *  @param inJingle
477     *  @param inAction
478     */
479    //    private void sendUnknownStateAction(Jingle inJingle, JingleActionEnum inAction) {
480    //
481    //        if (inAction == JingleActionEnum.SESSION_INITIATE) {
482    //            // Prepare to receive and act on response packets.
483    //            updatePacketListener();
484    //
485    //            // Send the actual packet.
486    //            sendStanza(inJingle);
487    //
488    //            // Change to the PENDING state.
489    //            setSessionState(JingleSessionStateEnum.PENDING);
490    //        } else {
491    //            throw new IllegalStateException("Only session-initiate allowed in the UNKNOWN state.");
492    //        }
493    //    }
494
495    /**
496     * Acknowledge a IQ packet.
497     * 
498     * @param iq
499     *            The IQ to acknowledge
500     */
501    public IQ createAck(IQ iq) {
502        IQ result = null;
503
504        if (iq != null) {
505            // Don't acknowledge ACKs, errors...
506            if (iq.getType().equals(IQ.Type.set)) {
507                IQ ack = IQ.createResultIQ(iq);
508
509                // No! Don't send it.  Let it flow to the normal way IQ results get processed and sent.
510                // getConnection().sendStanza(ack);
511                result = ack;
512            }
513        }
514        return result;
515    }
516
517    /**
518     * Send a content info message.
519     */
520    //    public synchronized void sendContentInfo(ContentInfo ci) {
521    //        sendStanza(new Jingle(new JingleContentInfo(ci)));
522    //    }
523    /*
524     * (non-Javadoc)
525     * 
526     * @see java.lang.Object#hashCode()
527     */
528    public int hashCode() {
529        return Jingle.getSessionHash(getSid(), getInitiator());
530    }
531
532    /*
533     * (non-Javadoc)
534     * 
535     * @see java.lang.Object#equals(java.lang.Object)
536     */
537    public boolean equals(Object obj) {
538        if (this == obj) {
539            return true;
540        }
541        if (obj == null) {
542            return false;
543        }
544        if (getClass() != obj.getClass()) {
545            return false;
546        }
547
548        final JingleSession other = (JingleSession) obj;
549
550        if (initiator == null) {
551            if (other.initiator != null) {
552                return false;
553            }
554        } else if (!initiator.equals(other.initiator)) {
555            // Todo check behavior
556            // return false;
557        }
558
559        if (responder == null) {
560            if (other.responder != null) {
561                return false;
562            }
563        } else if (!responder.equals(other.responder)) {
564            return false;
565        }
566
567        if (sid == null) {
568            if (other.sid != null) {
569                return false;
570            }
571        } else if (!sid.equals(other.sid)) {
572            return false;
573        }
574
575        return true;
576    }
577
578    // Instances management
579
580    /**
581     * Clean a session from the list.
582     * 
583     * @param connection
584     *            The connection to clean up
585     */
586    private void unregisterInstanceFor(XMPPConnection connection) {
587        synchronized (sessions) {
588            sessions.remove(connection);
589        }
590    }
591
592    /**
593     * Register this instance.
594     */
595    private void registerInstance() {
596        synchronized (sessions) {
597            sessions.put(getConnection(), this);
598        }
599    }
600
601    /**
602     * Returns the JingleSession related to a particular connection.
603     * 
604     * @param con
605     *            A XMPP connection
606     * @return a Jingle session
607     */
608    public static JingleSession getInstanceFor(XMPPConnection con) {
609        if (con == null) {
610            throw new IllegalArgumentException("XMPPConnection cannot be null");
611        }
612
613        JingleSession result = null;
614        synchronized (sessions) {
615            if (sessions.containsKey(con)) {
616                result = sessions.get(con);
617            }
618        }
619
620        return result;
621    }
622
623    /**
624     * Configure a session, setting some action listeners...
625     * 
626     * @param connection
627     *            The connection to set up
628     */
629    private void installConnectionListeners(final XMPPConnection connection) {
630        if (connection != null) {
631            connectionListener = new AbstractConnectionClosedListener() {
632                @Override
633                public void connectionTerminated() {
634                    unregisterInstanceFor(connection);
635                }
636            };
637            connection.addConnectionListener(connectionListener);
638        }
639    }
640    
641    private void removeConnectionListener() {
642        if (connectionListener != null) {
643                getConnection().removeConnectionListener(connectionListener);
644                
645                LOGGER.fine("JINGLE SESSION: REMOVE CONNECTION LISTENER");
646        }
647    }
648
649    /**
650     * Remove the stanza(/packet) listener used for processing packet.
651     */
652    protected void removeAsyncPacketListener() {
653        if (packetListener != null) {
654            getConnection().removeAsyncStanzaListener(packetListener);
655
656            LOGGER.fine("JINGLE SESSION: REMOVE PACKET LISTENER");
657        }
658    }
659
660    /**
661     * Install the stanza(/packet) listener. The listener is responsible for responding
662     * to any stanza(/packet) that we receive...
663     */
664    protected void updatePacketListener() {
665        removeAsyncPacketListener();
666
667        LOGGER.fine("UpdatePacketListener");
668
669        packetListener = new StanzaListener() {
670            public void processPacket(Stanza packet) {
671                try {
672                    receivePacketAndRespond((IQ) packet);
673                } catch (Exception e) {
674                    e.printStackTrace();
675                }
676            }
677        };
678
679        packetFilter = new StanzaFilter() {
680            public boolean accept(Stanza packet) {
681
682                if (packet instanceof IQ) {
683                    IQ iq = (IQ) packet;
684
685                    String me = getConnection().getUser();
686
687                    if (!iq.getTo().equals(me)) {
688                        return false;
689                    }
690
691                    String other = getResponder().equals(me) ? getInitiator() : getResponder();
692
693                    if (iq.getFrom() == null || !iq.getFrom().equals(other == null ? "" : other)) {
694                        return false;
695                    }
696
697                    if (iq instanceof Jingle) {
698                        Jingle jin = (Jingle) iq;
699
700                        String sid = jin.getSid();
701                        if (sid == null || !sid.equals(getSid())) {
702                            LOGGER.fine("Ignored Jingle(SID) " + sid + "|" + getSid() + " :" + iq.toXML());
703                            return false;
704                        }
705                        String ini = jin.getInitiator();
706                        if (!ini.equals(getInitiator())) {
707                            LOGGER.fine("Ignored Jingle(INI): " + iq.toXML());
708                            return false;
709                        }
710                    } else {
711                        // We accept some non-Jingle IQ packets: ERRORs and ACKs
712                        if (iq.getType().equals(IQ.Type.set)) {
713                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
714                            return false;
715                        } else if (iq.getType().equals(IQ.Type.get)) {
716                            LOGGER.fine("Ignored Jingle(TYPE): " + iq.toXML());
717                            return false;
718                        }
719                    }
720                    return true;
721                }
722                return false;
723            }
724        };
725
726        getConnection().addAsyncStanzaListener(packetListener, packetFilter);
727    }
728
729    // Listeners
730
731    /**
732     * Add a listener for jmf negotiation events
733     * 
734     * @param li
735     *            The listener
736     */
737    public void addMediaListener(JingleMediaListener li) {
738        for (ContentNegotiator contentNegotiator : contentNegotiators) {
739            if (contentNegotiator.getMediaNegotiator() != null) {
740                contentNegotiator.getMediaNegotiator().addListener(li);
741            }
742        }
743
744    }
745
746    /**
747     * Remove a listener for jmf negotiation events
748     * 
749     * @param li
750     *            The listener
751     */
752    public void removeMediaListener(JingleMediaListener li) {
753        for (ContentNegotiator contentNegotiator : contentNegotiators) {
754            if (contentNegotiator.getMediaNegotiator() != null) {
755                contentNegotiator.getMediaNegotiator().removeListener(li);
756            }
757        }
758    }
759
760    /**
761     * Add a listener for transport negotiation events
762     * 
763     * @param li
764     *            The listener
765     */
766    public void addTransportListener(JingleTransportListener li) {
767        for (ContentNegotiator contentNegotiator : contentNegotiators) {
768            if (contentNegotiator.getTransportNegotiator() != null) {
769                contentNegotiator.getTransportNegotiator().addListener(li);
770            }
771        }
772    }
773
774    /**
775     * Remove a listener for transport negotiation events
776     * 
777     * @param li
778     *            The listener
779     */
780    public void removeTransportListener(JingleTransportListener li) {
781        for (ContentNegotiator contentNegotiator : contentNegotiators) {
782            if (contentNegotiator.getTransportNegotiator() != null) {
783                contentNegotiator.getTransportNegotiator().removeListener(li);
784            }
785        }
786    }
787
788    /**
789     * Setup the listeners that act on events coming from the lower level negotiators.
790     */
791
792    public void setupListeners() {
793
794        JingleMediaListener jingleMediaListener = new JingleMediaListener() {
795            public void mediaClosed(PayloadType cand) {
796            }
797
798            public void mediaEstablished(PayloadType pt) throws NotConnectedException {
799                if (isFullyEstablished()) {
800                    Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
801
802                    // Build up a response packet from each media manager.
803                    for (ContentNegotiator contentNegotiator : contentNegotiators) {
804                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
805                            jout.addContent(contentNegotiator.getJingleContent());
806                    }
807                    // Send the "accept" and wait for the ACK
808                    addExpectedId(jout.getStanzaId());
809                    sendStanza(jout);
810
811                    //triggerSessionEstablished();
812
813                }
814            }
815        };
816
817        JingleTransportListener jingleTransportListener = new JingleTransportListener() {
818
819            public void transportEstablished(TransportCandidate local, TransportCandidate remote) throws NotConnectedException {
820                if (isFullyEstablished()) {
821 
822                        // Indicate that this session is active.
823                        setSessionState(JingleSessionStateActive.getInstance());
824                        
825                        for (ContentNegotiator contentNegotiator : contentNegotiators) {
826                        if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
827                            contentNegotiator.triggerContentEstablished();
828                    }
829
830                    if (getSessionState().equals(JingleSessionStatePending.getInstance())) {
831                        
832                        Jingle jout = new Jingle(JingleActionEnum.SESSION_ACCEPT);
833
834                        // Build up a response packet from each media manager.
835                        for (ContentNegotiator contentNegotiator : contentNegotiators) {
836                            if (contentNegotiator.getNegotiatorState() == JingleNegotiatorState.SUCCEEDED)
837                                jout.addContent(contentNegotiator.getJingleContent());
838                        }
839                        // Send the "accept" and wait for the ACK
840                        addExpectedId(jout.getStanzaId());
841                        sendStanza(jout);
842                    }
843                }
844            }
845
846            public void transportClosed(TransportCandidate cand) {
847            }
848
849            public void transportClosedOnError(XMPPException e) {
850            }
851        };
852
853        addMediaListener(jingleMediaListener);
854        addTransportListener(jingleTransportListener);
855    }
856
857    // Triggers
858
859    /**
860     * Trigger a session closed event.
861     */
862    protected void triggerSessionClosed(String reason) {
863        //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
864        //
865        //            contentNegotiator.stopJingleMediaSession();
866        //
867        //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
868        //                candidate.removeCandidateEcho();
869        //        }
870
871        List<JingleListener> listeners = getListenersList();
872        for (JingleListener li : listeners) {
873            if (li instanceof JingleSessionListener) {
874                JingleSessionListener sli = (JingleSessionListener) li;
875                sli.sessionClosed(reason, this);
876            }
877        }
878        close();
879    }
880
881    /**
882     * Trigger a session closed event due to an error.
883     */
884    protected void triggerSessionClosedOnError(XMPPException exc) {
885        for (ContentNegotiator contentNegotiator : contentNegotiators) {
886
887            contentNegotiator.stopJingleMediaSession();
888
889            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
890                candidate.removeCandidateEcho();
891        }
892        List<JingleListener> listeners = getListenersList();
893        for (JingleListener li : listeners) {
894            if (li instanceof JingleSessionListener) {
895                JingleSessionListener sli = (JingleSessionListener) li;
896                sli.sessionClosedOnError(exc, this);
897            }
898        }
899        close();
900    }
901
902    /**
903     * Trigger a session established event.
904     */
905    //    protected void triggerSessionEstablished() {
906    //        List<JingleListener> listeners = getListenersList();
907    //        for (JingleListener li : listeners) {
908    //            if (li instanceof JingleSessionListener) {
909    //                JingleSessionListener sli = (JingleSessionListener) li;
910    //                sli.sessionEstablished(this);
911    //            }
912    //        }
913    //    }
914    /**
915     * Trigger a media received event.
916     */
917    protected void triggerMediaReceived(String participant) {
918        List<JingleListener> listeners = getListenersList();
919        for (JingleListener li : listeners) {
920            if (li instanceof JingleSessionListener) {
921                JingleSessionListener sli = (JingleSessionListener) li;
922                sli.sessionMediaReceived(this, participant);
923            }
924        }
925    }
926
927    /**
928     * Trigger a session redirect event.
929     */
930    //    protected void triggerSessionRedirect(String arg) {
931    //        List<JingleListener> listeners = getListenersList();
932    //        for (JingleListener li : listeners) {
933    //            if (li instanceof JingleSessionListener) {
934    //                JingleSessionListener sli = (JingleSessionListener) li;
935    //                sli.sessionRedirected(arg, this);
936    //            }
937    //        }
938    //    }
939    /**
940     * Trigger a session decline event.
941     */
942    //    protected void triggerSessionDeclined(String reason) {
943    //        List<JingleListener> listeners = getListenersList();
944    //        for (JingleListener li : listeners) {
945    //            if (li instanceof JingleSessionListener) {
946    //                JingleSessionListener sli = (JingleSessionListener) li;
947    //                sli.sessionDeclined(reason, this);
948    //            }
949    //        }
950    //        for (ContentNegotiator contentNegotiator : contentNegotiators) {
951    //            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
952    //                candidate.removeCandidateEcho();
953    //        }
954    //    }
955    /**
956     * Terminates the session with default reason.
957     * 
958     * @throws XMPPException
959     * @throws NotConnectedException 
960     */
961    public void terminate() throws XMPPException, NotConnectedException {
962        terminate("Closed Locally");
963    }
964
965    /**
966     * Terminates the session with a custom reason.
967     * 
968     * @throws XMPPException
969     * @throws NotConnectedException 
970     */
971    public void terminate(String reason) throws XMPPException, NotConnectedException {
972        if (isClosed())
973            return;
974        LOGGER.fine("Terminate " + reason);
975        Jingle jout = new Jingle(JingleActionEnum.SESSION_TERMINATE);
976        jout.setType(IQ.Type.set);
977        sendStanza(jout);
978        triggerSessionClosed(reason);
979    }
980
981    /**
982     * Terminate negotiations.
983     */
984    public void close() {
985        if (isClosed())
986            return;
987
988        // Set the session state to ENDED.
989        setSessionState(JingleSessionStateEnded.getInstance());
990
991        for (ContentNegotiator contentNegotiator : contentNegotiators) {
992
993            contentNegotiator.stopJingleMediaSession();
994
995            for (TransportCandidate candidate : contentNegotiator.getTransportNegotiator().getOfferedCandidates())
996                candidate.removeCandidateEcho();
997
998            contentNegotiator.close();
999        }
1000        removeAsyncPacketListener();
1001        removeConnectionListener();
1002        getConnection().removeConnectionListener(connectionListener);
1003        LOGGER.fine("Negotiation Closed: " + getConnection().getUser() + " " + sid);
1004        super.close();
1005
1006    }
1007
1008    public boolean isClosed() {
1009        return getSessionState().equals(JingleSessionStateEnded.getInstance());
1010    }
1011
1012    // Packet and error creation
1013
1014    /**
1015     * Complete and send an error. Complete all the null fields in an IQ error
1016     * reponse, using the sesssion information we have or some info from the
1017     * incoming packet.
1018     * 
1019     * @param iq
1020     *            The Jingle stanza(/packet) we are responing to
1021     * @param jingleError
1022     *            the IQ stanza(/packet) we want to complete and send
1023     */
1024    public IQ createJingleError(IQ iq, JingleError jingleError) {
1025        IQ errorPacket = null;
1026        if (jingleError != null) {
1027            // TODO This is wrong according to XEP-166 ยง 10, but this jingle implementation is deprecated anyways
1028            XMPPError error = new XMPPError(XMPPError.Condition.undefined_condition, jingleError);
1029
1030            errorPacket = IQ.createErrorResponse(iq, error);
1031
1032            // Fill in the fields with the info from the Jingle packet
1033            errorPacket.setStanzaId(iq.getStanzaId());
1034            errorPacket.setError(error);
1035            //            errorPacket.addExtension(jingleError);
1036
1037            // NO! Let the normal state machinery do all of the sending.
1038            // getConnection().sendStanza(perror);
1039            LOGGER.severe("Error sent: " + errorPacket.toXML());
1040        }
1041        return errorPacket;
1042    }
1043
1044    /**
1045     * Called when new Media is received.
1046     */
1047    public void mediaReceived(String participant) {
1048        triggerMediaReceived(participant);
1049    }
1050
1051    /**
1052     * This is the starting point for intitiating a new session.
1053     * 
1054     * @throws IllegalStateException
1055     * @throws SmackException 
1056     */
1057    public void startOutgoing() throws IllegalStateException, SmackException {
1058
1059        updatePacketListener();
1060        setSessionState(JingleSessionStatePending.getInstance());
1061
1062        Jingle jingle = new Jingle(JingleActionEnum.SESSION_INITIATE);
1063
1064        // Create a content negotiator for each media manager on the session.
1065        for (JingleMediaManager mediaManager : getMediaManagers()) {
1066            ContentNegotiator contentNeg = new ContentNegotiator(this, ContentNegotiator.INITIATOR, mediaManager.getName());
1067
1068            // Create the media negotiator for this content description.
1069            contentNeg.setMediaNegotiator(new MediaNegotiator(this, mediaManager, mediaManager.getPayloads(), contentNeg));
1070
1071            JingleTransportManager transportManager = mediaManager.getTransportManager();
1072            TransportResolver resolver = null;
1073            try {
1074                resolver = transportManager.getResolver(this);
1075            } catch (XMPPException e) {
1076                e.printStackTrace();
1077            }
1078
1079            if (resolver.getType().equals(TransportResolver.Type.rawupd)) {
1080                contentNeg.setTransportNegotiator(new TransportNegotiator.RawUdp(this, resolver, contentNeg));
1081            }
1082            if (resolver.getType().equals(TransportResolver.Type.ice)) {
1083                contentNeg.setTransportNegotiator(new TransportNegotiator.Ice(this, resolver, contentNeg));
1084            }
1085
1086            addContentNegotiator(contentNeg);
1087        }
1088
1089        // Give each of the content negotiators a chance to return a portion of the structure to make the Jingle packet.
1090        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1091            jingle.addContent(contentNegotiator.getJingleContent());
1092        }
1093
1094        // Save the session-initiate packet ID, so that we can respond to it.
1095        sessionInitPacketID = jingle.getStanzaId();
1096
1097        sendStanza(jingle);
1098
1099        // Now setup to track the media negotiators, so that we know when (if) to send a session-accept.
1100        setupListeners();
1101
1102        // Give each of the content negotiators a chance to start 
1103        // and return a portion of the structure to make the Jingle packet.
1104        
1105// Don't do this anymore.  The problem is that the other side might not be ready.
1106// Later when we receive our first jingle packet from the other side we'll fire-up the negotiators
1107// before processing it.  (See receivePacketAndRespond() above.
1108//        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1109//            contentNegotiator.start();
1110//        }
1111    }
1112
1113    /**
1114     *  This is the starting point for responding to a new session.
1115     */
1116    public void startIncoming() {
1117
1118        //updatePacketListener();
1119    }
1120    
1121    protected void doStart() {
1122        
1123    }
1124
1125    /**
1126     * When we initiate a session we need to start a bunch of negotiators right after we receive the result
1127     * stanza(/packet) for our session-initiate.  This is where we start them.
1128     * 
1129     */
1130    private void startNegotiators() {
1131
1132        for (ContentNegotiator contentNegotiator : contentNegotiators) {
1133            TransportNegotiator transNeg = contentNegotiator.getTransportNegotiator();
1134            transNeg.start();
1135        }
1136    }
1137    
1138    /**
1139     * The jingle session may have one or more media managers that are trying to establish media sessions.
1140     * When the media manager succeeds in creating a media session is registers it with the session by the
1141     * media manager's static name.  This routine is where the media manager does the registering.
1142     */
1143    public void addJingleMediaSession(String mediaManagerName, JingleMediaSession mediaSession) {
1144        mediaSessionMap.put(mediaManagerName, mediaSession);
1145    }
1146    
1147    /**
1148     * The jingle session may have one or more media managers that are trying to establish media sessions.
1149     * When the media manager succeeds in creating a media session is registers it with the session by the
1150     * media manager's static name. This routine is where other objects can access the registered media sessions.
1151     * NB: If the media manager has not succeeded in establishing a media session then this could return null.
1152     */
1153    public JingleMediaSession getMediaSession(String mediaManagerName) {
1154        return mediaSessionMap.get(mediaManagerName);
1155    }
1156}