Java tutorial
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); } } }