Merge lp:~pbeaman/akiban-server/direct-updates-4 into lp:~akiban-technologies/akiban-server/trunk

Proposed by Peter Beaman
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
Reviewer Review Type Date Requested Status
Akiban Build User Needs Fixing
Mike McMahon Approve
Review via email: mp+161177@code.launchpad.net

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 RestResponseBuilder. The only change is support for a settable response MediaType.

Therefore the DirectService#invokeRestFunction is divided into two methods, one to look up the endpoint and the other to invoke it. The prepareRestInvocation method returns a DirectInvocation object that contains the necessary metadata. This includes the JDBCConnection on which the call is being made because that's needed to find the script library. This necessitates a modified try/finally structure in DirectResource to ensure that the connection gets closed.

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.

To post a comment you must log in.
Revision history for this message
Mike McMahon (mmcm) wrote :

Seems plausible.

review: Approve
Revision history for this message
Akiban Build User (build-akiban) wrote :

There were 2 failures during build/test:

* job system-tests-mtr failed at build number 3441: http://172.16.20.104:8080/job/system-tests-mtr/3441/

* view must-pass failed: system-tests-mtr is red

review: Needs Fixing
2645. By Peter Beaman

Tweaks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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

Subscribers

People subscribed via source and target branches