Skip to content

Commit

Permalink
1. add support for polymorphic type handling
Browse files Browse the repository at this point in the history
2. 0-indexed paging which Spring Data use
  • Loading branch information
onlyabout committed Nov 14, 2012
1 parent a173352 commit 3318231
Show file tree
Hide file tree
Showing 11 changed files with 404 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,29 +170,27 @@ public <T> List<T> queryForList(Class<T> type, Method queryMethod, Object[] para
Resources<Resource<T>> res = (Resources<Resource<T>>) executeGet(href, Resources.class, type);
return resourcesToIterable(res);
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public <T> Page<T> queryForPageable(Class<T> type, Method queryMethod, Object[] parameters) {
refresh();

String href = urlBuilder.buildQueryUrl(type, queryMethod, parameters);

RestEntityInformation entityInfo = (RestEntityInformation) RestEntityInformationSupport.getMetadata(type);

PagedResources<Resource<T>> resources = (PagedResources<Resource<T>>) executeGet(href, PagedResources.class,
entityInfo.getJavaType());

Pageable pageable = null;
for (Object parameter : parameters) {
if (Pageable.class.isAssignableFrom(parameter.getClass()))
pageable = (Pageable) parameter;
}

if (pageable == null) {
Long number = resources.getMetadata().getNumber();
Long size = resources.getMetadata().getSize();
pageable = new PageRequest(number.intValue(), size.intValue());
pageable = getPageableFrom(resources);
}

return new PageImpl<T>(resourcesToIterable(resources), pageable, resources.getMetadata().getTotalElements());
Expand Down Expand Up @@ -260,9 +258,7 @@ public <T, ID extends Serializable> Page<T> getForPageable(RestEntityInformation
entityInfo.getJavaType());

if (pageable == null) {
Long number = resources.getMetadata().getNumber();
Long size = resources.getMetadata().getSize();
pageable = new PageRequest(number.intValue(), size.intValue());
pageable = getPageableFrom(resources);
}

return new PageImpl<T>(resourcesToIterable(resources), pageable, resources.getMetadata().getTotalElements());
Expand Down Expand Up @@ -318,11 +314,17 @@ public <T, ID extends Serializable> T getLazyLoadingObjectFrom(Resource<T> resou

return entity;
}

@SuppressWarnings("rawtypes")
private Pageable getPageableFrom(PagedResources resources) {
Long number = resources.getMetadata().getNumber() - 1;
Long size = resources.getMetadata().getSize();
return new PageRequest(number.intValue(), size.intValue());
}

public abstract ResourceSupport executeGet(String url, Type resourceType, Type objectType);

protected abstract <V> Resource<Set<Resource<V>>> executeGetForSet(String url, Type resourceType,
Type valueType);
protected abstract <V> Resource<Set<Resource<V>>> executeGetForSet(String url, Type resourceType, Type valueType);

protected abstract <K, V> Resource<Map<K, Resource<V>>> executeGetForMap(String url, Type resourceType,
Type keyType, Type valueType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@ private void addPageable(Map<String, String> pMap, Pageable pageable) {

addSort(pMap, pageable.getSort());

pMap.put("page", String.valueOf(pageable.getPageNumber()));
// 0-indexed pages expected. spring-data-rest uses 1-indexed. so +1 will be fine.
pMap.put("page", String.valueOf(pageable.getPageNumber() + 1));
pMap.put("limit", String.valueOf(pageable.getPageSize()));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package net.daum.clix.springframework.data.rest.client.json;

import java.lang.reflect.Modifier;

import net.daum.clix.springframework.data.rest.client.repository.RestRepositories;

import org.springframework.hateoas.Link;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

/**
* Used to prepare json before polymorphic deserialization process by adding
* '@class' property to json object.
*
* @author 84june
*
*/
public class JacksonPolymorphicDeserializationPreProcessor implements JsonPreProcessor {

private RestRepositories repositories;

public JacksonPolymorphicDeserializationPreProcessor(RestRepositories repositories) {
Assert.notNull(repositories);

this.repositories = repositories;
}

@Override
public boolean canProcess(Class<?> objectType) {
return Modifier.isAbstract(objectType.getModifiers());
}

@Override
public byte[] process(byte[] jsonBody, Class<?> resourceType, Class<?> objectType) {
JsonParser parser = new JsonParser();
JsonObject json = parser.parse(new String(jsonBody)).getAsJsonObject();
boolean hasMultipleObjects = json.has("content");

if (hasMultipleObjects) {
for (JsonElement element : json.get("content").getAsJsonArray()) {
addClassProperty(element.getAsJsonObject());
}
} else {
addClassProperty(json);
}

return json.toString().getBytes();
}

private void addClassProperty(JsonObject object) {
JsonArray links = object.get("links").getAsJsonArray();

for (JsonElement ele : links) {
JsonObject link = ele.getAsJsonObject();
if (Link.REL_SELF.equals(link.get("rel").getAsString())) {
String resourcePath = getResourcePathFromSelfHref(link.get("href").getAsString());
String className = repositories.findDomainClassNameFor(resourcePath);
object.addProperty("@class", className);
break;
}
}
}

private String getResourcePathFromSelfHref(String selfHref) {
String[] tkns = StringUtils.tokenizeToStringArray(selfHref, "/");
return tkns[tkns.length - 2];
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package net.daum.clix.springframework.data.rest.client.json;


/**
* Used to prepare json body before (de)serialization process.
*
* @author 84june
*
*/
public interface JsonPreProcessor {

boolean canProcess(Class<?> objectType);

byte[] process(byte[] jsonBody, Class<?> resourceType, Class<?> objectType);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.daum.clix.springframework.data.rest.client.resolver;

import java.lang.reflect.Modifier;
import java.lang.reflect.Type;

import net.daum.clix.springframework.data.rest.client.repository.RestRepositories;

import org.springframework.util.Assert;

/**
* Resolve concrete type from abstract class by looking up json body.
*
* @author 84june
*
*/
@Deprecated
public class ConcreateTypeResolver implements TypeResolver {

@SuppressWarnings("unused")
private RestRepositories repositories;

public ConcreateTypeResolver(RestRepositories repositories) {
Assert.notNull(repositories);

this.repositories = repositories;
}

@Override
public boolean canResolve(Type objectType) {
return Modifier.isAbstract(objectType.getClass().getModifiers());
}

@SuppressWarnings("unused")
@Override
public Type resolve(byte[] body, Type typeToResolve) {
String resourcePath = getPathFromSelfHref(body);

// RepositoryFactoryInformation<Object, Serializable> repoInfo = repositories.findRepositoryInformationFor(resourcePath);
// return repoInfo.getRepositoryInformation().getDomainType();
throw new IllegalAccessError("ConcreateTypeResolver#resolve has not implemented yet!");
}

private String getPathFromSelfHref(byte[] body) {
// TODO Auto-generated method stub
throw new IllegalAccessError("ConcreateTypeResolver#getPathFromSelfHref has not implemented yet!");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.daum.clix.springframework.data.rest.client.resolver;

import java.lang.reflect.Type;

/**
* Resolve the type of object from json resource body.
*
* @author 84june
*
*/
@Deprecated
public interface TypeResolver {

boolean canResolve(Type objectType);

Type resolve(byte[] body, Type typeToResolve);

}
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,12 @@ public void findAllPageable() {
Person person3 = personRepository.save(new Person("name3", null, null));
Person person4 = personRepository.save(new Person("name4", null, null));

Pageable pageable = new PageRequest(1, 2, new Sort(Direction.ASC, "name"));
Pageable pageable = new PageRequest(0, 2, new Sort(Direction.ASC, "name"));
Page<Person> result = personRepository.findAll(pageable);

assertNotNull(result);
assertEquals(2, result.getSize());
assertEquals(1, result.getNumber());
assertEquals(0, result.getNumber());
assertEquals(2, result.getNumberOfElements());
assertEquals(4, result.getTotalElements());

Expand All @@ -224,12 +224,12 @@ public void findAllPageable() {
assertEquals(this.person.getName(), listResult.get(0).getName());
assertEquals(person2.getName(), listResult.get(1).getName());

pageable = new PageRequest(2, 2, new Sort(Direction.ASC, "name"));
pageable = new PageRequest(1, 2, new Sort(Direction.ASC, "name"));
result = personRepository.findAll(pageable);

assertNotNull(result);
assertEquals(2, result.getSize());
assertEquals(2, result.getNumber());
assertEquals(1, result.getNumber());
assertEquals(2, result.getNumberOfElements());
assertEquals(4, result.getTotalElements());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package net.daum.clix.springframework.data.rest.client.json;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import net.daum.clix.springframework.data.rest.client.json.domain.Animal;
import net.daum.clix.springframework.data.rest.client.json.domain.Cat;
import net.daum.clix.springframework.data.rest.client.json.domain.Dog;
import net.daum.clix.springframework.data.rest.client.repository.RestRepositories;

import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.PagedResources;
import org.springframework.hateoas.PagedResources.PageMetadata;
import org.springframework.hateoas.Resource;

@RunWith(MockitoJUnitRunner.class)
public class JacksonPolymorphicDeserializationPreProcessorTest {

@Mock
RestRepositories repositories;

@InjectMocks
JacksonPolymorphicDeserializationPreProcessor preProcessor;

private PagedResources<Resource<Animal>> pagedResources;

private Resource<Animal> resource;

@SuppressWarnings("unchecked")
@Before
public void setUp() throws Exception {
Resource<Animal> dog = new Resource<Animal>(new Dog("nameofdog", 1.0), new Link(
"http://anydomainaddr.com/dog/1", "self"));
Resource<Animal> cat = new Resource<Animal>(new Cat("nameofcat", 3), new Link(
"http://anydomainaddr.com/cat/1", "self"));
Resource<Animal> dog2 = new Resource<Animal>(new Dog("nameofdog2", 10.2), new Link(
"http://anydomainaddr.com/dog/2", "self"));

Collection<Resource<Animal>> animals = Arrays.asList(dog, cat, dog2);
PageMetadata metadata = new PageMetadata(10, 1, 3, 1);
List<Link> asList = Arrays.asList(new Link("http://1.2.3.4/dog/2", "next"), new Link(
"http://1.2.3.4/dog/search", "search"));

pagedResources = new PagedResources<Resource<Animal>>(animals, metadata, asList);
resource = dog;

when(repositories.findDomainClassNameFor("dog")).thenReturn(Dog.class.getName());
when(repositories.findDomainClassNameFor("cat")).thenReturn(Cat.class.getName());
}

@Test
public void canProcessPolymorphicTypeOnly() {
assertTrue(preProcessor.canProcess(Animal.class));
assertFalse(preProcessor.canProcess(Dog.class));
assertFalse(preProcessor.canProcess(Cat.class));
}

@Test
public void processShouldAddClassPropertyProperly() throws JsonParseException, JsonMappingException, IOException {
byte[] processed = preProcessor.process(new ObjectMapper().writeValueAsBytes(resource), Resource.class, Animal.class);

ObjectMapper mapper = new ObjectMapper();
JavaType valueType = mapper.getTypeFactory().constructParametricType(Resource.class, Animal.class);
Resource<Animal> processedResource = mapper.readValue(processed, valueType);

assertEquals(resource, processedResource);
}

@Test
public void processShouldAddClassPropertyProperlyToResources() throws JsonParseException, JsonMappingException, IOException {
byte[] processed = preProcessor.process(new ObjectMapper().writeValueAsBytes(pagedResources), PagedResources.class, Animal.class);

ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory = mapper.getTypeFactory();
JavaType wrappedType = typeFactory.constructParametricType(Resource.class, Animal.class);
JavaType valueType = typeFactory.constructParametricType(PagedResources.class, wrappedType);
PagedResources<Resource<Animal>> processedPagedResources = mapper.readValue(processed, valueType);

assertEquals(pagedResources, processedPagedResources);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.daum.clix.springframework.data.rest.client.json.domain;

import org.codehaus.jackson.annotate.JsonTypeInfo;

@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
public abstract class Animal {

private String name;

public Animal() {
}

public Animal(String name) {
super();
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}
Loading

0 comments on commit 3318231

Please sign in to comment.