From 399d49c0e83987252f22a18c5ee8629095c9ae57 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Thu, 4 Nov 2010 20:11:05 +0000 Subject: [PATCH] Revised RestClient to provide easy access to RestMethods. Updated Order and Cart to be rest resources. Added a Queryable interface that can be used to indicate that a Rest Resource supports querying. Added getValueAsString() method to Id. Removed spurious warnings. --- .../google/gson/rest/client/RestClient.java | 136 +++++++----------- .../gson/rest/client/RestClientStub.java | 130 +++++++++++++++++ .../client/SingleThreadExecutor.java | 4 +- .../client/ThreadPerTaskExecutor.java | 4 +- .../com/google/gson/rest/definition/Id.java | 5 + .../gson/rest/definition/Queryable.java | 13 ++ .../rest/definition/IdTypeAdapterTest.java | 2 +- .../gson/example/rest/client/OrderClient.java | 56 ++++++++ .../com/google/gson/example/model/Cart.java | 21 ++- .../com/google/gson/example/model/Order.java | 22 ++- 10 files changed, 303 insertions(+), 90 deletions(-) create mode 100644 wsclient/src/main/java/com/google/gson/rest/client/RestClientStub.java create mode 100644 wsdef/src/main/java/com/google/gson/rest/definition/Queryable.java create mode 100644 wsexample/client/src/main/java/com/google/gson/example/rest/client/OrderClient.java diff --git a/wsclient/src/main/java/com/google/gson/rest/client/RestClient.java b/wsclient/src/main/java/com/google/gson/rest/client/RestClient.java index 16bbc9d1..2db590f8 100644 --- a/wsclient/src/main/java/com/google/gson/rest/client/RestClient.java +++ b/wsclient/src/main/java/com/google/gson/rest/client/RestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 Google Inc. + * 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. @@ -15,105 +15,73 @@ */ package com.google.gson.rest.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.rest.definition.ID; import com.google.gson.rest.definition.RestCallSpec; import com.google.gson.rest.definition.RestRequest; import com.google.gson.rest.definition.RestResource; import com.google.gson.rest.definition.RestResponse; -import com.google.gson.webservice.definition.WebServiceSystemException; +import com.google.gson.webservice.definition.CallPath; +import com.google.gson.webservice.definition.HeaderMap; +import com.google.gson.webservice.definition.HttpMethod; + +import java.lang.reflect.Type; /** - * Main class used by clients to access a Gson Web service. - * - * @author inder + * A client class to access a rest resource + * + * @author Inderjeet Singh */ -public class RestClient { - private final RestServerConfig config; - private final Logger logger; - private final Level logLevel; +public class RestClient> { + private final RestClientStub stub; + private final RestCallSpec callSpec; + private final Type resourceType; - public RestClient(RestServerConfig serverConfig) { - this(serverConfig, null); + public RestClient(RestClientStub stub, CallPath callPath, Type resourceType) { + this(stub, resourceType, generateRestCallSpec(callPath, resourceType)); } - public RestClient(RestServerConfig serverConfig, Level logLevel) { - this.config = serverConfig; - this.logger = logLevel == null ? null : Logger.getLogger(RestClient.class.getName()); - this.logLevel = logLevel; - } - - private URL getWebServiceUrl(RestCallSpec callSpec) { - double version = callSpec.getVersion(); - String versionPath = version == -1 ? "" : "/" + version; - String url = config.getServiceBaseUrl() + versionPath + 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().setVersion(callSpec.getVersion()).create(); - return getResponse(callSpec, request, gson); + protected RestClient(RestClientStub stub, Type resourceType, RestCallSpec callSpec) { + this.stub = stub; + this.callSpec = callSpec; + this.resourceType = resourceType; } - public > RestResponse getResponse( - RestCallSpec callSpec, RestRequest request, Gson gson) { - HttpURLConnection conn = null; - try { - URL webServiceUrl = getWebServiceUrl(callSpec); - if (logger != null) { - logger.log(logLevel, "Opening connection to " + webServiceUrl); - } - conn = (HttpURLConnection) webServiceUrl.openConnection(); - return getResponse(callSpec, request, gson, conn); - } catch (IOException e) { - throw new WebServiceSystemException(e); - } finally { - closeIgnoringErrors(conn); - } + private static RestCallSpec generateRestCallSpec(CallPath callPath, Type resourceType) { + return new RestCallSpec.Builder(callPath, resourceType).build(); } - /** - * Use this method if you want to mange the HTTP Connection yourself. This is useful when you - * want to use HTTP pipelining. - */ - public > RestResponse getResponse( - RestCallSpec callSpec, RestRequest request, Gson gson, HttpURLConnection conn) { - try { - if (logger != null) { - URL webServiceUrl = getWebServiceUrl(callSpec); - logger.log(logLevel, "Opening connection to " + webServiceUrl); - } - RestRequestSender requestSender = new RestRequestSender(gson, logLevel); - requestSender.send(conn, request); - RestResponseReceiver responseReceiver = - new RestResponseReceiver(gson, callSpec.getResponseSpec(), logLevel); - return responseReceiver.receive(conn); - } catch (IllegalArgumentException e) { - throw new WebServiceSystemException(e); - } - } - - private static void closeIgnoringErrors(HttpURLConnection conn) { - if (conn != null) { - conn.disconnect(); - } + public R get(I resourceId) { + HeaderMap requestHeaders = + new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build(); + RestRequest request = + new RestRequest(HttpMethod.GET, requestHeaders, null, resourceType); + RestResponse response = stub.getResponse(callSpec, request); + return response.getBody(); } - @Override - public String toString() { - return String.format("config:%s", config); + public R post(R resource) { + HeaderMap requestHeaders = + new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build(); + RestRequest request = + new RestRequest(HttpMethod.POST, requestHeaders, resource, resourceType); + RestResponse response = stub.getResponse(callSpec, request); + return response.getBody(); + } + + public R put(R resource) { + HeaderMap requestHeaders = + new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build(); + RestRequest request = + new RestRequest(HttpMethod.PUT, requestHeaders, resource, resourceType); + RestResponse response = stub.getResponse(callSpec, request); + return response.getBody(); + } + + public void delete(I resourceId) { + HeaderMap requestHeaders = + new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build(); + RestRequest request = + new RestRequest(HttpMethod.DELETE, requestHeaders, null, resourceType); + stub.getResponse(callSpec, request); } } diff --git a/wsclient/src/main/java/com/google/gson/rest/client/RestClientStub.java b/wsclient/src/main/java/com/google/gson/rest/client/RestClientStub.java new file mode 100644 index 00000000..1dd9c89f --- /dev/null +++ b/wsclient/src/main/java/com/google/gson/rest/client/RestClientStub.java @@ -0,0 +1,130 @@ +/* + * 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.rest.client; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.rest.definition.ID; +import com.google.gson.rest.definition.RestCallSpec; +import com.google.gson.rest.definition.RestRequest; +import com.google.gson.rest.definition.RestResource; +import com.google.gson.rest.definition.RestResponse; +import com.google.gson.webservice.definition.WebServiceSystemException; + +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; + +/** + * A stub to access the rest service + * + * @author inder + */ +public class RestClientStub { + private final RestServerConfig config; + private final Logger logger; + private final Level logLevel; + + public RestClientStub(RestServerConfig serverConfig) { + this(serverConfig, null); + } + + public RestClientStub(RestServerConfig serverConfig, Level logLevel) { + this.config = serverConfig; + this.logger = logLevel == null ? null : Logger.getLogger(RestClientStub.class.getName()); + this.logLevel = logLevel; + } + + private URL getWebServiceUrl( + RestCallSpec callSpec, ID id) { + double version = callSpec.getVersion(); + StringBuilder url = new StringBuilder(config.getServiceBaseUrl()); + if (version != -1D) { + url.append('/').append(version); + } + url.append(callSpec.getPath().get()); + if (id != null) { + url.append('/').append(id.getValue()); + } + try { + return new URL(url.toString()); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + public > RestResponse getResponse( + RestCallSpec callSpec, RestRequest request) { + Gson gson = new GsonBuilder().setVersion(callSpec.getVersion()).create(); + return getResponse(callSpec, request, gson); + } + + public > RestResponse getResponse( + RestCallSpec callSpec, RestRequest request, Gson gson) { + HttpURLConnection conn = null; + try { + URL webServiceUrl = getWebServiceUrl(callSpec, getId(request.getBody())); + if (logger != null) { + logger.log(logLevel, "Opening connection to " + webServiceUrl); + } + conn = (HttpURLConnection) webServiceUrl.openConnection(); + return getResponse(callSpec, request, gson, conn); + } catch (IOException e) { + throw new WebServiceSystemException(e); + } finally { + closeIgnoringErrors(conn); + } + } + + /** + * Use this method if you want to mange the HTTP Connection yourself. This is useful when you + * want to use HTTP pipelining. + */ + public > RestResponse getResponse( + RestCallSpec callSpec, RestRequest request, Gson gson, HttpURLConnection conn) { + try { + if (logger != null) { + URL webServiceUrl = getWebServiceUrl(callSpec, getId(request.getBody())); + logger.log(logLevel, "Opening connection to " + webServiceUrl); + } + RestRequestSender requestSender = new RestRequestSender(gson, logLevel); + requestSender.send(conn, request); + RestResponseReceiver responseReceiver = + new RestResponseReceiver(gson, callSpec.getResponseSpec(), logLevel); + return responseReceiver.receive(conn); + } catch (IllegalArgumentException e) { + throw new WebServiceSystemException(e); + } + } + + private static void closeIgnoringErrors(HttpURLConnection conn) { + if (conn != null) { + conn.disconnect(); + } + } + + private static > I getId(R resource) { + return (resource == null || !resource.hasId()) ? null : resource.getId(); + } + + @Override + public String toString() { + return String.format("config:%s", config); + } +} \ No newline at end of file diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/SingleThreadExecutor.java b/wsclient/src/main/java/com/google/gson/webservice/client/SingleThreadExecutor.java index 100751df..a3ad4906 100644 --- a/wsclient/src/main/java/com/google/gson/webservice/client/SingleThreadExecutor.java +++ b/wsclient/src/main/java/com/google/gson/webservice/client/SingleThreadExecutor.java @@ -25,7 +25,9 @@ import java.util.concurrent.Executors; */ final class SingleThreadExecutor implements TaskExecutor { private ExecutorService executor; - public void execute(final Runnable r) { + + @Override + public void execute(Runnable r) { executor = Executors.newSingleThreadExecutor(); executor.execute(r); } diff --git a/wsclient/src/main/java/com/google/gson/webservice/client/ThreadPerTaskExecutor.java b/wsclient/src/main/java/com/google/gson/webservice/client/ThreadPerTaskExecutor.java index 2682a4ca..20e9c5c0 100644 --- a/wsclient/src/main/java/com/google/gson/webservice/client/ThreadPerTaskExecutor.java +++ b/wsclient/src/main/java/com/google/gson/webservice/client/ThreadPerTaskExecutor.java @@ -22,7 +22,9 @@ package com.google.gson.webservice.client; */ final class ThreadPerTaskExecutor implements TaskExecutor { private Thread thread; - public void execute(final Runnable r) { + + @Override + public void execute(Runnable r) { thread = new Thread(r); thread.start(); } diff --git a/wsdef/src/main/java/com/google/gson/rest/definition/Id.java b/wsdef/src/main/java/com/google/gson/rest/definition/Id.java index 0c93ba54..ceee974b 100644 --- a/wsdef/src/main/java/com/google/gson/rest/definition/Id.java +++ b/wsdef/src/main/java/com/google/gson/rest/definition/Id.java @@ -54,6 +54,11 @@ public final class Id implements ID { public static long getValue(Id id) { return id == null ? NULL_VALUE : id.getValue(); } + + public String getValueAsString() { + return String.valueOf(value); + } + public Type getTypeOfId() { return typeOfId; } diff --git a/wsdef/src/main/java/com/google/gson/rest/definition/Queryable.java b/wsdef/src/main/java/com/google/gson/rest/definition/Queryable.java new file mode 100644 index 00000000..fc372b40 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/rest/definition/Queryable.java @@ -0,0 +1,13 @@ +// Copyright 2010 Google Inc. All Rights Reserved. + +package com.google.gson.rest.definition; + +/** + * Implement this interface in a service to indicate that it allows querying + * by this type + * + * @author Inderjeet Singh + */ +public interface Queryable { + public RESULTS query(QUERY query); +} diff --git a/wsdef/src/test/java/com/google/gson/rest/definition/IdTypeAdapterTest.java b/wsdef/src/test/java/com/google/gson/rest/definition/IdTypeAdapterTest.java index ae14f322..4da0bc50 100644 --- a/wsdef/src/test/java/com/google/gson/rest/definition/IdTypeAdapterTest.java +++ b/wsdef/src/test/java/com/google/gson/rest/definition/IdTypeAdapterTest.java @@ -28,7 +28,7 @@ import com.google.gson.reflect.TypeToken; import com.google.gson.rest.definition.Id; /** - * Unit tests for {@link IdTypeAdapter} + * Unit tests for {@link Id.GsonTypeAdapter} * * @author inder */ diff --git a/wsexample/client/src/main/java/com/google/gson/example/rest/client/OrderClient.java b/wsexample/client/src/main/java/com/google/gson/example/rest/client/OrderClient.java new file mode 100644 index 00000000..7e55ac31 --- /dev/null +++ b/wsexample/client/src/main/java/com/google/gson/example/rest/client/OrderClient.java @@ -0,0 +1,56 @@ +/* + * 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.example.rest.client; + +import com.google.gson.example.model.Cart; +import com.google.gson.example.model.LineItem; +import com.google.gson.example.model.Order; +import com.google.gson.rest.client.RestClient; +import com.google.gson.rest.client.RestClientStub; +import com.google.gson.rest.client.RestServerConfig; +import com.google.gson.rest.definition.Id; +import com.google.gson.webservice.definition.CallPath; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +/** + * A sample client for the rest resource for {@link Order} + * + * @author Inderjeet Singh + */ +public class OrderClient { + public static final CallPath CALL_PATH = new CallPath("/rest/order"); + private final RestClient, Order> restClient; + public OrderClient() { + RestServerConfig serverConfig = new RestServerConfig("http://localhost"); + RestClientStub stub = new RestClientStub(serverConfig, Level.INFO); + restClient = new RestClient, Order>(stub, CALL_PATH, Order.class); + } + + public Order placeOrder(Cart cart) { + Order order = new Order(cart, cart.getId().getValueAsString()); + return restClient.post(order); + } + + public static void main(String[] args) { + OrderClient client = new OrderClient(); + List lineItems = new ArrayList(); + lineItems.add(new LineItem("item1", 2, 1000000L, "USD")); + Cart cart = new Cart(lineItems, "first last", "4111-1111-1111-1111"); + client.placeOrder(cart); + } +} diff --git a/wsexample/definition/src/main/java/com/google/gson/example/model/Cart.java b/wsexample/definition/src/main/java/com/google/gson/example/model/Cart.java index 61b6c434..810a0b54 100644 --- a/wsexample/definition/src/main/java/com/google/gson/example/model/Cart.java +++ b/wsexample/definition/src/main/java/com/google/gson/example/model/Cart.java @@ -15,6 +15,9 @@ */ package com.google.gson.example.model; +import com.google.gson.rest.definition.Id; +import com.google.gson.rest.definition.RestResource; + import java.util.List; /** @@ -22,10 +25,11 @@ import java.util.List; * * @author inder */ -public class Cart { +public class Cart implements RestResource, Cart> { private final List lineItems; private final String buyerName; private final String creditCard; + private Id id; public Cart(List lineItems, String buyerName, String creditCard) { this.lineItems = lineItems; @@ -44,4 +48,19 @@ public class Cart { public String getCreditCard() { return creditCard; } + + @Override + public Id getId() { + return id; + } + + @Override + public void setId(Id id) { + this.id = id; + } + + @Override + public boolean hasId() { + return Id.isValid(id); + } } diff --git a/wsexample/definition/src/main/java/com/google/gson/example/model/Order.java b/wsexample/definition/src/main/java/com/google/gson/example/model/Order.java index 2ff17a99..38e8ccb4 100644 --- a/wsexample/definition/src/main/java/com/google/gson/example/model/Order.java +++ b/wsexample/definition/src/main/java/com/google/gson/example/model/Order.java @@ -15,15 +15,19 @@ */ package com.google.gson.example.model; +import com.google.gson.rest.definition.Id; +import com.google.gson.rest.definition.RestResource; + /** * An order * * @author inder */ -public class Order { +public class Order implements RestResource, Order> { public final Cart postedCart; public final String orderNumber; - + private Id id; + public Order(Cart postedCart, String orderNumber) { this.postedCart = postedCart; this.orderNumber = orderNumber; @@ -37,4 +41,18 @@ public class Order { return orderNumber; } + @Override + public Id getId() { + return id; + } + + @Override + public void setId(Id id) { + this.id = id; + } + + @Override + public boolean hasId() { + return Id.isValid(id); + } }