<commit_before>/***************************************************************************
 *   Copyright (C) 2011 by <NAME> <<EMAIL>>          *
 *   Copyright (C) 2011 by <NAME>                                   *
 *   Copyright (C) 2011 by <NAME>                              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   For details about the authors of this software, see the AUTHORS file. *
 ***************************************************************************/

package jolie.net;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.Writer;
import java.util.Map;
import java.util.HashMap;
import java.net.URI;
import jolie.Interpreter;
import jolie.net.http.json.JsonUtils;
import jolie.net.protocols.ConcurrentCommProtocol;
import jolie.net.http.HttpMessage;
import jolie.net.http.HttpParser;
import jolie.net.http.HttpUtils;
import jolie.net.http.UnsupportedMethodException;
import jolie.net.http.UnsupportedHttpVersionException;
import jolie.runtime.FaultException;
import jolie.runtime.Value;
import jolie.runtime.VariablePath;
import jolie.runtime.typing.Type;

/**
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 *
 * 2014 <NAME>: conversion to JSONRPC over HTTP
 */
public class JsonRpcProtocol extends ConcurrentCommProtocol
{
	private final URI uri;
	private final Interpreter interpreter;
	private final boolean inInputPort;

	private final static int INITIAL_CAPACITY = 8;
	private final static float LOAD_FACTOR = 0.75f;
	
	private Map<Long, String> jsonRpcIdMap;
	private Map<String, String> jsonRpcOpMap;
	
	public String name()
	{
		return "jsonrpc";
	}

	public JsonRpcProtocol( VariablePath configurationPath, URI uri,
				Interpreter interpreter, boolean inInputPort )
	{
		super( configurationPath );
		this.uri = uri;
		this.interpreter = interpreter;
		this.inInputPort = inInputPort;
		
		// prepare the two maps
		this.jsonRpcIdMap = new HashMap<Long, String>(INITIAL_CAPACITY, LOAD_FACTOR);
		this.jsonRpcOpMap = new HashMap<String, String>(INITIAL_CAPACITY, LOAD_FACTOR);
	}

	public void send( OutputStream ostream, CommMessage message, InputStream istream )
		throws IOException
	{
		channel().setToBeClosed(!checkBooleanParameter("keepAlive", true));

		if (!message.isFault() && message.hasGenericId() && inInputPort) {
			// JSON-RPC notification mechanism (method call with dropped result)
			// we just send HTTP status code 204
			Writer writer = new OutputStreamWriter(ostream);
			writer.write("HTTP/1.1 204 No Content" + HttpUtils.CRLF);
			writer.write("Server: Jolie" + HttpUtils.CRLF + HttpUtils.CRLF);
			writer.flush();
			return;
		}
				
		Value value = Value.create();
		value.getFirstChild( "jsonrpc" ).setValue( "2.0" );
		if ( message.isFault() ) {
			Value error = value.getFirstChild( "error" );
			error.getFirstChild( "code" ).setValue( -32000 );
			error.getFirstChild( "message" ).setValue( message.fault().faultName() );
			error.getFirstChild( "data" ).setValue( message.fault().value() );
			String jsonRpcId = jsonRpcIdMap.get( message.id() );
			error.getFirstChild( "id" ).setValue( jsonRpcId );
		} else {
			if ( inInputPort ) {
				value.getChildren( "result" ).set( 0, message.value() );
				String jsonRpcId = jsonRpcIdMap.get( message.id() );
				value.getFirstChild( "id" ).setValue( jsonRpcId );
			} else {
				jsonRpcOpMap.put( message.id() + "", message.operationName() );
				value.getFirstChild( "method" ).setValue( message.operationName() );
				if ( message.value().isDefined() ) {
					// some implementations need an array here
					value.getFirstChild( "params" ).getChildren( JsonUtils.JSONARRAY_KEY ).set( 0, message.value() );
				}
				value.getFirstChild( "id" ).setValue( message.id() );
			}
		}
		StringBuilder builder = new StringBuilder();
		JsonUtils.valueToJsonString( value, Type.UNDEFINED, builder );
				
		String messageString = "";
		if (inInputPort) {
			// We're responding to a request
			messageString += "HTTP/1.1 200 OK" + HttpUtils.CRLF;
			messageString += "Server: Jolie" + HttpUtils.CRLF;
		} else {
			// We're sending a request
			String path = uri.getPath(); // TODO: fix this to consider resourcePaths
			if (path == null || path.length() == 0) {
				path = "*";
			}
			messageString += "POST " + path + " HTTP/1.1" + HttpUtils.CRLF;
			messageString += "User-Agent: Jolie" + HttpUtils.CRLF;
			messageString += "Host: " + uri.getHost() + HttpUtils.CRLF;
		}

		if (channel().toBeClosed()) {
			messageString += "Connection: close" + HttpUtils.CRLF;
		}

		//messageString += "Content-Type: application/json-rpc; charset=utf-8" + HttpUtils.CRLF;
		messageString += "Content-Type: application/json; charset=utf-8" + HttpUtils.CRLF;
		messageString += "Content-Length: " + builder.length() + HttpUtils.CRLF + HttpUtils.CRLF;
		messageString += builder.toString();

		if (checkBooleanParameter("debug", false)) {
			interpreter.logInfo("[JSON-RPC debug] Sending:\n" + messageString);
		}

		Writer writer = new OutputStreamWriter(ostream);
		writer.write(messageString);
		writer.flush();
	}

