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

Java tutorial

Introduction

Here is the source code for com.conwet.xjsp.session.Negotiator.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 java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import com.conwet.xjsp.errors.ConnError;
import com.conwet.xjsp.errors.ConnectionException;
import com.conwet.xjsp.features.DefaultFeatureMap;
import com.conwet.xjsp.features.FeatureHandler;
import com.conwet.xjsp.features.FeatureMap;
import com.conwet.xjsp.json.JSONUtil;

/**
 * This class hold common negotiation and decoding phases behavior
 * for clients and servers.
 *  
 * @author sergio
 */
public class Negotiator {

    private static final String PROTOCOL = "xjsp";
    private static final String VERSION = "1.0";

    static final Pattern START_PATTERN = Pattern.compile("^\\s*\\{\\s*\"header\"\\s*:\\s*");
    static final Pattern MESSAGE_PATTERN = Pattern.compile("^\\s*,\\s*\"messages\"\\s*:\\s*\\[\\s*");

    static final String OPENING_FRAGMENT = "{\"header\": {" + "\"protocol\": \"" + PROTOCOL + "\","
            + "\"version\" : \"" + VERSION + "\"," + "\"features\": [%s]},\"messages\":[";

    private final Map<String, FeatureHandler> handlers;
    private final DefaultFeatureMap prefixes; // the features read.
    private final JSONParser parser;
    private NegotiationPhase phase;

    public Negotiator(Map<String, FeatureHandler> handlers) {

        this.handlers = handlers;
        this.prefixes = new DefaultFeatureMap();
        this.parser = new JSONParser();
        this.phase = NegotiationPhase.start;
    }

    public NegotiationPhase processNegotiation(StringBuilder buffer) throws ConnectionException {

        NegotiationPhase newPhase;

        while ((newPhase = consumeData(buffer)) != null) {

            this.phase = newPhase;
        }

        return this.phase;
    }

    /**
     * @param featureMap the {@link FeatureMap} to encode
     * @return Returns the opening fragment with the given features to send
     * over the channel.
     */
    public String getOpeningFragment(FeatureMap featureMap) {

        StringBuilder features = new StringBuilder();

        for (String prefix : featureMap.getAllPrefixes()) {

            if (features.length() > 0) {

                features.append(", ");
            }

            features.append("\"").append(prefix).append("=")
                    .append(featureMap.resolveFeature(prefix).getFeatureName()).append("\"");
        }

        return String.format(OPENING_FRAGMENT, features.toString());
    }

    /**
     * @return the {@link FeatureMap} read from this connection.
     */
    public FeatureMap getFeatures() {

        return prefixes;
    }

    /**
     * Consume available data and returns a new state when a transition is
     * reached.
     *
     * TODO: violates the least-surprise principle
     *
     * @param buffer
     * @return State to transit to or null
     * @throws ConnectionException
     */
    private NegotiationPhase consumeData(StringBuilder buffer) throws ConnectionException {

        Matcher m;
        switch (phase) {
        case start:
            m = START_PATTERN.matcher(buffer);
            if (m.find()) {
                buffer.replace(0, m.end(), "");
                return NegotiationPhase.header;
            }
            return null;

        case header:
            try {
                String header = JSONUtil.extractStanza(buffer);
                if (header == null) {
                    return null;
                }
                JSONObject jsonHeader = (JSONObject) parser.parse(header);
                validateHeader(jsonHeader);
                extractFeaturePrefixes(jsonHeader);
                return NegotiationPhase.messages;
            } catch (ParseException ex) {
                throw new ConnectionException(ConnError.ProtocolSyntaxError, "Invalid header: " + ex.getMessage());
            }

        case messages:
            m = MESSAGE_PATTERN.matcher(buffer);
            if (m.find()) {
                buffer.replace(0, m.end(), "");
                return NegotiationPhase.finished;
            }
            return null;

        case finished:
            return null;

        default: // should never reach this point
            throw new IllegalStateException("Unexpected exception");
        }
    }

    private void validateHeader(JSONObject jsonHeader) throws ConnectionException {

        // No need to check if jsonHeader is null because JSONUtil.extractStanza
        // can't return the string "null"
        String protocol = (String) jsonHeader.get("protocol");

        if (!PROTOCOL.equals(protocol)) {
            throw new ConnectionException(ConnError.NegotiationFieldError, "Unknown protocol: " + protocol);
        }

        String version = (String) jsonHeader.get("version");

        if (!VERSION.equals(version)) {
            throw new ConnectionException(ConnError.NegotiationFieldError, "Unknown protocol version: " + version);
        }

        JSONArray features = (JSONArray) jsonHeader.get("features");

        if (features == null) {
            throw new ConnectionException(ConnError.NegotiationFieldError, "header.features");
        }
    }

    private void extractFeaturePrefixes(JSONObject jsonHeader) throws ConnectionException {

        JSONArray features = (JSONArray) jsonHeader.get("features");

        for (Object feature : features) {

            String[] pair = ((String) feature).split("=");
            FeatureHandler handler = handlers.get(pair[1]);

            if (handler == null) {

                throw new ConnectionException(ConnError.UnavailableFeature, pair[1]);
            }

            prefixes.mapFeature(pair[0], handler);
        }
    }
}