com.conwet.xjsp.session.SessionState.java Source code

Java tutorial

Introduction

Here is the source code for com.conwet.xjsp.session.SessionState.java

Source

package com.conwet.xjsp.session;

/*
 * #%L
 * eXtensible JSON Streaming Protocol
 * %%
 * Copyright (C) 2011 - 2014 CoNWeT Lab., Universidad Politcnica de Madrid
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */

import com.conwet.xjsp.errors.ConnectionException;
import com.conwet.xjsp.features.FeatureMap;
import com.conwet.xjsp.features.ImmutableMessage;
import com.conwet.xjsp.features.FeatureHandler;
import com.conwet.xjsp.features.Session;
import com.conwet.xjsp.XJSPConstants;
import com.conwet.xjsp.errors.ConnError;
import com.conwet.xjsp.features.SessionListener;
import com.conwet.xjsp.json.JSONUtil;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Normal connection state in which messages are interchanged.
 *
 * @apiviz.stereotype State
 * @apiviz.uses com.conwet.silbops.xjsp.session.ClosingState
 * @author sortega
 */
public class SessionState implements ConnectionState {

    private static final Logger logger = LoggerFactory.getLogger(SessionState.class);

    private final FeatureMap features;
    private Session session;
    private AtomicBoolean closed;

    public SessionState(FeatureMap features) {
        this.features = features;
        this.session = null; // Lazy initialization
        this.closed = new AtomicBoolean();
    }

    @Override
    public void start(final ConnectionContext context) throws ConnectionException {
        this.session = new AbstractSession(context.getChannel(), features) {

            @Override
            public void close() {
                SessionState.this.close(context);
            }
        };

        // register the features used so each SessionListener knows
        // if it has to register itself or not.
        this.session.setAttribute("features", features);

        for (SessionListener listener : context.getSessionListeners()) {
            listener.sessionCreated(this.session);
        }
    }

    @Override
    public void newData(ConnectionContext context) throws ConnectionException {
        String stanza;
        JSONParser parser = new JSONParser();
        try {

            while ((stanza = JSONUtil.extractStanza(context.getBuffer())) != null) {
                if ("]}".equals(stanza)) {
                    try {
                        context.getChannel().sendFragment("]}");
                    } catch (IOException ex) {
                        throw new ConnectionException(ConnError.ApplicationError, ex.getMessage());
                    } finally {
                        closed.set(Boolean.TRUE);
                        context.dispose();
                        removeListeners(context);
                    }
                } else {
                    try {
                        JSONObject json = (JSONObject) parser.parse(stanza);
                        handleMessage(json, context);

                    } catch (ParseException ex) {
                        throw new ConnectionException(ConnError.ProtocolSyntaxError,
                                "Invalid stanza: " + ex.getMessage());
                    }
                }
            }

        } catch (ParseException ex) {
            throw new ConnectionException(ConnError.ProtocolSyntaxError, ex.getMessage());
        }
    }

    @Override
    public long getTimeoutPeriod(ConnectionContext context) {

        if (context.getBuffer().toString().isEmpty()) {

            return 0;
        }

        return XJSPConstants.RESPONSE_TIMEOUT;
    }

    @Override
    public void timeout(ConnectionContext context) throws ConnectionException {
        if (!context.getBuffer().toString().isEmpty()) {
            context.getBuffer().setLength(0);
            throw new ConnectionException(ConnError.MessageTimeout);
        }
    }

    @Override
    public void abort(ConnectionContext context) {

        context.setState(new AbortedState(session));
    }

    private void handleMessage(JSONObject json, final ConnectionContext context) throws ConnectionException {

        hasFields(json, new String[] { "type", "id", "message" });

        String[] type = ((String) json.get("type")).split(":");
        if (type.length != 2) {
            throw new ConnectionException(ConnError.MessageFieldError, "type");
        }
        FeatureHandler feature = features.resolveFeature(type[0]);
        if (feature == null) {
            throw new ConnectionException(ConnError.UnknownPrefix, type[0]);
        }

        ImmutableMessage message = new ImmutableMessage(feature.getFeatureName(), type[1], json.get("message"),
                (String) json.get("id"));

        try {
            feature.handleMessage(message, this.session);
        } catch (Exception ex) {
            logger.warn("Application error when handling message {}", message, ex);
            throw new ConnectionException(ConnError.ApplicationError, ex.getMessage());
        }
    }

    private synchronized void close(ConnectionContext context) {
        if (!closed.getAndSet(Boolean.TRUE)) {

            context.setState(new ClosingState());
            removeListeners(context);
        }
    }

    private void removeListeners(ConnectionContext context) {

        for (SessionListener listener : context.getSessionListeners()) {

            listener.sessionDestroyed(session);
        }
    }

    private static void hasFields(JSONObject json, String[] keys) throws ConnectionException {
        for (String key : keys) {
            if (!json.containsKey(key)) {
                throw new ConnectionException(ConnError.MessageFieldError, key);
            }
        }
    }
}