	private CommMessage recv_internal( InputStream istream, OutputStream ostream )
		throws IOException
	{
		HttpParser parser = new HttpParser( istream );
		HttpMessage message = parser.parse();
		HttpUtils.recv_checkForChannelClosing( message, channel() );

		if (message.isError()) {
			throw new IOException("HTTP error: " + new String(message.content()));
		}
		if (inInputPort && message.type() != HttpMessage.Type.POST) {
			throw new UnsupportedMethodException("Only HTTP method POST allowed!", HttpMessage.Type.POST);
		}

		if (checkBooleanParameter("debug", false)) {
			interpreter.logInfo("[JSON-RPC debug] Receiving:\n" + new String(message.content()));
		}
		
		Value value = Value.create();
		JsonUtils.parseJsonIntoValue(new InputStreamReader(new ByteArrayInputStream(message.content())), value, false);
		if (!value.hasChildren("id")) {
			// JSON-RPC notification mechanism (method call with dropped result)
			if (!inInputPort) {
				throw new IOException("A JSON-RPC notification needs to be a request!");
			}
			return new CommMessage(CommMessage.GENERIC_ID, value.getFirstChild("method").strValue(),
					       "/", value.getFirstChild("params"), null);
		}
		String jsonRpcId = value.getFirstChild("id").strValue();
		if ( inInputPort ) {
			jsonRpcIdMap.put((long)jsonRpcId.hashCode(), jsonRpcId);
			return new CommMessage(jsonRpcId.hashCode(), value.getFirstChild("method").strValue(),
					       "/", value.getFirstChild("params"), null);
		} else if (value.hasChildren("error")) {
			String operationName = jsonRpcOpMap.get(jsonRpcId);
			return new CommMessage(Long.valueOf(jsonRpcId), operationName, "/", null,<commit_msg>JSONRPC - don't parse messages without content, the same is done in XML-RPC and SOAP<commit_after>/***************************************************************************
 *   Copyright (C) 2011 by <NAME> <<EMAIL>>          *
 *   Copyright (C) 2011 by <NAME>                                   *
 *   Copyright (C) 2011 by <NAME>                              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 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 Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 *   For details about the authors of this software, see the AUTHORS file. *
 ***************************************************************************/

package jolie.net;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.Writer;
import java.util.Map;
import java.util.HashMap;
import java.net.URI;
import jolie.Interpreter;
import jolie.net.http.json.JsonUtils;
import jolie.net.protocols.ConcurrentCommProtocol;
import jolie.net.http.HttpMessage;
import jolie.net.http.HttpParser;
import jolie.net.http.HttpUtils;
import jolie.net.http.UnsupportedMethodException;
import jolie.net.http.UnsupportedHttpVersionException;
import jolie.runtime.FaultException;
import jolie.runtime.Value;
import jolie.runtime.VariablePath;
import jolie.runtime.typing.Type;

/**
 * 
 * @author <NAME>
 * @author <NAME>
 * @author <NAME>
 *
 * 2014 <NAME>: conversion to JSONRPC over HTTP
 */
public class JsonRpcProtocol extends ConcurrentCommProtocol
{
	private final URI uri;
	private final Interpreter interpreter;
	private final boolean inInputPort;

	private final static int INITIAL_CAPACITY = 8;
	private final static float LOAD_FACTOR = 0.75f;
	
	private Map<Long, String> jsonRpcIdMap;
	private Map<String, String> jsonRpcOpMap;
	
	public String name()
	{
		return "jsonrpc";
	}

	public JsonRpcProtocol( VariablePath configurationPath, URI uri,
				Interpreter interpreter, boolean inInputPort )
	{
		super( configurationPath );
		this.uri = uri;
		this.interpreter = interpreter;
		this.inInputPort = inInputPort;
		
		// prepare the two maps
		this.jsonRpcIdMap = new HashMap<Long, String>(INITIAL_CAPACITY, LOAD_FACTOR);
		this.jsonRpcOpMap = new HashMap<String, String>(INITIAL_CAPACITY, LOAD_FACTOR);
	}

	public void send( OutputStream ostream, CommMessage message, InputStream istream )
		throws IOException
	{
		channel().setToBeClosed(!checkBooleanParameter("keepAlive", true));

		if (!message.isFault() && message.hasGenericId() && inInputPort) {
			// JSON-RPC notification mechanism (method call with dropped result)
			// we just send HTTP status code 204
			Writer writer = new OutputStreamWriter(ostream);
			writer.write("HTTP/1.1 204 No Content" + HttpUtils.CRLF);
			writer.write("Server: Jolie" + HttpUtils.CRLF + HttpUtils.CRLF);
			writer.flush();
			return;
		}
				
		Value value = Value.create();
		value.getFirstChild( "jsonrpc" ).setValue( "2.0" );
		if ( message.isFault() ) {
			Value error = value.getFirstChild( "error" );
			error.getFirstChild( "code" ).setValue( -32000 );
			error.getFirstChild( "message" ).setValue( message.fault().faultName() );
			error.getFirstChild( "data" ).setValue( message.fault().value() );
			String jsonRpcId = jsonRpcIdMap.get( message.id() );
			error.getFirstChild( "id" ).setValue( jsonRpcId );
		} else {
			if ( inInputPort ) {
				value.getChildren( "result" ).set( 0, message.value() );
				String jsonRpcId = jsonRpcIdMap.get( message.id() );
				value.getFirstChild( "id" ).setValue( jsonRpcId );
			} else {
				jsonRpcOpMap.put( message.id() + "", message.operationName() );
				value.getFirstChild( "method" ).setValue( message.operationName() );
				if ( message.value().isDefined() ) {
					// some implementations need an array here
					value.getFirstChild( "params" ).getChildren( JsonUtils.JSONARRAY_KEY ).set( 0, message.value() );
				}
				value.getFirstChild( "id" ).setValue( message.id() );
			}
		}
		StringBuilder builder = new StringBuilder();
		JsonUtils.valueToJsonString( value, Type.UNDEFINED, builder );
				
		String messageString = "";
		if (inInputPort) {
			// We're responding to a request
			messageString += "HTTP/1.1 200 OK" + HttpUtils.CRLF;
			messageString += "Server: Jolie" + HttpUtils.CRLF;
		} else {
			// We're sending a request
			String path = uri.getPath(); // TODO: fix this to consider resourcePaths
			if (path == null || path.length() == 0) {
				path = "*";
			}
			messageString += "POST " + path + " HTTP/1.1" + HttpUtils.CRLF;
			messageString += "User-Agent: Jolie" + HttpUtils.CRLF;
			messageString += "Host: " + uri.getHost() + HttpUtils.CRLF;
		}

		if (channel().toBeClosed()) {
			messageString += "Connection: close" + HttpUtils.CRLF;
		}

		//messageString += "Content-Type: application/json-rpc; charset=utf-8" + HttpUtils.CRLF;
		messageString += "Content-Type: application/json; charset=utf-8" + HttpUtils.CRLF;
		messageString += "Content-Length: " + builder.length() + HttpUtils.CRLF + HttpUtils.CRLF;
		messageString += builder.toString();

		if (checkBooleanParameter("debug", false)) {
			interpreter.logInfo("[JSON-RPC debug] Sending:\n" + messageString);
		}

		Writer writer = new OutputStreamWriter(ostream);
		writer.write(messageString);
		writer.flush();
	}

	private CommMessage recv_internal( InputStream istream, OutputStream ostream )
		throws IOException
	{
		HttpParser parser = new HttpParser( istream );
		HttpMessage message = parser.parse();
		HttpUtils.recv_checkForChannelClosing( message, channel() );

		if (message.isError()) {
			throw new IOException("HTTP error: " + new String(message.content()));
		}
		if (inInputPort && message.type() != HttpMessage.Type.POST) {
			throw new UnsupportedMethodException("Only HTTP method POST allowed!", HttpMessage.Type.POST);
		}

		if (checkBooleanParameter("debug", false)) {
			interpreter.logInfo("[JSON-RPC debug] Receiving:\n" + new String(message.content()));
		}
		
		Value value = Value.create();
		if (message.content() != null)
			JsonUtils.parseJsonIntoValue(new InputStreamReader(new ByteArrayInputStream(message.content())), value, false);
		if (!value.hasChildren("id")) {
			// JSON-RPC notification mechanism (method call with dropped result)
			if (!inInputPort) {
				throw new IOException("A JSON-RPC notification needs to be a request!");
			}
			return new CommMessage(CommMessage.GENERIC_ID, value.getFirstChild("method").strValue(),
					       "/", value.getFirstChild("params"), null);
		}
		String jsonRpcId = value.getFirstChild("id").strValue();
		if ( inInputPort ) {
			jsonRpcIdMap.put((long)jsonRpcId.hashCode(), jsonRpcId);
			return new CommMessage(jsonRpcId.hashCode(), value.getFirstChild("method").strValue(),
					       "/", value.getFirstChild("params"), null);
		} else if (value.hasChildren("error")) {
			String operationName = jsonRpcOpMap.get(jsonRpcId);
			return new CommMessage(Long.valueOf(jsonRpcId), operationName, "/", null,
{'fraction_non_alphanumeric': 0.13570065850206167, 'fraction_numerical': 0.007631238845467413, 'mean_word_length': 3.148583099310697, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6124381', 'n_tokens_mistral': 5177, 'n_tokens_neox': 4625, 'n_words': 1225}