summaryrefslogtreecommitdiffstats
path: root/sca-java-2.x/tags/2.0.1-RC1/modules/binding-http-runtime/src/main/java/org/apache/tuscany/sca/binding/http/wireformat/provider/HTTPJSONWireFormatServiceInterceptor.java
blob: 3323fd34fee2815fae60c6eac5e5f3012d7ac80d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
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<Object> os = new ArrayList<Object>();
        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<DataType> types = operation.getInputType().getLogical();
        int typesIndex = 0;
        
        List<String> jsonRequestArray = new ArrayList<String>();
        
        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<String> getOrderedParameterNames(ServletRequest servletRequest) {
        List<String> orderedNames = new ArrayList<String>();
        Set<String> parameterNames = servletRequest.getParameterMap().keySet();
        if (parameterNames.contains("arg0")) {
            for (int i=0; i<parameterNames.size(); i++) {
                String name = "arg" + i;
                if (parameterNames.contains(name)) {
                    orderedNames.add(name);
                } else {
                    break;
                }
            }
        } else {
            final String queryString = ((HttpServletRequest)servletRequest).getQueryString();
            SortedSet<String> sortedNames = new TreeSet<String>(new Comparator<String>(){
                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();
            }
        }
    }
}