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.
This commit is contained in:
Inderjeet Singh 2010-11-04 20:11:05 +00:00
parent 7e05cde813
commit 399d49c0e8
10 changed files with 303 additions and 90 deletions

View File

@ -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<I extends ID, R extends RestResource<I, R>> {
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 <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> 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 <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> 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 <T> 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 <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> 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<I, R> responseReceiver =
new RestResponseReceiver<I, R>(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<I, R> request =
new RestRequest<I, R>(HttpMethod.GET, requestHeaders, null, resourceType);
RestResponse<I, R> 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<I, R> request =
new RestRequest<I, R>(HttpMethod.POST, requestHeaders, resource, resourceType);
RestResponse<I, R> response = stub.getResponse(callSpec, request);
return response.getBody();
}
public R put(R resource) {
HeaderMap requestHeaders =
new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build();
RestRequest<I, R> request =
new RestRequest<I, R>(HttpMethod.PUT, requestHeaders, resource, resourceType);
RestResponse<I, R> response = stub.getResponse(callSpec, request);
return response.getBody();
}
public void delete(I resourceId) {
HeaderMap requestHeaders =
new HeaderMap.Builder(callSpec.getRequestSpec().getHeadersSpec()).build();
RestRequest<I, R> request =
new RestRequest<I, R>(HttpMethod.DELETE, requestHeaders, null, resourceType);
stub.getResponse(callSpec, request);
}
}

View File

@ -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 <I extends ID> 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 <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> request) {
Gson gson = new GsonBuilder().setVersion(callSpec.getVersion()).create();
return getResponse(callSpec, request, gson);
}
public <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> 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 <I extends ID, R extends RestResource<I, R>> RestResponse<I, R> getResponse(
RestCallSpec callSpec, RestRequest<I, R> 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<I, R> responseReceiver =
new RestResponseReceiver<I, R>(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 extends ID, R extends RestResource<I, R>> I getId(R resource) {
return (resource == null || !resource.hasId()) ? null : resource.getId();
}
@Override
public String toString() {
return String.format("config:%s", config);
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -54,6 +54,11 @@ public final class Id<R> 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;
}

View File

@ -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<QUERY, RESULTS> {
public RESULTS query(QUERY query);
}

View File

@ -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
*/

View File

@ -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<Id<Order>, Order> restClient;
public OrderClient() {
RestServerConfig serverConfig = new RestServerConfig("http://localhost");
RestClientStub stub = new RestClientStub(serverConfig, Level.INFO);
restClient = new RestClient<Id<Order>, 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<LineItem> lineItems = new ArrayList<LineItem>();
lineItems.add(new LineItem("item1", 2, 1000000L, "USD"));
Cart cart = new Cart(lineItems, "first last", "4111-1111-1111-1111");
client.placeOrder(cart);
}
}

View File

@ -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<Id<Cart>, Cart> {
private final List<LineItem> lineItems;
private final String buyerName;
private final String creditCard;
private Id<Cart> id;
public Cart(List<LineItem> lineItems, String buyerName, String creditCard) {
this.lineItems = lineItems;
@ -44,4 +48,19 @@ public class Cart {
public String getCreditCard() {
return creditCard;
}
@Override
public Id<Cart> getId() {
return id;
}
@Override
public void setId(Id<Cart> id) {
this.id = id;
}
@Override
public boolean hasId() {
return Id.isValid(id);
}
}

View File

@ -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<Id<Order>, Order> {
public final Cart postedCart;
public final String orderNumber;
private Id<Order> 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<Order> getId() {
return id;
}
@Override
public void setId(Id<Order> id) {
this.id = id;
}
@Override
public boolean hasId() {
return Id.isValid(id);
}
}