Big update. Bug fixes, code re-formatting, changing tests.

This commit is contained in:
Jon Brisbin
2012-07-26 13:31:33 -05:00
parent 6867d07295
commit 7f8abc5aa8
80 changed files with 2551 additions and 1541 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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() {
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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) {
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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")

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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>

View File

@@ -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"
))
}
}

View File

@@ -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
}
}

View File

@@ -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"
}
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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> {
}

View File

@@ -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() {
}

View File

@@ -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>();

View File

@@ -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;

View File

@@ -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.");
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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("/*");
}
}

View File

@@ -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);
}
}
}

View File

@@ -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> {
}

View File

@@ -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"

View File

@@ -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>

View File

@@ -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