diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/MetaData.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/MetaData.java new file mode 100644 index 00000000..24cb6fa0 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/MetaData.java @@ -0,0 +1,101 @@ +/* + * 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.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.reflect.TypeToken; + +/** + * Metadata associated with a repository for a rest resource + * + * @author inder + * + * @param The resource + */ +public final class MetaData> { + + private final Map map; + + public static > MetaData create() { + return new MetaData(); + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static MetaData createTypeUnsafe(Map values) { + return new MetaData(values); + } + + public MetaData() { + this(new HashMap()); + } + + private MetaData(Map values) { + this.map = values; + } + + public String getString(String key) { + return (String) map.get(key); + } + + public void putString(String key, String value) { + map.put(key, value); + } + + public boolean getBoolean(String key) { + String value = map.get(key); + return value == null ? false : Boolean.parseBoolean(value); + } + + public void putBoolean(String key, boolean value) { + map.put(key, String.valueOf(value)); + } + + public void remove(String key) { + map.remove(key); + } + + /** + * Gson Type adapter for {@link MetaData}. The serialized representation on wire is just a + * Map + */ + public static final class GsonTypeAdapter implements JsonSerializer>, + JsonDeserializer>{ + + private static final Type MAP_TYPE = new TypeToken>(){}.getType(); + + @Override + public MetaData deserialize(JsonElement json, Type typeOfT, + JsonDeserializationContext context) throws JsonParseException { + Map map = context.deserialize(json, MAP_TYPE); + return MetaData.createTypeUnsafe(map); + } + + @Override + public JsonElement serialize(MetaData src, Type typeOfSrc, + JsonSerializationContext context) { + return context.serialize(src.map, MAP_TYPE); + } + } +} \ No newline at end of file diff --git a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResource.java b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResource.java index ed414095..48361e0c 100644 --- a/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResource.java +++ b/wsdef/src/main/java/com/google/gson/webservice/definition/rest/RestResource.java @@ -23,5 +23,4 @@ package com.google.gson.webservice.definition.rest; * @param the rest resource type */ public interface RestResource extends HasId { - } diff --git a/wsdef/src/main/java/com/google/gson/webservice/typeadapters/IdTypeAdapter.java b/wsdef/src/main/java/com/google/gson/webservice/typeadapters/IdTypeAdapter.java new file mode 100644 index 00000000..811aa009 --- /dev/null +++ b/wsdef/src/main/java/com/google/gson/webservice/typeadapters/IdTypeAdapter.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.typeadapters; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.webservice.definition.rest.Id; + +/** + * Type adapter for converting an Id to its serialized form + * + * @author inder + * + */ +public final class IdTypeAdapter implements JsonSerializer>, JsonDeserializer> { + + @Override + public JsonElement serialize(Id src, Type typeOfSrc, JsonSerializationContext context) { + return new JsonPrimitive(src.getValue()); + } + + @Override + public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (!(typeOfT instanceof ParameterizedType)) { + throw new JsonParseException("Id of unknown type: " + typeOfT); + } + ParameterizedType parameterizedType = (ParameterizedType) typeOfT; + // Since Id takes only one TypeVariable, the actual type corresponding to the first + // TypeVariable is the Type we are looking for + Type typeOfId = parameterizedType.getActualTypeArguments()[0]; + return Id.get(json.getAsLong(), typeOfId); + } +} diff --git a/wsdef/src/test/java/com/google/gson/webservice/typeadapters/IdTypeAdapterTest.java b/wsdef/src/test/java/com/google/gson/webservice/typeadapters/IdTypeAdapterTest.java new file mode 100644 index 00000000..5aef1d4b --- /dev/null +++ b/wsdef/src/test/java/com/google/gson/webservice/typeadapters/IdTypeAdapterTest.java @@ -0,0 +1,134 @@ +/* + * 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.typeadapters; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import junit.framework.TestCase; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.google.gson.webservice.definition.rest.Id; + +/** + * Unit tests for {@link IdTypeAdapter} + * + * @author inder + */ +public class IdTypeAdapterTest extends TestCase { + private static final Id STUDENT1_ID = Id.get(5L, Student.class); + private static final Id STUDENT2_ID = Id.get(6L, Student.class); + private static final Student STUDENT1 = new Student(STUDENT1_ID, "first"); + private static final Student STUDENT2 = new Student(STUDENT2_ID, "second"); + private static final Type TYPE_COURSE_HISTORY = + new TypeToken>(){}.getType(); + private static final Id> COURSE_ID = Id.get(10L, TYPE_COURSE_HISTORY); + + private Gson gson; + private Course course; + + @Override + protected void setUp() { + gson = new GsonBuilder() + .registerTypeAdapter(Id.class, new IdTypeAdapter()) + .create(); + course = new Course(COURSE_ID, 4, + new Assignment(null, null), createList(STUDENT1, STUDENT2)); + } + + public void testSerializeId() { + String json = gson.toJson(course, TYPE_COURSE_HISTORY); + System.out.println(json); + assertTrue(json.contains(String.valueOf(COURSE_ID.getValue()))); + assertTrue(json.contains(String.valueOf(STUDENT1_ID.getValue()))); + assertTrue(json.contains(String.valueOf(STUDENT2_ID.getValue()))); + } + + public void testDeserializeId() { + String json = "{courseId:1,students:[{id:1,name:'first'},{id:6,name:'second'}]," + + "numAssignments:4,assignment:{}}"; + Course target = gson.fromJson(json, TYPE_COURSE_HISTORY); + assertEquals(1, target.getStudents().get(0).id.getValue()); + assertEquals(6, target.getStudents().get(1).id.getValue()); + assertEquals(1, target.getId().getValue()); + } + + @SuppressWarnings("unused") + private static class Student { + Id id; + String name; + + private Student() { + this(null, null); + } + public Student(Id id, String name) { + this.id = id; + this.name = name; + } + } + @SuppressWarnings("unused") + private static class Course { + final List students; + private final Id> courseId; + private final int numAssignments; + private final Assignment assignment; + + private Course() { + this(null, 0, null, new ArrayList()); + } + + public Course(Id> courseId, int numAssignments, + Assignment assignment, List players) { + this.courseId = courseId; + this.numAssignments = numAssignments; + this.assignment = assignment; + this.students = players; + } + public Id> getId() { + return courseId; + } + List getStudents() { + return students; + } + } + + @SuppressWarnings("unused") + private static class Assignment { + private final Id> id; + private final T data; + + private Assignment() { + this(null, null); + } + public Assignment(Id> id, T data) { + this.id = id; + this.data = data; + } + } + + @SuppressWarnings("unused") + private static class HistoryCourse { + int numClasses; + } + + private static List createList(T ...items) { + return Arrays.asList(items); + } +} diff --git a/wsf/src/main/java/com/google/gson/wsf/server/rest/MetaData.java b/wsf/src/main/java/com/google/gson/wsf/server/rest/MetaDataMap.java similarity index 52% rename from wsf/src/main/java/com/google/gson/wsf/server/rest/MetaData.java rename to wsf/src/main/java/com/google/gson/wsf/server/rest/MetaDataMap.java index 4b23a0f8..93b269f0 100644 --- a/wsf/src/main/java/com/google/gson/wsf/server/rest/MetaData.java +++ b/wsf/src/main/java/com/google/gson/wsf/server/rest/MetaDataMap.java @@ -15,24 +15,33 @@ */ package com.google.gson.wsf.server.rest; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.webservice.definition.rest.Id; +import com.google.gson.webservice.definition.rest.MetaData; import com.google.gson.webservice.definition.rest.RestResource; /** - * Metadata associated with a repository for a rest resource + * A map of resources to their MetaData * * @author inder * - * @param The resource + * @param the rest resource for whic the metadata is being stored */ -public final class MetaData> { +public class MetaDataMap> { + private final Map, MetaData> map; - final boolean freshlyAssignedId; - - public MetaData(boolean freshlyAssignedId) { - this.freshlyAssignedId = freshlyAssignedId; + public MetaDataMap() { + this.map = new HashMap, MetaData>(); } - public boolean isFreshlyAssignedId() { - return freshlyAssignedId; + public MetaData get(Id resourceId) { + MetaData metaData = map.get(resourceId); + if (metaData == null) { + metaData = MetaData.create(); + map.put(resourceId, metaData); + } + return metaData; } -} \ No newline at end of file +} diff --git a/wsf/src/main/java/com/google/gson/wsf/server/rest/RepositoryInMemory.java b/wsf/src/main/java/com/google/gson/wsf/server/rest/RepositoryInMemory.java index 9ccd7156..76914a97 100644 --- a/wsf/src/main/java/com/google/gson/wsf/server/rest/RepositoryInMemory.java +++ b/wsf/src/main/java/com/google/gson/wsf/server/rest/RepositoryInMemory.java @@ -18,6 +18,7 @@ package com.google.gson.wsf.server.rest; import java.util.Map; import com.google.gson.webservice.definition.rest.Id; +import com.google.gson.webservice.definition.rest.MetaData; import com.google.gson.webservice.definition.rest.RestResource; import com.google.inject.internal.Maps; import com.google.inject.internal.Preconditions; @@ -30,12 +31,14 @@ import com.google.inject.internal.Preconditions; * @param Type variable for the resource */ public class RepositoryInMemory> implements Repository { + private static final String METADATA_KEY_IS_FRESHLY_ASSIGNED_ID = "isFreshlyAssignedId"; + private final IdMap resources; - private final Map, MetaData> metaDataMap; + private final MetaDataMap metaDataMap; public RepositoryInMemory(Class classOfResource) { this.resources = IdMap.create(classOfResource); - this.metaDataMap = Maps.newHashMap(); + this.metaDataMap = new MetaDataMap(); } @Override @@ -48,7 +51,7 @@ public class RepositoryInMemory> implements Repository if (metaData == null) { return false; } - return metaData.isFreshlyAssignedId(); + return metaData.getBoolean(METADATA_KEY_IS_FRESHLY_ASSIGNED_ID); } @Override @@ -64,7 +67,7 @@ public class RepositoryInMemory> implements Repository } } resource = resources.put(resource); - metaDataMap.remove(resource.getId()); + metaDataMap.get(resource.getId()).remove(METADATA_KEY_IS_FRESHLY_ASSIGNED_ID); return resource; } @@ -88,7 +91,7 @@ public class RepositoryInMemory> implements Repository if (resource.getId() == null) { Id id = resources.getNextId(); resource.setId(id); - metaDataMap.put(id, new MetaData(true)); + metaDataMap.get(id).putBoolean(METADATA_KEY_IS_FRESHLY_ASSIGNED_ID, true); } return resource.getId(); }