Merge lp:~pbeaman/akiban-server/direct-updates-4 into lp:~akiban-technologies/akiban-server/trunk
- direct-updates-4
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Mike McMahon |
Approved revision: | 2645 |
Merged at revision: | 2650 |
Proposed branch: | lp:~pbeaman/akiban-server/direct-updates-4 |
Merge into: | lp:~akiban-technologies/akiban-server/trunk |
Diff against target: |
767 lines (+316/-155) 10 files modified
src/main/java/com/akiban/direct/AbstractDirectObject.java (+1/-1) src/main/java/com/akiban/rest/RestResponseBuilder.java (+8/-10) src/main/java/com/akiban/rest/resources/DirectResource.java (+40/-41) src/main/java/com/akiban/server/service/restdml/DirectInvocation.java (+62/-0) src/main/java/com/akiban/server/service/restdml/DirectService.java (+6/-4) src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java (+87/-93) src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java (+49/-4) src/test/resources/com/akiban/rest/direct/direct-output-types.body (+26/-0) src/test/resources/com/akiban/rest/direct/test-direct-output-types.script (+34/-0) src/test/resources/com/akiban/rest/direct/test-updates.script (+3/-2) |
To merge this branch: | bzr merge lp:~pbeaman/akiban-server/direct-updates-4 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Akiban Build User | Needs Fixing | ||
Mike McMahon | Approve | ||
Review via email: mp+161177@code.launchpad.net |
Commit message
Description of the change
Emit correct response status and content type.
This involved some refactoring because the setting of these header elements needs to occur before the ResponseBuilder calls write on the StreamingOutput. In the case of a NO_CONTENT return type (signified by an output parameter type "void") the builder should not have a StreamingOutput to invoke. However, unlike a previous incarnation this proposal does not significantly change RestResponseBui
Therefore the DirectService#
In DirectResource, the new doCall method conditionally creates a StreamingOutput to complete the call, or invokes the call directly depending on whether it of type void.
New tests verify that the correct response status is returned.
Akiban Build User (build-akiban) wrote : | # |
There were 2 failures during build/test:
* job system-tests-mtr failed at build number 3441: http://
* view must-pass failed: system-tests-mtr is red
- 2645. By Peter Beaman
-
Tweaks
Preview Diff
1 | === modified file 'src/main/java/com/akiban/direct/AbstractDirectObject.java' |
2 | --- src/main/java/com/akiban/direct/AbstractDirectObject.java 2013-04-24 19:15:51 +0000 |
3 | +++ src/main/java/com/akiban/direct/AbstractDirectObject.java 2013-04-26 22:25:31 +0000 |
4 | @@ -500,7 +500,7 @@ |
5 | updateColumns.append(','); |
6 | updateValues.append(','); |
7 | } |
8 | - updateColumns.append(columns[index].columnName); |
9 | + updateColumns.append('\"').append(columns[index].columnName).append('\"'); |
10 | updateValues.append('?'); |
11 | } |
12 | } |
13 | |
14 | === modified file 'src/main/java/com/akiban/rest/RestResponseBuilder.java' |
15 | --- src/main/java/com/akiban/rest/RestResponseBuilder.java 2013-04-24 15:50:16 +0000 |
16 | +++ src/main/java/com/akiban/rest/RestResponseBuilder.java 2013-04-26 22:25:31 +0000 |
17 | @@ -58,6 +58,7 @@ |
18 | private String outputBody; |
19 | private String jsonp; |
20 | private int status; |
21 | + private MediaType type; |
22 | |
23 | |
24 | public RestResponseBuilder(HttpServletRequest request, String jsonp) { |
25 | @@ -75,6 +76,11 @@ |
26 | this.status = status.getStatusCode(); |
27 | return this; |
28 | } |
29 | + |
30 | + public RestResponseBuilder type(MediaType type) { |
31 | + this.type = type; |
32 | + return this; |
33 | + } |
34 | |
35 | public RestResponseBuilder body(String outputBody) { |
36 | this.outputBody = outputBody; |
37 | @@ -101,7 +107,6 @@ |
38 | status(Response.Status.OK); |
39 | } |
40 | Response.ResponseBuilder builder; |
41 | - |
42 | if (this.status == Response.Status.NO_CONTENT.getStatusCode()) { |
43 | builder = Response.status(status).type((MediaType)null); |
44 | } else { |
45 | @@ -109,19 +114,12 @@ |
46 | } |
47 | if(isJsonp) { |
48 | builder.type(ResourceHelper.APPLICATION_JAVASCRIPT_TYPE); |
49 | + } else if (type != null) { |
50 | + builder.type(type); |
51 | } |
52 | return builder.build(); |
53 | } |
54 | |
55 | - public Response build(MediaType type) { |
56 | - if(outputBody == null && outputGenerator == null && jsonp == null) { |
57 | - status(Response.Status.NO_CONTENT); |
58 | - } |
59 | - Response.ResponseBuilder builder = Response.status(status).entity(createStreamingOutput()); |
60 | - builder.type(type); |
61 | - return builder.build(); |
62 | - } |
63 | - |
64 | public static void formatJsonError(StringBuilder builder, String code, String message) { |
65 | builder.append("{\"code\":\""); |
66 | builder.append(code); |
67 | |
68 | === modified file 'src/main/java/com/akiban/rest/resources/DirectResource.java' |
69 | --- src/main/java/com/akiban/rest/resources/DirectResource.java 2013-04-22 20:22:01 +0000 |
70 | +++ src/main/java/com/akiban/rest/resources/DirectResource.java 2013-04-26 22:25:31 +0000 |
71 | @@ -45,6 +45,7 @@ |
72 | import com.akiban.rest.RestResponseBuilder; |
73 | import com.akiban.rest.RestResponseBuilder.BodyGenerator; |
74 | import com.akiban.server.error.NoSuchSchemaException; |
75 | +import com.akiban.server.service.restdml.DirectInvocation; |
76 | import com.akiban.server.service.session.Session; |
77 | import com.akiban.server.service.transaction.TransactionService; |
78 | |
79 | @@ -221,68 +222,66 @@ |
80 | @Path("/call/{proc}{params:(/.*)?}") |
81 | public Response callGet(@Context final HttpServletRequest request, @PathParam("proc") final String proc, |
82 | @PathParam("params") final String pathParams, @Context final UriInfo uri) { |
83 | - final TableName procName = ResourceHelper.parseTableName(request, proc); |
84 | - ResourceHelper.checkSchemaAccessible(reqs.securityService, request, procName.getSchemaName()); |
85 | - final MediaType[] responseType = new MediaType[1]; |
86 | - |
87 | - return RestResponseBuilder.forRequest(request).body(new BodyGenerator() { |
88 | - @Override |
89 | - public void write(PrintWriter writer) throws Exception { |
90 | - reqs.directService.invokeRestEndpoint(writer, request, "GET", procName, pathParams, |
91 | - uri.getQueryParameters(), null, responseType); |
92 | - } |
93 | - }).build(responseType[0]); |
94 | + return doCall(request, "GET", proc, pathParams, uri, null); |
95 | } |
96 | |
97 | @POST |
98 | @Path("/call/{proc}{params:(/.*)?}") |
99 | public Response callPost(@Context final HttpServletRequest request, @PathParam("proc") final String proc, |
100 | @PathParam("params") final String pathParams, @Context final UriInfo uri, final byte[] content) { |
101 | - final TableName procName = ResourceHelper.parseTableName(request, proc); |
102 | - ResourceHelper.checkSchemaAccessible(reqs.securityService, request, procName.getSchemaName()); |
103 | - final MediaType[] responseType = new MediaType[1]; |
104 | - |
105 | - return RestResponseBuilder.forRequest(request).body(new BodyGenerator() { |
106 | - @Override |
107 | - public void write(PrintWriter writer) throws Exception { |
108 | - reqs.directService.invokeRestEndpoint(writer, request, "POST", procName, pathParams, |
109 | - uri.getQueryParameters(), content, responseType); |
110 | - } |
111 | - }).build(responseType[0]); |
112 | + return doCall(request, "POST", proc, pathParams, uri, content); |
113 | } |
114 | |
115 | @PUT |
116 | @Path("/call/{proc}{params:(/.*)?}") |
117 | public Response callPut(@Context final HttpServletRequest request, @PathParam("proc") final String proc, |
118 | @PathParam("params") final String pathParams, @Context final UriInfo uri, final byte[] content) { |
119 | - final TableName procName = ResourceHelper.parseTableName(request, proc); |
120 | - ResourceHelper.checkSchemaAccessible(reqs.securityService, request, procName.getSchemaName()); |
121 | - final MediaType[] responseType = new MediaType[1]; |
122 | - |
123 | - return RestResponseBuilder.forRequest(request).body(new BodyGenerator() { |
124 | - @Override |
125 | - public void write(PrintWriter writer) throws Exception { |
126 | - reqs.directService.invokeRestEndpoint(writer, request, "PUT", procName, pathParams, |
127 | - uri.getQueryParameters(), content, responseType); |
128 | - } |
129 | - }).build(responseType[0]); |
130 | + return doCall(request, "PUT", proc, pathParams, uri, content); |
131 | } |
132 | |
133 | @DELETE |
134 | @Path("/call/{proc}{params:(/.*)?}") |
135 | public Response callDelete(@Context final HttpServletRequest request, @PathParam("proc") final String proc, |
136 | @PathParam("params") final String pathParams, @Context final UriInfo uri, final byte[] content) { |
137 | + return doCall(request, "DELETE", proc, pathParams, uri, content); |
138 | + } |
139 | + |
140 | + private Response doCall(final HttpServletRequest request, final String method, final String proc, final String pathParams, |
141 | + final UriInfo uri, final byte[] content) { |
142 | final TableName procName = ResourceHelper.parseTableName(request, proc); |
143 | ResourceHelper.checkSchemaAccessible(reqs.securityService, request, procName.getSchemaName()); |
144 | - final MediaType[] responseType = new MediaType[1]; |
145 | - |
146 | - return RestResponseBuilder.forRequest(request).body(new BodyGenerator() { |
147 | - @Override |
148 | - public void write(PrintWriter writer) throws Exception { |
149 | - reqs.directService.invokeRestEndpoint(writer, request, "DELETE", procName, pathParams, |
150 | - uri.getQueryParameters(), content, responseType); |
151 | + |
152 | + RestResponseBuilder builder = RestResponseBuilder.forRequest(request); |
153 | + final DirectInvocation invocation; |
154 | + try { |
155 | + invocation = reqs.directService.prepareRestInvocation(method, procName, pathParams, |
156 | + uri.getQueryParameters(), content, request); |
157 | + invocation.getEndpointMetadata().setResponseHeaders(builder); |
158 | + } catch (Exception e) { |
159 | + throw builder.wrapException(e); |
160 | + } |
161 | + |
162 | + try { |
163 | + if (invocation.getEndpointMetadata().isVoid()) { |
164 | + try { |
165 | + reqs.directService.invokeRestEndpoint(null, request, method, invocation); |
166 | + } catch (Throwable t) { |
167 | + throw builder.wrapException(t); |
168 | + } |
169 | + return builder.build(); |
170 | + } else { |
171 | + return builder.body(new BodyGenerator() { |
172 | + @Override |
173 | + public void write(PrintWriter writer) throws Exception { |
174 | + reqs.directService.invokeRestEndpoint(writer, request, method, invocation); |
175 | + } |
176 | + }).build(); |
177 | } |
178 | - }).build(responseType[0]); |
179 | + } finally { |
180 | + invocation.finish(); |
181 | + } |
182 | } |
183 | |
184 | + |
185 | + |
186 | } |
187 | |
188 | === added file 'src/main/java/com/akiban/server/service/restdml/DirectInvocation.java' |
189 | --- src/main/java/com/akiban/server/service/restdml/DirectInvocation.java 1970-01-01 00:00:00 +0000 |
190 | +++ src/main/java/com/akiban/server/service/restdml/DirectInvocation.java 2013-04-26 22:25:31 +0000 |
191 | @@ -0,0 +1,62 @@ |
192 | +/** |
193 | + * Copyright (C) 2009-2013 Akiban Technologies, Inc. |
194 | + * |
195 | + * This program is free software: you can redistribute it and/or modify |
196 | + * it under the terms of the GNU Affero General Public License as published by |
197 | + * the Free Software Foundation, either version 3 of the License, or |
198 | + * (at your option) any later version. |
199 | + * |
200 | + * This program is distributed in the hope that it will be useful, |
201 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
202 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
203 | + * GNU Affero General Public License for more details. |
204 | + * |
205 | + * You should have received a copy of the GNU Affero General Public License |
206 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
207 | + */ |
208 | + |
209 | +package com.akiban.server.service.restdml; |
210 | + |
211 | +import java.sql.SQLException; |
212 | + |
213 | +import com.akiban.ais.model.TableName; |
214 | +import com.akiban.sql.embedded.JDBCConnection; |
215 | + |
216 | + |
217 | +public class DirectInvocation { |
218 | + private final JDBCConnection conn; |
219 | + private final TableName procName; |
220 | + private final EndpointMetadata em; |
221 | + private final Object[] args; |
222 | + |
223 | + public DirectInvocation(JDBCConnection conn, TableName procName, EndpointMetadata em, Object[] args) { |
224 | + this.conn = conn; |
225 | + this.procName = procName; |
226 | + this.em = em; |
227 | + this.args = args; |
228 | + } |
229 | + |
230 | + public JDBCConnection getConnection() { |
231 | + return conn; |
232 | + } |
233 | + |
234 | + public TableName getProcName() { |
235 | + return procName; |
236 | + } |
237 | + |
238 | + public EndpointMetadata getEndpointMetadata() { |
239 | + return em; |
240 | + } |
241 | + |
242 | + public Object[] getArgs() { |
243 | + return args; |
244 | + } |
245 | + |
246 | + public void finish() { |
247 | + try { |
248 | + conn.close(); |
249 | + } catch (SQLException e) { |
250 | + // probably the least of our problems. |
251 | + } |
252 | + } |
253 | +} |
254 | |
255 | === modified file 'src/main/java/com/akiban/server/service/restdml/DirectService.java' |
256 | --- src/main/java/com/akiban/server/service/restdml/DirectService.java 2013-04-04 21:46:56 +0000 |
257 | +++ src/main/java/com/akiban/server/service/restdml/DirectService.java 2013-04-26 22:25:31 +0000 |
258 | @@ -20,7 +20,6 @@ |
259 | import java.io.PrintWriter; |
260 | |
261 | import javax.servlet.http.HttpServletRequest; |
262 | -import javax.ws.rs.core.MediaType; |
263 | import javax.ws.rs.core.MultivaluedMap; |
264 | |
265 | import com.akiban.ais.model.TableName; |
266 | @@ -36,8 +35,11 @@ |
267 | public void reportStoredProcedures(PrintWriter writer, HttpServletRequest request, String query, String module, |
268 | Session session, boolean functionsOnly) throws Exception; |
269 | |
270 | - public void invokeRestEndpoint(PrintWriter writer, HttpServletRequest request, String method, TableName procName, |
271 | - String pathParams, MultivaluedMap<String, String> queryParameters, byte[] content, MediaType[] responseType) |
272 | - throws Exception; |
273 | + public DirectInvocation prepareRestInvocation(final String method, final TableName procName, |
274 | + final String pathParams, final MultivaluedMap<String, String> queryParameters, final byte[] content, |
275 | + final HttpServletRequest request) throws Exception; |
276 | + |
277 | + public void invokeRestEndpoint(final PrintWriter writer, final HttpServletRequest request, final String method, |
278 | + final DirectInvocation in) throws Exception; |
279 | |
280 | } |
281 | |
282 | === modified file 'src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java' |
283 | --- src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java 2013-04-26 13:06:29 +0000 |
284 | +++ src/main/java/com/akiban/server/service/restdml/DirectServiceImpl.java 2013-04-26 22:25:31 +0000 |
285 | @@ -33,8 +33,6 @@ |
286 | import java.util.regex.Matcher; |
287 | |
288 | import javax.servlet.http.HttpServletRequest; |
289 | -import javax.servlet.http.HttpServletResponse; |
290 | -import javax.ws.rs.WebApplicationException; |
291 | import javax.ws.rs.core.MediaType; |
292 | import javax.ws.rs.core.MultivaluedMap; |
293 | |
294 | @@ -50,7 +48,6 @@ |
295 | import com.akiban.ais.model.TableName; |
296 | import com.akiban.direct.Direct; |
297 | import com.akiban.rest.RestFunctionRegistrar; |
298 | -import com.akiban.rest.RestServiceImpl; |
299 | import com.akiban.rest.resources.ResourceHelper; |
300 | import com.akiban.server.error.DirectEndpointNotFoundException; |
301 | import com.akiban.server.error.DirectTransactionFailedException; |
302 | @@ -110,7 +107,8 @@ |
303 | private final DXLService dxlService; |
304 | private final EmbeddedJDBCService jdbcService; |
305 | private final RoutineLoader routineLoader; |
306 | - |
307 | + |
308 | + |
309 | @Inject |
310 | public DirectServiceImpl(SecurityService securityService, DXLService dxlService, EmbeddedJDBCService jdbcService, |
311 | RoutineLoader routineLoader) { |
312 | @@ -334,122 +332,118 @@ |
313 | json.writeEndArray(); |
314 | } |
315 | |
316 | - @Override |
317 | - public void invokeRestEndpoint(final PrintWriter writer, final HttpServletRequest request, final String method, |
318 | - final TableName procName, final String pathParams, final MultivaluedMap<String, String> queryParameters, |
319 | - final byte[] content, final MediaType[] responseType) throws Exception { |
320 | - try (JDBCConnection conn = jdbcConnection(request, procName.getSchemaName());) { |
321 | - LOG.debug("Invoking {} {}", method, request.getRequestURI()); |
322 | - conn.setAutoCommit(false); |
323 | - |
324 | - boolean completed = false; |
325 | - int repeat = TRANSACTION_RETRY_COUNT; |
326 | - |
327 | - while (--repeat >= 0) { |
328 | - try { |
329 | - Direct.enter(procName.getSchemaName(), dxlService.ddlFunctions().getAIS(conn.getSession())); |
330 | - Direct.getContext().setConnection(conn); |
331 | - conn.beginTransaction(); |
332 | - invokeRestFunction(writer, conn, method, procName, pathParams, queryParameters, content, |
333 | - request, responseType); |
334 | - conn.commitTransaction(); |
335 | - completed = true; |
336 | - return; |
337 | - } catch (RollbackException e) { |
338 | - if (repeat == 0) { |
339 | - LOG.error("Transaction failed " + TRANSACTION_RETRY_COUNT + " times: " |
340 | - + request.getRequestURI()); |
341 | - throw new DirectTransactionFailedException(method, request.getRequestURI()); |
342 | - } |
343 | - } catch (RegistrationException e) { |
344 | - throw new ScriptLibraryRegistrationException(e); |
345 | - } finally { |
346 | - try { |
347 | - if (!completed) { |
348 | - conn.rollbackTransaction(); |
349 | - } |
350 | - } finally { |
351 | - Direct.leave(); |
352 | - } |
353 | - } |
354 | - } |
355 | - } |
356 | - } |
357 | - |
358 | - /** |
359 | - * Invokes a function in a script library. The identity of the function is |
360 | - * determined from multiple factors including the schema name, the library |
361 | - * routine name, the URI of the request, the content type of the request, |
362 | - * etc. This method is called by {@link RestServiceImpl} which validates |
363 | - * security and supplies the JDBCConnection |
364 | - */ |
365 | - |
366 | - private void invokeRestFunction(final PrintWriter writer, JDBCConnection conn, final String method, |
367 | - final TableName procName, final String pathParams, final MultivaluedMap<String, String> queryParameters, |
368 | - final byte[] content, final HttpServletRequest request, final MediaType[] responseType) throws Exception { |
369 | - |
370 | + public DirectInvocation prepareRestInvocation(final String method, final TableName procName, |
371 | + final String pathParams, final MultivaluedMap<String, String> queryParameters, final byte[] content, |
372 | + final HttpServletRequest request) throws Exception { |
373 | + |
374 | + JDBCConnection conn = jdbcConnection(request, procName.getSchemaName()); |
375 | ParamCache cache = new ParamCache(); |
376 | final EndpointMap endpointMap = getEndpointMap(conn.getSession()); |
377 | final List<EndpointMetadata> list; |
378 | synchronized (endpointMap) { |
379 | list = endpointMap.getMap().get(new EndpointAddress(method, procName)); |
380 | } |
381 | - |
382 | - EndpointMetadata md = selectEndpoint(list, pathParams, request.getContentType(), responseType, cache); |
383 | - if (md == null) { |
384 | + EndpointMetadata em = selectEndpoint(list, pathParams, request.getContentType(), cache); |
385 | + if (em == null) { |
386 | throw new DirectEndpointNotFoundException(method, request.getRequestURI()); |
387 | } |
388 | - |
389 | - final Object[] args = createArgsArray(pathParams, queryParameters, content, cache, md); |
390 | - |
391 | - final ScriptPool<ScriptLibrary> libraryPool = conn.getRoutineLoader().getScriptLibrary(conn.getSession(), |
392 | - new TableName(procName.getSchemaName(), md.routineName)); |
393 | + final Object[] args = createArgsArray(pathParams, queryParameters, content, cache, em); |
394 | + return new DirectInvocation(conn, procName, em, args); |
395 | + } |
396 | + |
397 | + public void invokeRestEndpoint(final PrintWriter writer, final HttpServletRequest request, final String method, |
398 | + final DirectInvocation in) throws Exception { |
399 | + LOG.debug("Invoking {} {}", method, request.getRequestURI()); |
400 | + |
401 | + JDBCConnection conn = in.getConnection(); |
402 | + conn.setAutoCommit(false); |
403 | + boolean completed = false; |
404 | + int repeat = TRANSACTION_RETRY_COUNT; |
405 | + |
406 | + while (--repeat >= 0) { |
407 | + try { |
408 | + Direct.enter(in.getProcName().getSchemaName(), dxlService.ddlFunctions().getAIS(conn.getSession())); |
409 | + Direct.getContext().setConnection(conn); |
410 | + conn.beginTransaction(); |
411 | + invokeRestFunction(writer, in); |
412 | + conn.commitTransaction(); |
413 | + completed = true; |
414 | + return; |
415 | + } catch (RollbackException e) { |
416 | + if (repeat == 0) { |
417 | + LOG.error("Transaction failed " + TRANSACTION_RETRY_COUNT + " times: " + request.getRequestURI()); |
418 | + throw new DirectTransactionFailedException(method, request.getRequestURI()); |
419 | + } |
420 | + } catch (RegistrationException e) { |
421 | + throw new ScriptLibraryRegistrationException(e); |
422 | + } finally { |
423 | + try { |
424 | + if (!completed) { |
425 | + conn.rollbackTransaction(); |
426 | + } |
427 | + } finally { |
428 | + Direct.leave(); |
429 | + } |
430 | + } |
431 | + } |
432 | + } |
433 | + |
434 | + |
435 | + private void invokeRestFunction(final PrintWriter writer, DirectInvocation in) throws Exception { |
436 | + EndpointMetadata em = in.getEndpointMetadata(); |
437 | + Object[] args = in.getArgs(); |
438 | + |
439 | + final ScriptPool<ScriptLibrary> libraryPool = in |
440 | + .getConnection() |
441 | + .getRoutineLoader() |
442 | + .getScriptLibrary(in.getConnection().getSession(), |
443 | + new TableName(in.getProcName().getSchemaName(), em.routineName)); |
444 | final ScriptLibrary library = libraryPool.get(); |
445 | + |
446 | boolean success = false; |
447 | Object result; |
448 | |
449 | - LOG.debug("Endpoint {}", md); |
450 | + LOG.debug("Endpoint {}", in.getEndpointMetadata()); |
451 | try { |
452 | - result = library.invoke(md.function, args); |
453 | + result = library.invoke(em.function, args); |
454 | success = true; |
455 | } finally { |
456 | libraryPool.put(library, success); |
457 | } |
458 | |
459 | - switch (md.outParam.type) { |
460 | + switch (em.outParam.type) { |
461 | |
462 | case EndpointMetadata.X_TYPE_STRING: |
463 | - responseType[0] = MediaType.TEXT_PLAIN_TYPE; |
464 | if (result != null) { |
465 | writer.write(result.toString()); |
466 | - } else if (md.outParam.defaultValue != null) { |
467 | - writer.write(md.outParam.defaultValue.toString()); |
468 | + } else if (em.outParam.defaultValue != null) { |
469 | + writer.write(em.outParam.defaultValue.toString()); |
470 | } |
471 | break; |
472 | |
473 | case EndpointMetadata.X_TYPE_JSON: |
474 | - responseType[0] = MediaType.APPLICATION_JSON_TYPE; |
475 | JsonGenerator json = createJsonGenerator(writer); |
476 | if (result != null) { |
477 | json.writeObject(result); |
478 | - } else if (md.outParam.defaultValue != null) { |
479 | - json.writeObject(md.outParam.defaultValue); |
480 | + } else if (em.outParam.defaultValue != null) { |
481 | + json.writeObject(em.outParam.defaultValue); |
482 | } |
483 | - json.close(); |
484 | + json.flush(); |
485 | + break; |
486 | + |
487 | + case EndpointMetadata.X_TYPE_VOID: |
488 | break; |
489 | |
490 | case EndpointMetadata.X_TYPE_BYTEARRAY: |
491 | - responseType[0] = MediaType.APPLICATION_OCTET_STREAM_TYPE; |
492 | - // TODO: Unsupported - need to add a path for writing a stream |
493 | - break; |
494 | + /* |
495 | + * intentionally falls through TODO: support X_TYPE_BYTEARRAY |
496 | + */ |
497 | |
498 | default: |
499 | - // No response type specified |
500 | - responseType[0] = null; |
501 | - break; |
502 | + assert false : "Invalid output type"; |
503 | } |
504 | } |
505 | - |
506 | + |
507 | /** |
508 | * Select a registered <code>EndpointMetadata</code> from the supplied list. |
509 | * The first candidate in the list that matches the end-point pattern |
510 | @@ -472,8 +466,8 @@ |
511 | * @return the selected <code>EndpointMetadata</code> or <code>null</code>. |
512 | */ |
513 | EndpointMetadata selectEndpoint(final List<EndpointMetadata> list, final String pathParams, |
514 | - final String requestType, final MediaType[] responseType, ParamCache cache) { |
515 | - EndpointMetadata md = null; |
516 | + final String requestType, ParamCache cache) { |
517 | + EndpointMetadata em = null; |
518 | if (list != null) { |
519 | for (final EndpointMetadata candidate : list) { |
520 | if (requestType != null && candidate.expectedContentType != null |
521 | @@ -483,18 +477,18 @@ |
522 | if (candidate.pattern != null) { |
523 | Matcher matcher = candidate.getParamPathMatcher(cache, pathParams); |
524 | if (matcher.matches()) { |
525 | - md = candidate; |
526 | + em = candidate; |
527 | break; |
528 | } |
529 | } else { |
530 | if (pathParams == null || pathParams.isEmpty()) { |
531 | - md = candidate; |
532 | + em = candidate; |
533 | break; |
534 | } |
535 | } |
536 | } |
537 | } |
538 | - return md; |
539 | + return em; |
540 | } |
541 | |
542 | /** |
543 | @@ -515,17 +509,17 @@ |
544 | * <code>null</code> in the case of a GET request. |
545 | * @param cache |
546 | * A cache for partial results |
547 | - * @param md |
548 | + * @param em |
549 | * The <code>EndpointMetadata</code> instance selected by |
550 | * {@link #selectEndpoint(List, String, String, MediaType[], ParamCache)} |
551 | * @return the argument array |
552 | * @throws Exception |
553 | */ |
554 | Object[] createArgsArray(final String pathParams, final MultivaluedMap<String, String> queryParameters, |
555 | - final byte[] content, ParamCache cache, EndpointMetadata md) throws Exception { |
556 | - final Object[] args = new Object[md.inParams.length]; |
557 | - for (int index = 0; index < md.inParams.length; index++) { |
558 | - final ParamMetadata pm = md.inParams[index]; |
559 | + final byte[] content, ParamCache cache, EndpointMetadata em) throws Exception { |
560 | + final Object[] args = new Object[em.inParams.length]; |
561 | + for (int index = 0; index < em.inParams.length; index++) { |
562 | + final ParamMetadata pm = em.inParams[index]; |
563 | Object v = pm.source.value(pathParams, queryParameters, content, cache); |
564 | args[index] = EndpointMetadata.convertType(pm, v); |
565 | } |
566 | |
567 | === modified file 'src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java' |
568 | --- src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java 2013-04-26 02:10:55 +0000 |
569 | +++ src/main/java/com/akiban/server/service/restdml/EndpointMetadata.java 2013-04-26 22:25:31 +0000 |
570 | @@ -36,6 +36,7 @@ |
571 | import javax.ws.rs.core.Response.Status; |
572 | |
573 | import com.akiban.ais.model.TableName; |
574 | +import com.akiban.rest.RestResponseBuilder; |
575 | import com.fasterxml.jackson.databind.JsonNode; |
576 | |
577 | /** |
578 | @@ -71,6 +72,11 @@ |
579 | final static List<String> X_TYPES = Arrays.asList(new String[] { X_TYPE_INT, X_TYPE_LONG, X_TYPE_FLOAT, |
580 | X_TYPE_DOUBLE, X_TYPE_STRING, X_TYPE_DATE, X_TYPE_TIMESTAMP, X_TYPE_BYTEARRAY, X_TYPE_JSON, X_TYPE_VOID }); |
581 | |
582 | + /* |
583 | + * TODO: support X_TYPE_BYTEARRAY type |
584 | + */ |
585 | + final static List<String> OUT_TYPES = Arrays.asList(new String[] { X_TYPE_STRING, X_TYPE_JSON, X_TYPE_VOID }); |
586 | + |
587 | private final static Charset UTF8 = Charset.forName("UTF8"); |
588 | |
589 | static Object convertType(ParamMetadata pm, Object v) throws Exception { |
590 | @@ -88,7 +94,7 @@ |
591 | switch (pm.type) { |
592 | case EndpointMetadata.X_TYPE_INT: |
593 | if (v instanceof JsonNode) { |
594 | - if (((JsonNode) v).isInt()) { |
595 | + if (((JsonNode) v).isNumber()) { |
596 | return ((JsonNode) v).intValue(); |
597 | } else { |
598 | break; |
599 | @@ -99,7 +105,7 @@ |
600 | } |
601 | case EndpointMetadata.X_TYPE_LONG: |
602 | if (v instanceof JsonNode) { |
603 | - if (((JsonNode) v).isLong()) { |
604 | + if (((JsonNode) v).isNumber()) { |
605 | return ((JsonNode) v).longValue(); |
606 | } else { |
607 | break; |
608 | @@ -110,7 +116,7 @@ |
609 | } |
610 | case EndpointMetadata.X_TYPE_FLOAT: |
611 | if (v instanceof JsonNode) { |
612 | - if (((JsonNode) v).isFloatingPointNumber()) { |
613 | + if (((JsonNode) v).isNumber()) { |
614 | return ((JsonNode) v).numberValue().floatValue(); |
615 | } else { |
616 | break; |
617 | @@ -273,7 +279,7 @@ |
618 | |
619 | static ParamMetadata createInParameter(final Tokenizer tokens) throws Exception { |
620 | String v = tokens.next(true); |
621 | - ParamMetadata pm = createOutParameter(tokens); |
622 | + ParamMetadata pm = createParameter(tokens); |
623 | if (pm.type == X_TYPE_VOID) { |
624 | throw new IllegalArgumentException("Input parameter may not have type " + X_TYPE_VOID); |
625 | } |
626 | @@ -295,6 +301,15 @@ |
627 | } |
628 | |
629 | static ParamMetadata createOutParameter(final Tokenizer tokens) throws Exception { |
630 | + ParamMetadata pm = createParameter(tokens); |
631 | + if (!EndpointMetadata.OUT_TYPES.contains(pm.type)) { |
632 | + throw new IllegalArgumentException("Unsuported output parameter type " + pm.type); |
633 | + } |
634 | + |
635 | + return pm; |
636 | + } |
637 | + |
638 | + static ParamMetadata createParameter(final Tokenizer tokens) throws Exception { |
639 | ParamMetadata pm = new ParamMetadata(); |
640 | String v = tokens.nextName(true); |
641 | String type = v.toLowerCase(); |
642 | @@ -827,4 +842,34 @@ |
643 | && equals(em.pattern, pattern) && equals(em.outParam, outParam) && equals(em.inParams, inParams); |
644 | } |
645 | |
646 | + public void setResponseHeaders(final RestResponseBuilder builder) { |
647 | + switch (outParam.type) { |
648 | + |
649 | + case EndpointMetadata.X_TYPE_STRING: |
650 | + builder.type(MediaType.TEXT_PLAIN_TYPE); |
651 | + break; |
652 | + |
653 | + case EndpointMetadata.X_TYPE_JSON: |
654 | + builder.type(MediaType.APPLICATION_JSON_TYPE); |
655 | + break; |
656 | + |
657 | + case EndpointMetadata.X_TYPE_VOID: |
658 | + builder.type((MediaType)null); |
659 | + builder.status(Status.NO_CONTENT); |
660 | + break; |
661 | + |
662 | + case EndpointMetadata.X_TYPE_BYTEARRAY: |
663 | + /* |
664 | + * intentionally falls through TODO: support X_TYPE_BYTEARRAY |
665 | + */ |
666 | + |
667 | + default: |
668 | + assert false : "Invalid output type"; |
669 | + } |
670 | + } |
671 | + |
672 | + public boolean isVoid() { |
673 | + return X_TYPE_VOID.equals(outParam.type); |
674 | + } |
675 | + |
676 | } |
677 | |
678 | === added file 'src/test/resources/com/akiban/rest/direct/direct-output-types.body' |
679 | --- src/test/resources/com/akiban/rest/direct/direct-output-types.body 1970-01-01 00:00:00 +0000 |
680 | +++ src/test/resources/com/akiban/rest/direct/direct-output-types.body 2013-04-26 22:25:31 +0000 |
681 | @@ -0,0 +1,26 @@ |
682 | +function _register(registrar) { |
683 | + registrar.register( |
684 | + "method=GET path=out_string function=out_string in=() out=string"); |
685 | + registrar.register( |
686 | + "method=GET path=out_json function=out_json in=() out=json"); |
687 | + registrar.register( |
688 | + "method=GET path=out_void function=out_void in=() out=void"); |
689 | + registrar.register( |
690 | + "method=GET path=out_void_with_content function=out_void2 in=() out=void"); |
691 | +}; |
692 | + |
693 | +function out_string() { |
694 | + return "correct"; |
695 | +} |
696 | + |
697 | +function out_json() { |
698 | + return {status : "correct"}; |
699 | +} |
700 | + |
701 | +function out_void() { |
702 | +} |
703 | + |
704 | +function out_void2() { |
705 | + return "correct"; |
706 | +} |
707 | + |
708 | |
709 | === added file 'src/test/resources/com/akiban/rest/direct/test-direct-output-types.script' |
710 | --- src/test/resources/com/akiban/rest/direct/test-direct-output-types.script 1970-01-01 00:00:00 +0000 |
711 | +++ src/test/resources/com/akiban/rest/direct/test-direct-output-types.script 2013-04-26 22:25:31 +0000 |
712 | @@ -0,0 +1,34 @@ |
713 | +# Install a library with two endpoints |
714 | +# |
715 | +PUT /direct/library?module=test.output_types&language=Javascript @direct-output-types.body |
716 | +EQUALS {"functions":4}\n |
717 | + |
718 | + |
719 | +GET /direct/call/test.out_string |
720 | +HEADERS responseCode:200 |
721 | +HEADERS Content-Type:text/plain |
722 | +EQUALS correct\n |
723 | + |
724 | +GET /direct/call/test.out_json |
725 | +HEADERS responseCode:200 |
726 | +JSONEQ {"status":"correct"} |
727 | +HEADERS Content-Type:application/json |
728 | + |
729 | +GET /direct/call/test.out_void |
730 | +HEADERS responseCode:204 |
731 | + |
732 | +GET /direct/call/test.out_void_with_content |
733 | +HEADERS responseCode:204 |
734 | + |
735 | +PUT /direct/library?module=test.output_types2&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=() out=int");}; |
736 | +CONTAINS Invalid function specification |
737 | + |
738 | +PUT /direct/library?module=test.output_types2&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=() out=date");}; |
739 | +CONTAINS Invalid function specification |
740 | + |
741 | +PUT /direct/library?module=test.output_types2&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=() out=bytearray");}; |
742 | +CONTAINS Invalid function specification |
743 | + |
744 | +PUT /direct/library?module=test.output_types2&language=Javascript function _register(registrar) {registrar.register("method=GET path=x function=x in=() out=float");}; |
745 | +CONTAINS Invalid function specification |
746 | + |
747 | |
748 | === modified file 'src/test/resources/com/akiban/rest/direct/test-updates.script' |
749 | --- src/test/resources/com/akiban/rest/direct/test-updates.script 2013-04-22 21:09:54 +0000 |
750 | +++ src/test/resources/com/akiban/rest/direct/test-updates.script 2013-04-26 22:25:31 +0000 |
751 | @@ -11,14 +11,15 @@ |
752 | # Insert a new customer |
753 | # |
754 | PUT /direct/call/test.cadd/5 {"name":"Mary Johnson"} |
755 | -HEADERS responseCode:200 |
756 | +HEADERS responseCode:204 |
757 | + |
758 | GET /direct/call/test.cnames?prefix=SomeParameterValue |
759 | EQUALS SomeParameterValue,John Smith,Willy Jones,Jane Smith,Jonathan Smyth,Mary Johnson\n |
760 | |
761 | # Modify customer name |
762 | # |
763 | POST /direct/call/test.cchange {"cid":5, "name":"Mary Johnston"} |
764 | -HEADERS responseCode:200 |
765 | +HEADERS responseCode:204 |
766 | GET /direct/call/test.cnames?prefix=SomeParameterValue |
767 | EQUALS SomeParameterValue,John Smith,Willy Jones,Jane Smith,Jonathan Smyth,Mary Johnston\n |
768 |
Seems plausible.