From e5b7380c874745c989d1816b8f552504f038e1bc Mon Sep 17 00:00:00 2001 From: lresende Date: Thu, 26 Sep 2013 20:33:20 +0000 Subject: 2.0 branch for possible maintenance release git-svn-id: http://svn.us.apache.org/repos/asf/tuscany@1526672 13f79535-47bb-0310-9956-ffa450edef68 --- .../HTTPJSONWireFormatServiceInterceptor.java | 252 +++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 sca-java-2.x/branches/2.0/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java (limited to 'sca-java-2.x/branches/2.0/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java') diff --git a/sca-java-2.x/branches/2.0/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java b/sca-java-2.x/branches/2.0/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java new file mode 100644 index 0000000000..3323fd34fe --- /dev/null +++ b/sca-java-2.x/branches/2.0/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java @@ -0,0 +1,252 @@ +/* + * 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.http.wireformat.provider; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TreeSet; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.tuscany.sca.common.http.HTTPContext; +import org.apache.tuscany.sca.interfacedef.DataType; +import org.apache.tuscany.sca.interfacedef.Operation; +import org.apache.tuscany.sca.invocation.Interceptor; +import org.apache.tuscany.sca.invocation.Invoker; +import org.apache.tuscany.sca.invocation.Message; +import org.apache.tuscany.sca.runtime.RuntimeEndpoint; +import org.oasisopen.sca.ServiceRuntimeException; + +/** + * Handles the default wire format for the http binding + * + * 1- determine the request and response format (xml, json, etc) from the + * binding config or content type header and accept headers + * - TODO: need a way to configure the databinding framework based on that format + * 2- get the request contents from the HttpServletRequest + * - for a post its just the request body + * - for a get need to convert the query string into a body based on the format (xml, json, etc) + * 3- send the request on down the wire + * 4- set the response contents in the HttpServletResponse + * (the databinding should already have put it in the correct format) + * + */ +public class HTTPJSONWireFormatServiceInterceptor implements Interceptor { + + private Invoker next; + private String jsonpCallbackName = "callback"; + + public HTTPJSONWireFormatServiceInterceptor(RuntimeEndpoint endpoint) { + } + + @Override + public void setNext(Invoker next) { + this.next = next; + } + + @Override + public Invoker getNext() { + return next; + } + + @Override + public Message invoke(Message msg) { + try { + return invokeResponse(getNext().invoke(invokeRequest(msg))); + } catch (IOException e) { + throw new ServiceRuntimeException(e); + } + } + + private Message invokeRequest(Message msg) throws IOException { + HTTPContext context = msg.getBindingContext(); + HttpServletRequest servletRequest = context.getHttpRequest(); + if ("GET".equals(servletRequest.getMethod())) { + msg.setBody(getRequestFromQueryString(msg.getOperation(), servletRequest)); + } else { + msg.setBody(getRequestFromPost(msg.getOperation(), servletRequest)); + } + return msg; + } + + /** + * The data binding seems to be expecting an Object array of json strings so if the + * post data is a json array convert that to an array of strings + * TODO: should this be being done by the data binding framework? + */ + private Object[] getRequestFromPost(Operation operation, HttpServletRequest servletRequest) throws IOException { + List os = new ArrayList(); + String data = read(servletRequest); + if (data.length() > 0) { + if (data.startsWith("[") && data.endsWith("]")) { + data = data.substring(1, data.length()-1); + StringTokenizer st = new StringTokenizer(data, ","); + while (st.hasMoreElements()) { + os.add(st.nextElement()); + } + } else { + os.add(data); + } + } + return os.toArray(); + } + + private Message invokeResponse(Message msg) throws IOException { + HTTPContext context = msg.getBindingContext(); + HttpServletRequest servletRequest = context.getHttpRequest(); + HttpServletResponse servletResponse = context.getHttpResponse(); + + if (msg.isFault()) { + servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, String.valueOf(msg.getBody())); + } else { + String response = getResponseAsString(servletRequest, servletResponse, msg.getBody()); + servletResponse.getOutputStream().println(response); + } + + return msg; + } + + /** + * Turn the request into a string array of JSON structures. The data binding + * layer will then convert each of the individual parameters into the appropriate + * types for the implementation interface + * + * From ML thread: http://apache.markmail.org/message/ix3vvyomronellmi + * 1- if the binding configuration contains a mapping from query parameter name to operation parameter then use that. + * 2- if the service interface or impl uses jaxrs annotations to name the parameters then use that mapping + * 3- if the query parameters are name arg0, arg1 etc than use those names for the mapping, + * 4- otherwise use the order in the query string. + */ + protected Object[] getRequestFromQueryString(Operation operation, ServletRequest servletRequest) { + + List types = operation.getInputType().getLogical(); + int typesIndex = 0; + + List jsonRequestArray = new ArrayList(); + + for (String name : getOrderedParameterNames(servletRequest)) { + String jsonRequest = ""; + // quote string parameters so clients work in the usual javascript way + if (typesIndex < types.size() && String.class.equals(types.get(typesIndex++).getGenericType())) { + String x = servletRequest.getParameter(name); + if (x.startsWith("\"") || x.startsWith("'")) { + jsonRequest += x; + } else { + if (x.contains("\"")) { + jsonRequest += "'" + x + "'"; + } else { + jsonRequest += "\"" + x + "\""; + } + } + } else { + jsonRequest += servletRequest.getParameter(name); + } + jsonRequestArray.add(jsonRequest); + } + + return jsonRequestArray.toArray(); + } + + /** + * Get the request parameter names in the correct order. + * Either the query parameters are named arg0, arg1, arg2 etc or else use the order + * from the order in the query string. Eg, the url: + * http://localhost:8085/HelloWorldService/sayHello2?first=petra&last=arnold&callback=foo" + * should invoke: + * sayHello2("petra", "arnold") + * so the parameter names should be ordered: "first", "last" + */ + protected List getOrderedParameterNames(ServletRequest servletRequest) { + List orderedNames = new ArrayList(); + Set parameterNames = servletRequest.getParameterMap().keySet(); + if (parameterNames.contains("arg0")) { + for (int i=0; i sortedNames = new TreeSet(new Comparator(){ + public int compare(String o1, String o2) { + int i = queryString.indexOf(o1); + int j = queryString.indexOf(o2); + return i - j; + }}); + for (String name : parameterNames) { + // ignore system and jsonpCallbackName parameters + if (!name.startsWith("_") && !name.equals(jsonpCallbackName)) { + sortedNames.add(name); + } + } + orderedNames.addAll(sortedNames); + } + return orderedNames; + } + + /** + * The databinding layer will have converted the return type into a JSON string so simply + * add wrap it for return. + */ + protected String getResponseAsString(HttpServletRequest servletRequest, HttpServletResponse servletResponse, Object response) { + String jsonResponse = response == null ? "" : response.toString(); + + if ("GET".equals(servletRequest.getMethod())) { + // handle JSONP callback name padding + String callback = servletRequest.getParameter(jsonpCallbackName); + if (callback != null && callback.length() > 1) { + jsonResponse = callback + "(" + jsonResponse + ");"; + } + } + + return jsonResponse; + } + + protected static String read(HttpServletRequest servletRequest) throws IOException { + InputStream is = servletRequest.getInputStream(); + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(is)); + StringBuffer sb = new StringBuffer(); + String str; + while ((str = reader.readLine()) != null) { + sb.append(str); + } + return sb.toString().trim(); + } finally { + if (reader != null) { + reader.close(); + } + } + } +} -- cgit v1.2.3