edu.stanford.junction.provider.xmpp.JunctionProvider.java Source code

Java tutorial

Introduction

Here is the source code for edu.stanford.junction.provider.xmpp.JunctionProvider.java

Source

/*
 * Copyright (C) 2010 Stanford University
 *
 * 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 edu.stanford.junction.provider.xmpp;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.Date;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.RoomInfo;
import org.json.JSONException;
import org.json.JSONObject;

import edu.stanford.junction.Junction;
import edu.stanford.junction.JunctionMaker;
import edu.stanford.junction.JunctionException;
import edu.stanford.junction.api.activity.ActivityScript;
import edu.stanford.junction.api.activity.JunctionActor;
import edu.stanford.junction.api.messaging.MessageHeader;

public class JunctionProvider extends edu.stanford.junction.provider.JunctionProvider {
    protected XMPPSwitchboardConfig mConfig;
    protected static final boolean ONE_CONNECTION_PER_SESSION = false;
    protected static final int XMPP_PORT = 5222;
    private static boolean DBG = false;

    // TODO: Can't use a single connection right now-
    // must support multiple actors in the same activity
    // and this can't be done over a single XMPP connection now.
    // BJD 2/2/10

    //private XMPPConnection mXMPPConnection;

    public JunctionProvider(XMPPSwitchboardConfig config) {
        mConfig = config;
    }

    public Junction newJunction(URI invitation, ActivityScript script, JunctionActor actor)
            throws JunctionException {
        // this needs to be made more formal
        if (script == null) {
            script = new ActivityScript();
        }
        if (null == script.getHost()) {
            script.setUri(invitation);
        }

        XMPPConnection mXMPPConnection = getXMPPConnection(mConfig, JunctionMaker.getSessionIDFromURI(invitation));
        edu.stanford.junction.provider.xmpp.Junction jx = new edu.stanford.junction.provider.xmpp.Junction(script,
                mXMPPConnection, mConfig, this);

        jx.mAcceptedInvitation = invitation;
        jx.registerActor(actor);

        this.requestServices(jx, script);

        // creating an activity is an activity using a JunctionService.
        // Invite the JunctionMaker service to the session.
        // This service will be bundled with all Junction servers.
        //activity.requestService("JunctionMaker", mHostURL, "edu.stanford.prpl.junction.impl.JunctionMakerService");      
        return jx;
    }

    public ActivityScript getActivityScript(URI uri) throws JunctionException {

        // TODO: Move the XMPPConnection into the JunctionMaker
        // (out of Junction)
        /*
          JunctionMaker jm = null;
          String host = uri.getHost();
          if (host.equals(mSwitchboard)) {
          jm = this;
          } else {
          jm = new JunctionMaker(host);
          }
        */

        String host = uri.getHost();
        String sessionID = uri.getPath().substring(1);

        // pretty broken..
        XMPPSwitchboardConfig config = new XMPPSwitchboardConfig(host);
        XMPPConnection conn = getXMPPConnection(config, sessionID);

        String room = sessionID + "@" + config.getChatService();
        System.err.println("looking up info from xmpp room " + room);

        try {
            RoomInfo info = MultiUserChat.getRoomInfo(conn, room);
            String descString = info.getDescription();

            if (descString == null || descString.trim().length() == 0) {
                throw new JunctionException("No MUC room description found.");
            }

            JSONObject descJSON = new JSONObject(descString);

            return new ActivityScript(descJSON);

        } catch (Exception e) {
            throw new JunctionException("Failed to initialize XMPP Chat Room.", e);
        }

    }

    private static ArrayList<XMPPConnection> sConnections = new ArrayList<XMPPConnection>();
    private static ArrayList<HashSet<String>> sConnectionSessions = new ArrayList<HashSet<String>>();

    class ConnectionThread extends Thread {
        public volatile XMPPConnection connection = null;
        public volatile Throwable failureReason = null;
        public volatile boolean success = false;

        private CountDownLatch waitForConnect;
        private XMPPSwitchboardConfig config;

        public ConnectionThread(XMPPSwitchboardConfig config, CountDownLatch latch) {
            this.waitForConnect = latch;
            this.config = config;
        }

        public void run() {
            while (!isInterrupted()) {

                if (this.connection != null) {
                    connection.disconnect();
                }

                ConnectionConfiguration conf = new ConnectionConfiguration(config.host, XMPP_PORT);
                conf.setReconnectionAllowed(false);
                connection = new XMPPConnection(conf);

                try {
                    connection.connect();
                    if (config.user != null) {
                        connection.login(config.user, config.password);
                    } else {
                        connection.loginAnonymously();
                    }

                    if (isInterrupted()) {
                        success = false;
                        break;
                    } else { // All is good. Finish up. 
                        failureReason = null;
                        success = true;
                        waitForConnect.countDown();
                        if (DBG)
                            System.out.println("Got connection, ending connection thread.");
                        break;
                    }

                } catch (XMPPException e) {
                    Throwable ex = e.getWrappedThrowable();
                    if (ex instanceof IOException) {
                        failureReason = ex;
                        continue;
                    } else if (ex instanceof UnknownHostException) {
                        failureReason = ex;
                        continue;
                    } else {
                        // Otherwise, we consider the exception
                        // unrecoverable.
                        failureReason = e;
                        this.connection.disconnect();
                        waitForConnect.countDown();
                        break;
                    }
                } catch (Exception e) {
                    failureReason = e;
                    this.connection.disconnect();
                    waitForConnect.countDown();
                    break;
                }
            }
        }
    }

    private synchronized XMPPConnection getXMPPConnection(XMPPSwitchboardConfig config, String roomid)
            throws JunctionException {
        final CountDownLatch waitForConnect = new CountDownLatch(1);
        ConnectionThread t = new ConnectionThread(config, waitForConnect);
        t.start();

        boolean timedOut = false;
        try {
            timedOut = !(waitForConnect.await(config.connectionTimeout, TimeUnit.MILLISECONDS));
        } catch (InterruptedException e) {
            throw new JunctionException("Interrupted while waiting for connection.");
        }

        if (timedOut) {
            System.out.println("Connection timed out after " + config.connectionTimeout + " milliseconds.");
            t.interrupt(); // Thread may still be looping...

            String msg = "Connection attempt failed to complete within provided timeout of "
                    + config.connectionTimeout + " milliseconds.";
            throw new JunctionException(msg, new ConnectionTimeoutException(msg));
        }

        if (t.success) {
            if (DBG)
                System.out.println("Connection succeeded.");
            sConnections.add(t.connection);
            HashSet<String> set = new HashSet<String>();
            set.add(roomid);
            sConnectionSessions.add(set);
            return t.connection;
        } else {
            if (t.failureReason != null) {
                throw new JunctionException("Connection attempt failed.", t.failureReason);
            } else {
                throw new JunctionException("Connection attempt failed for unknown reason.", t.failureReason);
            }
        }
    }

    protected synchronized void remove(edu.stanford.junction.provider.xmpp.Junction jx) {
        // O(n) can be improved, but n is small.
        XMPPConnection conn = jx.mXMPPConnection;
        for (int i = 0; i < sConnections.size(); i++) {
            if (sConnections.get(i).equals(conn)) {
                sConnectionSessions.get(i).remove(jx.getSessionID());
            }
        }
    }

    // test
    public static void main(String[] args) {
        //ConnectionConfiguration config = new ConnectionConfiguration("prpl.stanford.edu",5222);
        //XMPPConnection con = new XMPPConnection(config);
        XMPPConnection con = new XMPPConnection("prpl.stanford.edu");

        try {
            con.connect();
            if (DBG)
                System.out.println("Connected.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public URI generateSessionUri() {
        String session = UUID.randomUUID().toString();
        try {
            return new URI("junction://" + mConfig.host + "/" + session);
        } catch (URISyntaxException e) {
            throw new AssertionError("Invalid URI");
        }
    }

}