Package rocks.xmpp.extensions.jingle

Source Code of rocks.xmpp.extensions.jingle.JingleManager

/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package rocks.xmpp.extensions.jingle;

import rocks.xmpp.core.Jid;
import rocks.xmpp.core.XmppException;
import rocks.xmpp.core.session.ExtensionManager;
import rocks.xmpp.core.session.SessionStatusEvent;
import rocks.xmpp.core.session.SessionStatusListener;
import rocks.xmpp.core.session.XmppSession;
import rocks.xmpp.core.stanza.IQEvent;
import rocks.xmpp.core.stanza.IQListener;
import rocks.xmpp.core.stanza.model.StanzaError;
import rocks.xmpp.core.stanza.model.client.IQ;
import rocks.xmpp.core.stanza.model.errors.BadRequest;
import rocks.xmpp.core.stanza.model.errors.ItemNotFound;
import rocks.xmpp.extensions.jingle.apps.filetransfer.model.JingleFileTransfer;
import rocks.xmpp.extensions.jingle.apps.model.ApplicationFormat;
import rocks.xmpp.extensions.jingle.model.Jingle;
import rocks.xmpp.extensions.jingle.model.errors.UnknownSession;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* @author Christian Schudt
*/
public final class JingleManager extends ExtensionManager {

    private static final Logger logger = Logger.getLogger(JingleManager.class.getName());

    private final Set<Class<? extends ApplicationFormat>> supportedApplicationFormats = new HashSet<>();

    private final Set<JingleListener> jingleListeners = new CopyOnWriteArraySet<>();

    private Map<String, JingleSession> jingleSessionMap = new ConcurrentHashMap<>();

    private JingleManager(final XmppSession xmppSession) {
        super(xmppSession);

        supportedApplicationFormats.add(JingleFileTransfer.class);

        xmppSession.addSessionStatusListener(new SessionStatusListener() {
            @Override
            public void sessionStatusChanged(SessionStatusEvent e) {
                if (e.getStatus() == XmppSession.Status.CLOSED) {
                    jingleListeners.clear();
                    jingleSessionMap.clear();
                }
            }
        });

        xmppSession.addIQListener(new IQListener() {
            @Override
            public void handle(IQEvent e) {
                IQ iq = e.getIQ();
                if (e.isIncoming() && isEnabled() && !e.isConsumed() && iq.getType() == IQ.Type.SET) {
                    Jingle jingle = iq.getExtension(Jingle.class);
                    if (jingle != null) {

                        // The value of the 'action' attribute MUST be one of the following.
                        // If an entity receives a value not defined here, it MUST ignore the attribute and MUST return a <bad-request/> error to the sender.
                        // There is no default value for the 'action' attribute.
                        if (jingle.getAction() == null) {
                            xmppSession.send(iq.createError(new StanzaError(new BadRequest(), "No valid action attribute set.")));
                        } else if (jingle.getSessionId() == null) {
                            xmppSession.send(iq.createError(new StanzaError(new BadRequest(), "No session id set.")));
                        } else if (jingle.getAction() == Jingle.Action.SESSION_INITIATE) {

                            // Check if the Jingle request is not mal-formed, otherwise return a bad-request error.
                            // See 6.3.2 Errors
                            if (jingle.getContents().isEmpty()) {
                                xmppSession.send(iq.createError(new StanzaError(new BadRequest(), "No contents found.")));
                            } else {
                                boolean hasContentWithDispositionSession = false;
                                boolean hasSupportedApplications = false;
                                boolean hasSupportedTransports = false;
                                // Check if we support the application format and transport method and at least one content element has a disposition of "session".
                                for (Jingle.Content content : jingle.getContents()) {
                                    // Check if the content disposition is "session" (default value is "session").
                                    if (!hasContentWithDispositionSession && ("session".equals(content.getDisposition()) || content.getDisposition() == null)) {
                                        hasContentWithDispositionSession = true;
                                    }

                                    if (!hasSupportedApplications && content.getApplicationFormat() != null) {
                                        hasSupportedApplications = true;
                                    }

                                    if (!hasSupportedTransports && content.getTransportMethod() != null) {
                                        hasSupportedTransports = true;
                                    }
                                }

                                if (!hasContentWithDispositionSession) {
                                    // When sending a session-initiate with one <content/> element,
                                    // the value of the <content/> element's 'disposition' attribute MUST be "session"
                                    // (if there are multiple <content/> elements then at least one MUST have a disposition of "session");
                                    // if this rule is violated, the responder MUST return a <bad-request/> error to the initiator.
                                    xmppSession.send(iq.createError(new StanzaError(new BadRequest(), "No content with disposition 'session' found.")));
                                } else {
                                    // If the request was ok, immediately acknowledge the initiation request.
                                    // See 6.3.1 Acknowledgement
                                    xmppSession.send(iq.createResult());

                                    // However, after acknowledging the session initiation request,
                                    // the responder might subsequently determine that it cannot proceed with negotiation of the session
                                    // (e.g., because it does not support any of the offered application formats or transport methods,
                                    // because a human user is busy or unable to accept the session, because a human user wishes to formally decline
                                    // the session, etc.). In these cases, the responder SHOULD immediately acknowledge the session initiation request
                                    // but then terminate the session with an appropriate reason as described in the Termination section of this document.

                                    if (!hasSupportedApplications) {
                                        // Terminate the session with <unsupported-applications/>.
                                        xmppSession.send(new IQ(iq.getFrom(), IQ.Type.SET, new Jingle(jingle.getSessionId(), Jingle.Action.SESSION_TERMINATE, new Jingle.Reason(new Jingle.Reason.UnsupportedApplications()))));
                                    } else if (!hasSupportedTransports) {
                                        // Terminate the session with <unsupported-transports/>.
                                        xmppSession.send(new IQ(iq.getFrom(), IQ.Type.SET, new Jingle(jingle.getSessionId(), Jingle.Action.SESSION_TERMINATE, new Jingle.Reason(new Jingle.Reason.UnsupportedTransports()))));
                                    } else {
                                        // Everything is fine, create the session and notify the listeners.
                                        JingleSession jingleSession = new JingleSession(jingle.getSessionId(), iq.getFrom(), false, xmppSession, JingleManager.this, jingle.getContents());
                                        jingleSessionMap.put(jingle.getSessionId(), jingleSession);
                                        notifyJingleListeners(new JingleEvent(JingleManager.this, xmppSession, iq, jingle));
                                    }
                                }
                            }
                        } else {

                            // Another action (!= session-initiate) has been sent.
                            // Check if we know the session.
                            JingleSession jingleSession = jingleSessionMap.get(jingle.getSessionId());

                            if (jingleSession == null) {
                                // If we receive a non-session-initiate Jingle action with an unknown session id,
                                // return <item-not-found/> and <unknown-session/>
                                StanzaError stanzaError = new StanzaError(new ItemNotFound());
                                stanzaError.setExtension(new UnknownSession());
                                xmppSession.send(iq.createError(stanzaError));
                            } else {
                                jingleSession.notifyJingleListeners(new JingleEvent(JingleManager.this, xmppSession, iq, jingle));
                                xmppSession.send(iq.createResult());
                            }
                        }
                        e.consume();
                    }
                }
            }
        });
    }

