From ad0e0d6f54058335714256f5b8b9433c6c10a8cc Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Mon, 15 Feb 2010 23:24:48 +0000 Subject: [PATCH] Added means to define and implement REST web-service and its clients. Added missing copyright headers. Added TypedKey based methods in request/response classes. --- .../webservice/client/ResponseReceiver.java | 3 +- .../gson/webservice/client/RestClient.java | 90 +++++++++++++++ .../webservice/client/RestRequestSender.java | 103 +++++++++++++++++ .../client/RestResponseReceiver.java | 94 ++++++++++++++++ .../definition/ContentBodySpec.java | 4 +- .../webservice/definition/HttpMethod.java | 7 ++ .../gson/webservice/definition/TypedKey.java | 22 ++++ .../definition/WebServiceResponse.java | 4 + .../definition/rest/Preconditions.java | 31 ++++++ .../definition/rest/ResourceMap.java | 55 +++++++++ .../webservice/definition/rest/RestCall.java | 47 ++++++++ .../definition/rest/RestCallSpec.java | 104 ++++++++++++++++++ .../definition/rest/RestRequest.java | 76 +++++++++++++ .../definition/rest/RestRequestSpec.java | 46 ++++++++ .../definition/rest/RestResponse.java | 93 ++++++++++++++++ .../definition/rest/RestResponseSpec.java | 46 ++++++++ .../gson/wsf/server/ResponseSender.java | 2 - .../wsf/server/rest/RestRequestReceiver.java | 91 +++++++++++++++ .../wsf/server/rest/RestResponseBuilder.java | 25 +++++ .../wsf/server/rest/RestResponseSender.java | 73 ++++++++++++ 20 files changed, 1011 insertions(+), 5 deletions(-) create mode 100644 wsclient/src/main/java/com/google/gson/webservice/client/RestClient.java create mode 100644 wsclient/src/main/java/com/google/gson/webservice/client/RestRequestSender.java create mode 100644 wsclient/src/main/java/com/google/gson/webservice/client/RestResponseReceiver.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/Preconditions.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/ResourceMap.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCall.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCallSpec.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequest.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequestSpec.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponse.java create mode 100644 wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponseSpec.java create mode 100644 wsf/src/main/java/com/google/gson/wsf/server/rest/RestRequestReceiver.java create mode 100644 wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseBuilder.java create mode 100644 wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseSender.java diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/ResponseReceiver.java b/wsclient/src/main/java/com/google/gson/webservice/client/ResponseReceiver.java index 87bf7dcb..b25abb4e 100644 --- a/wsclient/src/main/java/com/google/gson/webservice/client/ResponseReceiver.java +++ b/wsclient/src/main/java/com/google/gson/webservice/client/ResponseReceiver.java @@ -32,6 +32,7 @@ import com.google.gson.webservice.definition.ResponseBody; import com.google.gson.webservice.definition.ResponseBodySpec; import com.google.gson.webservice.definition.ResponseSpec; import com.google.gson.webservice.definition.WebServiceResponse; +import com.google.gson.webservice.definition.WebServiceSystemException; /** * Receives a response coming on an {@link HttpURLConnection}. @@ -63,7 +64,7 @@ public final class ResponseReceiver { ResponseBody responseBody = readResponseBody(conn, bodySpec); return new WebServiceResponse(responseParams, responseBody); } catch (IOException e) { - throw new RuntimeException(e); + throw new WebServiceSystemException(e); } } diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/RestClient.java b/wsclient/src/main/java/com/google/gson/webservice/client/RestClient.java new file mode 100644 index 00000000..c4124d32 --- /dev/null +++ b/wsclient/src/main/java/com/google/gson/webservice/client/RestClient.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed 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 com.google.gson.webservice.client; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.webservice.definition.WebServiceSystemException; +import com.google.gson.webservice.definition.rest.RestCallSpec; +import com.google.gson.webservice.definition.rest.RestRequest; +import com.google.gson.webservice.definition.rest.RestResponse; + +/** + * Main class used by clients to access a Gson Web service. + * + * @author inder + */ +public final class RestClient { + private final WebServiceConfig config; + private final Logger logger; + private final Level logLevel; + + public RestClient(WebServiceConfig serverConfig) { + this(serverConfig, null); + } + + public RestClient(WebServiceConfig serverConfig, Level logLevel) { + this.config = serverConfig; + this.logger = logLevel == null ? null : Logger.getLogger(RestClient.class.getName()); + this.logLevel = logLevel; + } + + private URL getWebServiceUrl(RestCallSpec callSpec) { + String url = config.getServiceBaseUrl() + callSpec.getPath().get(); + try { + return new URL(url); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public RestResponse getResponse(RestCallSpec callSpec, RestRequest request) { + Gson gson = new GsonBuilder().create(); + return getResponse(callSpec, request, gson); + } + + public RestResponse getResponse( + RestCallSpec callSpec, RestRequest request, Gson gson) { + try { + URL webServiceUrl = getWebServiceUrl(callSpec); + if (logger != null) { + logger.log(logLevel, "Opening connection to " + webServiceUrl); + } + HttpURLConnection conn = (HttpURLConnection) webServiceUrl.openConnection(); + RestRequestSender requestSender = new RestRequestSender(gson, logLevel); + requestSender.send(conn, request); + RestResponseReceiver responseReceiver = + new RestResponseReceiver(gson, callSpec.getResponseSpec(), logLevel); + return responseReceiver.receive(conn); + } catch (IOException e) { + throw new WebServiceSystemException(e); + } catch (IllegalArgumentException e) { + throw new WebServiceSystemException(e); + } + } + + @Override + public String toString() { + return String.format("config:%s", config); + } +} diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/RestRequestSender.java b/wsclient/src/main/java/com/google/gson/webservice/client/RestRequestSender.java new file mode 100644 index 00000000..caf3c2f1 --- /dev/null +++ b/wsclient/src/main/java/com/google/gson/webservice/client/RestRequestSender.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.client; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.Gson; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HeaderMapSpec; +import com.google.gson.webservice.definition.WebServiceSystemException; +import com.google.gson.webservice.definition.rest.RestRequest; + +/** + * Class to send Web service requests on a {@link HttpURLConnection}. + * + * @author inder + */ +public final class RestRequestSender { + private final Gson gson; + private final Logger logger; + private final Level logLevel; + + public RestRequestSender(Gson gson) { + this(gson, null); + } + + public RestRequestSender(Gson gson, Level logLevel) { + this.gson = gson; + logger = logLevel == null ? null : Logger.getLogger(RestRequestSender.class.getName()); + this.logLevel = logLevel; + } + + public void send(HttpURLConnection conn, RestRequest request) { + try { + conn.setRequestMethod(request.getHttpMethod().toString()); + setHeader(conn, "Content-Type", request.getContentType(), true); + + // Assume conservatively that the response will need to be read. + // This is done here instead of in the response receiver because this property must be set + // before sending any data on the connection. + conn.setDoInput(true); + + R requestBody = request.getBody(); + String requestBodyContents = ""; + // Android Java VM ignore Content-Length if setDoOutput is not set + conn.setDoOutput(true); + if (requestBody != null) { + requestBodyContents = gson.toJson(requestBody); + } + String contentLength = String.valueOf(requestBodyContents.length()); + setHeader(conn, "Content-Length", contentLength, true); + addRequestParams(conn, request.getHeaders()); + if (requestBodyContents != null) { + Streams.copy(requestBodyContents, conn.getOutputStream(), false); + } + + // Initiate the sending of the request. + conn.connect(); + } catch (IOException e) { + throw new WebServiceSystemException(e); + } + } + + private void addRequestParams(HttpURLConnection conn, HeaderMap requestParams) { + HeaderMapSpec spec = requestParams.getSpec(); + for (Map.Entry entry : requestParams.entrySet()) { + String paramName = entry.getKey(); + Type type = spec.getTypeFor(paramName); + Object value = entry.getValue(); + String json = gson.toJson(value, type); + setHeader(conn, paramName, json, false); + } + } + + private void setHeader(HttpURLConnection conn, String name, String value, boolean overwrite) { + if (logger != null) { + logger.log(logLevel, String.format("Request param: %s:%s", name, value)); + } + if (overwrite) { + conn.setRequestProperty(name, value); + } else { + conn.addRequestProperty(name, value); + } + } +} diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/RestResponseReceiver.java b/wsclient/src/main/java/com/google/gson/webservice/client/RestResponseReceiver.java new file mode 100644 index 00000000..f2d2a4cf --- /dev/null +++ b/wsclient/src/main/java/com/google/gson/webservice/client/RestResponseReceiver.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.gson.Gson; +import com.google.gson.webservice.definition.ContentBodySpec; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HeaderMapSpec; +import com.google.gson.webservice.definition.WebServiceSystemException; +import com.google.gson.webservice.definition.rest.RestResponse; +import com.google.gson.webservice.definition.rest.RestResponseSpec; + +/** + * Receives a response coming on an {@link HttpURLConnection}. + * + * @author inder + */ +public final class RestResponseReceiver { + private final Gson gson; + private final RestResponseSpec spec; + private final Logger logger; + private final Level logLevel; + + public RestResponseReceiver(Gson gson, RestResponseSpec spec) { + this(gson, spec, null); + } + public RestResponseReceiver(Gson gson, RestResponseSpec spec, Level logLevel) { + this.gson = gson; + this.spec = spec; + this.logger = logLevel == null ? null : Logger.getLogger(RestResponseReceiver.class.getName()); + this.logLevel = logLevel; + } + + public RestResponse receive(HttpURLConnection conn) { + try { + HeaderMapSpec paramSpec = spec.getHeadersSpec(); + Class bodySpec = spec.getResourceClass(); + // read response + HeaderMap responseParams = readResponseHeaders(conn, paramSpec); + R responseBody = readResponseBody(conn, bodySpec); + return new RestResponse(responseParams, responseBody); + } catch (IOException e) { + throw new WebServiceSystemException(e); + } + } + + private HeaderMap readResponseHeaders(HttpURLConnection conn, HeaderMapSpec paramsSpec) { + HeaderMap.Builder paramsBuilder = new HeaderMap.Builder(paramsSpec); + for (Map.Entry entry : paramsSpec.entrySet()) { + String paramName = entry.getKey(); + String json = conn.getHeaderField(paramName); + if (json != null) { + if (logger != null) { + logger.log(logLevel, String.format("Response Header: %s:%s\n", paramName, json)); + } + Type typeOfT = paramsSpec.getTypeFor(paramName); + Object value = gson.fromJson(json, typeOfT); + paramsBuilder.put(paramName, value, typeOfT); + } + } + return paramsBuilder.build(); + } + + private R readResponseBody(HttpURLConnection conn, Class resourceClass) throws IOException { + String connContentType = conn.getContentType(); + Preconditions.checkArgument(connContentType.contains(ContentBodySpec.JSON_CONTENT_TYPE), conn); + Reader reader = new BufferedReader(new InputStreamReader(conn.getInputStream())); + R body = gson.fromJson(reader, resourceClass); + return body; + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/ContentBodySpec.java b/wsdef/src/main/java/com/google/gson/webservice/definition/ContentBodySpec.java index 06a6b842..af8341eb 100644 --- a/wsdef/src/main/java/com/google/gson/webservice/definition/ContentBodySpec.java +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/ContentBodySpec.java @@ -25,10 +25,10 @@ import java.util.Set; * * @author inder */ -class ContentBodySpec implements ParamMapSpec { +public class ContentBodySpec implements ParamMapSpec { public static final String JSON_CONTENT_TYPE = "application/json"; - private static final String JSON_CHARACTER_ENCODING = "utf-8"; + public static final String JSON_CHARACTER_ENCODING = "utf-8"; private final Map paramsSpec; diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/HttpMethod.java b/wsdef/src/main/java/com/google/gson/webservice/definition/HttpMethod.java index 2891239f..753841fa 100644 --- a/wsdef/src/main/java/com/google/gson/webservice/definition/HttpMethod.java +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/HttpMethod.java @@ -15,6 +15,10 @@ */ package com.google.gson.webservice.definition; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + /** * An enum of Http methods to provide strongly-typed versions instead of strings. * @@ -29,4 +33,7 @@ public enum HttpMethod { public static HttpMethod getMethod(String method) { return valueOf(method.trim().toUpperCase()); } + + public static final List ALL_METHODS = + Collections.unmodifiableList(Arrays.asList(values())); } \ No newline at end of file diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/TypedKey.java b/wsdef/src/main/java/com/google/gson/webservice/definition/TypedKey.java index 1f61acc5..0556b55c 100644 --- a/wsdef/src/main/java/com/google/gson/webservice/definition/TypedKey.java +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/TypedKey.java @@ -1,5 +1,27 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition; +/** + * A typed key for use in a {@link ParamMap} or a {@link ParamMapSpec}. + * + * @author inder + * + * @param Intended type of the content for the key + */ public class TypedKey { private final String name; private final Class classOfT; diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/WebServiceResponse.java b/wsdef/src/main/java/com/google/gson/webservice/definition/WebServiceResponse.java index 1cb26c0e..cc9c8745 100644 --- a/wsdef/src/main/java/com/google/gson/webservice/definition/WebServiceResponse.java +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/WebServiceResponse.java @@ -50,6 +50,10 @@ public final class WebServiceResponse { return this; } + public Builder putBody(TypedKey paramName, T content) { + return putBody(paramName.getName(), content); + } + public Builder putBody(String paramName, Object content) { body.put(paramName, content); return this; diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/Preconditions.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/Preconditions.java new file mode 100644 index 00000000..0ba7d096 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/Preconditions.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +final class Preconditions { + + public static void checkArgument(boolean condition) { + if (!condition) { + throw new IllegalArgumentException(); + } + } + + public static void checkNotNull(Object obj) { + if (obj == null) { + throw new IllegalArgumentException(); + } + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/ResourceMap.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/ResourceMap.java new file mode 100644 index 00000000..920eb615 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/ResourceMap.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.webservice.definition.CallPath; + +/** + * A {@link Map} of {@link CallPath} to {@link RestCallSpec} + * + * @author inder + */ +public final class ResourceMap { + + public static final class Builder { + private final Map> resources = + new HashMap>(); + + public Builder set(CallPath callPath, RestCallSpec spec) { + resources.put(callPath, spec); + return this; + } + + public ResourceMap build() { + return new ResourceMap(resources); + } + } + + private final Map> resources; + + public ResourceMap(Map> resources) { + this.resources = resources; + } + + public RestCallSpec get(CallPath callPath) { + @SuppressWarnings("unchecked") + RestCallSpec restCallSpec = (RestCallSpec)resources.get(callPath); + return restCallSpec; + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCall.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCall.java new file mode 100644 index 00000000..9af43f5c --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCall.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +/** + * The data associated with a Rest Web service call. This includes http request header parameters + * (form and URL parameters), request body, response header parameters, and resource response body. + * + * @author inder + */ +public final class RestCall { + + private final RestCallSpec callSpec; + private final RestRequest request; + private final RestResponse response; + + public RestCall(RestCallSpec callSpec, RestRequest request, RestResponse response) { + this.callSpec = callSpec; + this.request = request; + this.response = response; + } + + public RestCallSpec getSpec() { + return callSpec; + } + + public RestRequest getRequest() { + return request; + } + + public RestResponse getResponse() { + return response; + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCallSpec.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCallSpec.java new file mode 100644 index 00000000..01369ca0 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestCallSpec.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; + +import com.google.gson.webservice.definition.CallPath; +import com.google.gson.webservice.definition.HeaderMapSpec; +import com.google.gson.webservice.definition.HttpMethod; +import com.google.gson.webservice.definition.TypedKey; + +/** + * Specification for a REST service + * + * @author inder + */ +public final class RestCallSpec { + public static class Builder { + private final CallPath callPath; + private final Set supportedHttpMethods = new LinkedHashSet(); + private final HeaderMapSpec.Builder reqParamsSpecBuilder = new HeaderMapSpec.Builder(); + private final HeaderMapSpec.Builder resParamsSpecBuilder = new HeaderMapSpec.Builder(); + private final Class resourceClass; + + public Builder(CallPath callPath, Class resourceClass) { + this.callPath = callPath; + supportedHttpMethods.addAll(HttpMethod.ALL_METHODS); + this.resourceClass = resourceClass; + } + + public Builder disableHttpMethod(HttpMethod httpMethod) { + supportedHttpMethods.remove(httpMethod); + return this; + } + + public Builder addRequestParam(TypedKey paramKey, Class typeOfParam) { + reqParamsSpecBuilder.put(paramKey.getName(), typeOfParam); + return this; + } + + public Builder addResponseParam(TypedKey paramKey, Class typeOfParam) { + resParamsSpecBuilder.put(paramKey.getName(), typeOfParam); + return this; + } + + public RestCallSpec build() { + if (supportedHttpMethods.isEmpty()) { + supportedHttpMethods.addAll(Arrays.asList(HttpMethod.values())); + } + RestRequestSpec requestSpec = + new RestRequestSpec(reqParamsSpecBuilder.build(), resourceClass); + RestResponseSpec responseSpec = + new RestResponseSpec(resParamsSpecBuilder.build(), resourceClass); + return new RestCallSpec(supportedHttpMethods, callPath, + requestSpec, responseSpec); + } + } + + private final Set supportedHttpMethods; + private final CallPath path; + private final RestRequestSpec requestSpec; + private final RestResponseSpec responseSpec; + + private RestCallSpec(Set supportedHttpMethods, CallPath path, + RestRequestSpec requestSpec, RestResponseSpec responseSpec) { + Preconditions.checkArgument(!supportedHttpMethods.isEmpty()); + Preconditions.checkNotNull(path); + this.supportedHttpMethods = supportedHttpMethods; + this.path = path; + this.requestSpec = requestSpec; + this.responseSpec = responseSpec; + } + + public CallPath getPath() { + return path; + } + + public Set getSupportedHttpMethods() { + return supportedHttpMethods; + } + + public RestResponseSpec getResponseSpec() { + return responseSpec; + } + + public RestRequestSpec getRequestSpec() { + return requestSpec; + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequest.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequest.java new file mode 100644 index 00000000..b1bd672a --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import com.google.gson.webservice.definition.ContentBodySpec; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HttpMethod; +import com.google.gson.webservice.definition.RequestBody; + +/** + * The data associated with a Web service request. This includes HTTP request header parameters + * (form and URL parameters), and {@link RequestBody}. + * + * @author inder + */ +public final class RestRequest { + private final HttpMethod method; + private final HeaderMap headers; + private final R body; + private final RestRequestSpec spec; + + public RestRequest(HttpMethod method, HeaderMap requestHeaders, + R requestBody, Class resourceClass) { + this.method = method; + this.body = requestBody; + this.headers = requestHeaders; + this.spec = new RestRequestSpec(requestHeaders.getSpec(), resourceClass); + } + + public HttpMethod getMethod() { + return method; + } + + public RestRequestSpec getSpec() { + return spec; + } + + public HttpMethod getHttpMethod() { + return method; + } + + public R getBody() { + return body; + } + + public HeaderMap getHeaders() { + return headers; + } + + public String getContentType() { + return ContentBodySpec.JSON_CONTENT_TYPE; + } + + @SuppressWarnings("unchecked") + public T getHeader(String headerName) { + return (T) headers.get(headerName); + } + + @Override + public String toString() { + return String.format("{method:%s,headers:%s,body:%s}", method, headers, body); + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequestSpec.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequestSpec.java new file mode 100644 index 00000000..4595fb36 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestRequestSpec.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import com.google.gson.webservice.definition.HeaderMapSpec; + +/** + * Specification for a {@link RestRequest}. + * + * @author inder + */ +public final class RestRequestSpec { + private final HeaderMapSpec headersSpec; + private final Class resourceClass; + + public RestRequestSpec(HeaderMapSpec headersSpec, Class resourceClass) { + this.headersSpec = headersSpec; + this.resourceClass = resourceClass; + } + + public Class getResourceClass() { + return resourceClass; + } + + public HeaderMapSpec getHeadersSpec() { + return headersSpec; + } + + @Override + public String toString() { + return String.format("{headersSpec:%s,resourceClass:%s}", headersSpec, resourceClass); + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponse.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponse.java new file mode 100644 index 00000000..e32829b8 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponse.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.TypedKey; + +/** + * The data associated with a REST Web service response. This includes http response header + * parameters, and the response body. + * + * @author inder + */ +public final class RestResponse { + + private final HeaderMap headers; + private final R body; + private final RestResponseSpec spec; + + public static class Builder { + private final HeaderMap.Builder headers; + private R body; + private final RestResponseSpec spec; + + public Builder(RestResponseSpec spec) { + this.spec = spec; + headers = new HeaderMap.Builder(spec.getHeadersSpec()); + } + + public Builder putHeader(TypedKey paramName, T content) { + headers.put(paramName.getName(), content, paramName.getClassOfT()); + return this; + } + + public Builder setBody(R body) { + this.body = body; + return this; + } + + public RestResponse build() { + return new RestResponse(spec, headers.build(), body); + } + } + + private RestResponse(RestResponseSpec spec, HeaderMap headers, R body) { + this.spec = spec; + this.headers = headers; + this.body = body; + } + + @SuppressWarnings("unchecked") + public RestResponse(HeaderMap responseHeaders, R responseBody) { + this.spec = new RestResponseSpec(responseHeaders.getSpec(), + (Class)responseBody.getClass()); + this.headers = responseHeaders; + this.body = responseBody; + } + + public RestResponseSpec getSpec() { + return spec; + } + + public HeaderMap getHeaders() { + return headers; + } + + public R getBody() { + return body; + } + + @SuppressWarnings("unchecked") + public T getHeader(String headerName) { + return (T) headers.get(headerName); + } + + @Override + public String toString() { + return String.format("{headers:%s, body:%s}", headers, body); + } +} diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponseSpec.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponseSpec.java new file mode 100644 index 00000000..679bc3fd --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResponseSpec.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.webservice.definition.rest; + +import com.google.gson.webservice.definition.HeaderMapSpec; + +/** + * Specification for a {@link RestResponse}. + * + * @author inder + */ +public final class RestResponseSpec { + private final HeaderMapSpec headersSpec; + private final Class resourceClass; + + public RestResponseSpec(HeaderMapSpec headersSpec, Class resourceClass) { + this.headersSpec = headersSpec; + this.resourceClass = resourceClass; + } + + public Class getResourceClass() { + return resourceClass; + } + + public HeaderMapSpec getHeadersSpec() { + return headersSpec; + } + + @Override + public String toString() { + return String.format("{headersSpec:%s,resourceClass:%s}", headersSpec, resourceClass); + } +} diff --git a/wsf/src/main/java/com/google/gson/wsf/server/ResponseSender.java b/wsf/src/main/java/com/google/gson/wsf/server/ResponseSender.java index 5cd4a7c7..2cccf9d4 100644 --- a/wsf/src/main/java/com/google/gson/wsf/server/ResponseSender.java +++ b/wsf/src/main/java/com/google/gson/wsf/server/ResponseSender.java @@ -26,7 +26,6 @@ import com.google.gson.Gson; import com.google.gson.webservice.definition.HeaderMap; import com.google.gson.webservice.definition.HeaderMapSpec; import com.google.gson.webservice.definition.ResponseBody; -import com.google.gson.webservice.definition.ResponseBodySpec; import com.google.gson.webservice.definition.WebServiceResponse; /** @@ -45,7 +44,6 @@ public final class ResponseSender { public void send(HttpServletResponse conn, WebServiceResponse response) { try { - conn.setContentType(ResponseBodySpec.JSON_CONTENT_TYPE); sendHeaders(conn, response.getHeaders()); sendBody(conn, response.getBody()); } catch (IOException e) { diff --git a/wsf/src/main/java/com/google/gson/wsf/server/rest/RestRequestReceiver.java b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestRequestReceiver.java new file mode 100644 index 00000000..196ef5da --- /dev/null +++ b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestRequestReceiver.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed 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 com.google.gson.wsf.server.rest; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import com.google.gson.Gson; +import com.google.gson.JsonParseException; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HeaderMapSpec; +import com.google.gson.webservice.definition.HttpMethod; +import com.google.gson.webservice.definition.WebServiceSystemException; +import com.google.gson.webservice.definition.rest.RestRequest; +import com.google.gson.webservice.definition.rest.RestRequestSpec; + +/** + * Receives and parses a request at the server side on a {@link HttpServletRequest}. + * + * @author inder + */ +public final class RestRequestReceiver { + + private final Gson gson; + private final RestRequestSpec spec; + + public RestRequestReceiver(Gson gson, RestRequestSpec spec) { + this.gson = gson; + this.spec = spec; + } + + public RestRequest receive(HttpServletRequest request) { + try { + HeaderMap requestParams = buildRequestParams(request); + R requestBody = buildRequestBody(request); + + HttpMethod method = HttpMethod.getMethod(request.getMethod()); + return new RestRequest(method, requestParams, requestBody, spec.getResourceClass()); + } catch (IOException e) { + throw new WebServiceSystemException(e); + } catch (JsonParseException e) { + // Not a Web service request + throw new WebServiceSystemException(e); + } + } + + private HeaderMap buildRequestParams(HttpServletRequest request) { + HeaderMapSpec paramsSpec = this.spec.getHeadersSpec(); + HeaderMap.Builder paramsBuilder = new HeaderMap.Builder(paramsSpec); + for (Map.Entry param : paramsSpec.entrySet()) { + String name = param.getKey(); + Type type = param.getValue(); + String header = request.getHeader(name); + if (header == null || header.equals("")) { + // check parameter map for the value + header = request.getParameter(name); + } + if (header != null && !header.equals("")) { + Object value = gson.fromJson(header, type); + paramsBuilder.put(name, value); + } + } + return paramsBuilder.build(); + } + + private R buildRequestBody(HttpServletRequest request) throws IOException { + Class resourceClass = spec.getResourceClass(); + Reader reader = new BufferedReader(new InputStreamReader(request.getInputStream())); + R requestBody = gson.fromJson(reader, resourceClass); + return requestBody; + } +} diff --git a/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseBuilder.java b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseBuilder.java new file mode 100644 index 00000000..65aeb9bd --- /dev/null +++ b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseBuilder.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 Google Inc. + * + * Licensed 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 com.google.gson.wsf.server.rest; + +import com.google.gson.webservice.definition.rest.RestRequest; +import com.google.gson.webservice.definition.rest.RestResponse; +import com.google.gson.webservice.definition.rest.RestCallSpec; + +public interface RestResponseBuilder { + public void buildResponse(RestCallSpec callSpec, RestRequest request, + RestResponse.Builder responseBuilder); +} diff --git a/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseSender.java b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseSender.java new file mode 100644 index 00000000..cad49138 --- /dev/null +++ b/wsf/src/main/java/com/google/gson/wsf/server/rest/RestResponseSender.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed 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 com.google.gson.wsf.server.rest; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletResponse; + +import com.google.gson.Gson; +import com.google.gson.webservice.definition.ContentBodySpec; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HeaderMapSpec; +import com.google.gson.webservice.definition.rest.RestResponse; + +/** + * Sends a JSON web service response on {@link HttpServletResponse}. + * + * @author inder + */ +public final class RestResponseSender { + private static final Logger logger = Logger.getLogger(RestResponseSender.class.getCanonicalName()); + + private Gson gson; + + public RestResponseSender(Gson gson) { + this.gson = gson; + } + + public void send(HttpServletResponse conn, RestResponse response) { + try { + sendHeaders(conn, response.getHeaders()); + sendBody(conn, response.getBody()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void sendHeaders(HttpServletResponse conn, HeaderMap responseParams) { + HeaderMapSpec spec = responseParams.getSpec(); + for (Map.Entry param : responseParams.entrySet()) { + String paramName = param.getKey(); + Object paramValue = param.getValue(); + Type paramType = spec.getTypeFor(paramName); + String json = gson.toJson(paramValue, paramType); + logger.fine("RESPONSE HEADER:{" + paramName + ", " + json + "}"); + conn.addHeader(paramName, json); + } + } + + private void sendBody(HttpServletResponse conn, R responseBody) throws IOException { + conn.setContentType(ContentBodySpec.JSON_CONTENT_TYPE); + conn.setCharacterEncoding(ContentBodySpec.JSON_CHARACTER_ENCODING); + String json = gson.toJson(responseBody); + logger.fine("RESPONSE BODY:" + json); + conn.getWriter().append(json); + } +}