Initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
.DS_Store
|
||||
.gradle/
|
||||
build/
|
||||
*.i*
|
||||
76
build.gradle
Normal file
76
build.gradle
Normal file
@@ -0,0 +1,76 @@
|
||||
apply plugin: "base"
|
||||
|
||||
allprojects {
|
||||
apply plugin: "idea"
|
||||
apply plugin: "maven"
|
||||
|
||||
group = "org.springframework.data.rest"
|
||||
version = "$sdRestVersion"
|
||||
|
||||
releaseBuild = version.endsWith("RELEASE")
|
||||
snapshotBuild = version.endsWith("SNAPSHOT")
|
||||
|
||||
sourceCompatibility = 6
|
||||
targetCompatibility = 6
|
||||
|
||||
configurations.all {
|
||||
exclude group: "commons-logging"
|
||||
exclude module: "slf4j-log4j12"
|
||||
exclude module: "groovy-all", version: "1.8.0-beta-3-SNAPSHOT"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
||||
repositories {
|
||||
mavenCentral(artifactUrls: [
|
||||
"http://maven.springframework.org/release",
|
||||
"http://maven.springframework.org/milestone",
|
||||
//"http://repository.jboss.org/maven2/",
|
||||
//"http://download.java.net/maven/2/"
|
||||
])
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
apply plugin: "java"
|
||||
apply plugin: "groovy"
|
||||
|
||||
[compileJava, compileTestJava]*.options*.compilerArgs = ["-Xlint:-serial", "-Xlint:-unchecked"]
|
||||
|
||||
dependencies {
|
||||
groovy "org.codehaus.groovy:groovy:$groovyVersion"
|
||||
|
||||
// Logging
|
||||
compile "org.slf4j:slf4j-api:$slf4jVersion"
|
||||
runtime "org.slf4j:jcl-over-slf4j:$slf4jVersion"
|
||||
runtime "ch.qos.logback:logback-classic:$logbackVersion"
|
||||
|
||||
// Jackson JSON
|
||||
compile "org.codehaus.jackson:jackson-mapper-asl:$jacksonVersion"
|
||||
|
||||
// Spring
|
||||
compile "org.springframework:spring-beans:$springVersion"
|
||||
compile "org.springframework:spring-context:$springVersion"
|
||||
compile "org.springframework:spring-core:$springVersion"
|
||||
compile "org.springframework:spring-web:$springVersion"
|
||||
|
||||
// Testing
|
||||
testCompile "org.spockframework:spock-core:$spockVersion"
|
||||
testCompile "org.spockframework:spock-spring:$spockVersion"
|
||||
testCompile "org.hamcrest:hamcrest-all:1.1"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testRuntime "org.springframework:spring-context-support:$springVersion"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) { gradleVersion = "1.0-milestone-8" }
|
||||
|
||||
idea {
|
||||
project.ipr.withXml { provider ->
|
||||
provider.node.component.find { it.@name == 'VcsDirectoryMappings' }.mapping.@vcs = 'Git'
|
||||
}
|
||||
module.jdkName = "OpenJDK 1.7"
|
||||
}
|
||||
6
core/build.gradle
Normal file
6
core/build.gradle
Normal file
@@ -0,0 +1,6 @@
|
||||
dependencies {
|
||||
|
||||
// Google Guava
|
||||
compile "com.google.guava:guava:11.0.1"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface Handler<T,V> {
|
||||
V handle(T t);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface Link {
|
||||
|
||||
String rel();
|
||||
|
||||
URI href();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class SimpleLink implements Link {
|
||||
|
||||
private String rel;
|
||||
private URI href;
|
||||
|
||||
public SimpleLink(String rel, URI href) {
|
||||
this.rel = rel;
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
@Override public String rel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
@Override public URI href() {
|
||||
return href;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "SimpleLink{" +
|
||||
"rel='" + rel + '\'' +
|
||||
", href=" + href +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import com.google.common.util.concurrent.UncheckedExecutionException;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class BeanUtils {
|
||||
|
||||
private BeanUtils() {
|
||||
}
|
||||
|
||||
public static ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
|
||||
|
||||
private static final LoadingCache<Object[], Field> fields = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Object[], Field>() {
|
||||
@Override public Field load(Object[] key) throws Exception {
|
||||
Class<?> clazz = (Class<?>) key[0];
|
||||
String name = (String) key[1];
|
||||
Field f = ReflectionUtils.findField(clazz, name);
|
||||
if (null != f) {
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
return f;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Field " + clazz.getName() + "." + name + " not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
private static final LoadingCache<Object[], Method> methods = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Object[], Method>() {
|
||||
@Override public Method load(Object[] key) throws Exception {
|
||||
Class<?> clazz = (Class<?>) key[0];
|
||||
String name = (String) key[1];
|
||||
Integer paramCnt = key.length == 3 ? (Integer) key[2] : 0;
|
||||
|
||||
for (Method m : clazz.getDeclaredMethods()) {
|
||||
if (m.getName().equals(name)) {
|
||||
if (m.getParameterTypes().length == paramCnt) {
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
return m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Method " + clazz.getName() + "." + name + " not found");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public static boolean hasProperty(String property, Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
if (obj instanceof Map) {
|
||||
return ((Map) obj).containsKey(property);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
try {
|
||||
if (FluentBeanUtils.isFluentBean(type)) {
|
||||
return null != methods.get(new Object[]{type, property});
|
||||
} else {
|
||||
if (null == methods.get(new Object[]{type, "get" + StringUtils.capitalize(property)})) {
|
||||
return null != fields.get(new Object[]{type, property});
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (UncheckedExecutionException e) {
|
||||
if (e.getCause().getClass() == IllegalArgumentException.class) {
|
||||
return false;
|
||||
} else {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T findFirst(Class<T> clazz, List<?> stack) {
|
||||
for (Object o : stack) {
|
||||
if (ClassUtils.isAssignable(clazz, o.getClass())) {
|
||||
return (T) o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static Object findFirst(Object o, Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
if (o == obj || null != o && o.equals(obj)) {
|
||||
return obj;
|
||||
} else if (obj instanceof List) {
|
||||
return Collections.binarySearch((List) obj, o);
|
||||
} else if (obj instanceof Object[]) {
|
||||
return Arrays.binarySearch((Object[]) obj, o);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Object findFirst(String property, Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
if (obj instanceof Map) {
|
||||
return ((Map) obj).get(property);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
try {
|
||||
Field f = fields.get(new Object[]{type, property});
|
||||
if (FluentBeanUtils.isFluentBean(type)) {
|
||||
return FluentBeanUtils.get(property, obj);
|
||||
} else {
|
||||
Method getter = methods.get(new Object[]{type, "get" + StringUtils.capitalize(property)});
|
||||
try {
|
||||
if (null != getter) {
|
||||
return getter.invoke(obj);
|
||||
} else {
|
||||
return f.get(obj);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean containsType(Class<?> type, List<Object> objs) {
|
||||
return containsType(type, objs.toArray());
|
||||
}
|
||||
|
||||
public static boolean containsType(Class<?> type, Object[] objs) {
|
||||
for (Object obj : objs) {
|
||||
if (null != obj && ClassUtils.isAssignable(obj.getClass(), type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static Object invoke(String methodName, Object target, Object... args) {
|
||||
return invoke(methodName, target, Object.class, args);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T invoke(String methodName, Object target, Class<T> returnType, Object... args) {
|
||||
if (null == target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = target.getClass();
|
||||
try {
|
||||
Method m = methods.get(new Object[]{type, methodName, args.length});
|
||||
List<Object> newArgs = new ArrayList<Object>(args.length);
|
||||
Class<?>[] paramTypes = m.getParameterTypes();
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
Object o = args[i];
|
||||
Class<?> oType = o.getClass();
|
||||
Class<?> pType = paramTypes[i];
|
||||
if (!ClassUtils.isAssignable(oType, pType)) {
|
||||
newArgs.add(CONVERSION_SERVICE.convert(o, pType));
|
||||
} else {
|
||||
newArgs.add(o);
|
||||
}
|
||||
}
|
||||
|
||||
Object rtnVal = m.invoke(target, newArgs.toArray());
|
||||
if ((returnType != Void.TYPE || returnType != Object.class)
|
||||
&& null != rtnVal
|
||||
&& !ClassUtils.isAssignable(returnType, rtnVal.getClass())) {
|
||||
return CONVERSION_SERVICE.convert(rtnVal, returnType);
|
||||
} else {
|
||||
return (T) rtnVal;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.codehaus.jackson.JsonParser;
|
||||
import org.codehaus.jackson.JsonProcessingException;
|
||||
import org.codehaus.jackson.JsonToken;
|
||||
import org.codehaus.jackson.map.DeserializationContext;
|
||||
import org.codehaus.jackson.map.deser.std.StdDeserializer;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class FluentBeanDeserializer extends StdDeserializer {
|
||||
|
||||
private ConversionService conversionService;
|
||||
private FluentBeanUtils.Metadata beanMeta;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public FluentBeanDeserializer(final Class<?> valueClass, ConversionService conversionService) {
|
||||
super(valueClass);
|
||||
this.conversionService = conversionService;
|
||||
this.beanMeta = FluentBeanUtils.metadata(valueClass);
|
||||
|
||||
if (!FluentBeanUtils.isFluentBean(valueClass)) {
|
||||
throw new IllegalArgumentException("Class of type " + valueClass + " is not a FluentBean");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(JsonParser jp,
|
||||
DeserializationContext ctxt)
|
||||
throws IOException,
|
||||
JsonProcessingException {
|
||||
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
|
||||
throw ctxt.mappingException(_valueClass);
|
||||
}
|
||||
|
||||
Object bean;
|
||||
try {
|
||||
bean = _valueClass.newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
while (jp.nextToken() != JsonToken.END_OBJECT) {
|
||||
String name = jp.getCurrentName();
|
||||
Method setter = beanMeta.setters().get(name);
|
||||
|
||||
Object obj;
|
||||
if (null != setter) {
|
||||
Class<?> targetType = setter.getParameterTypes()[0];
|
||||
if (ClassUtils.isAssignable(targetType, Long.class)) {
|
||||
obj = jp.nextLongValue(-1);
|
||||
} else if (ClassUtils.isAssignable(targetType, Integer.class)) {
|
||||
obj = jp.nextIntValue(-1);
|
||||
} else if (ClassUtils.isAssignable(targetType, Boolean.class)) {
|
||||
obj = jp.nextBooleanValue();
|
||||
} else {
|
||||
obj = jp.nextTextValue();
|
||||
}
|
||||
|
||||
if (null != obj) {
|
||||
if (!ClassUtils.isAssignable(obj.getClass(), targetType)) {
|
||||
obj = conversionService.convert(obj, targetType);
|
||||
}
|
||||
|
||||
try {
|
||||
setter.invoke(bean, obj);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.codehaus.jackson.JsonGenerationException;
|
||||
import org.codehaus.jackson.JsonGenerator;
|
||||
import org.codehaus.jackson.map.SerializerProvider;
|
||||
import org.codehaus.jackson.map.ser.std.SerializerBase;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class FluentBeanSerializer extends SerializerBase {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public FluentBeanSerializer(final Class<?> t) {
|
||||
super(t);
|
||||
|
||||
if (!FluentBeanUtils.isFluentBean(t)) {
|
||||
throw new IllegalArgumentException("Class of type " + t + " is not a FluentBean");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
public void serialize(final Object value,
|
||||
final JsonGenerator jgen,
|
||||
final SerializerProvider provider)
|
||||
throws IOException,
|
||||
JsonGenerationException {
|
||||
if (null == value) {
|
||||
provider.defaultSerializeNull(jgen);
|
||||
} else {
|
||||
Class<?> type = value.getClass();
|
||||
if (ClassUtils.isAssignable(type, Collection.class)) {
|
||||
jgen.writeStartArray();
|
||||
for (Object o : (Collection) value) {
|
||||
write(o, jgen, provider);
|
||||
}
|
||||
jgen.writeEndArray();
|
||||
} else if (ClassUtils.isAssignable(type, Map.class)) {
|
||||
jgen.writeStartObject();
|
||||
for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) {
|
||||
jgen.writeFieldName(entry.getKey());
|
||||
write(entry.getValue(), jgen, provider);
|
||||
}
|
||||
jgen.writeEndObject();
|
||||
} else {
|
||||
write(value, jgen, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write(final Object value,
|
||||
final JsonGenerator jgen,
|
||||
final SerializerProvider provider) throws IOException {
|
||||
Class<?> type = value.getClass();
|
||||
if (ClassUtils.isAssignable(type, _handledType)) {
|
||||
jgen.writeStartObject();
|
||||
for (String fname : FluentBeanUtils.metadata(type).fieldNames()) {
|
||||
jgen.writeFieldName(fname);
|
||||
write(FluentBeanUtils.get(fname, value), jgen, provider);
|
||||
}
|
||||
jgen.writeEndObject();
|
||||
} else {
|
||||
jgen.writeObject(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class FluentBeanUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FluentBeanUtils.class);
|
||||
private static final LoadingCache<Class<?>, Metadata> metadata = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Class<?>, Metadata>() {
|
||||
@Override public Metadata load(Class<?> type) throws Exception {
|
||||
final Metadata meta = new Metadata();
|
||||
ReflectionUtils.doWithFields(
|
||||
type,
|
||||
new ReflectionUtils.FieldCallback() {
|
||||
@Override public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
|
||||
final String fname = field.getName();
|
||||
if (!fname.startsWith("_")) {
|
||||
ReflectionUtils.doWithMethods(field.getDeclaringClass(), new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
if (method.getName().equals(fname)) {
|
||||
if (method.getParameterTypes().length == 0) {
|
||||
meta.getters.put(fname, method);
|
||||
} else if (method.getParameterTypes().length == 1) {
|
||||
meta.setters.put(fname, method);
|
||||
}
|
||||
meta.fieldNames.add(fname);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public static Metadata metadata(Class<?> targetType) {
|
||||
try {
|
||||
return metadata.get(targetType);
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object set(String property, Object value, Object bean) {
|
||||
if (null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method setter = metadata.get(type).setters.get(property);
|
||||
if (null != setter) {
|
||||
return setter.invoke(bean, value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(t.getMessage(), t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object get(String property, Object bean) {
|
||||
if (null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method getter = metadata.get(type).getters.get(property);
|
||||
if (null != getter) {
|
||||
return getter.invoke(bean);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(t.getMessage(), t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isFluentBean(Class<?> type) {
|
||||
try {
|
||||
return metadata.get(type).getters.size() > 0;
|
||||
} catch (ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Metadata {
|
||||
List<String> fieldNames = new ArrayList<String>();
|
||||
Map<String, Method> getters = new HashMap<String, Method>();
|
||||
Map<String, Method> setters = new HashMap<String, Method>();
|
||||
|
||||
public List<String> fieldNames() {
|
||||
return fieldNames;
|
||||
}
|
||||
|
||||
public Map<String, Method> getters() {
|
||||
return getters;
|
||||
}
|
||||
|
||||
public Map<String, Method> setters() {
|
||||
return setters;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public abstract class UriUtils {
|
||||
|
||||
private UriUtils() {
|
||||
}
|
||||
|
||||
public static boolean validBaseUri(URI baseUri, URI uri) {
|
||||
String path = UriUtils.path(baseUri.relativize(uri));
|
||||
return !StringUtils.hasText(path) || path.charAt(0) != '/';
|
||||
}
|
||||
|
||||
public static <V> V foreach(URI baseUri, URI uri, Handler<URI, V> handler) {
|
||||
List<URI> uris = explode(baseUri, uri);
|
||||
V v = null;
|
||||
for (URI u : uris) {
|
||||
v = handler.handle(u);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public static Stack<URI> explode(URI baseUri, URI uri) {
|
||||
Stack<URI> uris = new Stack<URI>();
|
||||
if (StringUtils.hasText(uri.getPath())) {
|
||||
URI relativeUri = baseUri.relativize(uri);
|
||||
if (StringUtils.hasText(relativeUri.getPath())) {
|
||||
for (String part : relativeUri.getPath().split("/")) {
|
||||
uris.add(URI.create(part + (StringUtils.hasText(uri.getQuery()) ? "?" + uri.getQuery() : "")));
|
||||
}
|
||||
}
|
||||
}
|
||||
return uris;
|
||||
}
|
||||
|
||||
public static URI merge(URI baseUri, URI... uris) {
|
||||
StringBuilder query = new StringBuilder();
|
||||
|
||||
UriComponentsBuilder ub = UriComponentsBuilder.fromUri(baseUri);
|
||||
for (URI uri : uris) {
|
||||
String s = uri.getScheme();
|
||||
if (null != s) {
|
||||
ub.scheme(s);
|
||||
}
|
||||
|
||||
s = uri.getUserInfo();
|
||||
if (null != s) {
|
||||
ub.userInfo(s);
|
||||
}
|
||||
|
||||
s = uri.getHost();
|
||||
if (null != s) {
|
||||
ub.host(s);
|
||||
}
|
||||
|
||||
int i = uri.getPort();
|
||||
if (i > 0) {
|
||||
ub.port(i);
|
||||
}
|
||||
|
||||
s = uri.getPath();
|
||||
if (null != s) {
|
||||
if (!uri.isAbsolute() && StringUtils.hasText(s)) {
|
||||
ub.pathSegment(s);
|
||||
} else {
|
||||
ub.path(s);
|
||||
}
|
||||
}
|
||||
|
||||
s = uri.getQuery();
|
||||
if (null != s) {
|
||||
if (query.length() > 0) {
|
||||
query.append("&");
|
||||
}
|
||||
query.append(s);
|
||||
}
|
||||
|
||||
s = uri.getFragment();
|
||||
if (null != s) {
|
||||
ub.fragment(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.length() > 0) {
|
||||
ub.query(query.toString());
|
||||
}
|
||||
|
||||
return ub.build().toUri();
|
||||
}
|
||||
|
||||
public static String path(URI uri) {
|
||||
if (null == uri) {
|
||||
return null;
|
||||
}
|
||||
String s = uri.getPath();
|
||||
if (s.endsWith("/")) {
|
||||
return s.substring(0, s.length() - 1);
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
public static URI tail(URI baseUri, URI uri) {
|
||||
Stack<URI> uris = explode(baseUri, uri);
|
||||
return uris.size() > 0 ? uris.get(Math.max(uris.size() - 1, 0)) : null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.springframework.data.rest.core
|
||||
|
||||
import org.springframework.data.rest.core.util.UriUtils
|
||||
import spock.lang.Specification
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
class UriUtilsSpec extends Specification {
|
||||
|
||||
def "merges URIs correctly"() {
|
||||
|
||||
given:
|
||||
// (absolute) URI of the base resource
|
||||
def baseUri = new URI("http://localhost:8080/baseUrl")
|
||||
// (relative) URI of the top-level Resource
|
||||
def uri2 = new URI("resource")
|
||||
// (relative) URI of the second-level Resource
|
||||
def uri3 = new URI("1")
|
||||
// (fragment) URI of the bottom-level Resource
|
||||
def uri4 = new URI("count")
|
||||
|
||||
when:
|
||||
def uri5 = UriUtils.merge(baseUri, uri2, uri3, uri4)
|
||||
|
||||
then:
|
||||
uri5.toString() == "http://localhost:8080/baseUrl/resource/1/count"
|
||||
|
||||
}
|
||||
|
||||
def "explodes URIs correctly"() {
|
||||
|
||||
given:
|
||||
// (absolute) URI of the base resource
|
||||
def baseUri = new URI("http://localhost:8080/baseUrl")
|
||||
// (absolute) URI of the full resource to get a path to
|
||||
def resourceUri = new URI("http://localhost:8080/baseUrl/resource/1/property")
|
||||
|
||||
when:
|
||||
def uris = UriUtils.explode(baseUri, resourceUri)
|
||||
|
||||
then:
|
||||
uris.size() == 3
|
||||
uris[2].path == "property"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
18
core/src/test/resources/logback.xml
Normal file
18
core/src/test/resources/logback.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>
|
||||
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data.services" level="DEBUG"/>
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
25
gradle.properties
Normal file
25
gradle.properties
Normal file
@@ -0,0 +1,25 @@
|
||||
# Logging
|
||||
slf4jVersion = 1.6.4
|
||||
logbackVersion = 1.0.0
|
||||
|
||||
# Libraries
|
||||
springVersion = 3.1.1.RELEASE
|
||||
cglibVersion = 2.2
|
||||
|
||||
# Languages
|
||||
groovyVersion = 1.8.6
|
||||
|
||||
# Supporting libraries
|
||||
sdCommonsVersion = 1.2.0.RELEASE
|
||||
sdJpaVersion = 1.0.1.RELEASE
|
||||
jacksonVersion = 1.9.2
|
||||
hibernateVersion = 3.5.6-Final
|
||||
|
||||
# Testing
|
||||
spockVersion = 0.5-groovy-1.8
|
||||
|
||||
## OSGi ranges
|
||||
spring.range = "[3.1.1, 4.0.0)"
|
||||
jackson.range = "[1.9, 2.0.0)"
|
||||
|
||||
sdRestVersion = 1.0.0.BUILD-SNAPSHOT
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Thu Mar 08 09:45:33 CST 2012
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=http\://repo.gradle.org/gradle/distributions/gradle-1.0-milestone-8-bin.zip
|
||||
164
gradlew
vendored
Executable file
164
gradlew
vendored
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/bin/bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/"
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED"
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query businessSystem maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
JAVA_OPTS="$JAVA_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
gradlew.bat
vendored
Normal file
90
gradlew.bat
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
22
repository/build.gradle
Normal file
22
repository/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
dependencies {
|
||||
|
||||
// Spring
|
||||
compile "org.springframework:spring-orm:$springVersion"
|
||||
compile "org.springframework:spring-oxm:$springVersion"
|
||||
compile "org.springframework:spring-tx:$springVersion"
|
||||
|
||||
// JPA
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final"
|
||||
|
||||
// Spring Data
|
||||
compile "org.springframework.data:spring-data-commons-core:$sdCommonsVersion"
|
||||
compile "org.springframework.data:spring-data-jpa:$sdJpaVersion"
|
||||
|
||||
// Exporter core
|
||||
compile project(":core")
|
||||
|
||||
// Testing
|
||||
testCompile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
testCompile "org.hsqldb:hsqldb:1.8.0.10"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaEntityMetadata {
|
||||
|
||||
private Class<?> targetType;
|
||||
private Map<String, Attribute> embeddedAttributes = new HashMap<String, Attribute>();
|
||||
private Map<String, Field> fields = new HashMap<String, Field>();
|
||||
private Map<String, Attribute> linkedAttributes = new HashMap<String, Attribute>();
|
||||
private final Attribute idAttribute;
|
||||
private final Attribute versionAttribute;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaEntityMetadata(EntityType entityType, JpaRepositoryMetadata repositoryMetadata) {
|
||||
targetType = entityType.getJavaType();
|
||||
|
||||
Attribute idAttribute = entityType.getId(entityType.getIdType().getJavaType());
|
||||
Attribute versionAttribute = entityType.getVersion(Long.class);
|
||||
for (Attribute attr : (Set<Attribute>) entityType.getAttributes()) {
|
||||
String name = attr.getName();
|
||||
Field f = ReflectionUtils.findField(targetType, attr.getName());
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
fields.put(name, f);
|
||||
|
||||
if (attr instanceof SingularAttribute) {
|
||||
SingularAttribute sattr = (SingularAttribute) attr;
|
||||
if (null != repositoryMetadata.repositoryFor(attr.getJavaType())) {
|
||||
linkedAttributes.put(name, attr);
|
||||
} else if (!sattr.isId() && !sattr.isVersion()) {
|
||||
embeddedAttributes.put(name, attr);
|
||||
}
|
||||
} else if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute pattr = (PluralAttribute) attr;
|
||||
if (pattr.getElementType() instanceof EntityType
|
||||
&& null != repositoryMetadata.repositoryFor(pattr.getElementType().getJavaType())) {
|
||||
linkedAttributes.put(name, attr);
|
||||
} else {
|
||||
embeddedAttributes.put(name, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.idAttribute = idAttribute;
|
||||
this.versionAttribute = versionAttribute;
|
||||
}
|
||||
|
||||
public Class<?> targetType() {
|
||||
return targetType;
|
||||
}
|
||||
|
||||
public Map<String, Attribute> embeddedAttributes() {
|
||||
return Collections.unmodifiableMap(embeddedAttributes);
|
||||
}
|
||||
|
||||
public Map<String, Attribute> linkedAttributes() {
|
||||
return Collections.unmodifiableMap(linkedAttributes);
|
||||
}
|
||||
|
||||
public Attribute idAttribute() {
|
||||
return idAttribute;
|
||||
}
|
||||
|
||||
public Attribute versionAttribute() {
|
||||
return versionAttribute;
|
||||
}
|
||||
|
||||
public void id(Serializable id, Object target) {
|
||||
set(idAttribute.getName(), id, target);
|
||||
}
|
||||
|
||||
public Object id(Object target) {
|
||||
return get(idAttribute.getName(), target);
|
||||
}
|
||||
|
||||
public Object version(Object target) {
|
||||
return (null != versionAttribute ? get(versionAttribute.getName(), target) : null);
|
||||
}
|
||||
|
||||
public <V> V doWithEmbedded(Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
for (Attribute attr : embeddedAttributes.values()) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public <V> V doWithLinked(String name, Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
Attribute attr = linkedAttributes.get(name);
|
||||
if (null != attr) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public <V> V doWithLinked(Handler<Attribute, V> handler) {
|
||||
if (null == handler) {
|
||||
return null;
|
||||
}
|
||||
V v = null;
|
||||
for (Attribute attr : linkedAttributes.values()) {
|
||||
v = handler.handle(attr);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
public Object get(String name, Object target) {
|
||||
try {
|
||||
return fields.get(name).get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void set(String name, Object arg, Object target) {
|
||||
try {
|
||||
fields.get(name).set(target, arg);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.Metamodel;
|
||||
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.aop.target.SingletonTargetSource;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata implements InitializingBean, ApplicationContextAware {
|
||||
|
||||
private ApplicationContext applicationContext;
|
||||
private Map<Class<?>, RepositoryCacheEntry> repositories = new HashMap<Class<?>, RepositoryCacheEntry>();
|
||||
private EntityManager entityManager;
|
||||
private Metamodel metamodel;
|
||||
|
||||
@PersistenceContext
|
||||
public void setEntityManager(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
this.metamodel = entityManager.getMetamodel();
|
||||
}
|
||||
|
||||
public CrudRepository repositoryFor(String name) {
|
||||
if (null != name) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (name.equals(repositoryNameFor(entry.getValue().repository))) {
|
||||
return entry.getValue().repository;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public CrudRepository repositoryFor(Class<?> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.repository;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public EntityInformation entityInfoFor(Class<?> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.entityInfo;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> EntityType<T> entityTypeFor(Class<T> domainClass) {
|
||||
return metamodel.entity(domainClass);
|
||||
}
|
||||
|
||||
public EntityInformation entityInfoFor(CrudRepository repository) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (entry.getValue().repository == repository) {
|
||||
return entry.getValue().entityInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T> JpaEntityMetadata entityMetadataFor(Class<T> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null == entry.entityMetadata) {
|
||||
entry.entityMetadata = new JpaEntityMetadata(metamodel.entity(domainClass), this);
|
||||
}
|
||||
return entry.entityMetadata;
|
||||
}
|
||||
|
||||
public String repositoryNameFor(Class<?> domainClass) {
|
||||
RepositoryCacheEntry entry = repositories.get(domainClass);
|
||||
if (null != entry) {
|
||||
return entry.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String repositoryNameFor(CrudRepository repository) {
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
if (entry.getValue().repository == repository) {
|
||||
return entry.getValue().name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public List<String> repositoryNames() {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (Map.Entry<Class<?>, RepositoryCacheEntry> entry : repositories.entrySet()) {
|
||||
names.add(entry.getValue().name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
public void setRepositories(Collection<CrudRepository> repositories) {
|
||||
for (CrudRepository repository : repositories) {
|
||||
Class<?> repoClass = AopUtils.getTargetClass(repository);
|
||||
Field infoField = ReflectionUtils.findField(repoClass, "entityInformation");
|
||||
ReflectionUtils.makeAccessible(infoField);
|
||||
Method m = ReflectionUtils.findMethod(repository.getClass(), "getTargetSource");
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
try {
|
||||
SingletonTargetSource targetRepo = (SingletonTargetSource) m.invoke(repository);
|
||||
EntityInformation entityInfo = (EntityInformation) infoField.get(targetRepo.getTarget());
|
||||
Class<?>[] intfs = repository.getClass().getInterfaces();
|
||||
String name = StringUtils.uncapitalize(intfs[0].getSimpleName().replaceAll("Repository", ""));
|
||||
this.repositories.put(entityInfo.getJavaType(), new RepositoryCacheEntry(name, repository, entityInfo, null));
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalStateException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (this.repositories.isEmpty()) {
|
||||
ApplicationContext appCtx = applicationContext;
|
||||
while (null != appCtx) {
|
||||
Map<String, CrudRepository> beans = appCtx.getBeansOfType(CrudRepository.class);
|
||||
setRepositories(beans.values());
|
||||
appCtx = appCtx.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RepositoryCacheEntry {
|
||||
String name;
|
||||
CrudRepository repository;
|
||||
EntityInformation entityInfo;
|
||||
JpaEntityMetadata entityMetadata;
|
||||
|
||||
private RepositoryCacheEntry(String name,
|
||||
CrudRepository repository,
|
||||
EntityInformation entityInfo,
|
||||
JpaEntityMetadata entityMetadata) {
|
||||
this.name = name;
|
||||
this.repository = repository;
|
||||
this.entityInfo = entityInfo;
|
||||
this.entityMetadata = entityMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
22
rest/build.gradle
Normal file
22
rest/build.gradle
Normal file
@@ -0,0 +1,22 @@
|
||||
apply plugin: "war"
|
||||
apply plugin: "jetty"
|
||||
|
||||
dependencies {
|
||||
|
||||
// APIS
|
||||
compile "javax.servlet:servlet-api:2.5"
|
||||
|
||||
// JPA
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final"
|
||||
compile "org.hibernate:hibernate-entitymanager:$hibernateVersion"
|
||||
|
||||
// H2
|
||||
compile "org.hsqldb:hsqldb:1.8.0.10"
|
||||
|
||||
// Spring
|
||||
compile "org.springframework:spring-webmvc:$springVersion"
|
||||
|
||||
// Repository Exporter support
|
||||
compile project(":repository")
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.ser.CustomSerializerFactory;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.FluentBeanSerializer;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public class JsonView extends AbstractView {
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
{
|
||||
CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory();
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink.class, new FluentBeanSerializer(SimpleLink.class));
|
||||
mapper.setSerializerFactory(customSerializerFactory);
|
||||
}
|
||||
|
||||
public JsonView(String mediaType) {
|
||||
setContentType(mediaType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
HttpStatus status = status(model);
|
||||
response.setStatus(status.value());
|
||||
|
||||
String contentType = getContentType();
|
||||
HttpHeaders headers = headers(model);
|
||||
if (null != headers) {
|
||||
for (Map.Entry<String, String> entry : headers.toSingleValueMap().entrySet()) {
|
||||
response.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (null != headers.getContentType()) {
|
||||
contentType = headers.getContentType().toString();
|
||||
}
|
||||
}
|
||||
response.setContentType(contentType);
|
||||
|
||||
Object resource = model.get("resource");
|
||||
if (null != resource) {
|
||||
if (resource instanceof Throwable) {
|
||||
resource = ((Throwable) resource).getMessage();
|
||||
}
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
mapper.writerWithDefaultPrettyPrinter().writeValue(bout, resource);
|
||||
|
||||
response.getOutputStream().write(bout.toByteArray());
|
||||
} else {
|
||||
response.setContentLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private HttpStatus status(Map<String, Object> model) {
|
||||
Object o = model.get("status");
|
||||
if (null != o && o instanceof HttpStatus) {
|
||||
return (HttpStatus) o;
|
||||
}
|
||||
throw new IllegalArgumentException("No status is set in the model.");
|
||||
}
|
||||
|
||||
private HttpHeaders headers(Map<String, Object> model) {
|
||||
Object o = model.get("headers");
|
||||
if (null != o && o instanceof HttpHeaders) {
|
||||
return (HttpHeaders) o;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportResource;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Configuration
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*-export.xml")
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
@Autowired(required = false)
|
||||
URI baseUri;
|
||||
@Autowired(required = false)
|
||||
JpaRepositoryMetadata jpaRepositoryMetadata;
|
||||
@Autowired(required = false)
|
||||
List<HttpMessageConverter<?>> httpMessageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
@Bean URI baseUri() {
|
||||
if (null == baseUri) {
|
||||
baseUri = URI.create("");
|
||||
}
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
@Bean List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
if (httpMessageConverters.isEmpty()) {
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
json.setSupportedMediaTypes(
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json"))
|
||||
);
|
||||
httpMessageConverters.add(json);
|
||||
}
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
@Bean JpaRepositoryMetadata jpaRepositoryMetadata() throws Exception {
|
||||
if (null == jpaRepositoryMetadata) {
|
||||
jpaRepositoryMetadata = new JpaRepositoryMetadata();
|
||||
}
|
||||
return jpaRepositoryMetadata;
|
||||
}
|
||||
|
||||
@Bean PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() {
|
||||
return new PersistenceAnnotationBeanPostProcessor();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,908 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Serializable;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import javax.persistence.metamodel.Attribute;
|
||||
import javax.persistence.metamodel.EntityType;
|
||||
import javax.persistence.metamodel.PluralAttribute;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.rest.core.Handler;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.core.util.UriUtils;
|
||||
import org.springframework.data.rest.repository.JpaEntityMetadata;
|
||||
import org.springframework.data.rest.repository.JpaRepositoryMetadata;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Controller
|
||||
public class RepositoryRestController implements InitializingBean {
|
||||
|
||||
public static final String STATUS = "status";
|
||||
public static final String HEADERS = "headers";
|
||||
public static final String RESOURCE = "resource";
|
||||
public static final String SELF = "self";
|
||||
public static final String LINKS = "_links";
|
||||
|
||||
public static final int HAS_RESOURCE = 1;
|
||||
public static final int HAS_RESOURCE_ID = 2;
|
||||
public static final int HAS_SECOND_LEVEL_RESOURCE = 3;
|
||||
public static final int HAS_SECOND_LEVEL_ID = 4;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(RepositoryRestController.class);
|
||||
|
||||
private URI baseUri = URI.create("http://localhost:8080");
|
||||
private MediaType uriListMediaType = MediaType.parseMediaType("text/uri-list");
|
||||
private MediaType jsonMediaType = MediaType.parseMediaType("application/x-spring-data+json");
|
||||
private JpaRepositoryMetadata repositoryMetadata;
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private List<HttpMessageConverter<?>> httpMessageConverters;
|
||||
|
||||
public URI getBaseUri() {
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
public void setBaseUri(URI baseUri) {
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
public URI baseUri() {
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
public RepositoryRestController baseUri(URI baseUri) {
|
||||
this.baseUri = baseUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata getRepositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public void setRepositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
}
|
||||
|
||||
public JpaRepositoryMetadata repositoryMetadata() {
|
||||
return repositoryMetadata;
|
||||
}
|
||||
|
||||
public RepositoryRestController repositoryMetadata(JpaRepositoryMetadata repositoryMetadata) {
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConversionService getConversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
public void setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
public ConversionService conversionService() {
|
||||
return conversionService;
|
||||
}
|
||||
|
||||
public RepositoryRestController conversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> getHttpMessageConverters() {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
public void setHttpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
}
|
||||
|
||||
public List<HttpMessageConverter<?>> httpMessageConverters() {
|
||||
return httpMessageConverters;
|
||||
}
|
||||
|
||||
public RepositoryRestController httpMessageConverters(List<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
this.httpMessageConverters = httpMessageConverters;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getUriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
|
||||
public void setUriListMediaType(MediaType uriListMediaType) {
|
||||
this.uriListMediaType = uriListMediaType;
|
||||
}
|
||||
|
||||
public void setUriListMediaType(String uriListMediaType) {
|
||||
this.uriListMediaType = MediaType.valueOf(uriListMediaType);
|
||||
}
|
||||
|
||||
public MediaType uriListMediaType() {
|
||||
return uriListMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestController uriListMediaType(MediaType uriListMediaType) {
|
||||
setUriListMediaType(uriListMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryRestController uriListMediaType(String uriListMediaType) {
|
||||
setUriListMediaType(uriListMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getJsonMediaType() {
|
||||
return jsonMediaType;
|
||||
}
|
||||
|
||||
public void setJsonMediaType(MediaType jsonMediaType) {
|
||||
this.jsonMediaType = jsonMediaType;
|
||||
}
|
||||
|
||||
public void setJsonMediaType(String jsonMediaType) {
|
||||
this.jsonMediaType = MediaType.valueOf(jsonMediaType);
|
||||
}
|
||||
|
||||
public MediaType jsonMediaType() {
|
||||
return jsonMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestController jsonMediaType(MediaType jsonMediaType) {
|
||||
setJsonMediaType(jsonMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryRestController jsonMediaType(String jsonMediaType) {
|
||||
setJsonMediaType(jsonMediaType);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
Assert.notNull(httpMessageConverters, "HttpMessageConverters cannot be null");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
public void get(ServerHttpRequest request, final Model model) {
|
||||
if (validBaseUri(request.getURI())) {
|
||||
|
||||
URI relativeUri = baseUri.relativize(request.getURI());
|
||||
final Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("uris: " + uris);
|
||||
}
|
||||
|
||||
final int uriCnt = uris.size();
|
||||
if (uris.size() > 0) {
|
||||
final String repoName = uris.get(0).getPath();
|
||||
final CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
final EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
final Class<?> domainClass = entityInfo.getJavaType();
|
||||
final Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
final EntityType entityType = repositoryMetadata.entityTypeFor(domainClass);
|
||||
final JpaEntityMetadata entityMetadata = repositoryMetadata.entityMetadataFor(domainClass);
|
||||
|
||||
switch (uriCnt) {
|
||||
|
||||
// List the entities
|
||||
case HAS_RESOURCE: {
|
||||
Map<String, List<Link>> resource = new HashMap<String, List<Link>>();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
Iterator iter = repo.findAll().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Object o = iter.next();
|
||||
Serializable id = entityInfo.getId(o);
|
||||
links.add(new SimpleLink(o.getClass().getSimpleName(),
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repoName, id.toString())
|
||||
.build()
|
||||
.toUri())
|
||||
);
|
||||
}
|
||||
resource.put(LINKS, links);
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve an entity
|
||||
case HAS_RESOURCE_ID: {
|
||||
final String sId = UriUtils.path(uris.get(1));
|
||||
Serializable serId;
|
||||
if (idType == String.class) {
|
||||
serId = sId;
|
||||
} else {
|
||||
serId = conversionService.convert(sId, idType);
|
||||
}
|
||||
|
||||
final Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(entity, entityMetadata, repoName, sId);
|
||||
addSelfLink(entityDto, repoName, sId);
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
model.addAttribute(RESOURCE, entityDto);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the linked entities
|
||||
case HAS_SECOND_LEVEL_RESOURCE:
|
||||
// Retrieve a child entity
|
||||
case HAS_SECOND_LEVEL_ID: {
|
||||
final String sId = UriUtils.path(uris.get(1));
|
||||
final Serializable serId;
|
||||
if (idType == String.class) {
|
||||
serId = sId;
|
||||
} else {
|
||||
serId = conversionService.convert(sId, idType);
|
||||
}
|
||||
|
||||
Object entity = repo.findOne(serId);
|
||||
if (null == entity) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
final String attrName = UriUtils.path(uris.get(2));
|
||||
Attribute attr = entityType.getAttribute(attrName);
|
||||
if (null != attr) {
|
||||
Class<?> childType;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
childType = ((PluralAttribute) attr).getElementType().getJavaType();
|
||||
} else {
|
||||
childType = attr.getJavaType();
|
||||
}
|
||||
final CrudRepository childRepo = repositoryMetadata.repositoryFor(childType);
|
||||
if (null == childRepo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
final EntityInformation childEntityInfo = repositoryMetadata.entityInfoFor(childRepo);
|
||||
final JpaEntityMetadata childEntityMetadata = repositoryMetadata.entityMetadataFor(childEntityInfo.getJavaType());
|
||||
|
||||
final Object child = entityMetadata.get(attrName, entity);
|
||||
if (uriCnt == 3) {
|
||||
Map<String, Object> resource = new HashMap<String, Object>();
|
||||
if (null != child) {
|
||||
if (child instanceof Collection) {
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
for (Object o : (Collection) child) {
|
||||
String childId = childEntityInfo.getId(o).toString();
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repoName, sId, attrName, childId)
|
||||
.build()
|
||||
.toUri();
|
||||
links.add(new SimpleLink(childType.getSimpleName(), uri));
|
||||
}
|
||||
resource.put(LINKS, links);
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
} else if (child instanceof Map) {
|
||||
List<Object> links = new ArrayList<Object>();
|
||||
for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) child).entrySet()) {
|
||||
String childId = childEntityInfo.getId(entry.getValue()).toString();
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repoName, sId, attrName, childId)
|
||||
.build()
|
||||
.toUri();
|
||||
Object oKey = entry.getKey();
|
||||
String sKey;
|
||||
if (ClassUtils.isAssignable(oKey.getClass(), String.class)) {
|
||||
sKey = (String) oKey;
|
||||
} else {
|
||||
sKey = conversionService.convert(oKey, String.class);
|
||||
}
|
||||
links.add(new SimpleLink(sKey, uri));
|
||||
}
|
||||
resource.put(attrName, links);
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
} else {
|
||||
model.addAttribute(RESOURCE, child);
|
||||
}
|
||||
}
|
||||
} else if (uriCnt == 4) {
|
||||
final String childId = UriUtils.path(uris.get(3));
|
||||
Class<? extends Serializable> childIdType = childEntityInfo.getIdType();
|
||||
final Serializable childSerId;
|
||||
if (idType == String.class) {
|
||||
childSerId = childId;
|
||||
} else {
|
||||
childSerId = conversionService.convert(childId, childIdType);
|
||||
}
|
||||
|
||||
final Object o = childRepo.findOne(childSerId);
|
||||
if (null != o) {
|
||||
Map<String, Object> entityDto = extractPropertiesLinkAware(o,
|
||||
childEntityMetadata,
|
||||
repoName,
|
||||
sId,
|
||||
attrName);
|
||||
addSelfLink(entityDto, repositoryMetadata.repositoryNameFor(childRepo), childId);
|
||||
model.addAttribute(RESOURCE, entityDto);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// List the repositories
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
|
||||
Map<String, List<Link>> resource = new HashMap<String, List<Link>>();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
for (String name : repositoryMetadata.repositoryNames()) {
|
||||
links.add(new SimpleLink(name,
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri())
|
||||
);
|
||||
}
|
||||
resource.put(LINKS, links);
|
||||
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
|
||||
public void createOrUpdate(ServerHttpRequest request, Model model) {
|
||||
if (validBaseUri(request.getURI())) {
|
||||
|
||||
URI relativeUri = baseUri.relativize(request.getURI());
|
||||
final Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("uris: " + uris);
|
||||
}
|
||||
|
||||
final int uriCnt = uris.size();
|
||||
if (uriCnt > 0) {
|
||||
final String repoName = UriUtils.path(uris.get(0));
|
||||
final CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
final EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
final Class<?> domainClass = entityInfo.getJavaType();
|
||||
final Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
final JpaEntityMetadata entityMetadata = repositoryMetadata.entityMetadataFor(domainClass);
|
||||
final MediaType incomingMediaType = request.getHeaders().getContentType();
|
||||
|
||||
switch (uriCnt) {
|
||||
|
||||
// Create a new entity
|
||||
case HAS_RESOURCE:
|
||||
case HAS_RESOURCE_ID: {
|
||||
if (incomingMediaType.equals(jsonMediaType)) {
|
||||
try {
|
||||
final Map incoming = readIncoming(request, incomingMediaType, Map.class);
|
||||
if (null == incoming) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
} else {
|
||||
String resourceId;
|
||||
Serializable serId = null;
|
||||
if (uriCnt == HAS_RESOURCE_ID) {
|
||||
resourceId = UriUtils.path(uris.get(1));
|
||||
if (idType == String.class) {
|
||||
serId = resourceId;
|
||||
} else {
|
||||
serId = conversionService.convert(resourceId, idType);
|
||||
}
|
||||
}
|
||||
|
||||
final Object entity = request.getMethod() == HttpMethod.PUT ?
|
||||
repo.findOne(serId) :
|
||||
entityMetadata.targetType().newInstance();
|
||||
|
||||
entityMetadata.doWithEmbedded(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attribute) {
|
||||
String name = attribute.getName();
|
||||
if (incoming.containsKey(name)) {
|
||||
Object val = incoming.get(name);
|
||||
entityMetadata.set(name, val, entity);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (uriCnt == HAS_RESOURCE_ID && request.getMethod() == HttpMethod.POST) {
|
||||
entityMetadata.id(serId, entity);
|
||||
}
|
||||
|
||||
Object savedEntity = repo.save(entity);
|
||||
String sId = entityInfo.getId(savedEntity).toString();
|
||||
|
||||
URI selfUri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(repoName, sId)
|
||||
.build()
|
||||
.toUri();
|
||||
|
||||
if (request.getMethod() == HttpMethod.POST) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Location", selfUri.toString());
|
||||
model.addAttribute(HEADERS, headers);
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_ACCEPTABLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
case HAS_SECOND_LEVEL_RESOURCE: {
|
||||
String propertyName = UriUtils.path(uris.get(2));
|
||||
Attribute attr = entityMetadata.linkedAttributes().get(propertyName);
|
||||
if (null != attr) {
|
||||
Object entity = resolveTopLevelResource(request.getURI().toString());
|
||||
if (null != entity) {
|
||||
try {
|
||||
if (incomingMediaType.equals(uriListMediaType)) {
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(request.getBody()));
|
||||
String line;
|
||||
while (null != (line = in.readLine())) {
|
||||
String sLinkUri = line.trim();
|
||||
Object childEntity = resolveTopLevelResource(sLinkUri);
|
||||
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
entityMetadata.set(propertyName, new ArrayList(), entity);
|
||||
}
|
||||
addToCollection(propertyName, entity, entityMetadata, ArrayList.class, childEntity);
|
||||
break;
|
||||
case SET:
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
entityMetadata.set(propertyName, new HashSet(), entity);
|
||||
}
|
||||
addToCollection(propertyName, entity, entityMetadata, HashSet.class, childEntity);
|
||||
break;
|
||||
case MAP:
|
||||
model.addAttribute(STATUS, HttpStatus.UNSUPPORTED_MEDIA_TYPE);
|
||||
return;
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
entityMetadata.set(propertyName, childEntity, entity);
|
||||
}
|
||||
}
|
||||
repo.save(entity);
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
}
|
||||
} else if (incomingMediaType.equals(jsonMediaType)) {
|
||||
final List<Map<String, String>> incoming = readIncoming(request, incomingMediaType, List.class);
|
||||
for (Map<String, String> link : incoming) {
|
||||
String sLinkUri = link.get("href");
|
||||
Object childEntity = resolveTopLevelResource(sLinkUri);
|
||||
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
entityMetadata.set(propertyName, new ArrayList(), entity);
|
||||
}
|
||||
addToCollection(propertyName, entity, entityMetadata, ArrayList.class, childEntity);
|
||||
break;
|
||||
case SET:
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
entityMetadata.set(propertyName, new HashSet(), entity);
|
||||
}
|
||||
addToCollection(propertyName, entity, entityMetadata, HashSet.class, childEntity);
|
||||
break;
|
||||
case MAP:
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
entityMetadata.set(propertyName, new HashMap(), entity);
|
||||
}
|
||||
addToMap(propertyName, entity, entityMetadata, link.get("rel"), childEntity);
|
||||
break;
|
||||
}
|
||||
} else if (attr instanceof SingularAttribute) {
|
||||
entityMetadata.set(propertyName, childEntity, entity);
|
||||
}
|
||||
}
|
||||
repo.save(entity);
|
||||
if (request.getMethod() == HttpMethod.PUT) {
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.CREATED);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
} catch (InstantiationException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.BAD_REQUEST);
|
||||
} catch (IllegalAccessException e) {
|
||||
model.addAttribute(STATUS, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// List resources
|
||||
default:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
|
||||
Map<String, List<Link>> resource = new HashMap<String, List<Link>>();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
for (String name : repositoryMetadata.repositoryNames()) {
|
||||
links.add(new SimpleLink(name,
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri())
|
||||
);
|
||||
}
|
||||
resource.put(LINKS, links);
|
||||
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@RequestMapping(method = RequestMethod.DELETE)
|
||||
public void delete(ServerHttpRequest request, Model model) {
|
||||
if (validBaseUri(request.getURI())) {
|
||||
|
||||
URI relativeUri = baseUri.relativize(request.getURI());
|
||||
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("uris: " + uris);
|
||||
}
|
||||
|
||||
int uriCnt = uris.size();
|
||||
if (uriCnt > 0) {
|
||||
String repoName = UriUtils.path(uris.get(0));
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
if (null == repo) {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
Class<?> domainClass = entityInfo.getJavaType();
|
||||
Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
JpaEntityMetadata entityMetadata = repositoryMetadata.entityMetadataFor(domainClass);
|
||||
|
||||
switch (uriCnt) {
|
||||
|
||||
case HAS_RESOURCE_ID: {
|
||||
String resourceId = UriUtils.path(uris.get(1));
|
||||
Serializable serId = conversionService.convert(resourceId, idType);
|
||||
repo.delete(serId);
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
return;
|
||||
}
|
||||
|
||||
case HAS_SECOND_LEVEL_ID: {
|
||||
String resourceId = UriUtils.path(uris.get(1));
|
||||
Serializable serId = conversionService.convert(resourceId, idType);
|
||||
|
||||
Object entity = repo.findOne(serId);
|
||||
|
||||
String propertyName = UriUtils.path(uris.get(2));
|
||||
Attribute attr = entityMetadata.linkedAttributes().get(propertyName);
|
||||
if (null != attr && null != entity) {
|
||||
Object childEntity = resolveSecondLevelResource(request.getURI().toString());
|
||||
if (attr instanceof PluralAttribute) {
|
||||
PluralAttribute plAttr = (PluralAttribute) attr;
|
||||
switch (plAttr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
case SET:
|
||||
removeFromCollection(propertyName, entity, entityMetadata, childEntity);
|
||||
break;
|
||||
case MAP:
|
||||
removeFromMap(propertyName, entity, entityMetadata, childEntity);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
entityMetadata.set(propertyName, null, entity);
|
||||
}
|
||||
|
||||
repo.save(entity);
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.NO_CONTENT);
|
||||
return;
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.addAttribute(STATUS, HttpStatus.NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
model.addAttribute(STATUS, HttpStatus.OK);
|
||||
|
||||
Map<String, List<Link>> resource = new HashMap<String, List<Link>>();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
for (String name : repositoryMetadata.repositoryNames()) {
|
||||
links.add(new SimpleLink(name,
|
||||
UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri())
|
||||
);
|
||||
}
|
||||
resource.put(LINKS, links);
|
||||
|
||||
model.addAttribute(RESOURCE, resource);
|
||||
}
|
||||
|
||||
|
||||
private boolean validBaseUri(URI requestUri) {
|
||||
String path = baseUri.relativize(requestUri).getPath();
|
||||
return !StringUtils.hasText(path) || path.charAt(0) != '/';
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void addSelfLink(Map<String, Object> model, String... pathComponents) {
|
||||
List<Link> links = (List<Link>) model.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
model.put(LINKS, links);
|
||||
}
|
||||
URI selfUri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(pathComponents)
|
||||
.build()
|
||||
.toUri();
|
||||
links.add(new SimpleLink(SELF, selfUri));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <V extends Serializable> V stringToSerializable(String s, Class<V> targetType) {
|
||||
if (ClassUtils.isAssignable(targetType, String.class)) {
|
||||
return (V) s;
|
||||
} else {
|
||||
return conversionService.convert(s, targetType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <V> V readIncoming(HttpInputMessage request, MediaType incomingMediaType, Class<V> targetType) throws IOException {
|
||||
for (HttpMessageConverter converter : httpMessageConverters) {
|
||||
if (converter.canRead(targetType, incomingMediaType)) {
|
||||
return (V) converter.read(targetType, request);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Object resolveTopLevelResource(String uri) {
|
||||
URI href = URI.create(uri);
|
||||
if (validBaseUri(href)) {
|
||||
URI relativeUri = baseUri.relativize(href);
|
||||
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
|
||||
if (uris.size() > 1) {
|
||||
String repoName = UriUtils.path(uris.get(0));
|
||||
String sId = UriUtils.path(uris.get(1));
|
||||
|
||||
CrudRepository repo = repositoryMetadata.repositoryFor(repoName);
|
||||
EntityInformation entityInfo = repositoryMetadata.entityInfoFor(repo);
|
||||
Class<? extends Serializable> idType = entityInfo.getIdType();
|
||||
|
||||
Serializable serId = stringToSerializable(sId, idType);
|
||||
|
||||
return repo.findOne(serId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Object resolveSecondLevelResource(String uri) {
|
||||
URI href = URI.create(uri);
|
||||
if (validBaseUri(href)) {
|
||||
URI relativeUri = baseUri.relativize(href);
|
||||
Stack<URI> uris = UriUtils.explode(baseUri, relativeUri);
|
||||
|
||||
if (uris.size() > 3) {
|
||||
String topLevelRepoName = UriUtils.path(uris.get(0));
|
||||
CrudRepository topLevelRepo = repositoryMetadata.repositoryFor(topLevelRepoName);
|
||||
EntityInformation topLevelEntityInfo = repositoryMetadata.entityInfoFor(topLevelRepo);
|
||||
JpaEntityMetadata topLevelEntityMetadata = repositoryMetadata.entityMetadataFor(topLevelEntityInfo.getJavaType());
|
||||
|
||||
String propertyName = UriUtils.path(uris.get(2));
|
||||
Attribute attr = topLevelEntityMetadata.linkedAttributes().get(propertyName);
|
||||
if (null != attr) {
|
||||
CrudRepository secondLevelRepo;
|
||||
if (attr instanceof PluralAttribute) {
|
||||
secondLevelRepo = repositoryMetadata.repositoryFor(((PluralAttribute) attr).getElementType().getJavaType());
|
||||
} else {
|
||||
secondLevelRepo = repositoryMetadata.repositoryFor(attr.getJavaType());
|
||||
}
|
||||
EntityInformation secondLevelEntityInfo = repositoryMetadata.entityInfoFor(secondLevelRepo);
|
||||
Class<? extends Serializable> secondLevelIdType = secondLevelEntityInfo.getIdType();
|
||||
Serializable secondLevelId = stringToSerializable(UriUtils.path(uris.get(3)), secondLevelIdType);
|
||||
|
||||
return secondLevelRepo.findOne(secondLevelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private <V extends Collection> void addToCollection(String name,
|
||||
Object entity,
|
||||
JpaEntityMetadata metadata,
|
||||
Class<V> containerClass,
|
||||
Object obj)
|
||||
throws IllegalAccessException,
|
||||
InstantiationException {
|
||||
Collection c = (V) metadata.get(name, entity);
|
||||
if (null == c) {
|
||||
c = containerClass.newInstance();
|
||||
metadata.set(name, c, entity);
|
||||
}
|
||||
c.add(obj);
|
||||
}
|
||||
|
||||
public void removeFromCollection(String name,
|
||||
Object entity,
|
||||
JpaEntityMetadata metadata,
|
||||
Object obj) {
|
||||
Collection c = (Collection) metadata.get(name, entity);
|
||||
if (null != c) {
|
||||
c.remove(obj);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private void addToMap(String name,
|
||||
Object entity,
|
||||
JpaEntityMetadata metadata,
|
||||
Object key,
|
||||
Object obj)
|
||||
throws IllegalAccessException,
|
||||
InstantiationException {
|
||||
Map m = (Map) metadata.get(name, entity);
|
||||
if (null == m) {
|
||||
m = new HashMap();
|
||||
metadata.set(name, m, entity);
|
||||
}
|
||||
m.put(key, obj);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void removeFromMap(String name,
|
||||
Object entity,
|
||||
JpaEntityMetadata metadata,
|
||||
Object obj) {
|
||||
Map<Object, Object> m = (Map<Object, Object>) metadata.get(name, entity);
|
||||
if (null != m) {
|
||||
LOG.debug("obj: " + obj);
|
||||
for (Map.Entry<Object, Object> entry : m.entrySet()) {
|
||||
LOG.debug("key: " + entry.getKey());
|
||||
LOG.debug("value: " + entry.getValue());
|
||||
LOG.debug("remove? " + (entry.getValue() == obj || entry.getValue().equals(obj)));
|
||||
if (entry.getValue() == obj || entry.getValue().equals(obj)) {
|
||||
m.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private Map<String, Object> extractPropertiesLinkAware(final Object entity,
|
||||
final JpaEntityMetadata entityMetadata,
|
||||
final String... pathSegs) {
|
||||
final Map<String, Object> entityDto = new HashMap<String, Object>();
|
||||
|
||||
entityMetadata.doWithEmbedded(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
Object val = entityMetadata.get(name, entity);
|
||||
if (null != val) {
|
||||
entityDto.put(name, val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
entityMetadata.doWithLinked(new Handler<Attribute, Void>() {
|
||||
@Override public Void handle(Attribute attr) {
|
||||
String name = attr.getName();
|
||||
URI uri = UriComponentsBuilder.fromUri(baseUri)
|
||||
.pathSegment(pathSegs)
|
||||
.pathSegment(name)
|
||||
.build()
|
||||
.toUri();
|
||||
Link l = new SimpleLink(name, uri);
|
||||
List<Link> links = (List<Link>) entityDto.get(LINKS);
|
||||
if (null == links) {
|
||||
links = new ArrayList<Link>();
|
||||
entityDto.put(LINKS, links);
|
||||
}
|
||||
links.add(l);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
return entityDto;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.servlet.View;
|
||||
import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Configuration
|
||||
public class RepositoryRestMvcConfiguration {
|
||||
|
||||
@Autowired
|
||||
RepositoryRestConfiguration repositoryRestConfiguration;
|
||||
RepositoryRestController repositoryRestController;
|
||||
|
||||
@Bean ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
|
||||
ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver();
|
||||
Map<String, String> jsonTypes = new HashMap<String, String>() {{
|
||||
put("json", "application/json");
|
||||
put("sdjson", "application/x-spring-data+json");
|
||||
put("urilist", "text/uri-list");
|
||||
}};
|
||||
|
||||
viewResolver.setMediaTypes(jsonTypes);
|
||||
viewResolver.setDefaultViews(
|
||||
Arrays.asList((View) new JsonView("application/json"),
|
||||
(View) new JsonView("application/x-spring-data+json"),
|
||||
(View) new UriListView())
|
||||
);
|
||||
return viewResolver;
|
||||
}
|
||||
|
||||
@Bean RepositoryRestController repositoryRestController() throws Exception {
|
||||
if (null == repositoryRestController) {
|
||||
this.repositoryRestController = new RepositoryRestController()
|
||||
.baseUri(repositoryRestConfiguration.baseUri())
|
||||
.repositoryMetadata(repositoryRestConfiguration.jpaRepositoryMetadata())
|
||||
.httpMessageConverters(repositoryRestConfiguration.httpMessageConverters())
|
||||
.jsonMediaType("application/json");
|
||||
}
|
||||
return repositoryRestController;
|
||||
}
|
||||
|
||||
@Bean RequestMappingHandlerMapping handlerMapping() {
|
||||
return new RequestMappingHandlerMapping();
|
||||
}
|
||||
|
||||
@Bean RequestMappingHandlerAdapter handlerAdapter() {
|
||||
RequestMappingHandlerAdapter handlerAdapter = new RequestMappingHandlerAdapter();
|
||||
handlerAdapter.setCustomArgumentResolvers(
|
||||
Arrays.asList((HandlerMethodArgumentResolver) new ServerHttpRequestMethodArgumentResolver())
|
||||
);
|
||||
return handlerAdapter;
|
||||
}
|
||||
|
||||
@Bean ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() {
|
||||
return new ExceptionHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Bean DefaultHandlerExceptionResolver handlerExceptionResolver() {
|
||||
return new DefaultHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Bean ResponseStatusExceptionResolver responseStatusExceptionResolver() {
|
||||
return new ResponseStatusExceptionResolver();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override public boolean supportsParameter(MethodParameter parameter) {
|
||||
return ClassUtils.isAssignable(parameter.getParameterType(), ServerHttpRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
return new ServletServerHttpRequest((HttpServletRequest) webRequest.getNativeRequest());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.springframework.data.rest.mvc;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.servlet.view.AbstractView;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class UriListView extends AbstractView {
|
||||
|
||||
public UriListView() {
|
||||
setContentType("text/uri-list");
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected void renderMergedOutputModel(Map<String, Object> model,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) throws Exception {
|
||||
|
||||
Object resource = model.get("resource");
|
||||
response.setContentType(getContentType());
|
||||
|
||||
HttpStatus status = (HttpStatus) model.get("status");
|
||||
HttpHeaders headers = (HttpHeaders) model.get("headers");
|
||||
List<Link> links = null;
|
||||
if (resource instanceof List) {
|
||||
links = (List<Link>) resource;
|
||||
} else if (resource instanceof Map) {
|
||||
Map m = (Map) resource;
|
||||
Object o = m.get("_links");
|
||||
if (null != o && o instanceof List) {
|
||||
links = (List<Link>) o;
|
||||
} else {
|
||||
response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (null != status) {
|
||||
response.setStatus(status.value());
|
||||
}
|
||||
|
||||
if (null != headers) {
|
||||
for (Map.Entry<String, String> entry : headers.toSingleValueMap().entrySet()) {
|
||||
response.setHeader(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
PrintWriter out = response.getWriter();
|
||||
if (null != links) {
|
||||
for (Link l : links) {
|
||||
out.println(l.href().toString());
|
||||
}
|
||||
}
|
||||
out.flush();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
8
rest/src/main/webapp/WEB-INF/repositories.xml
Normal file
8
rest/src/main/webapp/WEB-INF/repositories.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<import resource="classpath:META-INF/spring-data-rest/**/*-export.xml"/>
|
||||
|
||||
</beans>
|
||||
48
rest/src/main/webapp/WEB-INF/web.xml
Normal file
48
rest/src/main/webapp/WEB-INF/web.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
|
||||
version="2.5">
|
||||
|
||||
<context-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</context-param>
|
||||
<context-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.mvc.RepositoryRestConfiguration</param-value>
|
||||
</context-param>
|
||||
<listener>
|
||||
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<filter>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
|
||||
</filter>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
|
||||
<init-param>
|
||||
<param-name>contextClass</param-name>
|
||||
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
|
||||
</init-param>
|
||||
<init-param>
|
||||
<param-name>contextConfigLocation</param-name>
|
||||
<param-value>org.springframework.data.rest.mvc.RepositoryRestMvcConfiguration</param-value>
|
||||
</init-param>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<filter-mapping>
|
||||
<filter-name>entityManagerInViewFilter</filter-name>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
</filter-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,225 @@
|
||||
package org.springframework.data.rest.test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class RestBuilder {
|
||||
|
||||
private static final String[] DATE_FORMATS = new String[]{
|
||||
"EEE, dd MMM yyyy HH:mm:ss z",
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSZ",
|
||||
"yyyy-MM-dd HH:mm:ss"
|
||||
};
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private ClientHttpRequestFactory requestFactory;
|
||||
private RestTemplate restTemplate;
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private MediaType contentType;
|
||||
private Class<?> responseType = byte[].class;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Closure errorHandler;
|
||||
|
||||
public RestBuilder() {
|
||||
this.restTemplate = new RestTemplate();
|
||||
}
|
||||
|
||||
public RestBuilder(ClientHttpRequestFactory requestFactory) {
|
||||
this.requestFactory = requestFactory;
|
||||
this.restTemplate = new RestTemplate(requestFactory);
|
||||
}
|
||||
|
||||
public Object call(Closure cl) {
|
||||
RestBuilder b = null != requestFactory ? new RestBuilder(requestFactory) : new RestBuilder();
|
||||
if (null != errorHandler) {
|
||||
b.setErrorHandler(errorHandler);
|
||||
}
|
||||
b.conversionService = conversionService;
|
||||
cl.setDelegate(b);
|
||||
|
||||
return cl.call();
|
||||
}
|
||||
|
||||
public Object delete(String url) {
|
||||
restTemplate.delete(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object get(String url) {
|
||||
return restTemplate.getForEntity(maybeAddParams(url), responseType);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object post(String url) {
|
||||
if (responseType == URI.class) {
|
||||
return restTemplate.postForLocation(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
} else {
|
||||
return restTemplate.postForEntity(maybeAddParams(url), new HttpEntity(body, headers), responseType);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object put(String url) {
|
||||
if (null != uriParams) {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers), uriParams);
|
||||
} else {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object accept(String accept) {
|
||||
headers.setAccept(MediaType.parseMediaTypes(accept));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object body(Object body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object contentType(String contentType) {
|
||||
this.contentType = MediaType.parseMediaType(contentType);
|
||||
headers.setContentType(this.contentType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object date(Date date) {
|
||||
headers.setDate(date.getTime());
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object date(String date) {
|
||||
for (String fmt : DATE_FORMATS) {
|
||||
try {
|
||||
Date dte = new SimpleDateFormat(fmt).parse(date);
|
||||
headers.setDate(dte.getTime());
|
||||
break;
|
||||
} catch (ParseException e) {}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object header(String key, Object val) {
|
||||
if (null != val) {
|
||||
if (val instanceof List) {
|
||||
headers.put(key, (List) val);
|
||||
} else if (ClassUtils.isAssignable(val.getClass(), String.class)) {
|
||||
headers.set(key, (String) val);
|
||||
} else {
|
||||
headers.set(key, conversionService.convert(val, String.class));
|
||||
}
|
||||
} else {
|
||||
headers.remove(key);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object headers(Map headers) {
|
||||
this.headers.putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date now() {
|
||||
return Calendar.getInstance().getTime();
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object param(String key, String value) {
|
||||
if (null == uriParams) {
|
||||
uriParams = new HashMap();
|
||||
}
|
||||
uriParams.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object params(Map params) {
|
||||
this.uriParams = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object responseType(Class<?> responseType) {
|
||||
this.responseType = responseType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setConversionService(ConversionService conversionService) {
|
||||
this.conversionService = conversionService;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setErrorHandler(Closure errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
if (null != errorHandler) {
|
||||
this.restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@Override public void handleError(ClientHttpResponse response) throws IOException {
|
||||
RestBuilder.this.errorHandler.call(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Object setMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
restTemplate.setMessageConverters(converters);
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String maybeAddParams(String url) {
|
||||
StringBuffer buff = new StringBuffer(url);
|
||||
if (null != uriParams) {
|
||||
buff.append("?");
|
||||
for (Map.Entry<String, String> entry : ((Map<String, String>) uriParams).entrySet()) {
|
||||
try {
|
||||
buff.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), "UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "RestBuilder{" +
|
||||
"requestFactory=" + requestFactory +
|
||||
", restTemplate=" + restTemplate +
|
||||
", headers=" + headers +
|
||||
", params=" + uriParams +
|
||||
", contentType=" + contentType +
|
||||
", errorHandler=" + errorHandler +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
|
||||
public Address(String[] lines, String city, String province, String postalCode) {
|
||||
this.lines = lines;
|
||||
this.city = city;
|
||||
this.province = province;
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public String[] getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
public void setLines(String[] lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
return city;
|
||||
}
|
||||
|
||||
public void setCity(String city) {
|
||||
this.city = city;
|
||||
}
|
||||
|
||||
public String getProvince() {
|
||||
return province;
|
||||
}
|
||||
|
||||
public void setProvince(String province) {
|
||||
this.province = province;
|
||||
}
|
||||
|
||||
public String getPostalCode() {
|
||||
return postalCode;
|
||||
}
|
||||
|
||||
public void setPostalCode(String postalCode) {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Version;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id private Long id;
|
||||
private String name;
|
||||
@Version
|
||||
private Long version;
|
||||
@OneToMany
|
||||
private List<Address> addresses;
|
||||
@OneToMany
|
||||
private Map<String, Profile> profiles;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
public Person(Long id, String name, List<Address> addresses, Map<String, Profile> profiles) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.addresses = addresses;
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
public Person(String name, List<Address> addresses, Map<String, Profile> profiles) {
|
||||
this.name = name;
|
||||
this.addresses = addresses;
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public List<Address> getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public void setAddresses(List<Address> addresses) {
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public Map<String, Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public void setProfiles(Map<String, Profile> profiles) {
|
||||
this.profiles = profiles;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public class PersonLoader implements InitializingBean {
|
||||
|
||||
private PersonRepository personRepository;
|
||||
private ProfileRepository profileRepository;
|
||||
private AddressRepository addressRepository;
|
||||
|
||||
public PersonRepository getPersonRepository() {
|
||||
return personRepository;
|
||||
}
|
||||
|
||||
public void setPersonRepository(PersonRepository personRepository) {
|
||||
this.personRepository = personRepository;
|
||||
}
|
||||
|
||||
public ProfileRepository getProfileRepository() {
|
||||
return profileRepository;
|
||||
}
|
||||
|
||||
public void setProfileRepository(ProfileRepository profileRepository) {
|
||||
this.profileRepository = profileRepository;
|
||||
}
|
||||
|
||||
public AddressRepository getAddressRepository() {
|
||||
return addressRepository;
|
||||
}
|
||||
|
||||
public void setAddressRepository(AddressRepository addressRepository) {
|
||||
this.addressRepository = addressRepository;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
Address pers1addr = addressRepository.save(new Address(new String[]{"1234 W. 1st St."}, "Univille", "ST", "12345"));
|
||||
|
||||
Map<String, Profile> pers1profiles = new HashMap<String, Profile>();
|
||||
Profile twitter = profileRepository.save(new Profile("twitter", "#!/johndoe"));
|
||||
Profile fb = profileRepository.save(new Profile("facebook", "/johndoe"));
|
||||
pers1profiles.put("twitter", twitter);
|
||||
pers1profiles.put("facebook", fb);
|
||||
|
||||
personRepository.save(new Person(1L, "John Doe", Arrays.asList(pers1addr), pers1profiles));
|
||||
|
||||
Address pers2addr = addressRepository.save(new Address(new String[]{"1234 E. 2nd St."}, "Univille", "ST", "12345"));
|
||||
|
||||
Map<String, Profile> pers2profiles = new HashMap<String, Profile>();
|
||||
Profile twitter2 = profileRepository.save(new Profile("twitter", "#!/janedoe"));
|
||||
Profile fb2 = profileRepository.save(new Profile("facebook", "/janedoe"));
|
||||
pers2profiles.put("facebook", fb2);
|
||||
|
||||
personRepository.save(new Person(2L, "Jane Doe", Arrays.asList(pers2addr), pers2profiles));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface PersonRepository extends CrudRepository<Person, Long> {
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Profile {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
|
||||
public Profile() {
|
||||
}
|
||||
|
||||
public Profile(String type, String url) {
|
||||
this.type = type;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof Profile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profile p2 = (Profile) o;
|
||||
|
||||
boolean idEq;
|
||||
if (null != id) {
|
||||
idEq = id.equals(p2.id);
|
||||
} else {
|
||||
idEq = p2.id == null;
|
||||
}
|
||||
|
||||
boolean typeEq;
|
||||
if (null != type) {
|
||||
typeEq = type.equals(p2.type);
|
||||
} else {
|
||||
typeEq = p2.type == null;
|
||||
}
|
||||
|
||||
boolean urlEq;
|
||||
if (null != url) {
|
||||
urlEq = url.equals(p2.url);
|
||||
} else {
|
||||
urlEq = p2.url == null;
|
||||
}
|
||||
|
||||
return idEq && typeEq && urlEq;
|
||||
}
|
||||
|
||||
@Override public String toString() {
|
||||
return "Profile{" +
|
||||
"id=" + id +
|
||||
", type='" + type + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package org.springframework.data.rest.test.mvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, Long> {
|
||||
}
|
||||
16
rest/src/test/resouces/META-INF/persistence.xml
Normal file
16
rest/src/test/resouces/META-INF/persistence.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
|
||||
<persistence-unit name="jpa.sample">
|
||||
<class>org.springframework.data.rest.test.mvc.Person</class>
|
||||
<class>org.springframework.data.rest.test.mvc.Profile</class>
|
||||
<class>org.springframework.data.rest.test.mvc.Address</class>
|
||||
<properties>
|
||||
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
|
||||
<property name="hibernate.connection.url" value="jdbc:hsqldb:mem:spring"/>
|
||||
<property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/>
|
||||
<property name="hibernate.connection.username" value="sa"/>
|
||||
<property name="hibernate.connection.password" value=""/>
|
||||
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
|
||||
|
||||
<import resource="shared.xml"/>
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.mvc"/>
|
||||
|
||||
<bean class="org.springframework.data.rest.test.mvc.PersonLoader">
|
||||
<property name="personRepository" ref="personRepository"/>
|
||||
<property name="profileRepository" ref="profileRepository"/>
|
||||
<property name="addressRepository" ref="addressRepository"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
29
rest/src/test/resouces/META-INF/spring-data-rest/shared.xml
Normal file
29
rest/src/test/resouces/META-INF/spring-data-rest/shared.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd">
|
||||
|
||||
<bean id="baseUri" class="java.net.URI">
|
||||
<constructor-arg value="http://localhost:8080/data"/>
|
||||
</bean>
|
||||
|
||||
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="jpaVendorAdapter">
|
||||
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
|
||||
<property name="generateDdl" value="true"/>
|
||||
<property name="database" value="HSQL"/>
|
||||
</bean>
|
||||
</property>
|
||||
<property name="persistenceUnitName" value="jpa.sample"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
|
||||
<property name="entityManagerFactory" ref="entityManagerFactory"/>
|
||||
</bean>
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
</beans>
|
||||
19
rest/src/test/resouces/logback.xml
Normal file
19
rest/src/test/resouces/logback.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<configuration>
|
||||
|
||||
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>
|
||||
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
|
||||
</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.springframework.data.rest" level="DEBUG"/>
|
||||
<logger name="org.springframework.data" level="DEBUG"/>
|
||||
<logger name="org.springframework" level="INFO"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="stdout"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|
||||
3
settings.gradle
Normal file
3
settings.gradle
Normal file
@@ -0,0 +1,3 @@
|
||||
include "core",
|
||||
"repository",
|
||||
"rest"
|
||||
Reference in New Issue
Block a user