Big update. Bug fixes, code re-formatting, changing tests.
This commit is contained in:
@@ -16,7 +16,7 @@ allprojects {
|
||||
|
||||
repositories {
|
||||
//maven { url "http://repo.springsource.org/libs-snapshot" }
|
||||
//maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-milestone" }
|
||||
maven { url "http://repo.springsource.org/libs-release" }
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ configure(subprojects) { subproject ->
|
||||
compile("org.springframework:spring-context:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-core:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-orm:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-tx:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-web:$springVersion") { force = true }
|
||||
runtime "cglib:cglib-nodep:2.2.2"
|
||||
|
||||
// Testing
|
||||
testCompile "org.spockframework:spock-core:$spockVersion"
|
||||
|
||||
@@ -11,7 +11,7 @@ groovyVersion = 1.8.6
|
||||
|
||||
# Supporting libraries
|
||||
sdCommonsVersion = 1.3.2.RELEASE
|
||||
sdJpaVersion = 1.1.0.RELEASE
|
||||
sdJpaVersion = 1.2.0.M1
|
||||
jacksonVersion = 1.9.7
|
||||
hibernateVersion = 4.1.4.Final
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ public interface Handler<T, V> {
|
||||
/**
|
||||
* Accept an argument and possibly produce a result.
|
||||
*
|
||||
* @param t arg
|
||||
* @param t
|
||||
* arg
|
||||
*
|
||||
* @return Some object or {@literal null} if no result.
|
||||
*/
|
||||
V handle(T t);
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class Links {
|
||||
|
||||
private List<Link> links = new ArrayList<Link>();
|
||||
|
||||
public Links add(Link link) {
|
||||
links.add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonProperty("_links")
|
||||
public List<Link> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,10 +7,11 @@ import java.net.URI;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class SimpleLink implements Link {
|
||||
public class SimpleLink
|
||||
implements Link {
|
||||
|
||||
private String rel;
|
||||
private URI href;
|
||||
private URI href;
|
||||
|
||||
public SimpleLink() {
|
||||
}
|
||||
|
||||
@@ -7,9 +7,14 @@ import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
* This {@link ConversionService} implementation delegates the actual conversion the ConversionService if finds in its
|
||||
* internal List that claims to be able to convert a given class. It will roll through the internal <code>Stack</code>
|
||||
* of <code>ConversionService</code>s until it finds one that can convert the given type.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class DelegatingConversionService implements ConversionService {
|
||||
public class DelegatingConversionService
|
||||
implements ConversionService {
|
||||
|
||||
private Stack<ConversionService> conversionServices = new Stack<ConversionService>();
|
||||
|
||||
@@ -20,21 +25,39 @@ public class DelegatingConversionService implements ConversionService {
|
||||
addConversionServices(svcs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add {@link ConversionService}s to the internal list of those to delegate to.
|
||||
*
|
||||
* @param svcs
|
||||
* The ConversionServices to delegate to (in order).
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
public DelegatingConversionService addConversionServices(ConversionService... svcs) {
|
||||
for (ConversionService svc : svcs) {
|
||||
for(ConversionService svc : svcs) {
|
||||
conversionServices.add(svc);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link ConversionService} to the internal list at a specific index for controlling the priority.
|
||||
*
|
||||
* @param atIndex
|
||||
* Where in the stack to add this ConversionService.
|
||||
* @param svc
|
||||
* The ConversionService to add.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public DelegatingConversionService addConversionService(int atIndex, ConversionService svc) {
|
||||
conversionServices.add(atIndex, svc);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public boolean canConvert(Class<?> from, Class<?> to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
for(ConversionService svc : conversionServices) {
|
||||
if(svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -42,8 +65,8 @@ public class DelegatingConversionService implements ConversionService {
|
||||
}
|
||||
|
||||
@Override public boolean canConvert(TypeDescriptor from, TypeDescriptor to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
for(ConversionService svc : conversionServices) {
|
||||
if(svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -51,8 +74,8 @@ public class DelegatingConversionService implements ConversionService {
|
||||
}
|
||||
|
||||
@Override public <T> T convert(Object o, Class<T> type) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(o.getClass(), type)) {
|
||||
for(ConversionService svc : conversionServices) {
|
||||
if(svc.canConvert(o.getClass(), type)) {
|
||||
return svc.convert(o, type);
|
||||
}
|
||||
}
|
||||
@@ -60,8 +83,8 @@ public class DelegatingConversionService implements ConversionService {
|
||||
}
|
||||
|
||||
@Override public Object convert(Object o, TypeDescriptor from, TypeDescriptor to) {
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
for(ConversionService svc : conversionServices) {
|
||||
if(svc.canConvert(from, to)) {
|
||||
return svc.convert(o, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import org.springframework.core.convert.converter.Converter;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class StringToUUIDConverter implements Converter<String, UUID> {
|
||||
public class StringToUUIDConverter
|
||||
implements Converter<String, UUID> {
|
||||
@Override public UUID convert(String s) {
|
||||
return (null != s ? UUID.fromString(s) : null);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ import org.springframework.core.convert.converter.Converter;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class UUIDToStringConverter implements Converter<UUID, String> {
|
||||
public class UUIDToStringConverter
|
||||
implements Converter<UUID, String> {
|
||||
@Override public String convert(UUID uuid) {
|
||||
return (null != uuid ? uuid.toString() : null);
|
||||
}
|
||||
|
||||
@@ -30,13 +30,14 @@ public abstract class BeanUtils {
|
||||
|
||||
public static ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
|
||||
|
||||
private static final LoadingCache<Object[], Field> fields = CacheBuilder.newBuilder().build(
|
||||
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];
|
||||
@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) {
|
||||
if(null != f) {
|
||||
ReflectionUtils.makeAccessible(f);
|
||||
return f;
|
||||
} else {
|
||||
@@ -47,14 +48,15 @@ public abstract class BeanUtils {
|
||||
);
|
||||
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;
|
||||
@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) {
|
||||
for(Method m : clazz.getDeclaredMethods()) {
|
||||
if(m.getName().equals(name)) {
|
||||
if(m.getParameterTypes().length == paramCnt) {
|
||||
ReflectionUtils.makeAccessible(m);
|
||||
return m;
|
||||
}
|
||||
@@ -67,28 +69,28 @@ public abstract class BeanUtils {
|
||||
);
|
||||
|
||||
public static boolean hasProperty(String property, Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
if (obj instanceof Map) {
|
||||
return ((Map) obj).containsKey(property);
|
||||
for(Object obj : objs) {
|
||||
if(obj instanceof Map) {
|
||||
return ((Map)obj).containsKey(property);
|
||||
}
|
||||
Class<?> type = obj.getClass();
|
||||
try {
|
||||
if (FluentBeanUtils.isFluentBean(type)) {
|
||||
if(FluentBeanUtils.isFluentBean(type)) {
|
||||
return null != methods.get(new Object[]{type, property});
|
||||
} else {
|
||||
if (null == methods.get(new Object[]{type, "get" + StringUtils.capitalize(property)})) {
|
||||
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) {
|
||||
} catch(UncheckedExecutionException e) {
|
||||
if(e.getCause().getClass() == IllegalArgumentException.class) {
|
||||
return false;
|
||||
} else {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@@ -97,9 +99,9 @@ public abstract class BeanUtils {
|
||||
|
||||
@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;
|
||||
for(Object o : stack) {
|
||||
if(ClassUtils.isAssignable(clazz, o.getClass())) {
|
||||
return (T)o;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -107,44 +109,44 @@ public abstract class BeanUtils {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static Object findFirst(Object o, Object... objs) {
|
||||
for (Object obj : objs) {
|
||||
if (o == obj || null != o && o.equals(obj)) {
|
||||
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);
|
||||
} 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);
|
||||
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)) {
|
||||
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) {
|
||||
if(null != getter) {
|
||||
return getter.invoke(obj);
|
||||
} else {
|
||||
return f.get(obj);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
} catch(InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (ExecutionException e) {
|
||||
} catch(IllegalArgumentException e) {
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
@@ -157,8 +159,8 @@ public abstract class BeanUtils {
|
||||
}
|
||||
|
||||
public static boolean containsType(Class<?> type, Object[] objs) {
|
||||
for (Object obj : objs) {
|
||||
if (null != obj && ClassUtils.isAssignable(obj.getClass(), type)) {
|
||||
for(Object obj : objs) {
|
||||
if(null != obj && ClassUtils.isAssignable(obj.getClass(), type)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -172,7 +174,7 @@ public abstract class BeanUtils {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public static <T> T invoke(String methodName, Object target, Class<T> returnType, Object... args) {
|
||||
if (null == target) {
|
||||
if(null == target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -181,11 +183,11 @@ public abstract class BeanUtils {
|
||||
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++) {
|
||||
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)) {
|
||||
if(!ClassUtils.isAssignable(oType, pType)) {
|
||||
newArgs.add(CONVERSION_SERVICE.convert(o, pType));
|
||||
} else {
|
||||
newArgs.add(o);
|
||||
@@ -193,15 +195,15 @@ public abstract class BeanUtils {
|
||||
}
|
||||
|
||||
Object rtnVal = m.invoke(target, newArgs.toArray());
|
||||
if ((returnType != Void.TYPE || returnType != Object.class)
|
||||
if((returnType != Void.TYPE || returnType != Object.class)
|
||||
&& null != rtnVal
|
||||
&& !ClassUtils.isAssignable(returnType, rtnVal.getClass())) {
|
||||
return CONVERSION_SERVICE.convert(rtnVal, returnType);
|
||||
} else {
|
||||
return (T) rtnVal;
|
||||
return (T)rtnVal;
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (Exception e) {
|
||||
} catch(IllegalArgumentException e) {
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class FluentBeanDeserializer extends StdDeserializer {
|
||||
public class FluentBeanDeserializer
|
||||
extends StdDeserializer {
|
||||
|
||||
private ConversionService conversionService;
|
||||
private ConversionService conversionService;
|
||||
private FluentBeanUtils.Metadata beanMeta;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -29,7 +30,7 @@ public class FluentBeanDeserializer extends StdDeserializer {
|
||||
this.conversionService = conversionService;
|
||||
this.beanMeta = FluentBeanUtils.metadata(valueClass);
|
||||
|
||||
if (!FluentBeanUtils.isFluentBean(valueClass)) {
|
||||
if(!FluentBeanUtils.isFluentBean(valueClass)) {
|
||||
throw new IllegalArgumentException("Class of type " + valueClass + " is not a FluentBean");
|
||||
}
|
||||
}
|
||||
@@ -39,46 +40,46 @@ public class FluentBeanDeserializer extends StdDeserializer {
|
||||
DeserializationContext ctxt)
|
||||
throws IOException,
|
||||
JsonProcessingException {
|
||||
if (jp.getCurrentToken() != JsonToken.START_OBJECT) {
|
||||
if(jp.getCurrentToken() != JsonToken.START_OBJECT) {
|
||||
throw ctxt.mappingException(_valueClass);
|
||||
}
|
||||
|
||||
Object bean;
|
||||
try {
|
||||
bean = _valueClass.newInstance();
|
||||
} catch (InstantiationException e) {
|
||||
} catch(InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
while (jp.nextToken() != JsonToken.END_OBJECT) {
|
||||
while(jp.nextToken() != JsonToken.END_OBJECT) {
|
||||
String name = jp.getCurrentName();
|
||||
Method setter = beanMeta.setters().get(name);
|
||||
|
||||
Object obj;
|
||||
if (null != setter) {
|
||||
if(null != setter) {
|
||||
Class<?> targetType = setter.getParameterTypes()[0];
|
||||
if (ClassUtils.isAssignable(targetType, Long.class)) {
|
||||
if(ClassUtils.isAssignable(targetType, Long.class)) {
|
||||
obj = jp.nextLongValue(-1);
|
||||
} else if (ClassUtils.isAssignable(targetType, Integer.class)) {
|
||||
} else if(ClassUtils.isAssignable(targetType, Integer.class)) {
|
||||
obj = jp.nextIntValue(-1);
|
||||
} else if (ClassUtils.isAssignable(targetType, Boolean.class)) {
|
||||
} else if(ClassUtils.isAssignable(targetType, Boolean.class)) {
|
||||
obj = jp.nextBooleanValue();
|
||||
} else {
|
||||
obj = jp.nextTextValue();
|
||||
}
|
||||
|
||||
if (null != obj) {
|
||||
if (!ClassUtils.isAssignable(obj.getClass(), targetType)) {
|
||||
if(null != obj) {
|
||||
if(!ClassUtils.isAssignable(obj.getClass(), targetType)) {
|
||||
obj = conversionService.convert(obj, targetType);
|
||||
}
|
||||
|
||||
try {
|
||||
setter.invoke(bean, obj);
|
||||
} catch (IllegalAccessException e) {
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
} catch(InvocationTargetException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,60 +16,62 @@ import org.springframework.util.ClassUtils;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class FluentBeanSerializer extends SerializerBase<Object> {
|
||||
public class FluentBeanSerializer
|
||||
extends SerializerBase<Object> {
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public FluentBeanSerializer( final Class t ) {
|
||||
super( t );
|
||||
public FluentBeanSerializer(final Class t) {
|
||||
super(t);
|
||||
|
||||
if ( !FluentBeanUtils.isFluentBean( t ) ) {
|
||||
throw new IllegalArgumentException( "Class of type " + t + " is not a FluentBean" );
|
||||
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 )
|
||||
public void serialize(final Object value,
|
||||
final JsonGenerator jgen,
|
||||
final SerializerProvider provider)
|
||||
throws IOException,
|
||||
JsonGenerationException {
|
||||
if ( null == value ) {
|
||||
provider.defaultSerializeNull( jgen );
|
||||
if(null == value) {
|
||||
provider.defaultSerializeNull(jgen);
|
||||
} else {
|
||||
Class<?> type = value.getClass();
|
||||
if ( ClassUtils.isAssignable( type, Collection.class ) ) {
|
||||
if(ClassUtils.isAssignable(type, Collection.class)) {
|
||||
jgen.writeStartArray();
|
||||
for ( Object o : (Collection) value ) {
|
||||
write( o, jgen, provider );
|
||||
for(Object o : (Collection)value) {
|
||||
write(o, jgen, provider);
|
||||
}
|
||||
jgen.writeEndArray();
|
||||
} else if ( ClassUtils.isAssignable( type, Map.class ) ) {
|
||||
} 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 );
|
||||
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 );
|
||||
write(value, jgen, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write( final Object value,
|
||||
final JsonGenerator jgen,
|
||||
final SerializerProvider provider ) throws IOException {
|
||||
private void write(final Object value,
|
||||
final JsonGenerator jgen,
|
||||
final SerializerProvider provider)
|
||||
throws IOException {
|
||||
Class<?> type = value.getClass();
|
||||
if ( ClassUtils.isAssignable( type, _handledType ) ) {
|
||||
if(ClassUtils.isAssignable(type, _handledType)) {
|
||||
jgen.writeStartObject();
|
||||
for ( String fname : FluentBeanUtils.metadata( type ).fieldNames() ) {
|
||||
jgen.writeFieldName( fname );
|
||||
write( FluentBeanUtils.get( fname, value ), jgen, provider );
|
||||
for(String fname : FluentBeanUtils.metadata(type).fieldNames()) {
|
||||
jgen.writeFieldName(fname);
|
||||
write(FluentBeanUtils.get(fname, value), jgen, provider);
|
||||
}
|
||||
jgen.writeEndObject();
|
||||
} else {
|
||||
jgen.writeObject( value );
|
||||
jgen.writeObject(value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,24 +22,27 @@ import org.springframework.util.ReflectionUtils;
|
||||
*/
|
||||
public abstract class FluentBeanUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(FluentBeanUtils.class);
|
||||
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 {
|
||||
@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 {
|
||||
@Override public void doWith(Field field)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
final String fname = field.getName();
|
||||
if (!fname.startsWith("_")) {
|
||||
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) {
|
||||
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) {
|
||||
} else if(method.getParameterTypes().length == 1) {
|
||||
meta.setters.put(fname, method);
|
||||
}
|
||||
meta.fieldNames.add(fname);
|
||||
@@ -58,13 +61,15 @@ public abstract class FluentBeanUtils {
|
||||
/**
|
||||
* Interrogate a bean and collect {@link Metadata} on it.
|
||||
*
|
||||
* @param targetType The type to interrogate.
|
||||
* @param targetType
|
||||
* The type to interrogate.
|
||||
*
|
||||
* @return {@link Metadata} for the fluent bean.
|
||||
*/
|
||||
public static Metadata metadata(Class<?> targetType) {
|
||||
try {
|
||||
return metadata.get(targetType);
|
||||
} catch (ExecutionException e) {
|
||||
} catch(ExecutionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@@ -72,27 +77,31 @@ public abstract class FluentBeanUtils {
|
||||
/**
|
||||
* Set the property of a fluent bean.
|
||||
*
|
||||
* @param property Name of the property to set.
|
||||
* @param value Value of the property.
|
||||
* @param bean Bean on which to set this property.
|
||||
* @param property
|
||||
* Name of the property to set.
|
||||
* @param value
|
||||
* Value of the property.
|
||||
* @param bean
|
||||
* Bean on which to set this property.
|
||||
*
|
||||
* @return Usually {@literal null} but will return whatever the "setter" returns, which could be {@this} or something
|
||||
* else.
|
||||
*/
|
||||
public static Object set(String property, Object value, Object bean) {
|
||||
if (null == bean) {
|
||||
if(null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method setter = metadata.get(type).setters.get(property);
|
||||
if (null != setter) {
|
||||
if(null != setter) {
|
||||
return setter.invoke(bean, value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (log.isDebugEnabled()) {
|
||||
} catch(Throwable t) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug(t.getMessage(), t);
|
||||
}
|
||||
return null;
|
||||
@@ -102,25 +111,28 @@ public abstract class FluentBeanUtils {
|
||||
/**
|
||||
* Get the value of a property.
|
||||
*
|
||||
* @param property Name of the property.
|
||||
* @param bean Bean of which to get the property.
|
||||
* @param property
|
||||
* Name of the property.
|
||||
* @param bean
|
||||
* Bean of which to get the property.
|
||||
*
|
||||
* @return Value of the property. Could be {@literal null}
|
||||
*/
|
||||
public static Object get(String property, Object bean) {
|
||||
if (null == bean) {
|
||||
if(null == bean) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<?> type = bean.getClass();
|
||||
try {
|
||||
Method getter = metadata.get(type).getters.get(property);
|
||||
if (null != getter) {
|
||||
if(null != getter) {
|
||||
return getter.invoke(bean);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (log.isDebugEnabled()) {
|
||||
} catch(Throwable t) {
|
||||
if(log.isDebugEnabled()) {
|
||||
log.debug(t.getMessage(), t);
|
||||
}
|
||||
return null;
|
||||
@@ -132,21 +144,23 @@ public abstract class FluentBeanUtils {
|
||||
* to a field of the same name. A "getter" is that method which is named the same as the field and has 0 parameters.
|
||||
* The "setter" is that method which is named the same as the field and has a single argument.
|
||||
*
|
||||
* @param type The class to inspect.
|
||||
* @param type
|
||||
* The class to inspect.
|
||||
*
|
||||
* @return {@literal true} if this looks like a fluent bean, {@literal false} otherwise.
|
||||
*/
|
||||
public static boolean isFluentBean(Class<?> type) {
|
||||
try {
|
||||
return metadata.get(type).getters.size() > 0;
|
||||
} catch (ExecutionException e) {
|
||||
} 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>();
|
||||
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;
|
||||
|
||||
@@ -24,8 +24,11 @@ public abstract class UriUtils {
|
||||
* http://localhost:8080/data/person}, this method would report the baseUri being a valid base of the given URI.
|
||||
* </p>
|
||||
*
|
||||
* @param baseUri {@link URI} to check.
|
||||
* @param uri {@link URI} against which to compare the base.
|
||||
* @param baseUri
|
||||
* {@link URI} to check.
|
||||
* @param uri
|
||||
* {@link URI} against which to compare the base.
|
||||
*
|
||||
* @return {@literal true} if the baseUri is valid against the given {@link URI}, {@literal false} otherwise.
|
||||
*/
|
||||
public static boolean validBaseUri(URI baseUri, URI uri) {
|
||||
@@ -41,16 +44,21 @@ public abstract class UriUtils {
|
||||
* second time passing a relative {@link URI} of "1".
|
||||
* </p>
|
||||
*
|
||||
* @param baseUri base {@link URI}
|
||||
* @param uri {@link URI} to explode and iteratre over.
|
||||
* @param handler {@link Handler} to call for each segment of the URI's path.
|
||||
* @param <V> Return type of the handler.
|
||||
* @param baseUri
|
||||
* base {@link URI}
|
||||
* @param uri
|
||||
* {@link URI} to explode and iteratre over.
|
||||
* @param handler
|
||||
* {@link Handler} to call for each segment of the URI's path.
|
||||
* @param <V>
|
||||
* Return type of the handler.
|
||||
*
|
||||
* @return Handler return value, or possibly {@literal null}.
|
||||
*/
|
||||
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) {
|
||||
for(URI u : uris) {
|
||||
v = handler.handle(u);
|
||||
}
|
||||
return v;
|
||||
@@ -63,16 +71,19 @@ public abstract class UriUtils {
|
||||
* in
|
||||
* a {@link Stack} of relative {@link URI}s of size 2--one for "person" and one for "1".</p>
|
||||
*
|
||||
* @param baseUri base {@link URI}
|
||||
* @param uri {@link URI} to explode
|
||||
* @param baseUri
|
||||
* base {@link URI}
|
||||
* @param uri
|
||||
* {@link URI} to explode
|
||||
*
|
||||
* @return {@link Stack} of relative {@link URI}s.
|
||||
*/
|
||||
public static Stack<URI> explode(URI baseUri, URI uri) {
|
||||
Stack<URI> uris = new Stack<URI>();
|
||||
if (StringUtils.hasText(uri.getPath())) {
|
||||
if(StringUtils.hasText(uri.getPath())) {
|
||||
URI relativeUri = baseUri.relativize(uri);
|
||||
if (StringUtils.hasText(relativeUri.getPath())) {
|
||||
for (String part : relativeUri.getPath().split("/")) {
|
||||
if(StringUtils.hasText(relativeUri.getPath())) {
|
||||
for(String part : relativeUri.getPath().split("/")) {
|
||||
uris.add(URI.create(part + (StringUtils.hasText(uri.getQuery()) ? "?" + uri.getQuery() : "")));
|
||||
}
|
||||
}
|
||||
@@ -86,38 +97,41 @@ public abstract class UriUtils {
|
||||
* <p>e.g. merging base URI {@literal http://localhost:8080/data} and relative uri {@literal person/1?name=John+Doe}
|
||||
* would result in an absolute URI of {@literal http://localhost:8080/data/person/1?name=John+Doe}</p>
|
||||
*
|
||||
* @param baseUri base {@link URI}
|
||||
* @param uris {@link URI}s to merge
|
||||
* @param baseUri
|
||||
* base {@link URI}
|
||||
* @param uris
|
||||
* {@link URI}s to merge
|
||||
*
|
||||
* @return {@link URI} that is the combination of all the given (possibly relative, possibly absolute) URIs.
|
||||
*/
|
||||
public static URI merge(URI baseUri, URI... uris) {
|
||||
StringBuilder query = new StringBuilder();
|
||||
|
||||
UriComponentsBuilder ub = UriComponentsBuilder.fromUri(baseUri);
|
||||
for (URI uri : uris) {
|
||||
for(URI uri : uris) {
|
||||
String s = uri.getScheme();
|
||||
if (null != s) {
|
||||
if(null != s) {
|
||||
ub.scheme(s);
|
||||
}
|
||||
|
||||
s = uri.getUserInfo();
|
||||
if (null != s) {
|
||||
if(null != s) {
|
||||
ub.userInfo(s);
|
||||
}
|
||||
|
||||
s = uri.getHost();
|
||||
if (null != s) {
|
||||
if(null != s) {
|
||||
ub.host(s);
|
||||
}
|
||||
|
||||
int i = uri.getPort();
|
||||
if (i > 0) {
|
||||
if(i > 0) {
|
||||
ub.port(i);
|
||||
}
|
||||
|
||||
s = uri.getPath();
|
||||
if (null != s) {
|
||||
if (!uri.isAbsolute() && StringUtils.hasText(s)) {
|
||||
if(null != s) {
|
||||
if(!uri.isAbsolute() && StringUtils.hasText(s)) {
|
||||
ub.pathSegment(s);
|
||||
} else {
|
||||
ub.path(s);
|
||||
@@ -125,20 +139,20 @@ public abstract class UriUtils {
|
||||
}
|
||||
|
||||
s = uri.getQuery();
|
||||
if (null != s) {
|
||||
if (query.length() > 0) {
|
||||
if(null != s) {
|
||||
if(query.length() > 0) {
|
||||
query.append("&");
|
||||
}
|
||||
query.append(s);
|
||||
}
|
||||
|
||||
s = uri.getFragment();
|
||||
if (null != s) {
|
||||
if(null != s) {
|
||||
ub.fragment(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (query.length() > 0) {
|
||||
if(query.length() > 0) {
|
||||
ub.query(query.toString());
|
||||
}
|
||||
|
||||
@@ -149,14 +163,15 @@ public abstract class UriUtils {
|
||||
* Just the path portion of the {@link URI}, but with any trailing slash "/" removed.
|
||||
*
|
||||
* @param uri
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static String path(URI uri) {
|
||||
if (null == uri) {
|
||||
if(null == uri) {
|
||||
return null;
|
||||
}
|
||||
String s = uri.getPath();
|
||||
if (s.endsWith("/")) {
|
||||
if(s.endsWith("/")) {
|
||||
return s.substring(0, s.length() - 1);
|
||||
} else {
|
||||
return s;
|
||||
@@ -166,8 +181,11 @@ public abstract class UriUtils {
|
||||
/**
|
||||
* The very last segment of the {@link URI}.
|
||||
*
|
||||
* @param baseUri base {@link URI}
|
||||
* @param uri {@link URI} to explode
|
||||
* @param baseUri
|
||||
* base {@link URI}
|
||||
* @param uri
|
||||
* {@link URI} to explode
|
||||
*
|
||||
* @return Relative {@link URI} that is the last segment of the path for the given URI.
|
||||
*/
|
||||
public static URI tail(URI baseUri, URI uri) {
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
dependencies {
|
||||
|
||||
// Spring
|
||||
compile("org.springframework:spring-orm:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-oxm:$springVersion") { force = true }
|
||||
compile("org.springframework:spring-tx:$springVersion") { force = true }
|
||||
//compile("org.springframework:spring-orm:$springVersion") { force = true }
|
||||
//compile("org.springframework:spring-oxm:$springVersion") { force = true }
|
||||
|
||||
// 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-commons-core:$sdCommonsVersion"
|
||||
compile "org.springframework.data:spring-data-jpa:$sdJpaVersion"
|
||||
|
||||
// Exporter core
|
||||
|
||||
@@ -42,7 +42,9 @@ public interface AttributeMetadata {
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Collection}.
|
||||
*
|
||||
* @param target The entity to inspect for this attribute.
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Collection}
|
||||
*/
|
||||
Collection<?> asCollection(Object target);
|
||||
@@ -57,7 +59,9 @@ public interface AttributeMetadata {
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Set}.
|
||||
*
|
||||
* @param target The entity to inspect for this attribute.
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Set}
|
||||
*/
|
||||
Set<?> asSet(Object target);
|
||||
@@ -72,7 +76,9 @@ public interface AttributeMetadata {
|
||||
/**
|
||||
* Get the path of this attribute as a {@link Map}.
|
||||
*
|
||||
* @param target The entity to inspect for this attribute.
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value as a {@link Map}
|
||||
*/
|
||||
Map asMap(Object target);
|
||||
@@ -80,7 +86,9 @@ public interface AttributeMetadata {
|
||||
/**
|
||||
* Get the path of this attribute.
|
||||
*
|
||||
* @param target The entity to inspect for this attribute.
|
||||
* @param target
|
||||
* The entity to inspect for this attribute.
|
||||
*
|
||||
* @return attribute value
|
||||
*/
|
||||
Object get(Object target);
|
||||
@@ -88,8 +96,11 @@ public interface AttributeMetadata {
|
||||
/**
|
||||
* Set the path of this attribute.
|
||||
*
|
||||
* @param value Value to set on this attribute.
|
||||
* @param target The entity to set this attribute's value on.
|
||||
* @param value
|
||||
* Value to set on this attribute.
|
||||
* @param target
|
||||
* The entity to set this attribute's value on.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
AttributeMetadata set(Object value, Object target);
|
||||
|
||||
@@ -47,7 +47,9 @@ public interface EntityMetadata<A extends AttributeMetadata> {
|
||||
/**
|
||||
* Get {@link AttributeMetadata} by name.
|
||||
*
|
||||
* @param name The name of the attribute.
|
||||
* @param name
|
||||
* The name of the attribute.
|
||||
*
|
||||
* @return {@link AttributeMetadata} or {@literal null} if that attribute doesn't exist.
|
||||
*/
|
||||
A attribute(String name);
|
||||
|
||||
@@ -6,7 +6,8 @@ import org.springframework.validation.Errors;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryConstraintViolationException extends DataIntegrityViolationException {
|
||||
public class RepositoryConstraintViolationException
|
||||
extends DataIntegrityViolationException {
|
||||
|
||||
private Errors errors;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
InitializingBean {
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
protected Repositories repositories;
|
||||
protected Repositories repositories;
|
||||
protected List<String> exportOnlyTheseClasses = Collections.emptyList();
|
||||
protected Map<String, M> repositoryMetadata;
|
||||
|
||||
@@ -42,22 +42,24 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* Set the class names of only those Repositories you want exported.
|
||||
* Default is to export all found Repositories.
|
||||
*
|
||||
* @param exportOnlyTheseClasses {@link List} of class names to export.
|
||||
* @param exportOnlyTheseClasses
|
||||
* {@link List} of class names to export.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public M setExportOnlyTheseClasses(List<String> exportOnlyTheseClasses) {
|
||||
this.exportOnlyTheseClasses = exportOnlyTheseClasses;
|
||||
return (M) this;
|
||||
return (M)this;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,20 +68,22 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
* @return {@link List} of class names to export.
|
||||
*/
|
||||
public Set<String> repositoryNames() {
|
||||
findRepositories();
|
||||
refresh();
|
||||
return repositoryMetadata.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a Repository being exporter that supports this domain type?
|
||||
*
|
||||
* @param domainType Type of the domain class.
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@literal true} if a Repository is being exported, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean hasRepositoryFor(Class<?> domainType) {
|
||||
findRepositories();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
refresh();
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -89,13 +93,15 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
/**
|
||||
* Get the RepositoryMetadata for the Repository responsible for this domain type.
|
||||
*
|
||||
* @param domainType Type of the domain class.
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(Class<?> domainType) {
|
||||
findRepositories();
|
||||
for (M repoMeta : repositoryMetadata.values()) {
|
||||
if (repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
refresh();
|
||||
for(M repoMeta : repositoryMetadata.values()) {
|
||||
if(repoMeta.domainType().isAssignableFrom(domainType)) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
@@ -105,11 +111,13 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
/**
|
||||
* Get the {@link RepositoryMetadata} for the Repository exported under the given name.
|
||||
*
|
||||
* @param name Name a Repository would be exported under.
|
||||
* @param name
|
||||
* Name a Repository would be exported under.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} instance
|
||||
*/
|
||||
public M repositoryMetadataFor(String name) {
|
||||
findRepositories();
|
||||
refresh();
|
||||
return repositoryMetadata.get(name);
|
||||
}
|
||||
|
||||
@@ -118,25 +126,27 @@ public abstract class RepositoryExporter<M extends RepositoryMetadata<E>, E exte
|
||||
Class<?> repoClass,
|
||||
Repositories repositories);
|
||||
|
||||
private void findRepositories() {
|
||||
if (null == repositories) {
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
for (Class<?> domainType : repositories) {
|
||||
if (exportOnlyTheseClasses.isEmpty() || exportOnlyTheseClasses.contains(domainType.getName())) {
|
||||
Class<?> repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface();
|
||||
String name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
RestResource resourceAnno = repoClass.getAnnotation(RestResource.class);
|
||||
boolean exported = true;
|
||||
if (null != resourceAnno) {
|
||||
if (StringUtils.hasText(resourceAnno.path())) {
|
||||
name = resourceAnno.path();
|
||||
}
|
||||
exported = resourceAnno.exported();
|
||||
}
|
||||
if (exported) {
|
||||
repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories));
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public void refresh() {
|
||||
if(null != repositories) {
|
||||
return;
|
||||
}
|
||||
repositories = new Repositories(applicationContext);
|
||||
repositoryMetadata = new HashMap<String, M>();
|
||||
for(Class<?> domainType : repositories) {
|
||||
if(exportOnlyTheseClasses.isEmpty() || exportOnlyTheseClasses.contains(domainType.getName())) {
|
||||
Class<?> repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface();
|
||||
String name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", ""));
|
||||
RestResource resourceAnno = repoClass.getAnnotation(RestResource.class);
|
||||
boolean exported = true;
|
||||
if(null != resourceAnno) {
|
||||
if(StringUtils.hasText(resourceAnno.path())) {
|
||||
name = resourceAnno.path();
|
||||
}
|
||||
exported = resourceAnno.exported();
|
||||
}
|
||||
if(exported) {
|
||||
repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters Export this {@link List} of {@link RepositoryExporter}s.
|
||||
* @param repositoryExporters
|
||||
* Export this {@link List} of {@link RepositoryExporter}s.
|
||||
*/
|
||||
public void setRepositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
this.repositoryExporters = repositoryExporters;
|
||||
@@ -46,39 +47,44 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
/**
|
||||
* Set the List of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @param repositoryExporters Export this {@link List} of {@link RepositoryExporter}s.
|
||||
* @param repositoryExporters
|
||||
* Export this {@link List} of {@link RepositoryExporter}s.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(List<RepositoryExporter> repositoryExporters) {
|
||||
setRepositoryExporters(repositoryExporters);
|
||||
return (S) this;
|
||||
return (S)this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link RepositoryExporter}s to use.
|
||||
*
|
||||
* @param repositoryExporter
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public S repositoryExporters(RepositoryExporter... repositoryExporter) {
|
||||
setRepositoryExporters(Arrays.asList(repositoryExporter));
|
||||
return (S) this;
|
||||
return (S)this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find {@link RepositoryMetadata} for the {@link org.springframework.data.repository.Repository} exported under this
|
||||
* name.
|
||||
*
|
||||
* @param name URL segment name.
|
||||
* @param name
|
||||
* URL segment name.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(String name) {
|
||||
for (RepositoryExporter exporter : repositoryExporters) {
|
||||
for(RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(name);
|
||||
if (null != repoMeta) {
|
||||
if(null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
@@ -89,14 +95,16 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
* Find the {@link RepositoryMetadata} for the {@link org.springframework.data.repository.Repository} responsible for
|
||||
* the given domain type.
|
||||
*
|
||||
* @param domainType Type of the domain class.
|
||||
* @param domainType
|
||||
* Type of the domain class.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(Class<?> domainType) {
|
||||
for (RepositoryExporter exporter : repositoryExporters) {
|
||||
for(RepositoryExporter exporter : repositoryExporters) {
|
||||
RepositoryMetadata repoMeta = exporter.repositoryMetadataFor(domainType);
|
||||
if (null != repoMeta) {
|
||||
if(null != repoMeta) {
|
||||
return repoMeta;
|
||||
}
|
||||
}
|
||||
@@ -107,12 +115,14 @@ public abstract class RepositoryExporterSupport<S extends RepositoryExporterSupp
|
||||
* Find the {@link RepositoryMetadata} for an attribute of an entity which is possibly managed by a {@link
|
||||
* org.springframework.data.repository.Repository}.
|
||||
*
|
||||
* @param attrMeta {@link AttributeMetadata} of a possibly-managed entity.
|
||||
* @param attrMeta
|
||||
* {@link AttributeMetadata} of a possibly-managed entity.
|
||||
*
|
||||
* @return {@link RepositoryMetadata} or {@literal null} if none found.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked"})
|
||||
protected RepositoryMetadata repositoryMetadataFor(AttributeMetadata attrMeta) {
|
||||
if (null != attrMeta.elementType()) {
|
||||
if(null != attrMeta.elementType()) {
|
||||
return repositoryMetadataFor(attrMeta.elementType());
|
||||
} else {
|
||||
return repositoryMetadataFor(attrMeta.type());
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.repository.invoke.RepositoryQueryMethod;
|
||||
|
||||
/**
|
||||
* Encapsulates necessary metadata about a {@link Repository}.
|
||||
@@ -56,10 +57,13 @@ public interface RepositoryMetadata<E extends EntityMetadata<? extends Attribute
|
||||
E entityMetadata();
|
||||
|
||||
/**
|
||||
* Get a {@link RepositoryQueryMethod} by key.
|
||||
* Get a {@link org.springframework.data.rest.repository.invoke.RepositoryQueryMethod} by key.
|
||||
*
|
||||
* @param key Segment of the URL to find a query method for.
|
||||
* @return Found {@link RepositoryQueryMethod} or {@literal null} if none found.
|
||||
* @param key
|
||||
* Segment of the URL to find a query method for.
|
||||
*
|
||||
* @return Found {@link org.springframework.data.rest.repository.invoke.RepositoryQueryMethod} or {@literal null} if
|
||||
* none found.
|
||||
*/
|
||||
RepositoryQueryMethod queryMethod(String key);
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import org.springframework.dao.DataAccessResourceFailureException;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryNotFoundException extends DataAccessResourceFailureException {
|
||||
public class RepositoryNotFoundException
|
||||
extends DataAccessResourceFailureException {
|
||||
|
||||
public RepositoryNotFoundException(String msg) {
|
||||
super(msg);
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryQueryMethod {
|
||||
|
||||
private static final LocalVariableTableParameterNameDiscoverer nameLookup = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private Method method;
|
||||
private Class<?>[] paramTypes;
|
||||
private String[] paramNames;
|
||||
|
||||
public RepositoryQueryMethod(Method method) {
|
||||
this.method = method;
|
||||
paramTypes = method.getParameterTypes();
|
||||
paramNames = nameLookup.getParameterNames(method);
|
||||
if (null == paramNames) {
|
||||
paramNames = new String[paramTypes.length];
|
||||
}
|
||||
Annotation[][] paramAnnos = method.getParameterAnnotations();
|
||||
for (int i = 0; i < paramAnnos.length; i++) {
|
||||
if (paramAnnos[i].length > 0) {
|
||||
for (Annotation anno : paramAnnos[i]) {
|
||||
if (Param.class.isAssignableFrom(anno.getClass())) {
|
||||
Param p = (Param) anno;
|
||||
paramNames[i] = p.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null == paramNames[i]) {
|
||||
paramNames[i] = "arg" + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?>[] paramTypes() {
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
public String[] paramNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
public Method method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,13 +11,14 @@ import org.springframework.validation.ObjectError;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class ValidationErrors extends AbstractErrors {
|
||||
public class ValidationErrors
|
||||
extends AbstractErrors {
|
||||
|
||||
private String name;
|
||||
private Object entity;
|
||||
private String name;
|
||||
private Object entity;
|
||||
private EntityMetadata entityMetadata;
|
||||
private List<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
|
||||
public ValidationErrors(String name, Object entity, EntityMetadata entityMetadata) {
|
||||
this.name = name;
|
||||
|
||||
@@ -23,7 +23,8 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@@ -33,17 +34,17 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
}
|
||||
|
||||
@Override public final void onApplicationEvent(RepositoryEvent event) {
|
||||
if (event instanceof BeforeSaveEvent) {
|
||||
if(event instanceof BeforeSaveEvent) {
|
||||
onBeforeSave(event.getSource());
|
||||
} else if (event instanceof AfterSaveEvent) {
|
||||
} else if(event instanceof AfterSaveEvent) {
|
||||
onAfterSave(event.getSource());
|
||||
} else if (event instanceof BeforeLinkSaveEvent) {
|
||||
onBeforeLinkSave(event.getSource(), ((BeforeLinkSaveEvent) event).getLinked());
|
||||
} else if (event instanceof AfterLinkSaveEvent) {
|
||||
onAfterLinkSave(event.getSource(), ((AfterLinkSaveEvent) event).getLinked());
|
||||
} else if (event instanceof BeforeDeleteEvent) {
|
||||
} else if(event instanceof BeforeLinkSaveEvent) {
|
||||
onBeforeLinkSave(event.getSource(), ((BeforeLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof AfterLinkSaveEvent) {
|
||||
onAfterLinkSave(event.getSource(), ((AfterLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof BeforeDeleteEvent) {
|
||||
onBeforeDelete(event.getSource());
|
||||
} else if (event instanceof AfterDeleteEvent) {
|
||||
} else if(event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete(event.getSource());
|
||||
}
|
||||
}
|
||||
@@ -53,14 +54,16 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onBeforeSave(Object entity) {}
|
||||
protected void onBeforeSave(Object entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterSave} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onAfterSave(Object entity) {}
|
||||
protected void onAfterSave(Object entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkSave} events.
|
||||
@@ -68,7 +71,8 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
* @param parent
|
||||
* @param linked
|
||||
*/
|
||||
protected void onBeforeLinkSave(Object parent, Object linked) {}
|
||||
protected void onBeforeLinkSave(Object parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkSave} events.
|
||||
@@ -76,20 +80,23 @@ public abstract class AbstractRepositoryEventListener<T extends AbstractReposito
|
||||
* @param parent
|
||||
* @param linked
|
||||
*/
|
||||
protected void onAfterLinkSave(Object parent, Object linked) {}
|
||||
protected void onAfterLinkSave(Object parent, Object linked) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onBeforeDelete(Object entity) {}
|
||||
protected void onBeforeDelete(Object entity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterDelete} events.
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
protected void onAfterDelete(Object entity) {}
|
||||
protected void onAfterDelete(Object entity) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterDeleteEvent extends RepositoryEvent {
|
||||
public class AfterDeleteEvent
|
||||
extends RepositoryEvent {
|
||||
public AfterDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterLinkSaveEvent extends LinkSaveEvent {
|
||||
public class AfterLinkSaveEvent
|
||||
extends LinkSaveEvent {
|
||||
public AfterLinkSaveEvent(Object source, Object child) {
|
||||
super(source, child);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class AfterSaveEvent extends RepositoryEvent {
|
||||
public class AfterSaveEvent
|
||||
extends RepositoryEvent {
|
||||
public AfterSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
ApplicationContextAware,
|
||||
InitializingBean {
|
||||
|
||||
private String basePackage;
|
||||
private String basePackage;
|
||||
private ApplicationContext applicationContext;
|
||||
private Multimap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = ArrayListMultimap.create();
|
||||
|
||||
@@ -48,7 +48,8 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
this.basePackage = basePackage;
|
||||
}
|
||||
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
@Override public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@@ -64,7 +65,9 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
/**
|
||||
* Set the base package in which to search for event handlers.
|
||||
*
|
||||
* @param basePackage Base package to search for handlers.
|
||||
* @param basePackage
|
||||
* Base package to search for handlers.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
public AnnotatedHandlerRepositoryEventListener setBasePackage(String basePackage) {
|
||||
@@ -84,7 +87,9 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
/**
|
||||
* Set the base package in which to search for event handlers.
|
||||
*
|
||||
* @param basePackage Base package to search for handlers.
|
||||
* @param basePackage
|
||||
* Base package to search for handlers.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
public AnnotatedHandlerRepositoryEventListener basePackage(String basePackage) {
|
||||
@@ -92,23 +97,25 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
@Override public void afterPropertiesSet()
|
||||
throws Exception {
|
||||
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(RepositoryEventHandler.class, true, true));
|
||||
for (BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) {
|
||||
for(BeanDefinition beanDef : scanner.findCandidateComponents(basePackage)) {
|
||||
String typeName = beanDef.getBeanClassName();
|
||||
Class<?> handlerType = ClassUtils.forName(typeName, ClassUtils.getDefaultClassLoader());
|
||||
RepositoryEventHandler typeAnno = handlerType.getAnnotation(RepositoryEventHandler.class);
|
||||
Class<?>[] targetTypes = typeAnno.value();
|
||||
if (targetTypes.length == 0) {
|
||||
if(targetTypes.length == 0) {
|
||||
targetTypes = new Class<?>[]{null};
|
||||
}
|
||||
for (final Class<?> targetType : targetTypes) {
|
||||
for (final Object handler : applicationContext.getBeansOfType(handlerType).values()) {
|
||||
for(final Class<?> targetType : targetTypes) {
|
||||
for(final Object handler : applicationContext.getBeansOfType(handlerType).values()) {
|
||||
ReflectionUtils.doWithMethods(
|
||||
handler.getClass(),
|
||||
new ReflectionUtils.MethodCallback() {
|
||||
@Override public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
@Override public void doWith(Method method)
|
||||
throws IllegalArgumentException, IllegalAccessException {
|
||||
inspect(targetType, handler, method, HandleBeforeSave.class, BeforeSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleAfterSave.class, AfterSaveEvent.class);
|
||||
inspect(targetType, handler, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class);
|
||||
@@ -133,21 +140,21 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
|
||||
@Override public void onApplicationEvent(RepositoryEvent event) {
|
||||
Class<? extends RepositoryEvent> eventType = event.getClass();
|
||||
if (handlerMethods.containsKey(eventType)) {
|
||||
for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {
|
||||
if(handlerMethods.containsKey(eventType)) {
|
||||
for(EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {
|
||||
try {
|
||||
Object src = event.getSource();
|
||||
if (ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
if(ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
List<Object> params = new ArrayList<Object>();
|
||||
params.add(src);
|
||||
if (event instanceof BeforeLinkSaveEvent) {
|
||||
params.add(((BeforeLinkSaveEvent) event).getLinked());
|
||||
} else if (event instanceof AfterLinkSaveEvent) {
|
||||
params.add(((AfterLinkSaveEvent) event).getLinked());
|
||||
if(event instanceof BeforeLinkSaveEvent) {
|
||||
params.add(((BeforeLinkSaveEvent)event).getLinked());
|
||||
} else if(event instanceof AfterLinkSaveEvent) {
|
||||
params.add(((AfterLinkSaveEvent)event).getLinked());
|
||||
}
|
||||
handlerMethod.method.invoke(handlerMethod.handler, params.toArray());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch(Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@@ -160,31 +167,31 @@ public class AnnotatedHandlerRepositoryEventListener
|
||||
Class<T> annoType,
|
||||
Class<? extends RepositoryEvent> eventType) {
|
||||
T anno = method.getAnnotation(annoType);
|
||||
if (null != anno) {
|
||||
if(null != anno) {
|
||||
try {
|
||||
Class<?>[] targetTypes;
|
||||
if (null == targetType) {
|
||||
targetTypes = (Class<?>[]) anno.getClass().getMethod("value", new Class[0]).invoke(anno);
|
||||
if(null == targetType) {
|
||||
targetTypes = (Class<?>[])anno.getClass().getMethod("value", new Class[0]).invoke(anno);
|
||||
} else {
|
||||
targetTypes = new Class<?>[]{targetType};
|
||||
}
|
||||
for (Class<?> type : targetTypes) {
|
||||
for(Class<?> type : targetTypes) {
|
||||
handlerMethods.put(eventType,
|
||||
new EventHandlerMethod(type,
|
||||
handler,
|
||||
method));
|
||||
}
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (InvocationTargetException ignored) {
|
||||
} catch (IllegalAccessException ignored) {
|
||||
} catch(NoSuchMethodException ignored) {
|
||||
} catch(InvocationTargetException ignored) {
|
||||
} catch(IllegalAccessException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EventHandlerMethod {
|
||||
final Class<?> targetType;
|
||||
final Method method;
|
||||
final Object handler;
|
||||
final Method method;
|
||||
final Object handler;
|
||||
|
||||
private EventHandlerMethod(Class<?> targetType,
|
||||
Object handler,
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeDeleteEvent extends RepositoryEvent {
|
||||
public class BeforeDeleteEvent
|
||||
extends RepositoryEvent {
|
||||
public BeforeDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeLinkSaveEvent extends LinkSaveEvent {
|
||||
public class BeforeLinkSaveEvent
|
||||
extends LinkSaveEvent {
|
||||
public BeforeLinkSaveEvent(Object source, Object linked) {
|
||||
super(source, linked);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeSaveEvent extends RepositoryEvent {
|
||||
public class BeforeSaveEvent
|
||||
extends RepositoryEvent {
|
||||
public BeforeSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ package org.springframework.data.rest.repository.context;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class LinkSaveEvent extends RepositoryEvent {
|
||||
public abstract class LinkSaveEvent
|
||||
extends RepositoryEvent {
|
||||
|
||||
private final Object linked;
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import org.springframework.context.ApplicationEvent;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public abstract class RepositoryEvent extends ApplicationEvent {
|
||||
public abstract class RepositoryEvent
|
||||
extends ApplicationEvent {
|
||||
protected RepositoryEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
@@ -29,20 +29,21 @@ public class ValidatingRepositoryEventListener
|
||||
|
||||
private Multimap<String, Validator> validators = ArrayListMultimap.create();
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
if (validators.size() == 0) {
|
||||
for (Map.Entry<String, Validator> entry : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
|
||||
Validator.class)
|
||||
.entrySet()) {
|
||||
@Override public void afterPropertiesSet()
|
||||
throws Exception {
|
||||
if(validators.size() == 0) {
|
||||
for(Map.Entry<String, Validator> entry : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext,
|
||||
Validator.class)
|
||||
.entrySet()) {
|
||||
String name = null;
|
||||
Validator v = entry.getValue();
|
||||
|
||||
if (entry.getKey().contains("Save")) {
|
||||
if(entry.getKey().contains("Save")) {
|
||||
name = entry.getKey().substring(0, entry.getKey().indexOf("Save") + 4);
|
||||
} else if (entry.getKey().contains("Delete")) {
|
||||
} else if(entry.getKey().contains("Delete")) {
|
||||
name = entry.getKey().substring(0, entry.getKey().indexOf("Delete") + 6);
|
||||
}
|
||||
if (null != name) {
|
||||
if(null != name) {
|
||||
this.validators.put(name, v);
|
||||
}
|
||||
}
|
||||
@@ -61,11 +62,13 @@ public class ValidatingRepositoryEventListener
|
||||
/**
|
||||
* Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
|
||||
*
|
||||
* @param validators A Map of Validators to wire.
|
||||
* @param validators
|
||||
* A Map of Validators to wire.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
public ValidatingRepositoryEventListener setValidators(Map<String, Collection<Validator>> validators) {
|
||||
for (Map.Entry<String, Collection<Validator>> entry : validators.entrySet()) {
|
||||
for(Map.Entry<String, Collection<Validator>> entry : validators.entrySet()) {
|
||||
this.validators.replaceValues(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return this;
|
||||
@@ -74,8 +77,11 @@ public class ValidatingRepositoryEventListener
|
||||
/**
|
||||
* Add a {@link Validator} that will be triggered on the given event.
|
||||
*
|
||||
* @param event The event to listen for.
|
||||
* @param validator The Validator to execute when that event fires.
|
||||
* @param event
|
||||
* The event to listen for.
|
||||
* @param validator
|
||||
* The Validator to execute when that event fires.
|
||||
*
|
||||
* @return @this
|
||||
*/
|
||||
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
|
||||
@@ -109,21 +115,21 @@ public class ValidatingRepositoryEventListener
|
||||
|
||||
private Errors validate(String event, Object o) {
|
||||
Errors errors = null;
|
||||
if (null != o) {
|
||||
if(null != o) {
|
||||
Class<?> domainType = o.getClass();
|
||||
errors = new ValidationErrors(domainType.getSimpleName(),
|
||||
o,
|
||||
repositoryMetadataFor(domainType).entityMetadata());
|
||||
Collection<Validator> validators = this.validators.get(event);
|
||||
if (null != validators) {
|
||||
for (Validator v : validators) {
|
||||
if (v.supports(o.getClass())) {
|
||||
if(null != validators) {
|
||||
for(Validator v : validators) {
|
||||
if(v.supports(o.getClass())) {
|
||||
LOG.debug(event + ": " + o + " with " + v);
|
||||
ValidationUtils.invokeValidator(v, o, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.getErrorCount() > 0) {
|
||||
if(errors.getErrorCount() > 0) {
|
||||
throw new RepositoryConstraintViolationException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.springframework.data.rest.repository;
|
||||
package org.springframework.data.rest.repository.invoke;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
@@ -23,16 +22,16 @@ public class RepositoryMethod {
|
||||
FIND_ONE,
|
||||
SAVE;
|
||||
|
||||
public static Type fromMethodName( String s ) {
|
||||
if ( "count".equals( s ) ) {
|
||||
public static Type fromMethodName(String s) {
|
||||
if("count".equals(s)) {
|
||||
return COUNT;
|
||||
} else if ( "delete".equals( s ) ) {
|
||||
} else if("delete".equals(s)) {
|
||||
return DELETE;
|
||||
} else if ( "findAll".equals( s ) ) {
|
||||
} else if("findAll".equals(s)) {
|
||||
return FIND_ALL;
|
||||
} else if ( "findOne".equals( s ) ) {
|
||||
} else if("findOne".equals(s)) {
|
||||
return FIND_ONE;
|
||||
} else if ( "save".equals( s ) ) {
|
||||
} else if("save".equals(s)) {
|
||||
return SAVE;
|
||||
} else {
|
||||
return CUSTOM;
|
||||
@@ -40,7 +39,7 @@ public class RepositoryMethod {
|
||||
}
|
||||
|
||||
public String toMethodName() {
|
||||
switch (this) {
|
||||
switch(this) {
|
||||
case COUNT:
|
||||
return "count";
|
||||
case DELETE:
|
||||
@@ -58,49 +57,49 @@ public class RepositoryMethod {
|
||||
|
||||
}
|
||||
|
||||
public static final ReflectionUtils.MethodFilter USER_METHODS = new ReflectionUtils.MethodFilter() {
|
||||
@Override public boolean matches( Method method ) {
|
||||
public static final ReflectionUtils.MethodFilter USER_METHODS = new ReflectionUtils.MethodFilter() {
|
||||
@Override public boolean matches(Method method) {
|
||||
return (!method.isSynthetic()
|
||||
&& !method.isBridge()
|
||||
&& method.getDeclaringClass() != Object.class
|
||||
&& !method.getName().contains( "$" ));
|
||||
&& !method.getName().contains("$"));
|
||||
}
|
||||
};
|
||||
public static final LocalVariableTableParameterNameDiscoverer NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
private Method method;
|
||||
private Method method;
|
||||
private Class<?>[] paramTypes;
|
||||
private String[] paramNames;
|
||||
private String[] paramNames;
|
||||
private boolean pageable = false;
|
||||
private boolean sortable = false;
|
||||
|
||||
public RepositoryMethod( Method method ) {
|
||||
public RepositoryMethod(Method method) {
|
||||
this.method = method;
|
||||
paramTypes = method.getParameterTypes();
|
||||
for ( Class<?> type : paramTypes ) {
|
||||
if ( Pageable.class.isAssignableFrom( type ) ) {
|
||||
for(Class<?> type : paramTypes) {
|
||||
if(Pageable.class.isAssignableFrom(type)) {
|
||||
pageable = true;
|
||||
}
|
||||
if ( Sort.class.isAssignableFrom( type ) ) {
|
||||
if(Sort.class.isAssignableFrom(type)) {
|
||||
sortable = true;
|
||||
}
|
||||
}
|
||||
paramNames = NAME_DISCOVERER.getParameterNames( method );
|
||||
if ( null == paramNames ) {
|
||||
paramNames = NAME_DISCOVERER.getParameterNames(method);
|
||||
if(null == paramNames) {
|
||||
paramNames = new String[paramTypes.length];
|
||||
}
|
||||
Annotation[][] paramAnnos = method.getParameterAnnotations();
|
||||
for ( int i = 0; i < paramAnnos.length; i++ ) {
|
||||
if ( paramAnnos[i].length > 0 ) {
|
||||
for ( Annotation anno : paramAnnos[i] ) {
|
||||
if ( Param.class.isAssignableFrom( anno.getClass() ) ) {
|
||||
Param p = (Param) anno;
|
||||
for(int i = 0; i < paramAnnos.length; i++) {
|
||||
if(paramAnnos[i].length > 0) {
|
||||
for(Annotation anno : paramAnnos[i]) {
|
||||
if(Param.class.isAssignableFrom(anno.getClass())) {
|
||||
Param p = (Param)anno;
|
||||
paramNames[i] = p.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( null == paramNames[i] ) {
|
||||
if(null == paramNames[i]) {
|
||||
paramNames[i] = "arg" + i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package org.springframework.data.rest.repository.invoke;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryMethodResponse {
|
||||
|
||||
@JsonProperty("results")
|
||||
private List<Object> results = new ArrayList<Object>();
|
||||
@JsonProperty("_links")
|
||||
private List<Link> links = new ArrayList<Link>();
|
||||
private long totalCount = 0;
|
||||
private int totalPages = 1;
|
||||
private int currentPage = 1;
|
||||
|
||||
public RepositoryMethodResponse addLink(Link l) {
|
||||
links.add(l);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse addResult(Object obj) {
|
||||
results.add(obj);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse addAllResults(Iterator results) {
|
||||
if(null == results) {
|
||||
return this;
|
||||
}
|
||||
|
||||
while(results.hasNext()) {
|
||||
addResult(results.next());
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Object> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
public boolean hasResults() {
|
||||
return (results.size() > 0);
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse setResults(List<Object> results) {
|
||||
if(null == results) {
|
||||
this.results = Collections.emptyList();
|
||||
} else {
|
||||
this.results = results;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Link> getLinks() {
|
||||
return links;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse setLinks(List<Link> links) {
|
||||
if(null == links) {
|
||||
this.links = Collections.emptyList();
|
||||
} else {
|
||||
this.links = links;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getTotalCount() {
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse setTotalCount(long totalCount) {
|
||||
this.totalCount = totalCount;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getTotalPages() {
|
||||
return totalPages;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse setTotalPages(int totalPages) {
|
||||
this.totalPages = totalPages;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getCurrentPage() {
|
||||
return currentPage;
|
||||
}
|
||||
|
||||
public RepositoryMethodResponse setCurrentPage(int currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.data.rest.repository.invoke;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class RepositoryQueryMethod {
|
||||
|
||||
private Method method;
|
||||
private Class<?>[] paramTypes;
|
||||
private String[] paramNames;
|
||||
|
||||
public RepositoryQueryMethod(Method method) {
|
||||
this.method = method;
|
||||
paramTypes = method.getParameterTypes();
|
||||
paramNames = new String[paramTypes.length];
|
||||
if(null == paramNames) {
|
||||
paramNames = new String[paramTypes.length];
|
||||
}
|
||||
Annotation[][] paramAnnos = method.getParameterAnnotations();
|
||||
for(int i = 0; i < paramAnnos.length; i++) {
|
||||
if(paramAnnos[i].length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for(Annotation anno : paramAnnos[i]) {
|
||||
if(Param.class.isAssignableFrom(anno.getClass())) {
|
||||
Param p = (Param)anno;
|
||||
paramNames[i] = p.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(Pageable.class.isAssignableFrom(paramTypes[i])
|
||||
|| Sort.class.isAssignableFrom(paramTypes[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Assert.notNull(paramNames[i],
|
||||
"No @Param('name') was provided for parameter " + (i + 1) + " of type " + paramTypes[i]
|
||||
+ " on " + (method.getDeclaringClass().getName() + "." + method.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?>[] paramTypes() {
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
public String[] paramNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
public Method method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,14 +17,15 @@ import org.springframework.util.ReflectionUtils;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
public class JpaAttributeMetadata
|
||||
implements AttributeMetadata {
|
||||
|
||||
private String name;
|
||||
private String name;
|
||||
private Attribute attribute;
|
||||
private Class<?> type;
|
||||
private Field field;
|
||||
private Method getter;
|
||||
private Method setter;
|
||||
private Class<?> type;
|
||||
private Field field;
|
||||
private Method getter;
|
||||
private Method setter;
|
||||
|
||||
public JpaAttributeMetadata(EntityType<?> entityType, Attribute attribute) {
|
||||
this.attribute = attribute;
|
||||
@@ -35,14 +36,16 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
|
||||
PropertyDescriptor property = BeanUtils.getPropertyDescriptor(entityType.getJavaType(), name);
|
||||
if (null != property) {
|
||||
if(null != property) {
|
||||
getter = property.getReadMethod();
|
||||
if (null != getter)
|
||||
if(null != getter) {
|
||||
ReflectionUtils.makeAccessible(getter);
|
||||
}
|
||||
|
||||
setter = property.getWriteMethod();
|
||||
if (null != setter)
|
||||
if(null != setter) {
|
||||
ReflectionUtils.makeAccessible(setter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,14 +59,14 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
|
||||
@Override public Class<?> elementType() {
|
||||
return (attribute instanceof PluralAttribute
|
||||
? ((PluralAttribute) attribute).getElementType().getJavaType()
|
||||
: null);
|
||||
? ((PluralAttribute)attribute).getElementType().getJavaType()
|
||||
: null);
|
||||
}
|
||||
|
||||
@Override public boolean isCollectionLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
if(attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute)attribute;
|
||||
switch(plattr.getCollectionType()) {
|
||||
case COLLECTION:
|
||||
case LIST:
|
||||
return true;
|
||||
@@ -76,13 +79,13 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
}
|
||||
|
||||
@Override public Collection<?> asCollection(Object target) {
|
||||
return (Collection<?>) get(target);
|
||||
return (Collection<?>)get(target);
|
||||
}
|
||||
|
||||
@Override public boolean isSetLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
if(attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute)attribute;
|
||||
switch(plattr.getCollectionType()) {
|
||||
case SET:
|
||||
return true;
|
||||
default:
|
||||
@@ -94,13 +97,13 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
}
|
||||
|
||||
@Override public Set<?> asSet(Object target) {
|
||||
return (Set<?>) get(target);
|
||||
return (Set<?>)get(target);
|
||||
}
|
||||
|
||||
@Override public boolean isMapLike() {
|
||||
if (attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute) attribute;
|
||||
switch (plattr.getCollectionType()) {
|
||||
if(attribute instanceof PluralAttribute) {
|
||||
PluralAttribute plattr = (PluralAttribute)attribute;
|
||||
switch(plattr.getCollectionType()) {
|
||||
case MAP:
|
||||
return true;
|
||||
default:
|
||||
@@ -112,29 +115,29 @@ public class JpaAttributeMetadata implements AttributeMetadata {
|
||||
}
|
||||
|
||||
@Override public Map asMap(Object target) {
|
||||
return (Map) get(target);
|
||||
return (Map)get(target);
|
||||
}
|
||||
|
||||
@Override public Object get(Object target) {
|
||||
try {
|
||||
if (null != getter) {
|
||||
if(null != getter) {
|
||||
return getter.invoke(target);
|
||||
} else {
|
||||
return field.get(target);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch(Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override public AttributeMetadata set(Object value, Object target) {
|
||||
try {
|
||||
if (null != setter) {
|
||||
if(null != setter) {
|
||||
setter.invoke(target, value);
|
||||
} else {
|
||||
field.set(target, value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
} catch(Exception e) {
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -16,40 +16,41 @@ import org.springframework.util.ReflectionUtils;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
public class JpaEntityMetadata
|
||||
implements EntityMetadata<JpaAttributeMetadata> {
|
||||
|
||||
private Class<?> type;
|
||||
private Class<?> type;
|
||||
private JpaAttributeMetadata idAttribute;
|
||||
private JpaAttributeMetadata versionAttribute;
|
||||
private Map<String, JpaAttributeMetadata> embeddedAttributes = new HashMap<String, JpaAttributeMetadata>();
|
||||
private Map<String, JpaAttributeMetadata> linkedAttributes = new HashMap<String, JpaAttributeMetadata>();
|
||||
private Map<String, JpaAttributeMetadata> linkedAttributes = new HashMap<String, JpaAttributeMetadata>();
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public JpaEntityMetadata(Repositories repositories, EntityType<?> entityType) {
|
||||
type = entityType.getJavaType();
|
||||
idAttribute = new JpaAttributeMetadata(entityType, entityType.getId(entityType.getIdType().getJavaType()));
|
||||
if (null != entityType.getVersion(Long.class)) {
|
||||
if(null != entityType.getVersion(Long.class)) {
|
||||
versionAttribute = new JpaAttributeMetadata(entityType, entityType.getVersion(Long.class));
|
||||
}
|
||||
|
||||
for (Attribute attr : entityType.getAttributes()) {
|
||||
for(Attribute attr : entityType.getAttributes()) {
|
||||
boolean exported = true;
|
||||
Field field = ReflectionUtils.findField(type, attr.getJavaMember().getName());
|
||||
if (null != field) {
|
||||
if(null != field) {
|
||||
RestResource fieldResourceAnno = field.getAnnotation(RestResource.class);
|
||||
if (null != fieldResourceAnno) {
|
||||
if(null != fieldResourceAnno) {
|
||||
exported = fieldResourceAnno.exported();
|
||||
}
|
||||
}
|
||||
if (exported) {
|
||||
if(exported) {
|
||||
Class<?> attrType = (attr instanceof PluralAttribute
|
||||
? ((PluralAttribute) attr).getElementType().getJavaType()
|
||||
: attr.getJavaType());
|
||||
if (repositories.hasRepositoryFor(attrType)) {
|
||||
? ((PluralAttribute)attr).getElementType().getJavaType()
|
||||
: attr.getJavaType());
|
||||
if(repositories.hasRepositoryFor(attrType)) {
|
||||
linkedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
} else {
|
||||
if (!(attr instanceof SingularAttribute && ((SingularAttribute) attr).isId())
|
||||
&& !(attr instanceof SingularAttribute && ((SingularAttribute) attr).isVersion())) {
|
||||
if(!(attr instanceof SingularAttribute && ((SingularAttribute)attr).isId())
|
||||
&& !(attr instanceof SingularAttribute && ((SingularAttribute)attr).isVersion())) {
|
||||
embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr));
|
||||
}
|
||||
}
|
||||
@@ -78,13 +79,13 @@ public class JpaEntityMetadata implements EntityMetadata<JpaAttributeMetadata> {
|
||||
}
|
||||
|
||||
@Override public JpaAttributeMetadata attribute(String name) {
|
||||
if (idAttribute.name().equals(name)) {
|
||||
if(idAttribute.name().equals(name)) {
|
||||
return idAttribute;
|
||||
} else if (null != versionAttribute && versionAttribute.name().equals(name)) {
|
||||
} else if(null != versionAttribute && versionAttribute.name().equals(name)) {
|
||||
return versionAttribute;
|
||||
} else if (embeddedAttributes.containsKey(name)) {
|
||||
} else if(embeddedAttributes.containsKey(name)) {
|
||||
return embeddedAttributes.get(name);
|
||||
} else if (linkedAttributes.containsKey(name)) {
|
||||
} else if(linkedAttributes.containsKey(name)) {
|
||||
return linkedAttributes.get(name);
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -12,7 +12,8 @@ import org.springframework.data.rest.repository.RepositoryExporter;
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaRepositoryExporter extends RepositoryExporter<JpaRepositoryMetadata, JpaEntityMetadata> {
|
||||
public class JpaRepositoryExporter
|
||||
extends RepositoryExporter<JpaRepositoryMetadata, JpaEntityMetadata> {
|
||||
|
||||
protected EntityManager entityManager;
|
||||
|
||||
|
||||
@@ -12,23 +12,24 @@ import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata;
|
||||
import org.springframework.data.rest.repository.RepositoryQueryMethod;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
import org.springframework.data.rest.repository.invoke.RepositoryQueryMethod;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class JpaRepositoryMetadata implements RepositoryMetadata<JpaEntityMetadata> {
|
||||
public class JpaRepositoryMetadata
|
||||
implements RepositoryMetadata<JpaEntityMetadata> {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> repoClass;
|
||||
private final String name;
|
||||
private final Class<?> repoClass;
|
||||
private final CrudRepository<Object, Serializable> repository;
|
||||
private final EntityInformation entityInfo;
|
||||
private final EntityInformation entityInfo;
|
||||
private final Map<String, RepositoryQueryMethod> queryMethods = new HashMap<String, RepositoryQueryMethod>();
|
||||
|
||||
private String rel;
|
||||
private String rel;
|
||||
private JpaEntityMetadata entityMetadata;
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@@ -43,23 +44,23 @@ public class JpaRepositoryMetadata implements RepositoryMetadata<JpaEntityMetada
|
||||
this.entityInfo = repositories.getEntityInformationFor(domainType);
|
||||
|
||||
RestResource resourceAnno = repoClass.getAnnotation(RestResource.class);
|
||||
if (null != resourceAnno && StringUtils.hasText(resourceAnno.rel())) {
|
||||
if(null != resourceAnno && StringUtils.hasText(resourceAnno.rel())) {
|
||||
rel = resourceAnno.rel();
|
||||
} else {
|
||||
rel = name;
|
||||
}
|
||||
|
||||
for (Method method : repositories.getRepositoryInformationFor(domainType).getQueryMethods()) {
|
||||
for(Method method : repositories.getRepositoryInformationFor(domainType).getQueryMethods()) {
|
||||
String pathSeg = method.getName();
|
||||
RestResource methodResourceAnno = method.getAnnotation(RestResource.class);
|
||||
boolean methodExported = true;
|
||||
if (null != methodResourceAnno) {
|
||||
if (StringUtils.hasText(methodResourceAnno.path())) {
|
||||
if(null != methodResourceAnno) {
|
||||
if(StringUtils.hasText(methodResourceAnno.path())) {
|
||||
pathSeg = methodResourceAnno.path();
|
||||
}
|
||||
methodExported = methodResourceAnno.exported();
|
||||
}
|
||||
if (methodExported) {
|
||||
if(methodExported) {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
queryMethods.put(pathSeg, new RepositoryQueryMethod(method));
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.springframework.data.rest.repository.spec
|
||||
|
||||
import javax.persistence.EntityManager
|
||||
import javax.persistence.PersistenceContext
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
|
||||
import org.springframework.data.rest.repository.RepositoryExporter
|
||||
import org.springframework.data.rest.repository.RepositoryMetadata
|
||||
import org.springframework.data.rest.repository.test.ApplicationConfig
|
||||
import org.springframework.data.rest.repository.test.Family
|
||||
import org.springframework.data.rest.repository.test.FamilyRepository
|
||||
import org.springframework.data.rest.repository.test.Person
|
||||
@@ -14,10 +12,13 @@ import org.springframework.data.rest.repository.test.PersonRepository
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import spock.lang.Specification
|
||||
|
||||
import javax.persistence.EntityManager
|
||||
import javax.persistence.PersistenceContext
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@ContextConfiguration(locations = ["/JpaMetadataSpec-test.xml"])
|
||||
@ContextConfiguration(classes = [ApplicationConfig])
|
||||
class JpaMetadataSpec extends Specification {
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.springframework.data.rest.repository.test;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.rest.repository.jpa.JpaRepositoryExporter;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = ApplicationConfig.class)
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
return builder.setType(EmbeddedDatabaseType.HSQL).build();
|
||||
}
|
||||
|
||||
@Bean public EntityManagerFactory entityManagerFactory() {
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setJpaVendorAdapter(vendorAdapter);
|
||||
factory.setPackagesToScan(getClass().getPackage().getName());
|
||||
factory.setDataSource(dataSource());
|
||||
factory.setPersistenceXmlLocation("/JpaMetadataSpec-persistence.xml");
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean public PlatformTransactionManager transactionManager() {
|
||||
JpaTransactionManager txManager = new JpaTransactionManager();
|
||||
txManager.setEntityManagerFactory(entityManagerFactory());
|
||||
return txManager;
|
||||
}
|
||||
|
||||
@Bean public JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
return new JpaRepositoryExporter();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,14 @@
|
||||
apply plugin: "war"
|
||||
apply plugin: "jetty"
|
||||
|
||||
jettyRun {
|
||||
contextPath = ""
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// APIS
|
||||
compile "javax.servlet:servlet-api:2.5"
|
||||
compile "javax.servlet:javax.servlet-api:3.0.1"
|
||||
|
||||
// JPA
|
||||
compile "org.hibernate.javax.persistence:hibernate-jpa-2.0-api:1.0.1.Final"
|
||||
@@ -12,7 +19,6 @@ dependencies {
|
||||
|
||||
// Spring
|
||||
compile "org.springframework:spring-webmvc:$springVersion"
|
||||
runtime "cglib:cglib-nodep:2.2.2"
|
||||
|
||||
// Repository Exporter support
|
||||
compile project(":spring-data-rest-repository")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.codehaus.jackson.JsonEncoding;
|
||||
@@ -16,51 +15,50 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Utility class for creating a custom-configured {@see MappingJacksonHttpMessageConverter} that has our own
|
||||
* serializers and {@see MediaType} mappings on it.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class JacksonUtil {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName( "UTF-8" );
|
||||
public static final MediaType COMPACT_JSON = new MediaType( "application",
|
||||
"x-spring-data-compact+json",
|
||||
DEFAULT_CHARSET );
|
||||
public static final MediaType VERBOSE_JSON = new MediaType( "application",
|
||||
"x-spring-data-verbose+json",
|
||||
DEFAULT_CHARSET );
|
||||
public static final MediaType APPLICATION_JAVASCRIPT = new MediaType( "application",
|
||||
"javascript",
|
||||
DEFAULT_CHARSET );
|
||||
|
||||
private JacksonUtil() {
|
||||
}
|
||||
|
||||
public static MappingJacksonHttpMessageConverter createJacksonHttpMessageConverter( final ObjectMapper objectMapper ) {
|
||||
public static MappingJacksonHttpMessageConverter createJacksonHttpMessageConverter(final ObjectMapper objectMapper) {
|
||||
// We need a custom serializer for handling beans that don't conform to the JavaBeans standard 'get' and 'set'
|
||||
CustomSerializerFactory customSerializerFactory = new CustomSerializerFactory();
|
||||
customSerializerFactory.addSpecificMapping( SimpleLink.class, new FluentBeanSerializer( SimpleLink.class ) );
|
||||
objectMapper.setSerializerFactory( customSerializerFactory );
|
||||
customSerializerFactory.addSpecificMapping(SimpleLink.class, new FluentBeanSerializer(SimpleLink.class));
|
||||
objectMapper.setSerializerFactory(customSerializerFactory);
|
||||
// We want to support all our custom types of JSON and also the catch-all
|
||||
MappingJacksonHttpMessageConverter jsonConverter = new MappingJacksonHttpMessageConverter() {
|
||||
{
|
||||
setSupportedMediaTypes( Arrays.asList( MediaType.APPLICATION_JSON, COMPACT_JSON, VERBOSE_JSON ) );
|
||||
setSupportedMediaTypes(Arrays.asList(
|
||||
MediaType.APPLICATION_JSON,
|
||||
MediaTypes.COMPACT_JSON,
|
||||
MediaTypes.VERBOSE_JSON,
|
||||
MediaType.ALL
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal( Object object, HttpOutputMessage outputMessage )
|
||||
protected void writeInternal(Object object, HttpOutputMessage outputMessage)
|
||||
throws IOException,
|
||||
HttpMessageNotWritableException {
|
||||
JsonEncoding encoding = getJsonEncoding( outputMessage.getHeaders().getContentType() );
|
||||
JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
|
||||
// Believe it or not, this is the only way to get pretty-printing from Jackson in this configuration
|
||||
JsonGenerator jsonGenerator = objectMapper
|
||||
.getJsonFactory()
|
||||
.createJsonGenerator( outputMessage.getBody(), encoding )
|
||||
.createJsonGenerator(outputMessage.getBody(), encoding)
|
||||
.useDefaultPrettyPrinter();
|
||||
try {
|
||||
objectMapper.writeValue( jsonGenerator, object );
|
||||
} catch ( IOException ex ) {
|
||||
throw new HttpMessageNotWritableException( "Could not write JSON: " + ex.getMessage(), ex );
|
||||
objectMapper.writeValue(jsonGenerator, object);
|
||||
} catch(IOException ex) {
|
||||
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
};
|
||||
jsonConverter.setObjectMapper( objectMapper );
|
||||
jsonConverter.setObjectMapper(objectMapper);
|
||||
|
||||
return jsonConverter;
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.codehaus.jackson.annotate.JsonProperty;
|
||||
import org.codehaus.jackson.map.annotate.JsonDeserialize;
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class Links {
|
||||
|
||||
private List<SimpleLink> links = new ArrayList<SimpleLink>();
|
||||
|
||||
public Links add(SimpleLink link) {
|
||||
links.add(link);
|
||||
return this;
|
||||
}
|
||||
|
||||
@JsonProperty("_links")
|
||||
public List<SimpleLink> getLinks() {
|
||||
return this.links;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class MediaTypes {
|
||||
|
||||
private MediaTypes() {
|
||||
}
|
||||
|
||||
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
|
||||
|
||||
public static final List<MediaType> ACCEPT_ALL_TYPES = Collections.singletonList(MediaType.ALL);
|
||||
public static final MediaType COMPACT_JSON = new MediaType("application",
|
||||
"x-spring-data-compact+json",
|
||||
ISO_8859_1);
|
||||
public static final MediaType VERBOSE_JSON = new MediaType("application",
|
||||
"x-spring-data-verbose+json",
|
||||
ISO_8859_1);
|
||||
public static final MediaType APPLICATION_JAVASCRIPT = new MediaType("application",
|
||||
"javascript",
|
||||
ISO_8859_1);
|
||||
public static final MediaType URI_LIST = new MediaType("text",
|
||||
"uri-list",
|
||||
ISO_8859_1);
|
||||
|
||||
}
|
||||
@@ -10,12 +10,15 @@ import org.springframework.data.domain.Sort;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* Implementation of {@link Pageable} that is URL-aware.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PagingAndSorting implements Pageable {
|
||||
public class PagingAndSorting
|
||||
implements Pageable {
|
||||
|
||||
private final RepositoryRestConfiguration config;
|
||||
private final PageRequest pageRequest;
|
||||
private final PageRequest pageRequest;
|
||||
|
||||
public PagingAndSorting(RepositoryRestConfiguration config,
|
||||
PageRequest pageRequest) {
|
||||
@@ -23,17 +26,24 @@ public class PagingAndSorting implements Pageable {
|
||||
this.pageRequest = pageRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the current sort parameters to the URI.
|
||||
*
|
||||
* @param urib
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public PagingAndSorting addSortParameters(UriComponentsBuilder urib) {
|
||||
Sort sort = pageRequest.getSort();
|
||||
if (null != sort) {
|
||||
if(null != sort) {
|
||||
Iterator<Sort.Order> iter = sort.iterator();
|
||||
while (iter.hasNext()) {
|
||||
while(iter.hasNext()) {
|
||||
Sort.Order order = iter.next();
|
||||
urib.queryParam(config.getSortParamName(), order.getProperty());
|
||||
try {
|
||||
urib.queryParam(URLEncoder.encode(order.getProperty() + ".dir", "ISO-8859-1"),
|
||||
order.getDirection().toString().toLowerCase());
|
||||
} catch (UnsupportedEncodingException ignored) {
|
||||
} catch(UnsupportedEncodingException ignored) {
|
||||
// this should never happen
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.lang.ClassUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableDefaults;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
@@ -19,14 +19,15 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
public class PagingAndSortingMethodArgumentResolver
|
||||
implements HandlerMethodArgumentResolver {
|
||||
|
||||
private static final int DEFAULT_PAGE = 1; // We're 1-based, not 0-based
|
||||
|
||||
private RepositoryRestConfiguration config = RepositoryRestConfiguration.DEFAULT;
|
||||
|
||||
public PagingAndSortingMethodArgumentResolver(RepositoryRestConfiguration config) {
|
||||
if (null != config) {
|
||||
if(null != config) {
|
||||
this.config = config;
|
||||
}
|
||||
}
|
||||
@@ -40,47 +41,49 @@ public class PagingAndSortingMethodArgumentResolver implements HandlerMethodArgu
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory) throws Exception {
|
||||
HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
|
||||
HttpServletRequest request = (HttpServletRequest)webRequest.getNativeRequest();
|
||||
|
||||
PageRequest pr = null;
|
||||
for (Annotation annotation : parameter.getParameterAnnotations()) {
|
||||
if (annotation instanceof PageableDefaults) {
|
||||
PageableDefaults defaults = (PageableDefaults) annotation;
|
||||
for(Annotation annotation : parameter.getParameterAnnotations()) {
|
||||
if(annotation instanceof PageableDefaults) {
|
||||
PageableDefaults defaults = (PageableDefaults)annotation;
|
||||
pr = new PageRequest(defaults.pageNumber(), defaults.value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (null == pr) {
|
||||
if(null == pr) {
|
||||
int page = DEFAULT_PAGE;
|
||||
String sPage = request.getParameter(config.getPageParamName());
|
||||
if (StringUtils.hasText(sPage)) {
|
||||
if(StringUtils.hasText(sPage)) {
|
||||
try {
|
||||
page = Integer.parseInt(sPage);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} catch(NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
int limit = config.getDefaultPageSize();
|
||||
String sLimit = request.getParameter(config.getLimitParamName());
|
||||
if (StringUtils.hasText(sLimit)) {
|
||||
if(StringUtils.hasText(sLimit)) {
|
||||
try {
|
||||
limit = Integer.parseInt(sLimit);
|
||||
} catch (NumberFormatException ignored) {}
|
||||
} catch(NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
Sort sort = null;
|
||||
List<Sort.Order> orders = new ArrayList<Sort.Order>();
|
||||
String[] orderValues = request.getParameterValues(config.getSortParamName());
|
||||
if (null != orderValues) {
|
||||
for (String orderParam : orderValues) {
|
||||
if(null != orderValues) {
|
||||
for(String orderParam : orderValues) {
|
||||
String sortDir = request.getParameter(orderParam + ".dir");
|
||||
Sort.Direction dir = (null != sortDir ? Sort.Direction.valueOf(sortDir.toUpperCase()) : Sort.Direction.ASC);
|
||||
orders.add(new Sort.Order(dir, orderParam));
|
||||
}
|
||||
if (!orders.isEmpty()) {
|
||||
if(!orders.isEmpty()) {
|
||||
sort = new Sort(orders);
|
||||
}
|
||||
}
|
||||
|
||||
if (null != sort) {
|
||||
if(null != sort) {
|
||||
pr = new PageRequest(page - 1, limit, sort);
|
||||
} else {
|
||||
pr = new PageRequest(page - 1, limit);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.webmvc;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
/**
|
||||
@@ -12,19 +13,20 @@ public class RepositoryRestConfiguration {
|
||||
|
||||
public static final RepositoryRestConfiguration DEFAULT = new RepositoryRestConfiguration();
|
||||
|
||||
private int defaultPageSize = 20;
|
||||
private String pageParamName = "page";
|
||||
private String limitParamName = "limit";
|
||||
private String sortParamName = "sort";
|
||||
private String jsonpParamName = "callback";
|
||||
private String jsonpOnErrParamName = null;
|
||||
private List<HttpMessageConverter<?>> customConverters = Collections.emptyList();
|
||||
private int defaultPageSize = 20;
|
||||
private String pageParamName = "page";
|
||||
private String limitParamName = "limit";
|
||||
private String sortParamName = "sort";
|
||||
private String jsonpParamName = "callback";
|
||||
private String jsonpOnErrParamName = null;
|
||||
private List<HttpMessageConverter<?>> customConverters = Collections.emptyList();
|
||||
private MediaType defaultMediaType = MediaType.APPLICATION_JSON;
|
||||
|
||||
public int getDefaultPageSize() {
|
||||
return defaultPageSize;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setDefaultPageSize( int defaultPageSize ) {
|
||||
public RepositoryRestConfiguration setDefaultPageSize(int defaultPageSize) {
|
||||
this.defaultPageSize = defaultPageSize;
|
||||
return this;
|
||||
}
|
||||
@@ -33,7 +35,7 @@ public class RepositoryRestConfiguration {
|
||||
return pageParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setPageParamName( String pageParamName ) {
|
||||
public RepositoryRestConfiguration setPageParamName(String pageParamName) {
|
||||
this.pageParamName = pageParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -42,7 +44,7 @@ public class RepositoryRestConfiguration {
|
||||
return limitParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setLimitParamName( String limitParamName ) {
|
||||
public RepositoryRestConfiguration setLimitParamName(String limitParamName) {
|
||||
this.limitParamName = limitParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -51,7 +53,7 @@ public class RepositoryRestConfiguration {
|
||||
return sortParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setSortParamName( String sortParamName ) {
|
||||
public RepositoryRestConfiguration setSortParamName(String sortParamName) {
|
||||
this.sortParamName = sortParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -60,7 +62,7 @@ public class RepositoryRestConfiguration {
|
||||
return customConverters;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setCustomConverters( List<HttpMessageConverter<?>> customConverters ) {
|
||||
public RepositoryRestConfiguration setCustomConverters(List<HttpMessageConverter<?>> customConverters) {
|
||||
this.customConverters = customConverters;
|
||||
return this;
|
||||
}
|
||||
@@ -69,7 +71,7 @@ public class RepositoryRestConfiguration {
|
||||
return jsonpParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setJsonpParamName( String jsonpParamName ) {
|
||||
public RepositoryRestConfiguration setJsonpParamName(String jsonpParamName) {
|
||||
this.jsonpParamName = jsonpParamName;
|
||||
return this;
|
||||
}
|
||||
@@ -78,9 +80,18 @@ public class RepositoryRestConfiguration {
|
||||
return jsonpOnErrParamName;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setJsonpOnErrParamName( String jsonpOnErrParamName ) {
|
||||
public RepositoryRestConfiguration setJsonpOnErrParamName(String jsonpOnErrParamName) {
|
||||
this.jsonpOnErrParamName = jsonpOnErrParamName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MediaType getDefaultMediaType() {
|
||||
return defaultMediaType;
|
||||
}
|
||||
|
||||
public RepositoryRestConfiguration setDefaultMediaType(MediaType defaultMediaType) {
|
||||
this.defaultMediaType = defaultMediaType;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,27 +13,27 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
*/
|
||||
public class RepositoryRestHandlerAdapter extends RequestMappingHandlerAdapter {
|
||||
|
||||
public RepositoryRestHandlerAdapter( RepositoryRestConfiguration config ) {
|
||||
setCustomArgumentResolvers( Arrays.asList(
|
||||
public RepositoryRestHandlerAdapter(RepositoryRestConfiguration config) {
|
||||
setCustomArgumentResolvers(Arrays.asList(
|
||||
new ServerHttpRequestMethodArgumentResolver(),
|
||||
new PagingAndSortingMethodArgumentResolver( config )
|
||||
) );
|
||||
new PagingAndSortingMethodArgumentResolver(config)
|
||||
));
|
||||
|
||||
// Add JSON converter for special Spring Data media type
|
||||
MappingJacksonHttpMessageConverter json = new MappingJacksonHttpMessageConverter();
|
||||
json.setSupportedMediaTypes(
|
||||
Arrays.asList( MediaType.APPLICATION_JSON, MediaType.valueOf( "application/x-spring-data+json" ) )
|
||||
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.valueOf("application/x-spring-data+json"))
|
||||
);
|
||||
getMessageConverters().add( json );
|
||||
getMessageConverters().add(json);
|
||||
}
|
||||
|
||||
@Override public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
@Override protected boolean supportsInternal( HandlerMethod handlerMethod ) {
|
||||
return super.supportsInternal( handlerMethod )
|
||||
&& RepositoryRestController.class.isAssignableFrom( handlerMethod.getBeanType() );
|
||||
@Override protected boolean supportsInternal(HandlerMethod handlerMethod) {
|
||||
return super.supportsInternal(handlerMethod)
|
||||
&& RepositoryRestController.class.isAssignableFrom(handlerMethod.getBeanType());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
private EntityManagerFactory entityManagerFactory;
|
||||
@Autowired(required = false)
|
||||
private List<RepositoryExporter> repositoryExporters = Collections.emptyList();
|
||||
private Set<String> repositoryNames = new HashSet<String>();
|
||||
private Set<String> repositoryNames = new HashSet<String>();
|
||||
|
||||
public RepositoryRestHandlerMapping() {
|
||||
setOrder(Ordered.HIGHEST_PRECEDENCE);
|
||||
@@ -31,18 +31,19 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
|
||||
if (repositoryNames.isEmpty() && !repositoryExporters.isEmpty()) {
|
||||
for (RepositoryExporter re : repositoryExporters) {
|
||||
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request)
|
||||
throws Exception {
|
||||
if(repositoryNames.isEmpty() && !repositoryExporters.isEmpty()) {
|
||||
for(RepositoryExporter re : repositoryExporters) {
|
||||
repositoryNames.addAll(re.repositoryNames());
|
||||
}
|
||||
}
|
||||
String[] parts = lookupPath.split("/");
|
||||
if (parts.length == 0) {
|
||||
if(parts.length == 0) {
|
||||
// Root request
|
||||
return super.lookupHandlerMethod(lookupPath, request);
|
||||
} else {
|
||||
if (repositoryNames.contains(parts[1])) {
|
||||
if(repositoryNames.contains(parts[1])) {
|
||||
return super.lookupHandlerMethod(lookupPath, request);
|
||||
} else {
|
||||
return null;
|
||||
@@ -55,7 +56,7 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping {
|
||||
}
|
||||
|
||||
@Override protected void extendInterceptors(List<Object> interceptors) {
|
||||
if (null != entityManagerFactory) {
|
||||
if(null != entityManagerFactory) {
|
||||
OpenEntityManagerInViewInterceptor omivi = new OpenEntityManagerInViewInterceptor();
|
||||
omivi.setEntityManagerFactory(entityManagerFactory);
|
||||
interceptors.add(omivi);
|
||||
|
||||
@@ -29,7 +29,7 @@ public class RepositoryRestMvcConfiguration {
|
||||
}
|
||||
|
||||
@Bean public JpaRepositoryExporter jpaRepositoryExporter() {
|
||||
if ( null == customJpaRepositoryExporter ) {
|
||||
if(null == customJpaRepositoryExporter) {
|
||||
return new JpaRepositoryExporter();
|
||||
} else {
|
||||
return customJpaRepositoryExporter;
|
||||
@@ -37,19 +37,20 @@ public class RepositoryRestMvcConfiguration {
|
||||
}
|
||||
|
||||
@Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() {
|
||||
if ( null == validatingRepositoryEventListener ) {
|
||||
if(null == validatingRepositoryEventListener) {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
} else {
|
||||
return validatingRepositoryEventListener;
|
||||
}
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestController repositoryRestController() throws Exception {
|
||||
@Bean public RepositoryRestController repositoryRestController()
|
||||
throws Exception {
|
||||
return new RepositoryRestController();
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() {
|
||||
return new RepositoryRestHandlerAdapter( repositoryRestConfig );
|
||||
return new RepositoryRestHandlerAdapter(repositoryRestConfig);
|
||||
}
|
||||
|
||||
@Bean public RepositoryRestHandlerMapping repositoryExporterHandlerMapping() {
|
||||
|
||||
@@ -15,16 +15,17 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
*/
|
||||
public class ServerHttpRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
@Override public boolean supportsParameter( MethodParameter parameter ) {
|
||||
return ClassUtils.isAssignable( parameter.getParameterType(), ServletServerHttpRequest.class );
|
||||
@Override public boolean supportsParameter(MethodParameter parameter) {
|
||||
return ClassUtils.isAssignable(parameter.getParameterType(), ServletServerHttpRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object resolveArgument( MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory ) throws Exception {
|
||||
return new ServletServerHttpRequest( (HttpServletRequest) webRequest.getNativeRequest() );
|
||||
public Object resolveArgument(MethodParameter parameter,
|
||||
ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest,
|
||||
WebDataBinderFactory binderFactory)
|
||||
throws Exception {
|
||||
return new ServletServerHttpRequest((HttpServletRequest)webRequest.getNativeRequest());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,96 +5,101 @@ import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.rest.core.Link;
|
||||
import org.springframework.data.rest.core.Links;
|
||||
import org.springframework.data.rest.core.SimpleLink;
|
||||
import org.springframework.data.rest.repository.invoke.RepositoryMethodResponse;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.AbstractHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
|
||||
/**
|
||||
* A special {@link org.springframework.http.converter.HttpMessageConverter} that can take various input formats and
|
||||
* produce a plain-text list of URIs (or read the same).
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class UriListHttpMessageConverter extends AbstractHttpMessageConverter<Object> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = Charset.forName( "ISO-8859-1" );
|
||||
|
||||
public UriListHttpMessageConverter() {
|
||||
super( new MediaType( "text", "uri-list", DEFAULT_CHARSET ) );
|
||||
super(MediaTypes.URI_LIST);
|
||||
}
|
||||
|
||||
@Override protected boolean supports( Class<?> clazz ) {
|
||||
return (List.class.isAssignableFrom( clazz )
|
||||
|| Map.class.isAssignableFrom( clazz )
|
||||
|| Links.class.isAssignableFrom( clazz ));
|
||||
@Override protected boolean supports(Class<?> clazz) {
|
||||
return (RepositoryMethodResponse.class.isAssignableFrom(clazz)
|
||||
|| List.class.isAssignableFrom(clazz)
|
||||
|| Map.class.isAssignableFrom(clazz)
|
||||
|| Links.class.isAssignableFrom(clazz));
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
@Override
|
||||
protected Object readInternal( Class<?> clazz,
|
||||
HttpInputMessage inputMessage )
|
||||
protected Object readInternal(Class<?> clazz,
|
||||
HttpInputMessage inputMessage)
|
||||
throws IOException,
|
||||
HttpMessageNotReadableException {
|
||||
|
||||
String rel = inputMessage.getHeaders().getFirst( "x-spring-data-urilist-rel" );
|
||||
if ( null == rel ) {
|
||||
rel = inputMessage.getHeaders().getLocation().getPath().substring( 1 ).replaceAll( "/", "." );
|
||||
String rel = inputMessage.getHeaders().getFirst("x-spring-data-urilist-rel");
|
||||
if(null == rel && inputMessage instanceof ServletServerHttpRequest) {
|
||||
rel = ((ServletServerHttpRequest)inputMessage).getURI().getPath().substring(1).replaceAll("/", ".");
|
||||
}
|
||||
BufferedReader reader = new BufferedReader( new InputStreamReader( inputMessage.getBody() ) );
|
||||
String line = null;
|
||||
Object links = null;
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputMessage.getBody()));
|
||||
String line;
|
||||
Object links;
|
||||
try {
|
||||
links = clazz.newInstance();
|
||||
} catch ( InstantiationException e ) {
|
||||
throw new HttpMessageNotReadableException( e.getMessage(), e );
|
||||
} catch ( IllegalAccessException e ) {
|
||||
throw new HttpMessageNotReadableException( e.getMessage(), e );
|
||||
} catch(InstantiationException e) {
|
||||
throw new HttpMessageNotReadableException(e.getMessage(), e);
|
||||
} catch(IllegalAccessException e) {
|
||||
throw new HttpMessageNotReadableException(e.getMessage(), e);
|
||||
}
|
||||
while ( null != (line = reader.readLine()) ) {
|
||||
if ( links instanceof Links ) {
|
||||
((Links) links).add( new SimpleLink( rel, URI.create( line.trim() ) ) );
|
||||
} else if ( links instanceof List ) {
|
||||
((List) links).add( new SimpleLink( rel, URI.create( line.trim() ) ) );
|
||||
} else if ( links instanceof Map ) {
|
||||
List l = (List) ((Map) links).get( "_links" );
|
||||
if ( null == l ) {
|
||||
while(null != (line = reader.readLine())) {
|
||||
if(links instanceof Links) {
|
||||
((Links)links).add(new SimpleLink(rel, URI.create(line.trim())));
|
||||
} else if(links instanceof List) {
|
||||
((List)links).add(new SimpleLink(rel, URI.create(line.trim())));
|
||||
} else if(links instanceof Map) {
|
||||
List l = (List)((Map)links).get("_links");
|
||||
if(null == l) {
|
||||
l = new ArrayList();
|
||||
((Map) links).put( "_links", l );
|
||||
((Map)links).put("_links", l);
|
||||
}
|
||||
l.add( new SimpleLink( rel, URI.create( line.trim() ) ) );
|
||||
l.add(new SimpleLink(rel, URI.create(line.trim())));
|
||||
}
|
||||
}
|
||||
return links;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeInternal( Object links, HttpOutputMessage outputMessage )
|
||||
protected void writeInternal(Object links, HttpOutputMessage outputMessage)
|
||||
throws IOException,
|
||||
HttpMessageNotWritableException {
|
||||
OutputStream body = outputMessage.getBody();
|
||||
if ( links instanceof Links ) {
|
||||
for ( SimpleLink link : ((Links) links).getLinks() ) {
|
||||
body.write( link.href().toASCIIString().getBytes() );
|
||||
body.write( '\n' );
|
||||
if(links instanceof Links) {
|
||||
for(Link link : ((Links)links).getLinks()) {
|
||||
body.write(link.href().toASCIIString().getBytes());
|
||||
body.write('\n');
|
||||
}
|
||||
} else if ( links instanceof List ) {
|
||||
for ( Object o : (List) links ) {
|
||||
if ( o instanceof Link ) {
|
||||
body.write( ((Link) o).href().toASCIIString().getBytes() );
|
||||
} else if(links instanceof List) {
|
||||
for(Object o : (List)links) {
|
||||
if(o instanceof Link) {
|
||||
body.write(((Link)o).href().toASCIIString().getBytes());
|
||||
} else {
|
||||
body.write( o.toString().getBytes() );
|
||||
body.write(o.toString().getBytes());
|
||||
}
|
||||
body.write( '\n' );
|
||||
body.write('\n');
|
||||
}
|
||||
} else if ( links instanceof Map ) {
|
||||
writeInternal( ((Map) links).get( "_links" ), outputMessage );
|
||||
} else if(links instanceof Map) {
|
||||
writeInternal(((Map)links).get("_links"), outputMessage);
|
||||
} else if(links instanceof RepositoryMethodResponse) {
|
||||
writeInternal(((RepositoryMethodResponse)links).getLinks(), outputMessage);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<servlet>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<servlet-class>org.springframework.data.rest.webmvc.RepositoryRestExporterServlet</servlet-class>
|
||||
<load-on-startup>1</load-on-startup>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>exporter</servlet-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
</web-app>
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.codehaus.jackson.map.ObjectMapper
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.data.rest.test.webmvc.Address
|
||||
import org.springframework.data.rest.test.webmvc.AddressRepository
|
||||
import org.springframework.data.rest.test.webmvc.ApplicationConfig
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.data.rest.test.webmvc.PersonRepository
|
||||
import org.springframework.data.rest.test.webmvc.TestRepositoryEventListener
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestMvcConfiguration
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.http.server.ServletServerHttpRequest
|
||||
import org.springframework.mock.web.MockHttpServletRequest
|
||||
import org.springframework.orm.jpa.EntityManagerHolder
|
||||
import org.springframework.test.context.ContextConfiguration
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Specification
|
||||
|
||||
import javax.persistence.EntityManagerFactory
|
||||
|
||||
import static org.springframework.transaction.support.TransactionSynchronizationManager.*
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@ContextConfiguration(classes = [ApplicationConfig, RepositoryRestMvcConfiguration])
|
||||
abstract class BaseSpec extends Specification {
|
||||
|
||||
@Autowired ApplicationContext appCtx
|
||||
@Autowired TestRepositoryEventListener listener
|
||||
@Autowired RepositoryRestController controller
|
||||
@Autowired EntityManagerFactory emf
|
||||
@Autowired PersonRepository people
|
||||
@Autowired AddressRepository addresses
|
||||
UriComponentsBuilder baseUri
|
||||
ObjectMapper mapper = new ObjectMapper()
|
||||
|
||||
def setup() {
|
||||
baseUri = UriComponentsBuilder.fromUriString("http://localhost:8080/data")
|
||||
|
||||
if (!hasResource(emf)) {
|
||||
bindResource(emf, new EntityManagerHolder(emf.createEntityManager()))
|
||||
}
|
||||
|
||||
for (Person p : people.findAll()) {
|
||||
people.delete(p)
|
||||
}
|
||||
for (Address a : addresses.findAll()) {
|
||||
addresses.delete(a)
|
||||
}
|
||||
}
|
||||
|
||||
def readJson(ResponseEntity entity) {
|
||||
mapper.readValue((byte[]) entity.body, Map)
|
||||
}
|
||||
|
||||
def createJsonRequest(method, path, query, obj) {
|
||||
createRequest(method, path, null, "application/json", mapper.writeValueAsString(obj))
|
||||
}
|
||||
|
||||
def createUriListRequest(method, path, query, obj) {
|
||||
createRequest(method, path, null, "text/uri-list", obj.join("\n"))
|
||||
}
|
||||
|
||||
def createRequest(method, path, query) {
|
||||
createRequest(method, path, null, null, null)
|
||||
}
|
||||
|
||||
def createRequest(method, path, query, contentType, content) {
|
||||
def req = new MockHttpServletRequest(
|
||||
serverPort: 8080,
|
||||
requestURI: "/data/$path",
|
||||
method: method
|
||||
)
|
||||
if (query) {
|
||||
req.queryString = URLEncoder.encode(
|
||||
query.collect {k, v -> "$k=$v"}.join("&")
|
||||
)
|
||||
}
|
||||
if (contentType) {
|
||||
req.contentType = contentType
|
||||
}
|
||||
if (content) {
|
||||
req.content = content
|
||||
}
|
||||
|
||||
new ServletServerHttpRequest(req)
|
||||
}
|
||||
|
||||
def newPerson() {
|
||||
people.save(new Person(name: "John Doe", addresses: [newAddress("Univille")]))
|
||||
}
|
||||
|
||||
def newAddress(city) {
|
||||
addresses.save(new Address(
|
||||
["1234 W. 1st St."] as String[],
|
||||
city,
|
||||
"ST",
|
||||
"12345"
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.data.rest.repository.RepositoryConstraintViolationException
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class EventsSpec extends BaseSpec {
|
||||
|
||||
def "cannot save invalid entity"() {
|
||||
|
||||
given:
|
||||
def person = new Person()
|
||||
def request = createJsonRequest("POST", "people", null, person)
|
||||
|
||||
when:
|
||||
controller.create(request, baseUri, "people")
|
||||
|
||||
then:
|
||||
thrown(RepositoryConstraintViolationException)
|
||||
|
||||
}
|
||||
|
||||
def "captures before and after events"() {
|
||||
|
||||
given:
|
||||
def person = new Person(name: "John Doe")
|
||||
def request = createJsonRequest("POST", "people", ["returnBody": "true"], person)
|
||||
def persId
|
||||
listener.handlers << { evt, p ->
|
||||
if (evt == "afterSave")
|
||||
persId = "${p.id}"
|
||||
}
|
||||
|
||||
when:
|
||||
def response = controller.create(request, baseUri, "people")
|
||||
def returnedId = response.headers.getFirst('Location').tokenize("/").last()
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
persId == returnedId
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class RelationshipsSpec extends BaseSpec {
|
||||
|
||||
def "saves entity relationship"() {
|
||||
|
||||
given:
|
||||
def person = newPerson()
|
||||
def persId = person.id
|
||||
def addr = newAddress("Smallville")
|
||||
def addrId = addr.id
|
||||
def request = createUriListRequest(
|
||||
"POST",
|
||||
"people/$persId/addresses",
|
||||
null,
|
||||
[baseUri.pathSegment("address", "$addrId").build().toUriString()]
|
||||
)
|
||||
|
||||
when:
|
||||
def response = controller.updatePropertyOfEntity(request, baseUri, "people", "$persId", "addresses")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
|
||||
when:
|
||||
request = createRequest("GET", "people/$persId/addresses/$addrId", null)
|
||||
response = controller.linkedEntity(request, baseUri, "people", "$persId", "addresses", "$addrId")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.OK
|
||||
readJson(response).city == "Smallville"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.springframework.transaction.support.TransactionSynchronizationManager
|
||||
import org.springframework.ui.ExtendedModelMap
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext
|
||||
import org.springframework.web.util.UriComponentsBuilder
|
||||
import spock.lang.Ignore
|
||||
import spock.lang.Shared
|
||||
import spock.lang.Specification
|
||||
|
||||
@@ -29,6 +30,7 @@ import javax.persistence.EntityManagerFactory
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Ignore
|
||||
class RepositoryRestControllerSpec extends Specification {
|
||||
|
||||
@Shared
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package org.springframework.data.rest.webmvc.spec
|
||||
|
||||
import org.springframework.data.rest.test.webmvc.Person
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
class TopLevelEntitySpec extends BaseSpec {
|
||||
|
||||
def "saves top-level entity"() {
|
||||
|
||||
given:
|
||||
def person = new Person(name: "John Doe")
|
||||
def request = createJsonRequest("POST", "people/1", null, person)
|
||||
|
||||
when:
|
||||
def response = controller.createOrUpdate(request, baseUri, "people", "1")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.CREATED
|
||||
|
||||
}
|
||||
|
||||
def "retrieves top-level entity"() {
|
||||
|
||||
given:
|
||||
def person = newPerson()
|
||||
def request = createRequest("GET", "people/${person.id}", null)
|
||||
|
||||
when:
|
||||
def response = controller.entity(request, baseUri, "people", "${person.id}")
|
||||
|
||||
then:
|
||||
response.statusCode == HttpStatus.OK
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,12 +38,12 @@ public class RestBuilder {
|
||||
|
||||
private ConversionService conversionService = new DefaultConversionService();
|
||||
private ClientHttpRequestFactory requestFactory;
|
||||
private RestTemplate restTemplate;
|
||||
private RestTemplate restTemplate;
|
||||
private HttpHeaders headers = new HttpHeaders();
|
||||
private MediaType contentType;
|
||||
private Class<?> responseType = byte[].class;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Map uriParams;
|
||||
private Object body;
|
||||
private Closure errorHandler;
|
||||
|
||||
public RestBuilder() {
|
||||
@@ -57,7 +57,7 @@ public class RestBuilder {
|
||||
|
||||
public Object call(Closure cl) {
|
||||
RestBuilder b = null != requestFactory ? new RestBuilder(requestFactory) : new RestBuilder();
|
||||
if (null != errorHandler) {
|
||||
if(null != errorHandler) {
|
||||
b.setErrorHandler(errorHandler);
|
||||
}
|
||||
b.conversionService = conversionService;
|
||||
@@ -78,7 +78,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object post(String url) {
|
||||
if (responseType == URI.class) {
|
||||
if(responseType == URI.class) {
|
||||
return restTemplate.postForLocation(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
} else {
|
||||
return restTemplate.postForEntity(maybeAddParams(url), new HttpEntity(body, headers), responseType);
|
||||
@@ -87,7 +87,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object put(String url) {
|
||||
if (null != uriParams) {
|
||||
if(null != uriParams) {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers), uriParams);
|
||||
} else {
|
||||
restTemplate.put(maybeAddParams(url), new HttpEntity(body, headers));
|
||||
@@ -118,23 +118,24 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object date(String date) {
|
||||
for (String fmt : DATE_FORMATS) {
|
||||
for(String fmt : DATE_FORMATS) {
|
||||
try {
|
||||
Date dte = new SimpleDateFormat(fmt).parse(date);
|
||||
headers.setDate(dte.getTime());
|
||||
break;
|
||||
} catch (ParseException e) {}
|
||||
} 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);
|
||||
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));
|
||||
}
|
||||
@@ -156,7 +157,7 @@ public class RestBuilder {
|
||||
|
||||
@SuppressWarnings({"unchecked"})
|
||||
public Object param(String key, String value) {
|
||||
if (null == uriParams) {
|
||||
if(null == uriParams) {
|
||||
uriParams = new HashMap();
|
||||
}
|
||||
uriParams.put(key, value);
|
||||
@@ -180,9 +181,10 @@ public class RestBuilder {
|
||||
|
||||
public Object setErrorHandler(Closure errorHandler) {
|
||||
this.errorHandler = errorHandler;
|
||||
if (null != errorHandler) {
|
||||
if(null != errorHandler) {
|
||||
this.restTemplate.setErrorHandler(new DefaultResponseErrorHandler() {
|
||||
@Override public void handleError(ClientHttpResponse response) throws IOException {
|
||||
@Override public void handleError(ClientHttpResponse response)
|
||||
throws IOException {
|
||||
RestBuilder.this.errorHandler.call(response);
|
||||
}
|
||||
});
|
||||
@@ -198,12 +200,12 @@ public class RestBuilder {
|
||||
@SuppressWarnings({"unchecked"})
|
||||
private String maybeAddParams(String url) {
|
||||
StringBuffer buff = new StringBuffer(url);
|
||||
if (null != uriParams) {
|
||||
if(null != uriParams) {
|
||||
buff.append("?");
|
||||
for (Map.Entry<String, String> entry : ((Map<String, String>) uriParams).entrySet()) {
|
||||
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) {
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
@@ -10,11 +11,13 @@ import javax.persistence.Id;
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String[] lines;
|
||||
private String city;
|
||||
private String province;
|
||||
private String postalCode;
|
||||
@ManyToOne
|
||||
private Person person;
|
||||
|
||||
public Address() {
|
||||
}
|
||||
@@ -62,4 +65,12 @@ public class Address {
|
||||
this.postalCode = postalCode;
|
||||
}
|
||||
|
||||
public Person getPerson() {
|
||||
return person;
|
||||
}
|
||||
|
||||
public void setPerson(Person person) {
|
||||
this.person = person;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
|
||||
public Address findByPerson(@Param("person") Person person);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
|
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
|
||||
import org.springframework.orm.jpa.JpaDialect;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
|
||||
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = ApplicationConfig.class)
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class ApplicationConfig {
|
||||
|
||||
@Bean public DataSource dataSource() {
|
||||
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
|
||||
return builder.setType(EmbeddedDatabaseType.HSQL).build();
|
||||
}
|
||||
|
||||
@Bean public EntityManagerFactory entityManagerFactory() {
|
||||
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
|
||||
vendorAdapter.setDatabase(Database.HSQL);
|
||||
vendorAdapter.setGenerateDdl(true);
|
||||
|
||||
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
|
||||
factory.setJpaVendorAdapter(vendorAdapter);
|
||||
factory.setPackagesToScan(getClass().getPackage().getName());
|
||||
factory.setDataSource(dataSource());
|
||||
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
return factory.getObject();
|
||||
}
|
||||
|
||||
@Bean public JpaDialect jpaDialect() {
|
||||
return new HibernateJpaDialect();
|
||||
}
|
||||
|
||||
@Bean public PlatformTransactionManager transactionManager() {
|
||||
JpaTransactionManager txManager = new JpaTransactionManager();
|
||||
txManager.setEntityManagerFactory(entityManagerFactory());
|
||||
return txManager;
|
||||
}
|
||||
|
||||
@Bean public TestRepositoryEventListener testRepositoryEventListener() {
|
||||
return new TestRepositoryEventListener();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,10 +12,10 @@ import javax.persistence.OneToMany;
|
||||
@Entity
|
||||
public class Family {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String surname;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String surname;
|
||||
@OneToMany
|
||||
private List<Person> members;
|
||||
private List<Person> members;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
|
||||
@@ -5,5 +5,6 @@ import org.springframework.data.repository.CrudRepository;
|
||||
/**
|
||||
* @author Jon Brisbin <jon@jbrisbin.com>
|
||||
*/
|
||||
public interface FamilyRepository extends CrudRepository<Family, Long> {
|
||||
public interface FamilyRepository
|
||||
extends CrudRepository<Family, Long> {
|
||||
}
|
||||
|
||||
@@ -5,25 +5,25 @@ import java.util.Map;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MapKey;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Version;
|
||||
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String name;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String name;
|
||||
@Version
|
||||
private Long version;
|
||||
private Long version;
|
||||
@OneToMany
|
||||
private List<Address> addresses;
|
||||
private List<Address> addresses;
|
||||
@OneToMany
|
||||
private Map<String, Profile> profiles;
|
||||
@MapKey(name = "type")
|
||||
private Map<String, Profile> profiles;
|
||||
|
||||
public Person() {
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@ import org.springframework.beans.factory.InitializingBean;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonLoader implements InitializingBean {
|
||||
public class PersonLoader
|
||||
implements InitializingBean {
|
||||
|
||||
private PersonRepository personRepository;
|
||||
private PersonRepository personRepository;
|
||||
private ProfileRepository profileRepository;
|
||||
private AddressRepository addressRepository;
|
||||
|
||||
@@ -39,7 +40,8 @@ public class PersonLoader implements InitializingBean {
|
||||
this.addressRepository = addressRepository;
|
||||
}
|
||||
|
||||
@Override public void afterPropertiesSet() throws Exception {
|
||||
@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>();
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
|
||||
@@ -10,7 +10,8 @@ import org.springframework.validation.Validator;
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class PersonValidator implements Validator {
|
||||
public class PersonValidator
|
||||
implements Validator {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PersonValidator.class);
|
||||
|
||||
@@ -19,7 +20,7 @@ public class PersonValidator implements Validator {
|
||||
}
|
||||
|
||||
@Override public void validate(Object target, Errors errors) {
|
||||
Person p = (Person) target;
|
||||
Person p = (Person)target;
|
||||
LOG.debug("validating Person " + p);
|
||||
ValidationUtils.rejectIfEmpty(errors, "name", "field.name.required", "Field 'name' cannot be blank.");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.springframework.data.rest.test.webmvc;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
@@ -10,9 +11,11 @@ import javax.persistence.Id;
|
||||
@Entity
|
||||
public class Profile {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String type;
|
||||
private String url;
|
||||
@ManyToOne
|
||||
private Person person;
|
||||
|
||||
public Profile() {
|
||||
}
|
||||
@@ -38,29 +41,37 @@ public class Profile {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public Person getPerson() {
|
||||
return person;
|
||||
}
|
||||
|
||||
public void setPerson(Person person) {
|
||||
this.person = person;
|
||||
}
|
||||
|
||||
@Override public boolean equals(Object o) {
|
||||
if (!(o instanceof Profile)) {
|
||||
if(!(o instanceof Profile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Profile p2 = (Profile) o;
|
||||
Profile p2 = (Profile)o;
|
||||
|
||||
boolean idEq;
|
||||
if (null != id) {
|
||||
if(null != id) {
|
||||
idEq = id.equals(p2.id);
|
||||
} else {
|
||||
idEq = p2.id == null;
|
||||
}
|
||||
|
||||
boolean typeEq;
|
||||
if (null != type) {
|
||||
if(null != type) {
|
||||
typeEq = type.equals(p2.type);
|
||||
} else {
|
||||
typeEq = p2.type == null;
|
||||
}
|
||||
|
||||
boolean urlEq;
|
||||
if (null != url) {
|
||||
if(null != url) {
|
||||
urlEq = url.equals(p2.url);
|
||||
} else {
|
||||
urlEq = p2.url == null;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, Long> {
|
||||
|
||||
public Address findByPerson(@Param("person") Person person);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRegistration;
|
||||
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestExporterServlet;
|
||||
import org.springframework.web.WebApplicationInitializer;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RestExporterWebInitializer implements WebApplicationInitializer {
|
||||
|
||||
@Override public void onStartup(ServletContext servletContext) throws ServletException {
|
||||
// Create the 'root' Spring application context
|
||||
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
|
||||
rootContext.register(ApplicationConfig.class);
|
||||
|
||||
// Manage the lifecycle of the root application context
|
||||
servletContext.addListener(new ContextLoaderListener(rootContext));
|
||||
|
||||
// Register and map the dispatcher servlet
|
||||
DispatcherServlet servlet = new RepositoryRestExporterServlet();
|
||||
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", servlet);
|
||||
dispatcher.setLoadOnStartup(1);
|
||||
dispatcher.addMapping("/*");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.data.rest.test.webmvc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import groovy.lang.Closure;
|
||||
import org.springframework.data.rest.repository.context.AbstractRepositoryEventListener;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class TestRepositoryEventListener extends AbstractRepositoryEventListener<TestRepositoryEventListener> {
|
||||
|
||||
private List<Closure> handlers = new ArrayList<Closure>();
|
||||
|
||||
public List<Closure> getHandlers() {
|
||||
return handlers;
|
||||
}
|
||||
|
||||
@Override protected void onBeforeSave(Object entity) {
|
||||
for(Closure cl : handlers) {
|
||||
cl.call("beforeSave", entity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override protected void onAfterSave(Object entity) {
|
||||
for(Closure cl : handlers) {
|
||||
cl.call("afterSave", entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,5 +9,6 @@ import org.springframework.data.rest.repository.annotation.RestResource;
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@RestResource(exported = false)
|
||||
public interface UuidTestRepository extends CrudRepository<UuidTest, UUID> {
|
||||
public interface UuidTestRepository
|
||||
extends CrudRepository<UuidTest, UUID> {
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<?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:p="http://www.springframework.org/schema/p"
|
||||
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">
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<import resource="shared.xml"/>
|
||||
|
||||
<jpa:repositories base-package="org.springframework.data.rest.test.webmvc"/>
|
||||
<bean class="org.springframework.data.rest.test.webmvc.ApplicationConfig"/>
|
||||
|
||||
<bean id="config" class="org.springframework.data.rest.webmvc.RepositoryRestConfiguration"
|
||||
p:jsonpParamName="callback"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?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">
|
||||
|
||||
<jdbc:embedded-database id="dataSource" type="HSQL"/>
|
||||
|
||||
<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>
|
||||
|
||||
</beans>
|
||||
@@ -6,6 +6,7 @@ curl -d 'http://localhost:8080/people/1
|
||||
http://localhost:8080/people/2' -H "Content-Type: text/uri-list" http://localhost:8080/family/1/members
|
||||
curl -d '{"postalCode":"12345","province":"MO","lines":["1 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
|
||||
curl -d "http://localhost:8080/address/1" -H "Content-Type: text/uri-list" http://localhost:8080/people/1/addresses
|
||||
curl -d "http://localhost:8080/people/1" -X PUT -H "Content-Type: text/uri-list" http://localhost:8080/address/1/person
|
||||
curl -d '{"postalCode":"54321","province":"MO","lines":["2 W 1st St."],"city":"Univille"}' -H "Content-Type: application/json" http://localhost:8080/address
|
||||
curl -d "http://localhost:8080/address/2" -H "Content-Type: text/uri-list" http://localhost:8080/people/2/addresses
|
||||
curl -d '{"type" : "twitter", "url": "#!/johndoe"}' -H "Content-Type: application/json" http://localhost:8080/profile
|
||||
|
||||
Reference in New Issue
Block a user