blob: e4374af233befda2e67647773ae11cf03da480cd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.tuscany.sca.binding.jsonrpc.provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.UUID;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentProducer;
import org.apache.http.entity.EntityTemplate;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.apache.tuscany.sca.assembly.EndpointReference;
import org.apache.tuscany.sca.binding.jsonrpc.JSONRPCBinding;
import org.apache.tuscany.sca.binding.jsonrpc.protocol.JsonRpc10Request;
import org.apache.tuscany.sca.binding.jsonrpc.protocol.JsonRpc20Request;
import org.apache.tuscany.sca.binding.jsonrpc.protocol.JsonRpcRequest;
import org.apache.tuscany.sca.databinding.json.JSONDataBinding;
import org.apache.tuscany.sca.databinding.json.jackson.JacksonHelper;
import org.apache.tuscany.sca.interfacedef.DataType;
import org.apache.tuscany.sca.interfacedef.Operation;
import org.apache.tuscany.sca.interfacedef.java.JavaOperation;
import org.apache.tuscany.sca.invocation.DataExchangeSemantics;
import org.apache.tuscany.sca.invocation.Invoker;
import org.apache.tuscany.sca.invocation.Message;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.node.NullNode;
import org.codehaus.jackson.node.ObjectNode;
import org.oasisopen.sca.ServiceRuntimeException;
/**
* Invoker for the JSONRPC Binding
*
* @version $Rev$ $Date$
*/
public class JsonRpcInvoker implements Invoker, DataExchangeSemantics {
private EndpointReference endpointReference;
private Operation operation;
private String uri;
private HttpClient httpClient;
public JsonRpcInvoker(EndpointReference endpointReference, Operation operation, HttpClient httpClient) {
this.endpointReference = endpointReference;
this.operation = operation;
this.uri = endpointReference.getDeployedURI();
this.httpClient = httpClient;
}
public Message invoke(Message msg) {
HttpPost post = null;
HttpResponse response = null;
try {
String requestId = UUID.randomUUID().toString();
post = new HttpPost(uri);
HttpEntity entity = null;
Object[] args = msg.getBody();
final String db = msg.getOperation().getInputWrapper().getDataBinding();
if (!db.equals(JSONDataBinding.NAME)) {
Object[] params = new Object[0];
// Extract the arguments
args = msg.getBody();
if (args instanceof Object[]) {
params = (Object[])args;
}
JsonRpcRequest req = null;
if (JSONRPCBinding.VERSION_20.equals(((JSONRPCBinding)endpointReference.getBinding()).getVersion())) {
req = new JsonRpc20Request(requestId, msg.getOperation().getName(), params);
} else {
req = new JsonRpc10Request(requestId, msg.getOperation().getName(), params);
}
final JsonRpcRequest json = req;
// Create content producer so that we can stream the json result out
ContentProducer cp = new ContentProducer() {
public void writeTo(OutputStream outstream) throws IOException {
// mapper.writeValue(outstream, req.toJSONObject().toString());
try {
json.write(outstream);
} catch (Exception e) {
throw new IOException(e);
}
}
};
entity = new EntityTemplate(cp);
} else {
// Single string argument
entity = new StringEntity((String)args[0], "UTF-8");
}
post.setEntity(entity);
response = httpClient.execute(post);
try {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
//success
entity = response.getEntity();
String entityResponse = EntityUtils.toString(entity);
// entity.consumeContent();
if (!db.equals(JSONDataBinding.NAME)) {
ObjectNode jsonResponse = (ObjectNode)JacksonHelper.MAPPER.readTree(entityResponse);
if (jsonResponse.has("error") && jsonResponse.get("error") != NullNode.instance) {
processException(jsonResponse);
}
DataType<List<DataType>> outputType = operation.getOutputType();
DataType returnType =
(outputType != null && !outputType.getLogical().isEmpty()) ? outputType.getLogical().get(0)
: null;
if (returnType == null) {
msg.setBody(null);
return msg;
}
//check requestId
if (!requestId.equalsIgnoreCase(jsonResponse.get("id").getTextValue())) {
throw new ServiceRuntimeException("Invalid response id:" + requestId);
}
JsonNode rawResult = jsonResponse.get("result");
Class<?> returnClass = returnType.getPhysical();
Type genericReturnType = returnType.getGenericType();
ObjectMapper mapper = createObjectMapper(returnClass);
String json = mapper.writeValueAsString(rawResult);
Object body = mapper.readValue(json, TypeFactory.type(genericReturnType));
msg.setBody(body);
} else {
msg.setBody(entityResponse);
}
} else {
throw new ServiceRuntimeException("Abnormal HTTP response: " + response.getStatusLine().toString());
}
} finally {
// Consume the content so the connection can be released
EntityUtils.consumeQuietly(response.getEntity());
}
} catch (RuntimeException e) {
throw e;
} catch (Error e) {
throw e;
} catch (Exception e) {
// e.printStackTrace();
msg.setFaultBody(e);
} catch (Throwable e) {
throw new ServiceRuntimeException(e);
}
return msg;
}
public static ObjectMapper createObjectMapper(Class<?> cls) {
return JacksonHelper.createObjectMapper(cls);
}
private String opt(ObjectNode node, String name) {
JsonNode value = node.get(name);
if (value == null) {
return null;
} else {
return value.getValueAsText();
}
}
/**
* Generate and throw exception based on the data in the 'responseMessage'
*/
protected void processException(ObjectNode responseMessage) throws Throwable {
// FIXME: We need to find a way to build Java exceptions out of the json-rpc error
JsonNode error = responseMessage.get("error");
if (error != null) {
Object data = error.get("data");
if (data instanceof ObjectNode) {
ObjectNode fault = (ObjectNode)data;
String javaClass = opt(fault, "class");
String message = opt(fault, "message");
String stackTrace = opt(fault, "stackTrace");
if (javaClass != null) {
if (stackTrace != null) {
message = message + "\n" + stackTrace;
}
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if (operation instanceof JavaOperation) {
Method method = ((JavaOperation)operation).getJavaMethod();
classLoader = method.getDeclaringClass().getClassLoader();
}
Class<? extends Throwable> exceptionClass =
(Class<? extends Throwable>)Class.forName(javaClass, false, classLoader);
Constructor<? extends Throwable> ctor = null;
Throwable ex = null;
try {
ctor = exceptionClass.getConstructor(String.class, Throwable.class);
ex = ctor.newInstance(message, null);
} catch (NoSuchMethodException e1) {
try {
ctor = exceptionClass.getConstructor(String.class);
ex = ctor.newInstance(message);
} catch (NoSuchMethodException e2) {
try {
ctor = exceptionClass.getConstructor(Throwable.class);
ex = ctor.newInstance(null);
} catch (NoSuchMethodException e3) {
ctor = exceptionClass.getConstructor();
ex = ctor.newInstance();
}
}
}
throw ex;
}
}
throw new ServiceRuntimeException(error.toString());
}
}
@Override
public boolean allowsPassByReference() {
return true;
}
}