View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations under the License.
17   */
18  package org.apache.shindig.social.opensocial.service;
19  
20  import org.apache.shindig.auth.SecurityToken;
21  import org.apache.shindig.common.util.JsonConversionUtil;
22  import org.apache.shindig.social.ResponseError;
23  import org.apache.shindig.social.opensocial.spi.DataCollection;
24  import org.apache.shindig.social.opensocial.spi.RestfulCollection;
25  
26  import com.google.common.collect.Lists;
27  import org.apache.commons.io.IOUtils;
28  import org.apache.commons.lang.StringUtils;
29  import org.json.JSONArray;
30  import org.json.JSONException;
31  import org.json.JSONObject;
32  
33  import java.io.IOException;
34  import java.util.List;
35  import java.util.concurrent.Future;
36  import javax.servlet.ServletException;
37  import javax.servlet.http.HttpServletRequest;
38  import javax.servlet.http.HttpServletResponse;
39  
40  /***
41   * JSON-RPC handler servlet.
42   */
43  public class JsonRpcServlet extends ApiServlet {
44  
45    @Override
46    protected void doGet(HttpServletRequest servletRequest,
47        HttpServletResponse servletResponse)
48        throws ServletException, IOException {
49      SecurityToken token = getSecurityToken(servletRequest);
50      if (token == null) {
51        sendSecurityError(servletResponse);
52        return;
53      }
54  
55      try {
56        setCharacterEncodings(servletRequest, servletResponse);
57        JSONObject request = JsonConversionUtil.fromRequest(servletRequest);
58        dispatch(request, servletRequest, servletResponse, token);
59      } catch (JSONException je) {
60        // FIXME
61      }
62    }
63  
64    @Override
65    protected void doPost(HttpServletRequest servletRequest,
66        HttpServletResponse servletResponse)
67        throws ServletException, IOException {
68      SecurityToken token = getSecurityToken(servletRequest);
69      if (token == null) {
70        sendSecurityError(servletResponse);
71        return;
72      }
73  
74      setCharacterEncodings(servletRequest, servletResponse);
75      servletResponse.setContentType("application/json");
76  
77      try {
78        String content = IOUtils.toString(servletRequest.getInputStream(),
79            servletRequest.getCharacterEncoding());
80        if ((content.indexOf('[') != -1) && content.indexOf('[') < content.indexOf('{')) {
81          // Is a batch
82          JSONArray batch = new JSONArray(content);
83          dispatchBatch(batch, servletRequest, servletResponse, token);
84        } else {
85          JSONObject request = new JSONObject(content);
86          dispatch(request, servletRequest, servletResponse, token);
87        }
88      } catch (JSONException je) {
89        sendBadRequest(je, servletResponse);
90      }
91    }
92  
93    protected void dispatchBatch(JSONArray batch, HttpServletRequest servletRequest,
94        HttpServletResponse servletResponse, SecurityToken token) throws JSONException, IOException {
95      // Use linked hash map to preserve order
96      List<Future<?>> responses = Lists.newArrayListWithExpectedSize(batch.length());
97  
98      // Gather all Futures.  We do this up front so that
99      // the first call to get() comes after all futures are created,
100     // which allows for implementations that batch multiple Futures
101     // into single requests.
102     for (int i = 0; i < batch.length(); i++) {
103       JSONObject batchObj = batch.getJSONObject(i);
104       RpcRequestItem requestItem = new RpcRequestItem(batchObj, token, jsonConverter);
105       responses.add(handleRequestItem(requestItem, servletRequest));
106     }
107 
108     // Resolve each Future into a response.
109     // TODO: should use shared deadline across each request
110     JSONArray result = new JSONArray();
111     for (int i = 0; i < batch.length(); i++) {
112       JSONObject batchObj = batch.getJSONObject(i);
113       String key = null;
114       if (batchObj.has("id")) {
115         key = batchObj.getString("id");
116       }
117       result.put(getJSONResponse(key, getResponseItem(responses.get(i))));
118     }
119     servletResponse.getWriter().write(result.toString());
120   }
121 
122   protected void dispatch(JSONObject request, HttpServletRequest servletRequest,
123       HttpServletResponse servletResponse, SecurityToken token) throws JSONException, IOException {
124     String key = null;
125     if (request.has("id")) {
126       key = request.getString("id");
127     }
128     RpcRequestItem requestItem = new RpcRequestItem(request, token, jsonConverter);
129 
130     // Resolve each Future into a response.
131     // TODO: should use shared deadline across each request
132     ResponseItem response = getResponseItem(handleRequestItem(requestItem, servletRequest));
133     JSONObject result = getJSONResponse(key, response);
134     servletResponse.getWriter().write(result.toString());
135   }
136 
137   private JSONObject getJSONResponse(String key, ResponseItem responseItem) throws JSONException {
138     JSONObject result = new JSONObject();
139     if (key != null) {
140       result.put("id", key);
141     }
142     if (responseItem.getError() != null) {
143       result.put("error", getErrorJson(responseItem));
144     } else {
145       Object response = responseItem.getResponse();
146       JSONObject converted = (JSONObject) jsonConverter.convertToJson(response);
147 
148       if (response instanceof RestfulCollection) {
149         // FIXME this is a little hacky because of the field names in the RestfulCollection
150         converted.put("list", converted.remove("entry"));
151         result.put("data", converted);
152       } else if (response instanceof DataCollection) {
153         if (converted.has("entry")) {
154           result.put("data", converted.get("entry"));
155         }
156       } else {
157         result.put("data", converted);
158       }
159     }
160     return result;
161   }
162 
163   // TODO(doll): Refactor the responseItem so that the fields on it line up with this format.
164   // Then we can use the general converter to output the response to the client and we won't
165   // be harcoded to json.
166   private JSONObject getErrorJson(ResponseItem responseItem) throws JSONException {
167     JSONObject error = new JSONObject();
168     error.put("code", responseItem.getError().getHttpErrorCode());
169 
170     String message = responseItem.getError().toString();
171     if (StringUtils.isNotBlank(responseItem.getErrorMessage())) {
172       message += ": " + responseItem.getErrorMessage();
173     }
174     error.put("message", message);
175     return error;
176   }
177 
178   @Override
179   protected void sendError(HttpServletResponse servletResponse, ResponseItem responseItem)
180       throws IOException {
181     try {
182       JSONObject error = getErrorJson(responseItem);
183       servletResponse.getWriter().write(error.toString());
184     } catch (JSONException je) {
185       // This really shouldn't ever happen
186       servletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
187           "Error generating error response " + je.getMessage());
188     }
189   }
190 
191   private void sendBadRequest(Throwable t, HttpServletResponse response) throws IOException {
192     sendError(response, new ResponseItem(ResponseError.BAD_REQUEST,
193         "Invalid batch - " + t.getMessage()));
194   }
195 }