Added ability to specify a different ID than the Id class for a RestResource.

This commit is contained in:
Inderjeet Singh 2010-11-04 15:25:43 +00:00
parent 7b99419aea
commit 7e05cde813
19 changed files with 141 additions and 107 deletions

View File

@ -24,6 +24,7 @@ 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;
@ -61,14 +62,14 @@ public class RestClient {
}
}
public <R extends RestResource<R>> RestResponse<R> getResponse(
RestCallSpec callSpec, RestRequest<R> request) {
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 <R extends RestResource<R>> RestResponse<R> getResponse(
RestCallSpec callSpec, RestRequest<R> request, Gson 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);
@ -88,8 +89,8 @@ public class RestClient {
* Use this method if you want to mange the HTTP Connection yourself. This is useful when you
* want to use HTTP pipelining.
*/
public <R extends RestResource<R>> RestResponse<R> getResponse(
RestCallSpec callSpec, RestRequest<R> request, Gson gson, HttpURLConnection conn) {
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);
@ -97,8 +98,8 @@ public class RestClient {
}
RestRequestSender requestSender = new RestRequestSender(gson, logLevel);
requestSender.send(conn, request);
RestResponseReceiver<R> responseReceiver =
new RestResponseReceiver<R>(gson, callSpec.getResponseSpec(), logLevel);
RestResponseReceiver<I, R> responseReceiver =
new RestResponseReceiver<I, R>(gson, callSpec.getResponseSpec(), logLevel);
return responseReceiver.receive(conn);
} catch (IllegalArgumentException e) {
throw new WebServiceSystemException(e);

View File

@ -23,6 +23,7 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.RestRequest;
import com.google.gson.rest.definition.RestResource;
import com.google.gson.webservice.definition.HeaderMap;
@ -50,7 +51,7 @@ public final class RestRequestSender {
this.logLevel = logLevel;
}
public <R extends RestResource<R>> void send(HttpURLConnection conn, RestRequest<R> request) {
public <I extends ID, R extends RestResource<I, R>> void send(HttpURLConnection conn, RestRequest<I, R> request) {
try {
conn.setRequestMethod(request.getHttpMethod().toString());
setHeader(conn, "Content-Type", request.getContentType(), true);

View File

@ -15,16 +15,6 @@
*/
package com.google.gson.rest.client;
import com.google.gson.Gson;
import com.google.gson.rest.definition.RestResource;
import com.google.gson.rest.definition.RestResponse;
import com.google.gson.rest.definition.RestResponseSpec;
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.wsclient.internal.utils.ConnectionPreconditions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
@ -35,12 +25,23 @@ import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.Gson;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.RestResource;
import com.google.gson.rest.definition.RestResponse;
import com.google.gson.rest.definition.RestResponseSpec;
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.wsclient.internal.utils.ConnectionPreconditions;
/**
* Receives a response coming on an {@link HttpURLConnection}.
*
* @author inder
*/
public final class RestResponseReceiver<R extends RestResource<R>> {
public final class RestResponseReceiver<I extends ID, R extends RestResource<I, R>> {
private final Gson gson;
private final RestResponseSpec spec;
private final Logger logger;
@ -56,14 +57,14 @@ public final class RestResponseReceiver<R extends RestResource<R>> {
this.logLevel = logLevel;
}
public RestResponse<R> receive(HttpURLConnection conn) {
public RestResponse<I, R> receive(HttpURLConnection conn) {
try {
HeaderMapSpec paramSpec = spec.getHeadersSpec();
Type bodyType = spec.getResourceType();
// read response
HeaderMap responseParams = readResponseHeaders(conn, paramSpec);
R responseBody = readResponseBody(conn, bodyType);
return new RestResponse<R>(responseParams, responseBody, bodyType);
return new RestResponse<I, R>(responseParams, responseBody, bodyType);
} catch (IOException e) {
throw new WebServiceSystemException(e);
}

View File

@ -22,8 +22,8 @@ package com.google.gson.rest.definition;
*
* @param <R> type of object
*/
public interface HasId<R> {
public Id<R> getId();
public void setId(Id<R> id);
public interface HasId<I extends ID> {
public I getId();
public void setId(I id);
public boolean hasId();
}

View File

@ -0,0 +1,7 @@
package com.google.gson.rest.definition;
public interface ID {
public static final long INVALID_ID = 0L;
public long getValue();
}

View File

@ -0,0 +1,20 @@
package com.google.gson.rest.definition;
import java.lang.reflect.Type;
public class IDFactory<I extends ID> {
private final Class<? super I> classOfI;
private final Type typeOfId;
public IDFactory(Class<? super I> classOfI, Type typeOfId) {
this.classOfI = classOfI;
this.typeOfId = typeOfId;
}
public I createId(long value) {
if (classOfI.isAssignableFrom(Id.class)) {
return (I)Id.get(value, typeOfId);
}
throw new UnsupportedOperationException();
}
}

View File

@ -35,7 +35,7 @@ import java.lang.reflect.WildcardType;
*
* @param <R> type variable for the rest resource
*/
public final class Id<R> {
public final class Id<R> implements ID {
private static final long NULL_VALUE = -1;
private final long value;
private final Type typeOfId;
@ -46,6 +46,7 @@ public final class Id<R> {
this.typeOfId = typeOfId;
}
@Override
public long getValue() {
return value;
}

View File

@ -38,17 +38,17 @@ import com.google.gson.reflect.TypeToken;
*
* @param <R> The resource
*/
public final class MetaData<R extends RestResource<R>> {
public final class MetaData<I extends ID, R extends RestResource<I, R>> {
private final Map<String, String> map;
private final transient Map<Object, Object> mapTransient;
public static <RS extends RestResource<RS>> MetaData<RS> create() {
return new MetaData<RS>();
public static <II extends ID, RS extends RestResource<II, RS>> MetaData<II, RS> create() {
return new MetaData<II, RS>();
}
@SuppressWarnings({"unchecked", "rawtypes"})
private static MetaData<?> createTypeUnsafe(Map<String, String> values) {
private static MetaData<?, ?> createTypeUnsafe(Map<String, String> values) {
return new MetaData(values);
}
@ -103,20 +103,20 @@ public final class MetaData<R extends RestResource<R>> {
* Gson Type adapter for {@link MetaData}. The serialized representation on wire is just a
* Map<String, String>
*/
public static final class GsonTypeAdapter implements JsonSerializer<MetaData<?>>,
JsonDeserializer<MetaData<?>>{
public static final class GsonTypeAdapter implements JsonSerializer<MetaData<?, ?>>,
JsonDeserializer<MetaData<?, ?>>{
private static final Type MAP_TYPE = new TypeToken<Map<String, String>>(){}.getType();
@Override
public MetaData<?> deserialize(JsonElement json, Type typeOfT,
public MetaData<?, ?> deserialize(JsonElement json, Type typeOfT,
JsonDeserializationContext context) throws JsonParseException {
Map<String, String> map = context.deserialize(json, MAP_TYPE);
return MetaData.createTypeUnsafe(map);
}
@Override
public JsonElement serialize(MetaData<?> src, Type typeOfSrc,
public JsonElement serialize(MetaData<?, ?> src, Type typeOfSrc,
JsonSerializationContext context) {
return context.serialize(src.map, MAP_TYPE);
}

View File

@ -21,13 +21,13 @@ package com.google.gson.rest.definition;
*
* @author inder
*/
public final class RestCall<R extends RestResource<R>> {
public final class RestCall<I extends ID, R extends RestResource<I, R>> {
private final RestCallSpec callSpec;
private final RestRequest<R> request;
private final RestResponse<R> response;
private final RestRequest<I, R> request;
private final RestResponse<I, R> response;
public RestCall(RestCallSpec callSpec, RestRequest<R> request, RestResponse<R> response) {
public RestCall(RestCallSpec callSpec, RestRequest<I, R> request, RestResponse<I, R> response) {
this.callSpec = callSpec;
this.request = request;
this.response = response;
@ -37,11 +37,11 @@ public final class RestCall<R extends RestResource<R>> {
return callSpec;
}
public RestRequest<R> getRequest() {
public RestRequest<I, R> getRequest() {
return request;
}
public RestResponse<R> getResponse() {
public RestResponse<I, R> getResponse() {
return response;
}
}

View File

@ -15,19 +15,19 @@
*/
package com.google.gson.rest.definition;
import java.lang.reflect.Type;
import com.google.gson.webservice.definition.HeaderMap;
import com.google.gson.webservice.definition.HttpMethod;
import com.google.gson.webservice.definition.TypedKey;
import java.lang.reflect.Type;
/**
* The data associated with a Web service request. This includes HTTP request header parameters
* (form and URL parameters), and request body.
*
* @author inder
*/
public final class RestRequest<R extends RestResource<R>> {
public final class RestRequest<I extends ID, R extends RestResource<I, R>> {
public static final String JSON_CONTENT_TYPE = "application/json";
private final HttpMethod method;
@ -43,7 +43,7 @@ public final class RestRequest<R extends RestResource<R>> {
this.spec = new RestRequestSpec(requestHeaders.getSpec(), resourceType);
}
public Id<R> getId() {
public I getId() {
return body.getId();
}

View File

@ -22,5 +22,5 @@ package com.google.gson.rest.definition;
*
* @param <R> the rest resource type
*/
public interface RestResource<R> extends HasId<R> {
public interface RestResource<I extends ID, R> extends HasId<I> {
}

View File

@ -26,13 +26,13 @@ import com.google.gson.webservice.definition.TypedKey;
*
* @author inder
*/
public final class RestResponse<R extends RestResource<R>> {
public final class RestResponse<I extends ID, R extends RestResource<I, R>> {
private final HeaderMap headers;
private final R body;
private final RestResponseSpec spec;
public static class Builder<RS extends RestResource<RS>> {
public static class Builder<II extends ID, RS extends RestResource<II, RS>> {
private final HeaderMap.Builder headers;
private RS body;
private final RestResponseSpec spec;
@ -42,18 +42,18 @@ public final class RestResponse<R extends RestResource<R>> {
headers = new HeaderMap.Builder(spec.getHeadersSpec());
}
public <T> Builder<RS> putHeader(TypedKey<T> paramName, T content) {
public <T> Builder<II, RS> putHeader(TypedKey<T> paramName, T content) {
headers.put(paramName.getName(), content, paramName.getClassOfT());
return this;
}
public Builder<RS> setBody(RS body) {
public Builder<II, RS> setBody(RS body) {
this.body = body;
return this;
}
public RestResponse<RS> build() {
return new RestResponse<RS>(spec, headers.build(), body);
public RestResponse<II, RS> build() {
return new RestResponse<II, RS>(spec, headers.build(), body);
}
}

View File

@ -22,7 +22,8 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gson.rest.definition.HasId;
import com.google.gson.rest.definition.Id;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.IDFactory;
/**
* This class provides a type-safe map to access values associated with Ids
@ -31,22 +32,22 @@ import com.google.gson.rest.definition.Id;
*
* @param <T> the type of the objects being kept in the map
*/
public class IdMap<T extends HasId<T>> {
public class IdMap<I extends ID, T extends HasId<I>> {
public static final Logger LOG = Logger.getLogger(IdMap.class.getName());
protected final Map<Id<T>, T> map;
protected final Map<I, T> map;
private volatile long nextAvailableId;
private final Type typeOfId;
private final IDFactory<I> idFactory;
/**
* Use {@link #create(Type)} instead of constructor
*/
protected IdMap(Type typeOfId) {
this.typeOfId = typeOfId;
map = new ConcurrentHashMap<Id<T>, T>();
protected IdMap(Class<? super I> classOfI, Type typeOfId) {
map = new ConcurrentHashMap<I, T>();
nextAvailableId = 0;
this.idFactory = new IDFactory<I>(classOfI, typeOfId);
}
public T get(Id<T> id) {
public T get(I id) {
return map.get(id);
}
@ -55,23 +56,23 @@ public class IdMap<T extends HasId<T>> {
return obj;
}
public void delete(Id<T> id) {
public void delete(I id) {
T removed = map.remove(id);
if (removed == null) {
LOG.log(Level.WARNING, "Attempted to delete non-existent id: {0}", id);
}
}
public boolean exists(Id<T> id) {
public boolean exists(I id) {
return map.containsKey(id);
}
public synchronized Id<T> getNextId() {
public synchronized I getNextId() {
long id = nextAvailableId++;
return Id.get(id, typeOfId);
return idFactory.createId(id);
}
public static <S extends HasId<S>> IdMap<S> create(Type typeOfId) {
return new IdMap<S>(typeOfId);
public static <II extends ID, S extends HasId<II>> IdMap<II, S> create(Class<? super II> classOfII, Type typeOfId) {
return new IdMap<II, S>(classOfII, typeOfId);
}
}

View File

@ -18,7 +18,7 @@ package com.google.gson.rest.server;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.rest.definition.Id;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.MetaData;
import com.google.gson.rest.definition.RestResource;
@ -29,15 +29,15 @@ import com.google.gson.rest.definition.RestResource;
*
* @param <R> the rest resource for whic the metadata is being stored
*/
public class MetaDataMap<R extends RestResource<R>> {
private final Map<Id<R>, MetaData<R>> map;
public class MetaDataMap<I extends ID, R extends RestResource<I, R>> {
private final Map<I, MetaData<I, R>> map;
public MetaDataMap() {
this.map = new HashMap<Id<R>, MetaData<R>>();
this.map = new HashMap<I, MetaData<I, R>>();
}
public MetaData<R> get(Id<R> resourceId) {
MetaData<R> metaData = map.get(resourceId);
public MetaData<I, R> get(I resourceId) {
MetaData<I, R> metaData = map.get(resourceId);
if (metaData == null) {
metaData = MetaData.create();
map.put(resourceId, metaData);

View File

@ -16,7 +16,7 @@
package com.google.gson.rest.server;
import com.google.gson.rest.definition.HasId;
import com.google.gson.rest.definition.Id;
import com.google.gson.rest.definition.ID;
/**
* An interface for a repository of rest resources. Meant for abstracting the server-side
@ -26,8 +26,8 @@ import com.google.gson.rest.definition.Id;
*
* @param <R> the type of rest resource
*/
public interface Repository<R extends HasId<R>> {
public R get(Id<R> resourceId);
public interface Repository<I extends ID, R extends HasId<I>> {
public R get(I resourceId);
/**
* if resource.getId() == null, inserts the resource after assigning it a new id.
@ -35,14 +35,14 @@ public interface Repository<R extends HasId<R>> {
*/
public R put(R resource);
public void delete(Id<R> resourceId);
public void delete(I resourceId);
public boolean exists(Id<R> resourceId);
public boolean exists(I resourceId);
/**
* Ensures that the specified resource has a valid id that will be used when it is saved
*/
public Id<R> assignId(R resource);
public I assignId(R resource);
public Id<R> getNextId();
public I getNextId();
}

View File

@ -16,7 +16,7 @@
package com.google.gson.rest.server;
import com.google.common.base.Preconditions;
import com.google.gson.rest.definition.Id;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.MetaData;
import com.google.gson.rest.definition.RestResource;
@ -27,24 +27,24 @@ import com.google.gson.rest.definition.RestResource;
*
* @param <R> Type variable for the resource
*/
public class RepositoryInMemory<R extends RestResource<R>> implements Repository<R> {
public class RepositoryInMemory<I extends ID, R extends RestResource<I, R>> implements Repository<I, R> {
private static final String METADATA_KEY_IS_FRESHLY_ASSIGNED_ID = "isFreshlyAssignedId";
private final IdMap<R> resources;
private final MetaDataMap<R> metaDataMap;
private final IdMap<I, R> resources;
private final MetaDataMap<I, R> metaDataMap;
public RepositoryInMemory(Class<? super R> classOfResource) {
this.resources = IdMap.create(classOfResource);
this.metaDataMap = new MetaDataMap<R>();
public RepositoryInMemory(Class<? super I> classOfI, Class<? super R> classOfResource) {
this.resources = IdMap.create(classOfI, classOfResource);
this.metaDataMap = new MetaDataMap<I, R>();
}
@Override
public R get(Id<R> resourceId) {
public R get(I resourceId) {
return resources.get(resourceId);
}
public boolean isFreshlyAssignedId(Id<R> resourceId) {
MetaData<R> metaData = metaDataMap.get(resourceId);
public boolean isFreshlyAssignedId(I resourceId) {
MetaData<I, R> metaData = metaDataMap.get(resourceId);
if (metaData == null) {
return false;
}
@ -57,7 +57,7 @@ public class RepositoryInMemory<R extends RestResource<R>> implements Repository
// insert semantics
assignId(resource);
} else {
Id<R> id = resource.getId();
I id = resource.getId();
if (!isFreshlyAssignedId(id)) {
// update semantics
Preconditions.checkState(resources.exists(resource.getId()));
@ -69,24 +69,24 @@ public class RepositoryInMemory<R extends RestResource<R>> implements Repository
}
@Override
public void delete(Id<R> resourceId) {
public void delete(I resourceId) {
resources.delete(resourceId);
}
@Override
public boolean exists(Id<R> resourceId) {
public boolean exists(I resourceId) {
return resources.exists(resourceId);
}
@Override
public Id<R> getNextId() {
public I getNextId() {
return resources.getNextId();
}
@Override
public Id<R> assignId(R resource) {
public I assignId(R resource) {
if (resource.getId() == null) {
Id<R> id = resources.getNextId();
I id = resources.getNextId();
resource.setId(id);
metaDataMap.get(id).putBoolean(METADATA_KEY_IS_FRESHLY_ASSIGNED_ID, true);
}

View File

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.RestRequest;
import com.google.gson.rest.definition.RestRequestSpec;
import com.google.gson.rest.definition.RestResource;
@ -39,7 +40,7 @@ import com.google.gson.webservice.definition.WebServiceSystemException;
*
* @author inder
*/
public final class RestRequestReceiver<R extends RestResource<R>> {
public final class RestRequestReceiver<I extends ID, R extends RestResource<I, R>> {
private final Gson gson;
private final RestRequestSpec spec;
@ -49,13 +50,13 @@ public final class RestRequestReceiver<R extends RestResource<R>> {
this.spec = spec;
}
public RestRequest<R> receive(HttpServletRequest request) {
public RestRequest<I, R> receive(HttpServletRequest request) {
try {
HeaderMap requestParams = buildRequestParams(request);
R requestBody = buildRequestBody(request);
HttpMethod method = HttpMethod.getMethod(request.getMethod());
return new RestRequest<R>(method, requestParams, requestBody, spec.getResourceType());
return new RestRequest<I, R>(method, requestParams, requestBody, spec.getResourceType());
} catch (IOException e) {
throw new WebServiceSystemException(e);
} catch (JsonParseException e) {

View File

@ -15,22 +15,22 @@
*/
package com.google.gson.rest.server;
import com.google.gson.rest.definition.Id;
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.HttpMethod;
public abstract class RestResponseBuilder<R extends RestResource<R>> {
protected final Repository<R> resources;
public abstract class RestResponseBuilder<I extends ID, R extends RestResource<I, R>> {
protected final Repository<I, R> resources;
public RestResponseBuilder(Repository<R> resources) {
public RestResponseBuilder(Repository<I, R> resources) {
this.resources = resources;
}
public void buildResponse(RestCallSpec callSpec, RestRequest<R> request,
RestResponse.Builder<R> responseBuilder) {
public void buildResponse(RestCallSpec callSpec, RestRequest<I, R> request,
RestResponse.Builder<I, R> responseBuilder) {
HttpMethod method = request.getMethod();
R responseBody = null;
switch (method) {
@ -50,7 +50,7 @@ public abstract class RestResponseBuilder<R extends RestResource<R>> {
responseBuilder.setBody(responseBody);
}
public R get(Id<R> resourceId) {
public R get(I resourceId) {
return resources.get(resourceId);
}
@ -58,7 +58,7 @@ public abstract class RestResponseBuilder<R extends RestResource<R>> {
return resources.put(resource);
}
public void delete(Id<R> resourceId) {
public void delete(I resourceId) {
resources.delete(resourceId);
}

View File

@ -23,6 +23,7 @@ import java.util.logging.Logger;
import javax.servlet.http.HttpServletResponse;
import com.google.gson.Gson;
import com.google.gson.rest.definition.ID;
import com.google.gson.rest.definition.RestResource;
import com.google.gson.rest.definition.RestResponse;
import com.google.gson.webservice.definition.ContentBodySpec;
@ -34,7 +35,7 @@ import com.google.gson.webservice.definition.HeaderMapSpec;
*
* @author inder
*/
public final class RestResponseSender<R extends RestResource<R>> {
public final class RestResponseSender<I extends ID, R extends RestResource<I, R>> {
private static final Logger logger = Logger.getLogger(RestResponseSender.class.getCanonicalName());
private Gson gson;
@ -43,7 +44,7 @@ public final class RestResponseSender<R extends RestResource<R>> {
this.gson = gson;
}
public void send(HttpServletResponse conn, RestResponse<R> response) {
public void send(HttpServletResponse conn, RestResponse<I, R> response) {
try {
sendHeaders(conn, response.getHeaders());
sendBody(conn, response.getBody());