    public JingleSession createSession(Jid responder, Jingle.Content... contents) throws XmppException {
        if (responder == null) {
            throw new IllegalArgumentException("responder must not be null.");
        }
        if (contents.length == 0) {
            throw new IllegalArgumentException("no content provided.");
        }
        String sessionId = UUID.randomUUID().toString();
        JingleSession jingleSession = new JingleSession(sessionId, responder, true, xmppSession, this, contents);

        // Register the session. It is now in pending state until it has been accepted or terminated.
        jingleSessionMap.put(sessionId, jingleSession);

        return jingleSession;
    }

    void removeSession(String sessionId) {
        jingleSessionMap.remove(sessionId);
    }

    /**
     * Adds a Jingle listener, which allows to listen for Jingle events.
     *
     * @param jingleListener The listener.
     * @see #removeJingleListener(JingleListener)
     */
    public final void addJingleListener(JingleListener jingleListener) {
        jingleListeners.add(jingleListener);
    }

    /**
     * Removes a previously added Jingle listener.
     *
     * @param jingleListener The listener.
     * @see #addJingleListener(JingleListener)
     */
    public final void removeJingleListener(JingleListener jingleListener) {
        jingleListeners.remove(jingleListener);
    }


    /**
     * Notifies the Jingle listeners.
     *
     * @param jingleEvent The Jingle event.
     */
    void notifyJingleListeners(JingleEvent jingleEvent) {
        for (JingleListener jingleListener : jingleListeners) {
            try {
                jingleListener.jingleReceived(jingleEvent);
            } catch (Exception exc) {
                logger.log(Level.WARNING, exc.getMessage(), exc);
            }
        }
    }
}
TOP

Related Classes of rocks.xmpp.extensions.jingle.JingleManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.