| /* |
| * 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; |
| } |
| |
| } |