DATAREST-93 - More cleanups.
Merged core and repository modules into core. Renamed some packages for consistency in naming and in preparation to break up some package cycles. Removed @BaseUri and the according resolver. Refactored controllers a bit to have more reusable chunks of code.
This commit is contained in:
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import java.util.Stack;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.ConverterNotFoundException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* This {@link ConversionService} implementation delegates the actual conversion to the {@literal ConversionService} it
|
||||
* finds in its internal {@link Stack} that claims to be able to convert a given class. It will roll through the
|
||||
* {@literal ConversionService}s until it finds one that can convert the given type.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @authot Oliver Gierke
|
||||
*/
|
||||
public class DelegatingConversionService implements ConversionService {
|
||||
|
||||
private final Stack<ConversionService> conversionServices;
|
||||
|
||||
public DelegatingConversionService(ConversionService... svcs) {
|
||||
|
||||
this.conversionServices = new Stack<ConversionService>();
|
||||
|
||||
for (ConversionService svc : svcs) {
|
||||
Assert.notNull(svc);
|
||||
conversionServices.add(svc);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.ConversionService#canConvert(java.lang.Class, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public boolean canConvert(Class<?> from, Class<?> to) {
|
||||
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.ConversionService#canConvert(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean canConvert(TypeDescriptor from, TypeDescriptor to) {
|
||||
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.ConversionService#convert(java.lang.Object, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> T convert(Object o, Class<T> type) {
|
||||
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(o.getClass(), type)) {
|
||||
return svc.convert(o, type);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ConverterNotFoundException(TypeDescriptor.forObject(o), TypeDescriptor.valueOf(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.ConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object o, TypeDescriptor from, TypeDescriptor to) {
|
||||
|
||||
for (ConversionService svc : conversionServices) {
|
||||
if (svc.canConvert(from, to)) {
|
||||
return svc.convert(o, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ConverterNotFoundException(from, to);
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ISO8601DateConverter implements ConditionalGenericConverter, Converter<String[], Date> {
|
||||
|
||||
public static final ConditionalGenericConverter INSTANCE = new ISO8601DateConverter();
|
||||
|
||||
private static final Set<ConvertiblePair> CONVERTIBLE_PAIRS = new HashSet<ConvertiblePair>();
|
||||
|
||||
static {
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(String.class, Date.class));
|
||||
CONVERTIBLE_PAIRS.add(new ConvertiblePair(Date.class, String.class));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
if (String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return Date.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
return Date.class.isAssignableFrom(sourceType.getType()) && String.class.isAssignableFrom(targetType.getType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return CONVERTIBLE_PAIRS;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
DateFormat dateFmt = iso8601DateFormat();
|
||||
|
||||
if (String.class.isAssignableFrom(sourceType.getType())) {
|
||||
return dateFmt.format(source);
|
||||
}
|
||||
|
||||
try {
|
||||
return dateFmt.parse(source.toString());
|
||||
} catch (ParseException e) {
|
||||
throw new ConversionFailedException(sourceType, targetType, source, e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Date convert(String[] source) {
|
||||
|
||||
if (source.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return iso8601DateFormat().parse(source[0]);
|
||||
} catch (ParseException e) {
|
||||
throw new ConversionFailedException(TypeDescriptor.valueOf(String[].class), TypeDescriptor.valueOf(Date.class),
|
||||
source[0], new IllegalArgumentException(
|
||||
"Source does not conform to ISO8601 date format (YYYY-MM-DDTHH:MM:SS-0000"));
|
||||
}
|
||||
}
|
||||
|
||||
private DateFormat iso8601DateFormat() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* {@link org.springframework.core.convert.ConversionService} and {@link org.springframework.core.convert.converter.Converter} integration for Spring Data REST.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
@@ -63,6 +63,10 @@ public class Path {
|
||||
return new Path(this.path + cleanUp(path), false);
|
||||
}
|
||||
|
||||
public Path slash(Path path) {
|
||||
return slash(path.toString());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
@@ -79,13 +83,14 @@ public class Path {
|
||||
}
|
||||
|
||||
String trimmed = path.trim().replaceAll(" ", "");
|
||||
trimmed = SLASH + trimmed.substring(getFirstNoneSlashIndex(trimmed));
|
||||
|
||||
while (trimmed.endsWith("/")) {
|
||||
trimmed = trimmed.substring(0, trimmed.length() - 1);
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
trimmed = trimmed.substring(getFirstNoneSlashIndex(trimmed));
|
||||
|
||||
return trimmed.contains("://") ? trimmed : SLASH + trimmed;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.validation.Errors;
|
||||
|
||||
/**
|
||||
* Exception that is thrown when a Spring {@link org.springframework.validation.Validator} throws an error.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryConstraintViolationException extends DataIntegrityViolationException {
|
||||
|
||||
private static final long serialVersionUID = -4789377071564956366L;
|
||||
|
||||
private final Errors errors;
|
||||
|
||||
public RepositoryConstraintViolationException(Errors errors) {
|
||||
super("Validation failed");
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
public Errors getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.ConversionFailedException;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.ConditionalGenericConverter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ConditionalGenericConverter} that can convert a {@link URI} domain entity.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class UriDomainClassConverter implements ConditionalGenericConverter {
|
||||
|
||||
private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class);
|
||||
|
||||
private final Repositories repositories;
|
||||
private final DomainClassConverter<?> domainClassConverter;
|
||||
private final Set<ConvertiblePair> convertiblePairs;
|
||||
|
||||
/**
|
||||
* Creates a new {@link UriDomainClassConverter} using the given {@link Repositories} and {@link DomainClassConverter}
|
||||
* .
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param domainClassConverter must not be {@literal null}.
|
||||
*/
|
||||
public UriDomainClassConverter(Repositories repositories, DomainClassConverter<?> domainClassConverter) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(domainClassConverter, "DomainClassConverter must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.domainClassConverter = domainClassConverter;
|
||||
this.convertiblePairs = new HashSet<ConvertiblePair>();
|
||||
|
||||
for (Class<?> domainType : repositories) {
|
||||
convertiblePairs.add(new ConvertiblePair(URI.class, domainType));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
return URI.class.isAssignableFrom(sourceType.getType())
|
||||
&& repositories.getPersistentEntity(targetType.getType()) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes()
|
||||
*/
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return convertiblePairs;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor)
|
||||
*/
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(targetType.getType());
|
||||
|
||||
if (entity == null || !domainClassConverter.matches(STRING_TYPE, targetType)) {
|
||||
throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException(
|
||||
"No PersistentEntity information available for " + targetType.getType()));
|
||||
}
|
||||
|
||||
URI uri = (URI) source;
|
||||
String[] parts = uri.getPath().split("/");
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException(
|
||||
"Cannot resolve URI " + uri + ". Is it local or remote? Only local URIs are resolvable."));
|
||||
}
|
||||
|
||||
return domainClassConverter.convert(parts[parts.length - 1], STRING_TYPE, targetType);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.validation.AbstractErrors;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
|
||||
/**
|
||||
* An {@link Errors} implementation for use in the events mechanism of Spring Data REST.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ValidationErrors extends AbstractErrors {
|
||||
|
||||
private static final long serialVersionUID = 8141826537389141361L;
|
||||
|
||||
private String name;
|
||||
private Object entity;
|
||||
private PersistentEntity<?, ?> persistentEntity;
|
||||
private List<ObjectError> globalErrors = new ArrayList<ObjectError>();
|
||||
private List<FieldError> fieldErrors = new ArrayList<FieldError>();
|
||||
|
||||
public ValidationErrors(String name, Object entity, PersistentEntity<?, ?> persistentEntity) {
|
||||
this.name = name;
|
||||
this.entity = entity;
|
||||
this.persistentEntity = persistentEntity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getObjectName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reject(String errorCode, Object[] errorArgs, String defaultMessage) {
|
||||
globalErrors.add(new ObjectError(name, new String[] { errorCode }, errorArgs, defaultMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rejectValue(String field, String errorCode, Object[] errorArgs, String defaultMessage) {
|
||||
fieldErrors.add(new FieldError(name, field, getFieldValue(field), true, new String[] { errorCode }, errorArgs,
|
||||
defaultMessage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addAllErrors(Errors errors) {
|
||||
globalErrors.addAll(errors.getAllErrors());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ObjectError> getGlobalErrors() {
|
||||
return globalErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FieldError> getFieldErrors() {
|
||||
return fieldErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getFieldValue(String field) {
|
||||
PersistentProperty<?> prop = persistentEntity != null ? persistentEntity.getPersistentProperty(field) : null;
|
||||
if (null == prop) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method getter = prop.getGetter();
|
||||
if (null != getter) {
|
||||
return invokeMethod(getter, entity);
|
||||
}
|
||||
Field fld = prop.getField();
|
||||
if (null != fld) {
|
||||
return getField(fld, entity);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Description {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterCreate {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal afterDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal afterLinkDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterLinkDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal afterLinkSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterLinkSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal afterSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleAfterSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeCreate {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal beforeDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal beforeLinkDelete} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeLinkDelete {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal beforeLinkSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeLinkSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Denotes a component that should handle the {@literal beforeSave} event.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.TYPE, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface HandleBeforeSave {
|
||||
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Advertises classes annotated with this that they are event handlers.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RepositoryEventHandler {
|
||||
|
||||
/**
|
||||
* The list of {@link org.springframework.context.ApplicationEvent} classes this event handler cares about.
|
||||
*/
|
||||
Class<?>[] value() default {};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package org.springframework.data.rest.core.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotate a {@link org.springframework.data.repository.Repository} with this to influence how it is exported and what
|
||||
* the value of the {@literal rel} attribute will be in links.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
public @interface RestResource {
|
||||
|
||||
/**
|
||||
* Flag indicating whether this resource is exported at all.
|
||||
*
|
||||
* @return {@literal true} if the resource is to be exported, {@literal false} otherwise.
|
||||
*/
|
||||
boolean exported() default true;
|
||||
|
||||
/**
|
||||
* The path segment under which this resource is to be exported.
|
||||
*
|
||||
* @return A valid path segment.
|
||||
*/
|
||||
String path() default "";
|
||||
|
||||
/**
|
||||
* The rel value to use when generating links to this resource.
|
||||
*
|
||||
* @return A valid rel value.
|
||||
*/
|
||||
String rel() default "";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.config;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class RepositoryRestConfiguration {
|
||||
|
||||
private URI baseUri = null;
|
||||
private int defaultPageSize = 20;
|
||||
private int maxPageSize = 1000;
|
||||
private String pageParamName = "page";
|
||||
private String limitParamName = "limit";
|
||||
private String sortParamName = "sort";
|
||||
private MediaType defaultMediaType = MediaType.APPLICATION_JSON;
|
||||
private boolean returnBodyOnCreate = false;
|
||||
private boolean returnBodyOnUpdate = false;
|
||||
private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
|
||||
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
|
||||
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
|
||||
|
||||
/**
|
||||
* The base URI against which the exporter should calculate its links.
|
||||
*
|
||||
* @return The base URI.
|
||||
*/
|
||||
public URI getBaseUri() {
|
||||
return baseUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The base URI against which the exporter should calculate its links.
|
||||
*
|
||||
* @param baseUri The base URI.
|
||||
*/
|
||||
public RepositoryRestConfiguration setBaseUri(URI baseUri) {
|
||||
Assert.notNull(baseUri, "The baseUri cannot be null.");
|
||||
this.baseUri = baseUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default size of {@link org.springframework.data.domain.Pageable}s. Default is 20.
|
||||
*
|
||||
* @return The default page size.
|
||||
*/
|
||||
public int getDefaultPageSize() {
|
||||
return defaultPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default size of {@link org.springframework.data.domain.Pageable}s.
|
||||
*
|
||||
* @param defaultPageSize The default page size.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDefaultPageSize(int defaultPageSize) {
|
||||
Assert.isTrue((defaultPageSize > 0), "Page size must be greater than 0.");
|
||||
this.defaultPageSize = defaultPageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum size of pages.
|
||||
*
|
||||
* @return Maximum page size.
|
||||
*/
|
||||
public int getMaxPageSize() {
|
||||
return maxPageSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum size of pages.
|
||||
*
|
||||
* @param maxPageSize Maximum page size.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setMaxPageSize(int maxPageSize) {
|
||||
Assert.isTrue((defaultPageSize > 0), "Maximum page size must be greater than 0.");
|
||||
this.maxPageSize = maxPageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the URL query string parameter that indicates what page to return. Default is 'page'.
|
||||
*
|
||||
* @return Name of the query parameter used to indicate the page number to return.
|
||||
*/
|
||||
public String getPageParamName() {
|
||||
return pageParamName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the URL query string parameter that indicates what page to return.
|
||||
*
|
||||
* @param pageParamName Name of the query parameter used to indicate the page number to return.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setPageParamName(String pageParamName) {
|
||||
Assert.notNull(pageParamName, "Page param name cannot be null.");
|
||||
this.pageParamName = pageParamName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the URL query string parameter that indicates how many results to return at once. Default is
|
||||
* 'limit'.
|
||||
*
|
||||
* @return Name of the query parameter used to indicate the maximum number of entries to return at a time.
|
||||
*/
|
||||
public String getLimitParamName() {
|
||||
return limitParamName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the URL query string parameter that indicates how many results to return at once.
|
||||
*
|
||||
* @param limitParamName Name of the query parameter used to indicate the maximum number of entries to return at a
|
||||
* time.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setLimitParamName(String limitParamName) {
|
||||
Assert.notNull(limitParamName, "Limit param name cannot be null.");
|
||||
this.limitParamName = limitParamName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the URL query string parameter that indicates what direction to sort results. Default is 'sort'.
|
||||
*
|
||||
* @return Name of the query string parameter used to indicate what field to sort on.
|
||||
*/
|
||||
public String getSortParamName() {
|
||||
return sortParamName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the URL query string parameter that indicates what direction to sort results.
|
||||
*
|
||||
* @param sortParamName Name of the query string parameter used to indicate what field to sort on.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setSortParamName(String sortParamName) {
|
||||
Assert.notNull(sortParamName, "Sort param name cannot be null.");
|
||||
this.sortParamName = sortParamName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link MediaType} to use as a default when none is specified.
|
||||
*
|
||||
* @return Default content type if none has been specified.
|
||||
*/
|
||||
public MediaType getDefaultMediaType() {
|
||||
return defaultMediaType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link MediaType} to use as a default when none is specified.
|
||||
*
|
||||
* @param defaultMediaType Default content type if none has been specified.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setDefaultMediaType(MediaType defaultMediaType) {
|
||||
this.defaultMediaType = defaultMediaType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to return a response body after creating an entity.
|
||||
*
|
||||
* @return {@literal true} to return a body on create, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isReturnBodyOnCreate() {
|
||||
return returnBodyOnCreate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to return a response body after creating an entity.
|
||||
*
|
||||
* @param returnBodyOnCreate {@literal true} to return a body on create, {@literal false} otherwise.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration setReturnBodyOnCreate(boolean returnBodyOnCreate) {
|
||||
this.returnBodyOnCreate = returnBodyOnCreate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to return a response body after updating an entity.
|
||||
*
|
||||
* @return {@literal true} to return a body on update, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isReturnBodyOnUpdate() {
|
||||
return returnBodyOnUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to return a response body after updating an entity.
|
||||
*
|
||||
* @param returnBodyOnUpdate
|
||||
* @return
|
||||
*/
|
||||
public RepositoryRestConfiguration setReturnBodyOnUpdate(boolean returnBodyOnUpdate) {
|
||||
this.returnBodyOnUpdate = returnBodyOnUpdate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start configuration a {@link ResourceMapping} for a specific domain type.
|
||||
*
|
||||
* @param domainType The {@link Class} of the domain type to configure a mapping for.
|
||||
* @return A new {@link ResourceMapping} for configuring how a domain type is mapped.
|
||||
*/
|
||||
public ResourceMapping setResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.setResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceMapping} for a specific domain type.
|
||||
*
|
||||
* @param domainType The {@link Class} of the domain type.
|
||||
* @return A {@link ResourceMapping} for that domain type or {@literal null} if none exists.
|
||||
*/
|
||||
public ResourceMapping getResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.getResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether there is a {@link ResourceMapping} for the given domain type.
|
||||
*
|
||||
* @param domainType The domain type to find a {@link ResourceMapping} for.
|
||||
* @return {@literal true} if a {@link ResourceMapping} exists for this domain class, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean hasResourceMappingForDomainType(Class<?> domainType) {
|
||||
return domainMappings.hasResourceMappingFor(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceMappingConfiguration} that is currently configured.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ResourceMappingConfiguration getDomainTypesResourceMappingConfiguration() {
|
||||
return domainMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start configuration a {@link ResourceMapping} for a specific repository interface.
|
||||
*
|
||||
* @param repositoryInterface The {@link Class} of the repository interface to configure a mapping for.
|
||||
* @return A new {@link ResourceMapping} for configuring how a repository interface is mapped.
|
||||
*/
|
||||
public ResourceMapping setResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.setResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link ResourceMapping} for a specific repository interface.
|
||||
*
|
||||
* @param repositoryInterface The {@link Class} of the repository interface.
|
||||
* @return A {@link ResourceMapping} for that repository interface or {@literal null} if none exists.
|
||||
*/
|
||||
public ResourceMapping getResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.getResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether there is a {@link ResourceMapping} configured for this {@literal Repository} class.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
public boolean hasResourceMappingForRepository(Class<?> repositoryInterface) {
|
||||
return repoMappings.hasResourceMappingFor(repositoryInterface);
|
||||
}
|
||||
|
||||
public ResourceMapping findRepositoryMappingForPath(String path) {
|
||||
Class<?> type = repoMappings.findTypeForPath(path);
|
||||
if (null == type) {
|
||||
return null;
|
||||
}
|
||||
return repoMappings.getResourceMappingFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we expose the ID property for this domain type?
|
||||
*
|
||||
* @param domainType The domain type we may need to expose the ID for.
|
||||
* @return {@literal true} is the ID is to be exposed, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isIdExposedFor(Class<?> domainType) {
|
||||
return exposeIdsFor.contains(domainType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of domain types for which we will expose the ID value as a normal property.
|
||||
*
|
||||
* @param domainTypes Array of types to expose IDs for.
|
||||
* @return {@literal this}
|
||||
*/
|
||||
public RepositoryRestConfiguration exposeIdsFor(Class<?>... domainTypes) {
|
||||
Collections.addAll(exposeIdsFor, domainTypes);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.config;
|
||||
|
||||
import static org.springframework.data.rest.core.support.ResourceMappingUtils.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Deprecated
|
||||
public class ResourceMapping {
|
||||
|
||||
private String rel;
|
||||
private String path;
|
||||
private boolean exported = true;
|
||||
private final Map<String, ResourceMapping> resourceMappings = new HashMap<String, ResourceMapping>();
|
||||
|
||||
public ResourceMapping() {}
|
||||
|
||||
public ResourceMapping(Class<?> type) {
|
||||
rel = findRel(type);
|
||||
path = findPath(type);
|
||||
exported = findExported(type);
|
||||
}
|
||||
|
||||
public ResourceMapping(String rel, String path) {
|
||||
this.rel = rel;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public ResourceMapping(String rel, String path, boolean exported) {
|
||||
this.rel = rel;
|
||||
this.path = path;
|
||||
this.exported = exported;
|
||||
}
|
||||
|
||||
public String getRel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
public ResourceMapping setRel(String rel) {
|
||||
this.rel = rel;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public ResourceMapping setPath(String path) {
|
||||
this.path = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isExported() {
|
||||
return exported;
|
||||
}
|
||||
|
||||
public ResourceMapping setExported(boolean exported) {
|
||||
this.exported = exported;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceMapping addResourceMappings(Map<String, ResourceMapping> mappings) {
|
||||
if (null == mappings) {
|
||||
return this;
|
||||
}
|
||||
|
||||
resourceMappings.putAll(mappings);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ResourceMapping addResourceMappingFor(String name) {
|
||||
ResourceMapping rm = new ResourceMapping();
|
||||
resourceMappings.put(name, rm);
|
||||
return rm;
|
||||
}
|
||||
|
||||
public ResourceMapping getResourceMappingFor(String name) {
|
||||
return resourceMappings.get(name);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingFor(String name) {
|
||||
return resourceMappings.containsKey(name);
|
||||
}
|
||||
|
||||
public Map<String, ResourceMapping> getResourceMappings() {
|
||||
return resourceMappings;
|
||||
}
|
||||
|
||||
public String getNameForPath(String path) {
|
||||
for (Map.Entry<String, ResourceMapping> mapping : resourceMappings.entrySet()) {
|
||||
if (mapping.getValue().getPath().equals(path)) {
|
||||
return mapping.getKey();
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResourceMapping{" + "rel='" + rel + '\'' + ", path='" + path + '\'' + ", exported=" + exported
|
||||
+ ", resourceMappings=" + resourceMappings + '}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Manages the {@link ResourceMapping} configurations for any resources being exported. This includes domain entities
|
||||
* and repositories.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ResourceMappingConfiguration {
|
||||
|
||||
private final Map<Class<?>, ResourceMapping> resourceMappings = new HashMap<Class<?>, ResourceMapping>();
|
||||
|
||||
public ResourceMapping setResourceMappingFor(Class<?> type) {
|
||||
ResourceMapping rm = resourceMappings.get(type);
|
||||
if (null == rm) {
|
||||
rm = new ResourceMapping(type);
|
||||
resourceMappings.put(type, rm);
|
||||
}
|
||||
return rm;
|
||||
}
|
||||
|
||||
public ResourceMapping getResourceMappingFor(Class<?> type) {
|
||||
return resourceMappings.get(type);
|
||||
}
|
||||
|
||||
public boolean hasResourceMappingFor(Class<?> type) {
|
||||
return resourceMappings.containsKey(type);
|
||||
}
|
||||
|
||||
public Class<?> findTypeForPath(String path) {
|
||||
if (null == path) {
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<Class<?>, ResourceMapping> entry : resourceMappings.entrySet()) {
|
||||
if (path.equals(entry.getValue().getPath())) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
|
||||
/**
|
||||
* Abstract class that listens for generic {@link RepositoryEvent}s and dispatches them to a specific method based on
|
||||
* the event type.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class AbstractRepositoryEventListener<T> implements ApplicationListener<RepositoryEvent>,
|
||||
ApplicationContextAware {
|
||||
|
||||
private final Class<?> INTERESTED_TYPE = resolveTypeArgument(getClass(), AbstractRepositoryEventListener.class);
|
||||
protected ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@Override
|
||||
public final void onApplicationEvent(RepositoryEvent event) {
|
||||
Class<?> srcType = event.getSource().getClass();
|
||||
if (null != INTERESTED_TYPE && !INTERESTED_TYPE.isAssignableFrom(srcType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event instanceof BeforeSaveEvent) {
|
||||
onBeforeSave((T) event.getSource());
|
||||
} else if (event instanceof BeforeCreateEvent) {
|
||||
onBeforeCreate((T) event.getSource());
|
||||
} else if (event instanceof AfterCreateEvent) {
|
||||
onAfterCreate((T) event.getSource());
|
||||
} else if (event instanceof AfterSaveEvent) {
|
||||
onAfterSave((T) event.getSource());
|
||||
} else if (event instanceof BeforeLinkSaveEvent) {
|
||||
onBeforeLinkSave((T) event.getSource(), ((BeforeLinkSaveEvent) event).getLinked());
|
||||
} else if (event instanceof AfterLinkSaveEvent) {
|
||||
onAfterLinkSave((T) event.getSource(), ((AfterLinkSaveEvent) event).getLinked());
|
||||
} else if (event instanceof BeforeLinkDeleteEvent) {
|
||||
onBeforeLinkDelete((T) event.getSource(), ((BeforeLinkDeleteEvent) event).getLinked());
|
||||
} else if (event instanceof AfterLinkDeleteEvent) {
|
||||
onAfterLinkDelete((T) event.getSource(), ((AfterLinkDeleteEvent) event).getLinked());
|
||||
} else if (event instanceof BeforeDeleteEvent) {
|
||||
onBeforeDelete((T) event.getSource());
|
||||
} else if (event instanceof AfterDeleteEvent) {
|
||||
onAfterDelete((T) event.getSource());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeCreate} events.
|
||||
*
|
||||
* @param entity The entity being created.
|
||||
*/
|
||||
protected void onBeforeCreate(T entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterCreate} events.
|
||||
*
|
||||
* @param entity The entity that was created.
|
||||
*/
|
||||
protected void onAfterCreate(T entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeSave} events.
|
||||
*
|
||||
* @param entity The entity being saved.
|
||||
*/
|
||||
protected void onBeforeSave(T entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterSave} events.
|
||||
*
|
||||
* @param entity The entity that was just saved.
|
||||
*/
|
||||
protected void onAfterSave(T entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkSave} events.
|
||||
*
|
||||
* @param parent The parent entity to which the child object is linked.
|
||||
* @param linked The linked, child entity.
|
||||
*/
|
||||
protected void onBeforeLinkSave(T parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkSave} events.
|
||||
*
|
||||
* @param parent The parent entity to which the child object is linked.
|
||||
* @param linked The linked, child entity.
|
||||
*/
|
||||
protected void onAfterLinkSave(T parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeLinkDelete} events.
|
||||
*
|
||||
* @param parent The parent entity to which the child object is linked.
|
||||
* @param linked The linked, child entity.
|
||||
*/
|
||||
protected void onBeforeLinkDelete(T parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterLinkDelete} events.
|
||||
*
|
||||
* @param parent The parent entity to which the child object is linked.
|
||||
* @param linked The linked, child entity.
|
||||
*/
|
||||
protected void onAfterLinkDelete(T parent, Object linked) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal beforeDelete} events.
|
||||
*
|
||||
* @param entity The entity that is being deleted.
|
||||
*/
|
||||
protected void onBeforeDelete(T entity) {}
|
||||
|
||||
/**
|
||||
* Override this method if you are interested in {@literal afterDelete} events.
|
||||
*
|
||||
* @param entity The entity that was just deleted.
|
||||
*/
|
||||
protected void onAfterDelete(T entity) {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Event that is emitted after a new entity is saved.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterCreateEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = -7673953693485678403L;
|
||||
|
||||
public AfterCreateEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted after the entity is deleted from the repository.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterDeleteEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = -6090615345948638970L;
|
||||
|
||||
public AfterDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted after a link to a related object is deleted from the parent.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterLinkDeleteEvent extends LinkSaveEvent {
|
||||
|
||||
private static final long serialVersionUID = 3887575011761146290L;
|
||||
|
||||
public AfterLinkDeleteEvent(Object source, Object linked) {
|
||||
super(source, linked);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted after saving a linked object to its parent in the repository.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterLinkSaveEvent extends LinkSaveEvent {
|
||||
|
||||
private static final long serialVersionUID = 261522353893713633L;
|
||||
|
||||
public AfterLinkSaveEvent(Object source, Object child) {
|
||||
super(source, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted after a save to the repository.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AfterSaveEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = 8568843338617401903L;
|
||||
|
||||
public AfterSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class AnnotatedHandlerBeanPostProcessor implements ApplicationListener<RepositoryEvent>, BeanPostProcessor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AnnotatedHandlerBeanPostProcessor.class);
|
||||
private final MultiValueMap<Class<? extends RepositoryEvent>, EventHandlerMethod> handlerMethods = new LinkedMultiValueMap<Class<? extends RepositoryEvent>, AnnotatedHandlerBeanPostProcessor.EventHandlerMethod>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RepositoryEvent event) {
|
||||
Class<? extends RepositoryEvent> eventType = event.getClass();
|
||||
if (!handlerMethods.containsKey(eventType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (EventHandlerMethod handlerMethod : handlerMethods.get(eventType)) {
|
||||
try {
|
||||
Object src = event.getSource();
|
||||
|
||||
if (!ClassUtils.isAssignable(handlerMethod.targetType, src.getClass())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
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 (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + event.getClass().getSimpleName() + " handler for " + event.getSource());
|
||||
}
|
||||
handlerMethod.method.invoke(handlerMethod.handler, params.toArray());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
|
||||
final Class<?> beanType = bean.getClass();
|
||||
|
||||
RepositoryEventHandler typeAnno = AnnotationUtils.findAnnotation(beanType, RepositoryEventHandler.class);
|
||||
if (null == typeAnno) {
|
||||
return bean;
|
||||
}
|
||||
|
||||
Class<?>[] targetTypes = typeAnno.value();
|
||||
if (targetTypes.length == 0) {
|
||||
targetTypes = new Class<?>[] { null };
|
||||
}
|
||||
|
||||
for (final Class<?> targetType : targetTypes) {
|
||||
ReflectionUtils.doWithMethods(beanType, new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
inspect(targetType, bean, method, HandleBeforeCreate.class, BeforeCreateEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterCreate.class, AfterCreateEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeSave.class, BeforeSaveEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterSave.class, AfterSaveEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeLinkSave.class, BeforeLinkSaveEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterLinkSave.class, AfterLinkSaveEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeDelete.class, BeforeDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterDelete.class, AfterDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleBeforeLinkDelete.class, BeforeLinkDeleteEvent.class);
|
||||
inspect(targetType, bean, method, HandleAfterLinkDelete.class, AfterLinkDeleteEvent.class);
|
||||
}
|
||||
}, new ReflectionUtils.MethodFilter() {
|
||||
@Override
|
||||
public boolean matches(Method method) {
|
||||
return (!method.isSynthetic() && !method.isBridge() && method.getDeclaringClass() != Object.class && !method
|
||||
.getName().contains("$"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
private <T extends Annotation> void inspect(Class<?> targetType, Object handler, Method method, Class<T> annoType,
|
||||
Class<? extends RepositoryEvent> eventType) {
|
||||
T anno = method.getAnnotation(annoType);
|
||||
if (null != anno) {
|
||||
try {
|
||||
Class<?>[] targetTypes;
|
||||
if (null == targetType) {
|
||||
targetTypes = (Class<?>[]) anno.getClass().getMethod("value", new Class[0]).invoke(anno);
|
||||
} else {
|
||||
targetTypes = new Class<?>[] { targetType };
|
||||
}
|
||||
for (Class<?> type : targetTypes) {
|
||||
EventHandlerMethod m = new EventHandlerMethod(type, handler, method);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Annotated handler method found: " + m);
|
||||
}
|
||||
handlerMethods.add(eventType, m);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class EventHandlerMethod {
|
||||
final Class<?> targetType;
|
||||
final Method method;
|
||||
final Object handler;
|
||||
|
||||
private EventHandlerMethod(Class<?> targetType, Object handler, Method method) {
|
||||
this.targetType = targetType;
|
||||
this.method = method;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventHandlerMethod{" + "targetType=" + targetType + ", method=" + method + ", handler=" + handler + '}';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Event emitted before an entity is saved for the first time.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class BeforeCreateEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = -1642841708537223975L;
|
||||
|
||||
public BeforeCreateEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted before an entity is deleted from the repository.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeDeleteEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = 9150212393209433211L;
|
||||
|
||||
public BeforeDeleteEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted before a link to a related object is deleted from the parent.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class BeforeLinkDeleteEvent extends LinkSaveEvent {
|
||||
|
||||
private static final long serialVersionUID = -973540913790564962L;
|
||||
|
||||
public BeforeLinkDeleteEvent(Object source, Object linked) {
|
||||
super(source, linked);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted before a linked object is saved to the repository.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class BeforeLinkSaveEvent extends LinkSaveEvent {
|
||||
|
||||
private static final long serialVersionUID = 4836932640633578985L;
|
||||
|
||||
public BeforeLinkSaveEvent(Object source, Object linked) {
|
||||
super(source, linked);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Emitted before an entity is saved into the repository.
|
||||
*/
|
||||
public class BeforeSaveEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = -1404580942928384726L;
|
||||
|
||||
public BeforeSaveEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* An event to encapsulate an exception occurring anywhere within the REST exporter.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class ExceptionEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = 6614805546974091704L;
|
||||
|
||||
public ExceptionEvent(Throwable t) {
|
||||
super(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of this exception event.
|
||||
*
|
||||
* @return The {@link Throwable} that is the source of this exception event.
|
||||
*/
|
||||
public Throwable getException() {
|
||||
return (Throwable) getSource();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
/**
|
||||
* Base class for {@link RepositoryEvent}s that deal with saving/updating or deleting a linked object.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class LinkSaveEvent extends RepositoryEvent {
|
||||
|
||||
private static final long serialVersionUID = -9071648572128698903L;
|
||||
private final Object linked;
|
||||
|
||||
public LinkSaveEvent(Object source, Object linked) {
|
||||
super(source);
|
||||
this.linked = linked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the linked object.
|
||||
*
|
||||
* @return The entity representing the right-hand side of this relationship.
|
||||
*/
|
||||
public Object getLinked() {
|
||||
return linked;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
/**
|
||||
* Abstract base class for events emitted by the REST exporter.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class RepositoryEvent extends ApplicationEvent {
|
||||
|
||||
private static final long serialVersionUID = -966689410815418259L;
|
||||
|
||||
protected RepositoryEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package org.springframework.data.rest.core.event;
|
||||
|
||||
import static org.springframework.beans.factory.BeanFactoryUtils.*;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.core.ValidationErrors;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.core.util.MapUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.ValidationUtils;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.context.ApplicationListener} implementation that dispatches {@link RepositoryEvent}s to a
|
||||
* specific {@link Validator}.
|
||||
*
|
||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
||||
*/
|
||||
public class ValidatingRepositoryEventListener extends AbstractRepositoryEventListener<Object> implements
|
||||
InitializingBean {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ValidatingRepositoryEventListener.class);
|
||||
@SuppressWarnings({ "unchecked" }) private static final List<Class<? extends Annotation>> ANNOTATIONS_TO_FIND = Arrays
|
||||
.asList(HandleBeforeSave.class, HandleAfterSave.class, HandleBeforeDelete.class, HandleAfterDelete.class,
|
||||
HandleBeforeLinkSave.class, HandleAfterLinkSave.class, HandleBeforeLinkDelete.class,
|
||||
HandleAfterLinkDelete.class);
|
||||
@Autowired private Repositories repositories;
|
||||
private MultiValueMap<String, Validator> validators = new LinkedMultiValueMap<String, Validator>();
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
if (validators.size() == 0) {
|
||||
for (Map.Entry<String, Validator> entry : beansOfTypeIncludingAncestors(applicationContext, Validator.class)
|
||||
.entrySet()) {
|
||||
String name = null;
|
||||
Validator v = entry.getValue();
|
||||
|
||||
if (entry.getKey().contains("Save")) {
|
||||
name = entry.getKey().substring(0, entry.getKey().indexOf("Save") + 4);
|
||||
} else if (entry.getKey().contains("Create")) {
|
||||
name = entry.getKey().substring(0, entry.getKey().indexOf("Create") + 6);
|
||||
} else if (entry.getKey().contains("Delete")) {
|
||||
name = entry.getKey().substring(0, entry.getKey().indexOf("Delete") + 6);
|
||||
} else {
|
||||
|
||||
for (Class<? extends Annotation> annoType : ANNOTATIONS_TO_FIND) {
|
||||
if (findAnnotation(v.getClass(), annoType) != null) {
|
||||
name = uncapitalize(annoType.getSimpleName().substring(6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (null != name) {
|
||||
this.validators.add(name, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
|
||||
*
|
||||
* @return Validators assigned to events.
|
||||
*/
|
||||
public Map<String, Collection<Validator>> getValidators() {
|
||||
return MapUtils.toMap(validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a Map of {@link Validator}s that are assigned to the various {@link RepositoryEvent}s.
|
||||
*
|
||||
* @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()) {
|
||||
this.validators.put(entry.getKey(), new ArrayList<Validator>(entry.getValue()));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @return @this
|
||||
*/
|
||||
public ValidatingRepositoryEventListener addValidator(String event, Validator validator) {
|
||||
validators.add(event, validator);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeCreate(Object entity) {
|
||||
validate("beforeCreate", entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterCreate(Object entity) {
|
||||
validate("afterCreate", entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeSave(Object entity) {
|
||||
validate("beforeSave", entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterSave(Object entity) {
|
||||
validate("afterSave", entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeLinkSave(Object parent, Object linked) {
|
||||
validate("beforeLinkSave", parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterLinkSave(Object parent, Object linked) {
|
||||
validate("afterLinkSave", parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBeforeDelete(Object entity) {
|
||||
validate("beforeDelete", entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAfterDelete(Object entity) {
|
||||
validate("afterDelete", entity);
|
||||
}
|
||||
|
||||
private Errors validate(String event, Object o) {
|
||||
Errors errors = null;
|
||||
if (null != o) {
|
||||
Class<?> domainType = o.getClass();
|
||||
errors = new ValidationErrors(domainType.getSimpleName(), o, repositories.getPersistentEntity(domainType));
|
||||
|
||||
Collection<Validator> validators = this.validators.get(event);
|
||||
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) {
|
||||
throw new RepositoryConstraintViolationException(errors);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Represents one of the CRUD methods supported by
|
||||
* {@link org.springframework.data.repository.PagingAndSortingRepository} or
|
||||
* {@link org.springframework.data.repository.CrudRepository}.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public enum CrudMethod {
|
||||
|
||||
COUNT, DELETE_ALL, DELETE_ONE, DELETE_SOME, FIND_ALL, FIND_ONE, FIND_SOME, SAVE_ONE, SAVE_SOME;
|
||||
|
||||
/**
|
||||
* Get an enum from a {@link Method}. Narrow down overridden methods by looking for {@link Iterable} in the first
|
||||
* parameter, which tells us it is a '_SOME' type.
|
||||
*
|
||||
* @param m The CRUD method from the repository interface.
|
||||
* @return An enum representing which CRUD operation this method represents.
|
||||
*/
|
||||
public static CrudMethod fromMethod(Method m) {
|
||||
String s = m.getName();
|
||||
Class<?>[] paramTypes = m.getParameterTypes();
|
||||
boolean some = (paramTypes.length > 0 && Iterable.class.isAssignableFrom(paramTypes[0]));
|
||||
if ("count".equals(s)) {
|
||||
return COUNT;
|
||||
} else if ("delete".equals(s)) {
|
||||
return (some ? DELETE_SOME : DELETE_ONE);
|
||||
} else if ("deleteAll".equals(s)) {
|
||||
return DELETE_ALL;
|
||||
} else if ("findAll".equals(s)) {
|
||||
return (some ? FIND_SOME : FIND_ALL);
|
||||
} else if ("findOne".equals(s)) {
|
||||
return FIND_ONE;
|
||||
} else if ("save".equals(s)) {
|
||||
return (some ? SAVE_SOME : SAVE_ONE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn this enum into a method name.
|
||||
*
|
||||
* @return The method name as a string.
|
||||
*/
|
||||
public String toMethodName() {
|
||||
switch (this) {
|
||||
case COUNT:
|
||||
return "count";
|
||||
case DELETE_ALL:
|
||||
return "deleteAll";
|
||||
case DELETE_ONE:
|
||||
case DELETE_SOME:
|
||||
return "delete";
|
||||
case FIND_ALL:
|
||||
case FIND_SOME:
|
||||
return "findAll";
|
||||
case FIND_ONE:
|
||||
return "findOne";
|
||||
case SAVE_ONE:
|
||||
case SAVE_SOME:
|
||||
return "save";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
|
||||
/**
|
||||
* {@link RepositoryInvoker} to shortcut execution of CRUD methods into direct calls on a {@link CrudRepository}. Used
|
||||
* to avoid reflection overhead introduced by the base class if we know we work with a {@link CrudRepository}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class CrudRepositoryInvoker extends ReflectionRepositoryInvoker {
|
||||
|
||||
private final CrudRepository<Object, Serializable> repository;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CrudRepositoryInvoker} for the given {@link CrudRepository}, {@link RepositoryInformation} and
|
||||
* {@link ConversionService}.
|
||||
*
|
||||
* @param repository must not be {@literal null}.
|
||||
* @param information must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public CrudRepositoryInvoker(CrudRepository<Object, Serializable> repository, RepositoryInformation information,
|
||||
ConversionService conversionService) {
|
||||
|
||||
super(repository, information, conversionService);
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method equivalent to {@link CrudRepository#findAll()}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected Iterable<Object> invokeFindAll() {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public Iterable<Object> invokeFindAll(Sort pageable) {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Iterable<Object> invokeFindAll(Pageable pageable) {
|
||||
return repository.findAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public Object invokeFindOne(Serializable id) {
|
||||
return repository.findOne(convertId(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object invokeSave(Object entity) {
|
||||
return repository.save(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public void invokeDelete(Serializable id) {
|
||||
repository.delete(convertId(id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
|
||||
/**
|
||||
* A special {@link RepositoryInvoker} that shortcuts invocations to methods on {@link PagingAndSortingRepository} to
|
||||
* avoid reflection overhead introduced by the superclass.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
|
||||
|
||||
private final PagingAndSortingRepository<Object, Serializable> repository;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PagingAndSortingRepositoryInvoker} using the given repository, {@link RepositoryInformation}
|
||||
* and {@link ConversionService}.
|
||||
*
|
||||
* @param repository must not be {@literal null}.
|
||||
* @param information must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Serializable> repository,
|
||||
RepositoryInformation information, ConversionService conversionService) {
|
||||
|
||||
super(repository, information, conversionService);
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public Iterable<Object> invokeFindAll(Sort sort) {
|
||||
return sort == null ? invokeFindAll() : repository.findAll(sort);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Iterable<Object> invokeFindAll(Pageable pageable) {
|
||||
return pageable == null ? invokeFindAll() : repository.findAll(pageable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.core.CrudMethods;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.hateoas.core.AnnotationAttribute;
|
||||
import org.springframework.hateoas.core.MethodParameters;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class ReflectionRepositoryInvoker implements RepositoryInvoker {
|
||||
|
||||
private static final AnnotationAttribute PARAM_ANNOTATION = new AnnotationAttribute(Param.class);
|
||||
|
||||
private final Object repository;
|
||||
private final CrudMethods methods;
|
||||
private final RepositoryInformation information;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReflectionRepositoryInvoker} for the given repository, {@link RepositoryInformation} and
|
||||
* {@link ConversionService}.
|
||||
*
|
||||
* @param repository must not be {@literal null}.
|
||||
* @param information must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public ReflectionRepositoryInvoker(Object repository, RepositoryInformation information,
|
||||
ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(repository, "Repository must not be null!");
|
||||
Assert.notNull(information, "RepositoryInformation must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
this.repository = repository;
|
||||
this.methods = information.getCrudMethods();
|
||||
this.information = information;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindAll()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesFindAll() {
|
||||
return methods.hasFindAllMethod() && exposes(methods.getFindAllMethod());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Iterable<Object> invokeFindAll(Sort sort) {
|
||||
return (Iterable<Object>) invoke(methods.getFindAllMethod(), sort);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Iterable<Object> invokeFindAll(Pageable pageable) {
|
||||
|
||||
if (!exposesFindAll()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Method method = methods.getFindAllMethod();
|
||||
Class<?>[] types = method.getParameterTypes();
|
||||
|
||||
if (types.length == 0) {
|
||||
return invoke(method);
|
||||
}
|
||||
|
||||
if (Sort.class.isAssignableFrom(types[0])) {
|
||||
return invoke(method, pageable == null ? null : pageable.getSort());
|
||||
}
|
||||
|
||||
return invoke(method, pageable);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesSave()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesSave() {
|
||||
return methods.hasSaveMethod() && exposes(methods.getSaveMethod());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeSave(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object invokeSave(Object object) {
|
||||
return invoke(methods.getSaveMethod(), object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindOne()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesFindOne() {
|
||||
return methods.hasFindOneMethod() && exposes(methods.getFindOneMethod());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public Object invokeFindOne(Serializable id) {
|
||||
return invoke(methods.getFindOneMethod(), convertId(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesDelete()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesDelete() {
|
||||
return methods.hasDelete() && exposes(methods.getDeleteMethod());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public void invokeDelete(Serializable id) {
|
||||
|
||||
Method method = methods.getDeleteMethod();
|
||||
|
||||
if (method.getParameterTypes()[0].equals(Serializable.class)) {
|
||||
invoke(method, convertId(id));
|
||||
} else {
|
||||
invoke(method, invokeFindOne(id));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean exposes(Method method) {
|
||||
|
||||
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
|
||||
return annotation == null ? true : annotation.exported();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeQueryMethod(java.lang.reflect.Method, java.util.Map, org.springframework.data.domain.Pageable, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort) {
|
||||
return invoke(method, prepareParameters(method, parameters, pageable, sort));
|
||||
}
|
||||
|
||||
private Object[] prepareParameters(Method method, Map<String, String[]> rawParameters, Pageable pageable, Sort sort) {
|
||||
|
||||
List<MethodParameter> parameters = new MethodParameters(method, PARAM_ANNOTATION).getParameters();
|
||||
|
||||
if (parameters.isEmpty()) {
|
||||
return new Object[0];
|
||||
}
|
||||
|
||||
Object[] result = new Object[parameters.size()];
|
||||
Sort sortToUse = pageable == null ? sort : pageable.getSort();
|
||||
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
|
||||
MethodParameter param = parameters.get(i);
|
||||
Class<?> targetType = param.getParameterType();
|
||||
|
||||
if (Pageable.class.isAssignableFrom(targetType)) {
|
||||
result[i] = pageable;
|
||||
} else if (Sort.class.isAssignableFrom(targetType)) {
|
||||
result[i] = sortToUse;
|
||||
} else {
|
||||
|
||||
String parameterName = param.getParameterName();
|
||||
|
||||
if (!StringUtils.hasText(parameterName)) {
|
||||
throw new IllegalArgumentException("No @Param annotation found on query method " + method.getName()
|
||||
+ " for parameter " + parameterName);
|
||||
}
|
||||
|
||||
String[] parameterValue = rawParameters.get(parameterName);
|
||||
Object value = parameterValue == null ? null : parameterValue.length == 1 ? parameterValue[0] : parameterValue;
|
||||
|
||||
result[i] = conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(param));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the given method with the given arguments on the backing repository.
|
||||
*
|
||||
* @param method
|
||||
* @param arguments
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T invoke(Method method, Object... arguments) {
|
||||
return (T) ReflectionUtils.invokeMethod(method, repository, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given id into the id type of the backing repository.
|
||||
*
|
||||
* @param id must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected Serializable convertId(Serializable id) {
|
||||
Assert.notNull(id, "Id must not be null!");
|
||||
return conversionService.convert(id, information.getIdType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
/**
|
||||
* Meta-information about the methods a repository exposes.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RepositoryInvocationInformation {
|
||||
|
||||
boolean exposesSave();
|
||||
|
||||
boolean exposesDelete();
|
||||
|
||||
boolean exposesFindOne();
|
||||
|
||||
boolean exposesFindAll();
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RepositoryInvoker extends RepositoryInvocationInformation {
|
||||
|
||||
Object invokeSave(Object object);
|
||||
|
||||
Object invokeFindOne(Serializable id);
|
||||
|
||||
Iterable<Object> invokeFindAll(Pageable pageable);
|
||||
|
||||
Iterable<Object> invokeFindAll(Sort pageable);
|
||||
|
||||
void invokeDelete(Serializable serializable);
|
||||
|
||||
Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryInvokerFactory {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
private final Map<Class<?>, RepositoryInvoker> invokers;
|
||||
|
||||
/**
|
||||
* @param repositories
|
||||
*/
|
||||
public RepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) {
|
||||
|
||||
this.repositories = repositories;
|
||||
this.conversionService = conversionService;
|
||||
this.invokers = new HashMap<Class<?>, RepositoryInvoker>();
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private RepositoryInvoker prepareInvokers(Class<?> domainType) {
|
||||
|
||||
Object repository = repositories.getRepositoryFor(domainType);
|
||||
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
|
||||
|
||||
if (repository instanceof PagingAndSortingRepository) {
|
||||
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository<Object, Serializable>) repository,
|
||||
information, conversionService);
|
||||
} else if (repository instanceof CrudRepository) {
|
||||
return new CrudRepositoryInvoker((CrudRepository<Object, Serializable>) repository, information,
|
||||
conversionService);
|
||||
} else {
|
||||
return new ReflectionRepositoryInvoker(repository, information, conversionService);
|
||||
}
|
||||
}
|
||||
|
||||
public RepositoryInvoker getInvokerFor(Class<?> domainType) {
|
||||
|
||||
RepositoryInvoker invoker = invokers.get(domainType);
|
||||
|
||||
if (invoker != null) {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
invoker = prepareInvokers(domainType);
|
||||
invokers.put(domainType, invoker);
|
||||
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.support.Methods;
|
||||
|
||||
/**
|
||||
* An abstraction to encapsulate metadata about a repository method.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryMethod {
|
||||
|
||||
private Method method;
|
||||
private List<MethodParameter> methodParameters = new ArrayList<MethodParameter>();
|
||||
private List<String> paramNames = new ArrayList<String>();
|
||||
private boolean pageable = false;
|
||||
private boolean sortable = false;
|
||||
|
||||
public RepositoryMethod(Method method) {
|
||||
this.method = method;
|
||||
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
String[] paramNames = Methods.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;
|
||||
paramNames[i] = p.value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (null == paramNames[i]) {
|
||||
paramNames[i] = "arg" + i;
|
||||
}
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (Class<?> type : paramTypes) {
|
||||
if (Pageable.class.isAssignableFrom(type)) {
|
||||
pageable = true;
|
||||
}
|
||||
if (Sort.class.isAssignableFrom(type)) {
|
||||
sortable = true;
|
||||
}
|
||||
methodParameters.add(new MethodParameter(method, idx));
|
||||
idx++;
|
||||
}
|
||||
|
||||
Collections.addAll(this.paramNames, paramNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method parameter types.
|
||||
*
|
||||
* @return Array of parameter types.
|
||||
*/
|
||||
public List<MethodParameter> getParameters() {
|
||||
return methodParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the method parameter names.
|
||||
*
|
||||
* @return Array of parameter names.
|
||||
*/
|
||||
public List<String> getParameterNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reflected {@link Method} to invoke.
|
||||
*
|
||||
* @return The {@link Method} to invoke.
|
||||
*/
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag denoting whether this repository method returns a {@link org.springframework.data.domain.Page} result or not.
|
||||
*
|
||||
* @return {@literal true} if this method returns a {@link org.springframework.data.domain.Page}, {@literal false}
|
||||
* otherwise.
|
||||
*/
|
||||
public boolean isPageable() {
|
||||
return pageable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag denoting whether this repository method accepts sorting information.
|
||||
*
|
||||
* @return {@literal true} if this method accepts a {@link Sort}, {@literal false} otherwise.
|
||||
*/
|
||||
public boolean isSortable() {
|
||||
return sortable;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.springframework.data.rest.core.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;
|
||||
|
||||
/**
|
||||
* Represents a query method on a repository interface.
|
||||
*
|
||||
* @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()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The method's parameter types.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Class<?>[] paramTypes() {
|
||||
return paramTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parameter names as pulled from the {@link Param} annotations.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] paramNames() {
|
||||
return paramNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link Method} to invoke.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Method method() {
|
||||
return method;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
/**
|
||||
* A custom resource mapping for collection resources.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface CollectionResourceMapping extends ResourceMapping {
|
||||
|
||||
String getSingleResourceRel();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import org.atteo.evo.inflector.English;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
|
||||
/**
|
||||
* {@link TypeBasedCollectionResourceMapping} extension to use Evo Inflector to pluralize the simple class name by as
|
||||
* default path.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class EvoInflectorTypeBasedCollectionResourceMapping extends TypeBasedCollectionResourceMapping {
|
||||
|
||||
/**
|
||||
* Creates a new {@link EvoInflectorTypeBasedCollectionResourceMapping} for the given type and {@link RelProvider}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param relProvider must not be {@literal null}.
|
||||
*/
|
||||
public EvoInflectorTypeBasedCollectionResourceMapping(Class<?> type, RelProvider relProvider) {
|
||||
super(type, relProvider);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.TypeBasedCollectionResourceMapping#getDefaultPathFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
protected String getDefaultPathFor(Class<?> type) {
|
||||
return English.plural(super.getDefaultPathFor(type));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* A {@link ResourceMapping} that is backed by a {@link Method}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface MethodResourceMapping extends ResourceMapping {
|
||||
|
||||
/**
|
||||
* Returns the {@link Method} backing the resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Method getMethod();
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RepositoryAwareResourceInformation implements ResourceMetadata {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final CollectionResourceMapping mapping;
|
||||
private final ResourceMappings provider;
|
||||
private final RepositoryInformation repositoryInterface;
|
||||
|
||||
/**
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param mapping must not be {@literal null}.
|
||||
* @param provider must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryAwareResourceInformation(Repositories repositories, CollectionResourceMapping mapping,
|
||||
ResourceMappings provider, RepositoryInformation repositoryInterface) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(mapping, "ResourceMapping must not be null!");
|
||||
Assert.notNull(provider, "ResourceMetadataProvider must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.mapping = mapping;
|
||||
this.provider = provider;
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
}
|
||||
|
||||
public boolean isPrimary() {
|
||||
return AnnotationUtils.findAnnotation(repositoryInterface.getRepositoryInterface(), Primary.class) != null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getDomainType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getDomainType() {
|
||||
return repositoryInterface.getDomainType();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.DelegatingResourceInformation#isManaged(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public boolean isManagedResource(PersistentProperty<?> property) {
|
||||
|
||||
Assert.notNull(property, "PersistentProperty must not be null!");
|
||||
return repositories.hasRepositoryFor(property.getActualType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public ResourceMapping getMappingFor(PersistentProperty<?> property) {
|
||||
return provider.getMappingFor(property);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
@Override
|
||||
public boolean isExported(PersistentProperty<?> property) {
|
||||
return provider.isMapped(property);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#isExported()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isExported() {
|
||||
return mapping.isExported();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getCollectionRel()
|
||||
*/
|
||||
@Override
|
||||
public String getRel() {
|
||||
return mapping.getRel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getSingleResourceRel()
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRel() {
|
||||
return mapping.getSingleResourceRel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getPath()
|
||||
*/
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return mapping.getPath();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSearchResourceMappings()
|
||||
*/
|
||||
@Override
|
||||
public SearchResourceMappings getSearchResourceMappings() {
|
||||
return provider.getSearchResourceMappings(repositoryInterface.getRepositoryInterface());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.support.RepositoriesUtils;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link CollectionResourceMapping} to be built from repository interfaces. Will inspect {@link RestResource}
|
||||
* annotations on the repository interface but fall back to the mapping information of the managed domain type for
|
||||
* defaults.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RepositoryCollectionResourceMapping implements CollectionResourceMapping {
|
||||
|
||||
private final boolean EVO_INFLECTOR_IS_PRESENT = ClassUtils.isPresent("org.atteo.evo.inflector.English", null);
|
||||
|
||||
private final RestResource annotation;
|
||||
private final CollectionResourceMapping domainTypeMapping;
|
||||
private final boolean repositoryIsExportCandidate;
|
||||
|
||||
public RepositoryCollectionResourceMapping(Class<?> repositoryType) {
|
||||
this(repositoryType, new EvoInflectorRelProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryCollectionResourceMapping} for the given repository using the given
|
||||
* {@link RelProvider}.
|
||||
*
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @param relProvider must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryCollectionResourceMapping(Class<?> repositoryType, RelProvider relProvider) {
|
||||
|
||||
Assert.isTrue(RepositoriesUtils.isRepositoryInterface(repositoryType), "Given type is not a repository!");
|
||||
Assert.notNull(relProvider, "RelProvider must not be null!");
|
||||
|
||||
this.annotation = AnnotationUtils.findAnnotation(repositoryType, RestResource.class);
|
||||
this.repositoryIsExportCandidate = Modifier.isPublic(repositoryType.getModifiers());
|
||||
|
||||
Class<?> domainType = RepositoriesUtils.getDomainType(repositoryType);
|
||||
this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType,
|
||||
relProvider) : new TypeBasedCollectionResourceMapping(domainType, relProvider);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
|
||||
*/
|
||||
@Override
|
||||
public Path getPath() {
|
||||
|
||||
return annotation == null || !StringUtils.hasText(annotation.path()) ? domainTypeMapping.getPath() : new Path(
|
||||
annotation.path());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
|
||||
*/
|
||||
@Override
|
||||
public String getRel() {
|
||||
return annotation == null || !StringUtils.hasText(annotation.rel()) ? domainTypeMapping.getRel() : annotation.rel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getSingleResourceRel()
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRel() {
|
||||
return domainTypeMapping.getSingleResourceRel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isExported() {
|
||||
return annotation == null ? repositoryIsExportCandidate && domainTypeMapping.isExported() : annotation.exported();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A {@link RepositoryMethodResourceMapping} created from a {@link Method}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
|
||||
private final boolean isExported;
|
||||
private final String rel;
|
||||
private final Path path;
|
||||
private final Method method;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryMethodResourceMapping} for the given {@link Method}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param resourceMapping must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
|
||||
|
||||
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
|
||||
|
||||
this.isExported = annotation != null ? annotation.exported() : true;
|
||||
this.rel = annotation != null ? annotation.rel() : method.getName();
|
||||
this.path = annotation == null || !StringUtils.hasText(annotation.path()) ? new Path(method.getName()) : new Path(
|
||||
annotation.path());
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isExported() {
|
||||
return isExported;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
|
||||
*/
|
||||
@Override
|
||||
public String getRel() {
|
||||
return rel;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
|
||||
*/
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.MethodResourceMapping#getMethod()
|
||||
*/
|
||||
@Override
|
||||
public Method getMethod() {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import org.springframework.data.rest.core.Path;
|
||||
|
||||
/**
|
||||
* Mapping information for components to be exported as REST resources.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ResourceMapping {
|
||||
|
||||
/**
|
||||
* Returns whether the component shall be exported at all.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
Boolean isExported();
|
||||
|
||||
/**
|
||||
* Returns the relation for the resource exported.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
String getRel();
|
||||
|
||||
/**
|
||||
* Returns the path the resource is exposed under.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
Path getPath();
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.support.RepositoriesUtils;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Central abstraction obtain {@link ResourceMetadata} and {@link ResourceMapping} instances for domain types and
|
||||
* repositories.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ResourceMappings implements Iterable<ResourceMetadata> {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final RelProvider relProvider;
|
||||
|
||||
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
|
||||
private final Map<Class<?>, SearchResourceMappings> searchCache = new HashMap<Class<?>, SearchResourceMappings>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceMappings} using the given {@link RepositoryRestConfiguration} and {@link Repositories}
|
||||
* .
|
||||
*
|
||||
* @param config
|
||||
* @param repositories
|
||||
*/
|
||||
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories) {
|
||||
this(config, repositories, new EvoInflectorRelProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ResourceMappings} from the given {@link RepositoryRestConfiguration}, {@link Repositories} and
|
||||
* {@link RelProvider}.
|
||||
*
|
||||
* @param config must not be {@literal null}.
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param relProvider must not be {@literal null}.
|
||||
*/
|
||||
public ResourceMappings(RepositoryRestConfiguration config, Repositories repositories, RelProvider relProvider) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(relProvider, "RelProvider must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.relProvider = relProvider;
|
||||
|
||||
this.populateCache(repositories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link ResourceMetadata} for the given type if available.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public ResourceMetadata getMappingFor(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
return cache.get(type);
|
||||
}
|
||||
|
||||
private final void populateCache(Repositories repositories) {
|
||||
|
||||
for (Class<?> type : repositories) {
|
||||
|
||||
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(type);
|
||||
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
|
||||
|
||||
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInterface, relProvider);
|
||||
|
||||
RepositoryAwareResourceInformation information = new RepositoryAwareResourceInformation(repositories, mapping,
|
||||
this, repositoryInformation);
|
||||
|
||||
cache.put(repositoryInterface, information);
|
||||
|
||||
if (!cache.containsKey(type) || information.isPrimary()) {
|
||||
cache.put(type, information);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ResourceMapping}s for the search resources of the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public SearchResourceMappings getSearchResourceMappings(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
if (searchCache.containsKey(type)) {
|
||||
return searchCache.get(type);
|
||||
}
|
||||
|
||||
Class<?> domainType = RepositoriesUtils.getDomainType(type);
|
||||
|
||||
if (searchCache.containsKey(domainType)) {
|
||||
return searchCache.get(domainType);
|
||||
}
|
||||
|
||||
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
|
||||
List<MethodResourceMapping> mappings = new ArrayList<MethodResourceMapping>();
|
||||
ResourceMetadata resourceMapping = getMappingFor(domainType);
|
||||
|
||||
if (resourceMapping.isExported()) {
|
||||
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
|
||||
mappings.add(new RepositoryMethodResourceMapping(queryMethod, resourceMapping));
|
||||
}
|
||||
}
|
||||
|
||||
SearchResourceMappings searchResourceMappings = new SearchResourceMappings(mappings);
|
||||
searchCache.put(type, searchResourceMappings);
|
||||
searchCache.put(domainType, searchResourceMappings);
|
||||
return searchResourceMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a {@link ResourceMapping} for the given type and it is exported.
|
||||
*
|
||||
* @param type
|
||||
* @return
|
||||
*/
|
||||
public boolean exportsMappingFor(Class<?> type) {
|
||||
|
||||
if (!hasMappingFor(type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResourceMetadata metadata = getMappingFor(type);
|
||||
return metadata.isExported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we have a {@link ResourceMapping} for the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean hasMappingFor(Class<?> type) {
|
||||
|
||||
if (cache.containsKey(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (repositories.hasRepositoryFor(type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RepositoriesUtils.isRepositoryInterface(type) && hasMappingFor(RepositoriesUtils.getDomainType(type))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#getMappingFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
ResourceMapping getMappingFor(PersistentProperty<?> property) {
|
||||
return getMappingFor(property.getActualType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
|
||||
*/
|
||||
public boolean isMapped(PersistentProperty<?> property) {
|
||||
|
||||
ResourceMapping metadata = getMappingFor(property);
|
||||
return metadata != null && metadata.isExported();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<ResourceMetadata> iterator() {
|
||||
return cache.values().iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
/**
|
||||
* Interface for metadata of resources exposed throught the system.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ResourceMetadata extends CollectionResourceMapping {
|
||||
|
||||
/**
|
||||
* Returns the domain type that is exposed through the resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> getDomainType();
|
||||
|
||||
/**
|
||||
* Returns whether the type of the given {@link PersistentProperty} is exposed as resource itself.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean isManagedResource(PersistentProperty<?> property);
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link PersistentProperty} is a managed resource and in fact exported.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean isExported(PersistentProperty<?> property);
|
||||
|
||||
/**
|
||||
* Returns the {@link ResourceMapping} for the given {@link PersistentProperty} or {@literal null} if not managed.
|
||||
*
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
ResourceMapping getMappingFor(PersistentProperty<?> property);
|
||||
|
||||
/**
|
||||
* Returns the {@link SearchResourceMappings}, i.e. the mappings for the search resource exposed for the current
|
||||
* resource.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
SearchResourceMappings getSearchResourceMappings();
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ResourceMapping} for all search resources.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SearchResourceMappings implements Iterable<MethodResourceMapping>, ResourceMapping {
|
||||
|
||||
private static final String AMBIGUOUS_MAPPING = "Ambiguous search mapping detected. Both %s and "
|
||||
+ "%s are mapped to %s! Tweak configuration to get to unambiguous paths!";
|
||||
|
||||
private static final Path PATH = new Path("/search");
|
||||
private static final String REL = "search";
|
||||
|
||||
private final Map<Path, MethodResourceMapping> mappings;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SearchResourceMappings} from the given
|
||||
*
|
||||
* @param mappings
|
||||
*/
|
||||
public SearchResourceMappings(List<MethodResourceMapping> mappings) {
|
||||
|
||||
Assert.notNull(mappings, "MethodResourceMappings must not be null!");
|
||||
|
||||
this.mappings = new HashMap<Path, MethodResourceMapping>(mappings.size());
|
||||
|
||||
for (MethodResourceMapping mapping : mappings) {
|
||||
|
||||
MethodResourceMapping existing = this.mappings.get(mapping.getPath());
|
||||
|
||||
if (existing != null) {
|
||||
throw new IllegalStateException(String.format(AMBIGUOUS_MAPPING, existing.getMethod(), mapping.getMethod(),
|
||||
existing.getPath()));
|
||||
}
|
||||
|
||||
this.mappings.put(mapping.getPath(), mapping);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method mapped to the given path.
|
||||
*
|
||||
* @param path must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public Method getMappedMethod(String path) {
|
||||
|
||||
Assert.hasText(path, "Path must not be null or empty!");
|
||||
|
||||
MethodResourceMapping mapping = mappings.get(new Path(path));
|
||||
return mapping == null ? null : mapping.getMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
|
||||
*/
|
||||
@Override
|
||||
public Path getPath() {
|
||||
return PATH;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
|
||||
*/
|
||||
@Override
|
||||
public String getRel() {
|
||||
return REL;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isExported() {
|
||||
return !mappings.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
*/
|
||||
@Override
|
||||
public Iterator<MethodResourceMapping> iterator() {
|
||||
return mappings.values().iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link CollectionResourceMapping} based on a type. Will derive default relation types and pathes from the type but
|
||||
* inspect it for {@link RestResource} annotations for customization.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class TypeBasedCollectionResourceMapping implements CollectionResourceMapping {
|
||||
|
||||
private final Class<?> type;
|
||||
private final RestResource annotation;
|
||||
private final RelProvider relProvider;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedCollectionResourceMapping} using the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public TypeBasedCollectionResourceMapping(Class<?> type) {
|
||||
this(type, new EvoInflectorRelProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedCollectionResourceMapping} using the given type and {@link RelProvider}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param relProvider must not be {@literal null}.
|
||||
*/
|
||||
public TypeBasedCollectionResourceMapping(Class<?> type, RelProvider relProvider) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(relProvider, "RelProvider must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.relProvider = relProvider;
|
||||
this.annotation = AnnotationUtils.findAnnotation(type, RestResource.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()
|
||||
*/
|
||||
@Override
|
||||
public Path getPath() {
|
||||
|
||||
String path = annotation == null ? null : annotation.path().trim();
|
||||
path = StringUtils.hasText(path) ? path : getDefaultPathFor(type);
|
||||
return new Path(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#isExported()
|
||||
*/
|
||||
@Override
|
||||
public Boolean isExported() {
|
||||
return annotation == null ? Modifier.isPublic(type.getModifiers()) : annotation.exported();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getRel()
|
||||
*/
|
||||
@Override
|
||||
public String getRel() {
|
||||
|
||||
if (annotation == null || !StringUtils.hasText(annotation.rel())) {
|
||||
return relProvider.getCollectionResourceRelFor(type);
|
||||
}
|
||||
|
||||
return annotation.rel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.CollectionResourceMapping#getSingleResourceRel()
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRel() {
|
||||
return relProvider.getSingleResourceRelFor(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default path to be used if the path is not configured manually.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected String getDefaultPathFor(Class<?> type) {
|
||||
return StringUtils.uncapitalize(type.getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Core components used across Spring Data REST.
|
||||
*/
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.mapping.Association;
|
||||
import org.springframework.data.mapping.AssociationHandler;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class DomainObjectMerger {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@Autowired
|
||||
public DomainObjectMerger(Repositories repositories, ConversionService conversionService) {
|
||||
this.repositories = repositories;
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void merge(Object from, Object target) {
|
||||
if (null == from || null == target) {
|
||||
return;
|
||||
}
|
||||
final BeanWrapper<?, Object> fromWrapper = BeanWrapper.create(from, conversionService);
|
||||
final BeanWrapper<?, Object> targetWrapper = BeanWrapper.create(target, conversionService);
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(target.getClass());
|
||||
entity.doWithProperties(new PropertyHandler() {
|
||||
@Override
|
||||
public void doWithPersistentProperty(PersistentProperty persistentProperty) {
|
||||
Object fromVal = fromWrapper.getProperty(persistentProperty);
|
||||
if (null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) {
|
||||
targetWrapper.setProperty(persistentProperty, fromVal);
|
||||
}
|
||||
}
|
||||
});
|
||||
entity.doWithAssociations(new AssociationHandler() {
|
||||
@Override
|
||||
public void doWithAssociation(Association association) {
|
||||
PersistentProperty persistentProperty = association.getInverse();
|
||||
Object fromVal = fromWrapper.getProperty(persistentProperty);
|
||||
if (null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) {
|
||||
targetWrapper.setProperty(persistentProperty, fromVal);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public abstract class Methods {
|
||||
|
||||
private Methods() {}
|
||||
|
||||
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("$"));
|
||||
}
|
||||
};
|
||||
public static final LocalVariableTableParameterNameDiscoverer NAME_DISCOVERER = new LocalVariableTableParameterNameDiscoverer();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.RepositoryDefinition;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoriesUtils {
|
||||
|
||||
/**
|
||||
* Resolves the domain type from the given type. Will resolve the repository domain type if the given type is a
|
||||
* repository or return the type as is if not.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static Class<?> getDomainType(Class<?> type) {
|
||||
|
||||
if (!isRepositoryInterface(type)) {
|
||||
return type;
|
||||
}
|
||||
|
||||
return getMetadataFor(type).getDomainType();
|
||||
}
|
||||
|
||||
public static boolean isRepositoryInterface(Class<?> type) {
|
||||
return Repository.class.isAssignableFrom(type)
|
||||
|| AnnotationUtils.findAnnotation(type, RepositoryDefinition.class) != null;
|
||||
}
|
||||
|
||||
private static RepositoryMetadata getMetadataFor(Class<?> type) {
|
||||
return Repository.class.isAssignableFrom(type) ? new DefaultRepositoryMetadata(type)
|
||||
: new AnnotationRepositoryMetadata(type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryRelProvider implements RelProvider {
|
||||
|
||||
private final ResourceMappings mappings;
|
||||
|
||||
/**
|
||||
* @param repositories
|
||||
* @param config
|
||||
*/
|
||||
public RepositoryRelProvider(ResourceMappings mappings) {
|
||||
|
||||
this.mappings = mappings;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getCollectionResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionResourceRelFor(Class<?> type) {
|
||||
return mappings.getMappingFor(type).getRel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getSingleResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRelFor(Class<?> type) {
|
||||
return mappings.getMappingFor(type).getSingleResourceRel();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return mappings.hasMappingFor(delimiter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import static org.springframework.data.rest.core.support.ResourceStringUtils.*;
|
||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.config.ResourceMapping;
|
||||
|
||||
/**
|
||||
* Helper methods to get the default rel and path values or to use values supplied by annotations.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Florent Biville
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class ResourceMappingUtils {
|
||||
|
||||
protected ResourceMappingUtils() {}
|
||||
|
||||
public static String findRel(Class<?> type) {
|
||||
|
||||
RestResource anno = findAnnotation(type, RestResource.class);
|
||||
if (anno != null) {
|
||||
if (hasText(anno.rel())) {
|
||||
return anno.rel();
|
||||
}
|
||||
}
|
||||
|
||||
return uncapitalize(type.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
|
||||
public static String findRel(Method method) {
|
||||
|
||||
RestResource anno = findAnnotation(method, RestResource.class);
|
||||
|
||||
if (anno != null) {
|
||||
if (hasText(anno.rel())) {
|
||||
return anno.rel();
|
||||
}
|
||||
}
|
||||
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
public static String formatRel(RepositoryRestConfiguration config, RepositoryInformation repoInfo,
|
||||
PersistentProperty<?> persistentProperty) {
|
||||
|
||||
if (persistentProperty == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ResourceMapping repoMapping = getResourceMapping(config, repoInfo);
|
||||
ResourceMapping entityMapping = getResourceMapping(config, persistentProperty.getOwner());
|
||||
ResourceMapping propertyMapping = entityMapping.getResourceMappingFor(persistentProperty.getName());
|
||||
|
||||
return String.format("%s.%s.%s", repoMapping.getRel(), entityMapping.getRel(),
|
||||
(null != propertyMapping ? propertyMapping.getRel() : persistentProperty.getName()));
|
||||
}
|
||||
|
||||
public static String findPath(Class<?> type) {
|
||||
|
||||
RestResource anno = findAnnotation(type, RestResource.class);
|
||||
|
||||
if (anno != null) {
|
||||
if (hasTextExceptSlash(anno.path())) {
|
||||
return removeLeadingSlash(anno.path());
|
||||
}
|
||||
}
|
||||
|
||||
return uncapitalize(type.getSimpleName().replaceAll("Repository", ""));
|
||||
}
|
||||
|
||||
public static String findPath(Method method) {
|
||||
|
||||
RestResource anno = findAnnotation(method, RestResource.class);
|
||||
|
||||
if (anno != null) {
|
||||
if (hasTextExceptSlash(anno.path())) {
|
||||
return removeLeadingSlash(anno.path());
|
||||
}
|
||||
}
|
||||
|
||||
return method.getName();
|
||||
}
|
||||
|
||||
public static boolean findExported(Class<?> type) {
|
||||
RestResource anno = findAnnotation(type, RestResource.class);
|
||||
return anno == null || anno.exported();
|
||||
}
|
||||
|
||||
public static boolean findExported(Method method) {
|
||||
RestResource anno = findAnnotation(method, RestResource.class);
|
||||
return anno == null || anno.exported();
|
||||
}
|
||||
|
||||
public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config, RepositoryInformation repoInfo) {
|
||||
if (null == repoInfo) {
|
||||
return null;
|
||||
}
|
||||
Class<?> repoType = repoInfo.getRepositoryInterface();
|
||||
ResourceMapping mapping = (null != config ? config.getResourceMappingForRepository(repoType) : null);
|
||||
return merge(repoType, mapping);
|
||||
}
|
||||
|
||||
public static ResourceMapping getResourceMapping(RepositoryRestConfiguration config,
|
||||
PersistentEntity<?, ?> persistentEntity) {
|
||||
if (null == persistentEntity) {
|
||||
return null;
|
||||
}
|
||||
Class<?> domainType = persistentEntity.getType();
|
||||
ResourceMapping mapping = (null != config ? config.getResourceMappingForDomainType(domainType) : null);
|
||||
return merge(domainType, mapping);
|
||||
}
|
||||
|
||||
public static ResourceMapping merge(Method method, ResourceMapping mapping) {
|
||||
ResourceMapping defaultMapping = new ResourceMapping(findRel(method), findPath(method), findExported(method));
|
||||
if (null != mapping) {
|
||||
return new ResourceMapping((null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()),
|
||||
(null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()),
|
||||
(mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported()));
|
||||
}
|
||||
return defaultMapping;
|
||||
}
|
||||
|
||||
public static ResourceMapping merge(Class<?> type, ResourceMapping mapping) {
|
||||
ResourceMapping defaultMapping = new ResourceMapping(findRel(type), findPath(type), findExported(type));
|
||||
if (null != mapping) {
|
||||
return new ResourceMapping((null != mapping.getRel() ? mapping.getRel() : defaultMapping.getRel()),
|
||||
(null != mapping.getPath() ? mapping.getPath() : defaultMapping.getPath()),
|
||||
(mapping.isExported() != defaultMapping.isExported() ? mapping.isExported() : defaultMapping.isExported()))
|
||||
.addResourceMappings(mapping.getResourceMappings());
|
||||
}
|
||||
return defaultMapping;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
/**
|
||||
* Helper methods aiming at handling String representations of resources.
|
||||
*
|
||||
* @author Florent Biville
|
||||
*/
|
||||
public class ResourceStringUtils {
|
||||
|
||||
/**
|
||||
* Checks whether the given input contains actual text (slash excluded). This is a specializing variant of
|
||||
* {@link org.springframework.util.StringUtils )}#hasText.
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
public static boolean hasTextExceptSlash(CharSequence input) {
|
||||
|
||||
int strLen = input.length();
|
||||
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if (!Character.isWhitespace(input.charAt(i)) && !startsWithSlash(input.charAt(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string without the leading slash, if any.
|
||||
*
|
||||
* @param path
|
||||
*/
|
||||
public static String removeLeadingSlash(String path) {
|
||||
|
||||
if (path.length() == 0) {
|
||||
return path;
|
||||
}
|
||||
|
||||
boolean hasLeadingSlash = startsWithSlash(path);
|
||||
|
||||
if (path.length() == 1) {
|
||||
return hasLeadingSlash ? "" : path;
|
||||
}
|
||||
|
||||
return hasLeadingSlash ? path.substring(1) : path;
|
||||
}
|
||||
|
||||
private static boolean startsWithSlash(String path) {
|
||||
return path.charAt(0) == '/';
|
||||
}
|
||||
|
||||
private static boolean startsWithSlash(char c) {
|
||||
return c == '/';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.support;
|
||||
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class SimpleRelProvider implements RelProvider {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getSingleResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getSingleResourceRelFor(Class<?> type) {
|
||||
String collectionRel = getCollectionResourceRelFor(type);
|
||||
return String.format("%s.%s", collectionRel, collectionRel);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.hateoas.RelProvider#getCollectionResourceRelFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public String getCollectionResourceRelFor(Class<?> type) {
|
||||
return StringUtils.uncapitalize(type.getSimpleName());
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
package org.springframework.data.rest.core.util;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Spring Data REST
|
||||
*/
|
||||
package org.springframework.data.rest;
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.convert;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
|
||||
/**
|
||||
* Tests to ensure the {@link DelegatingConversionService} properly delegates conversions to the
|
||||
* {@link org.springframework.core.convert.ConversionService} that is appropriate for the given source and return types.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DelegatingConversionServiceUnitTests {
|
||||
|
||||
private static final UUID RANDOM_UUID = UUID.fromString("9deccfd7-f892-4e26-a4d5-c92893392e78");
|
||||
|
||||
@Mock ConversionService conversionService;
|
||||
DelegatingConversionService delegatingConversionService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
DefaultFormattingConversionService cs = new DefaultFormattingConversionService(false);
|
||||
cs.addConverter(UUIDConverter.INSTANCE);
|
||||
|
||||
delegatingConversionService = new DelegatingConversionService(conversionService, cs);
|
||||
|
||||
when(conversionService.canConvert(String.class, UUID.class)).thenReturn(false);
|
||||
when(conversionService.canConvert(UUID.class, String.class)).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDelegateToProperConversionService() {
|
||||
|
||||
assertThat(delegatingConversionService.canConvert(String.class, UUID.class), is(true));
|
||||
assertThat(delegatingConversionService.convert(RANDOM_UUID.toString(), UUID.class), is(RANDOM_UUID));
|
||||
|
||||
verifyConversionService();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertUUIDToString() {
|
||||
|
||||
assertThat(delegatingConversionService.canConvert(UUID.class, String.class), is(true));
|
||||
assertThat(delegatingConversionService.convert(RANDOM_UUID, String.class), is(RANDOM_UUID.toString()));
|
||||
|
||||
verifyConversionService();
|
||||
}
|
||||
|
||||
private void verifyConversionService() {
|
||||
|
||||
verify(conversionService, times(0)).convert(Matchers.any(String.class), eq(UUID.class));
|
||||
verify(conversionService, times(0)).convert(Matchers.any(UUID.class), eq(String.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonRepository;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Base class for integration tests loading {@link RepositoryTestsConfig} and populating the {@link PersonRepository}
|
||||
* with a {@link Person}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = RepositoryTestsConfig.class)
|
||||
@Transactional
|
||||
public abstract class AbstractIntegrationTests {
|
||||
|
||||
@Autowired PersonRepository repository;
|
||||
|
||||
@Before
|
||||
public void populateDatabase() {
|
||||
repository.save(new Person("John", "Doe"));
|
||||
}
|
||||
}
|
||||
@@ -62,4 +62,9 @@ public class PathUnitTests {
|
||||
public void doesNotMatchIfDifferent() {
|
||||
assertThat(new Path("/foobar").matches("barfoo"), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotPrefixAbsoluteUris() {
|
||||
assertThat(new Path("http://localhost").toString(), is("http://localhost"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.config.ResourceMapping;
|
||||
import org.springframework.data.rest.core.domain.jpa.ConfiguredPersonRepository;
|
||||
|
||||
/**
|
||||
* Tests to check that {@link ResourceMapping}s are handled correctly.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class RepositoryRestConfigurationIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Autowired RepositoryRestConfiguration config;
|
||||
|
||||
@Test
|
||||
public void shouldProvideResourceMappingForConfiguredRepository() throws Exception {
|
||||
ResourceMapping mapping = config.getResourceMappingForRepository(ConfiguredPersonRepository.class);
|
||||
|
||||
assertThat(mapping, notNullValue());
|
||||
assertThat(mapping.getRel(), is("people"));
|
||||
assertThat(mapping.getPath(), is("people"));
|
||||
assertThat(mapping.isExported(), is(false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.UriDomainClassConverter;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.domain.jpa.ConfiguredPersonRepository;
|
||||
import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonRepository;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@Import({ JpaRepositoryConfig.class })
|
||||
public class RepositoryTestsConfig {
|
||||
|
||||
@Autowired private ApplicationContext appCtx;
|
||||
|
||||
@Bean
|
||||
public Repositories repositories() {
|
||||
return new Repositories(appCtx);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Bean
|
||||
public RepositoryRestConfiguration config() {
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
|
||||
|
||||
config.setResourceMappingForDomainType(Person.class).setRel("person");
|
||||
|
||||
config.setResourceMappingForRepository(ConfiguredPersonRepository.class).setRel("people").setPath("people")
|
||||
.setExported(false);
|
||||
|
||||
config.setResourceMappingForRepository(PersonRepository.class).setRel("people").setPath("people")
|
||||
.addResourceMappingFor("findByFirstName").setRel("firstname").setPath("firstname");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultFormattingConversionService defaultConversionService() {
|
||||
return new DefaultFormattingConversionService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DomainClassConverter<?> domainClassConverter() {
|
||||
return new DomainClassConverter<DefaultFormattingConversionService>(defaultConversionService());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public UriDomainClassConverter uriDomainClassConverter() {
|
||||
return new UriDomainClassConverter(repositories(), domainClassConverter());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.config;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.data.rest.core.support.ResourceMappingUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
|
||||
/**
|
||||
* Ensure the {@link ResourceMapping} components convey the correct information.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class ResourceMappingUnitTests {
|
||||
|
||||
RelProvider relProvider = new EvoInflectorRelProvider();
|
||||
|
||||
@Test
|
||||
public void shouldDetectPathAndRemoveLeadingSlashIfAny() {
|
||||
org.springframework.data.rest.core.config.ResourceMapping mapping = new org.springframework.data.rest.core.config.ResourceMapping(
|
||||
findRel(AnnotatedWithLeadingSlashPersonRepository.class),
|
||||
findPath(AnnotatedWithLeadingSlashPersonRepository.class),
|
||||
findExported(AnnotatedWithLeadingSlashPersonRepository.class));
|
||||
|
||||
// The rel attribute defaults to class name
|
||||
assertThat(mapping.getRel(), is("annotatedWithLeadingSlashPerson"));
|
||||
assertThat(mapping.getPath(), is("people"));
|
||||
// The exported defaults to true
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDetectPathAndRemoveLeadingSlashIfAnyOnMethod() throws Exception {
|
||||
Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByFirstName", String.class,
|
||||
Pageable.class);
|
||||
org.springframework.data.rest.core.config.ResourceMapping mapping = new org.springframework.data.rest.core.config.ResourceMapping(
|
||||
findRel(method), findPath(method), findExported(method));
|
||||
|
||||
// The rel attribute defaults to class name
|
||||
assertThat(mapping.getRel(), is("findByFirstName"));
|
||||
assertThat(mapping.getPath(), is("firstname"));
|
||||
// The exported defaults to true
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnDefaultIfPathContainsOnlySlashTextOnMethod() throws Exception {
|
||||
Method method = AnnotatedWithLeadingSlashPersonRepository.class.getMethod("findByLastName", String.class,
|
||||
Pageable.class);
|
||||
org.springframework.data.rest.core.config.ResourceMapping mapping = new org.springframework.data.rest.core.config.ResourceMapping(
|
||||
findRel(method), findPath(method), findExported(method));
|
||||
|
||||
// The rel defaults to method name
|
||||
assertThat(mapping.getRel(), is("findByLastName"));
|
||||
// The path contains only a leading slash therefore defaults to method name
|
||||
assertThat(mapping.getPath(), is("findByLastName"));
|
||||
// The exported defaults to true
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@RestResource(path = "/people")
|
||||
interface AnnotatedWithLeadingSlashPersonRepository {
|
||||
|
||||
@RestResource(path = "/firstname")
|
||||
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
|
||||
|
||||
@RestResource(path = " / ")
|
||||
Page<Person> findByLastName(@Param("lastName") String firstName, Pageable pageable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package org.springframework.data.rest.core.context;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.rest.core.RepositoryTestsConfig;
|
||||
import org.springframework.data.rest.core.domain.jpa.AnnotatedPersonEventHandler;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonBeforeSaveHandler;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonRepository;
|
||||
import org.springframework.data.rest.core.event.AfterCreateEvent;
|
||||
import org.springframework.data.rest.core.event.AfterDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.AfterLinkDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.AfterLinkSaveEvent;
|
||||
import org.springframework.data.rest.core.event.AfterSaveEvent;
|
||||
import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor;
|
||||
import org.springframework.data.rest.core.event.BeforeCreateEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeLinkSaveEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeSaveEvent;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Tests around the {@link org.springframework.context.ApplicationEvent} handling abstractions.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class RepositoryEventIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ RepositoryTestsConfig.class })
|
||||
static class RepositoryEventTestsConfig {
|
||||
|
||||
@Bean
|
||||
public PersonBeforeSaveHandler personBeforeSaveHandler() {
|
||||
return new PersonBeforeSaveHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AnnotatedPersonEventHandler beforeSaveHandler() {
|
||||
return new AnnotatedPersonEventHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() {
|
||||
return new AnnotatedHandlerBeanPostProcessor();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ApplicationContext appCtx;
|
||||
@Autowired PersonRepository people;
|
||||
Person person;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
person = people.save(new Person("Jane", "Doe"));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchBeforeCreate() throws Exception {
|
||||
appCtx.publishEvent(new BeforeCreateEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchAfterCreate() throws Exception {
|
||||
appCtx.publishEvent(new AfterCreateEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchBeforeSave() throws Exception {
|
||||
appCtx.publishEvent(new BeforeSaveEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchAfterSave() throws Exception {
|
||||
appCtx.publishEvent(new AfterSaveEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchBeforeDelete() throws Exception {
|
||||
appCtx.publishEvent(new BeforeDeleteEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchAfterDelete() throws Exception {
|
||||
appCtx.publishEvent(new AfterDeleteEvent(person));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchBeforeLinkSave() throws Exception {
|
||||
appCtx.publishEvent(new BeforeLinkSaveEvent(person, new Object()));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchAfterLinkSave() throws Exception {
|
||||
appCtx.publishEvent(new AfterLinkSaveEvent(person, new Object()));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchBeforeLinkDelete() throws Exception {
|
||||
appCtx.publishEvent(new BeforeLinkDeleteEvent(person, new Object()));
|
||||
}
|
||||
|
||||
@Test(expected = RuntimeException.class)
|
||||
public void shouldDispatchAfterLinkDelete() throws Exception {
|
||||
appCtx.publishEvent(new AfterLinkDeleteEvent(person, new Object()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.data.rest.core.context;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.rest.core.RepositoryConstraintViolationException;
|
||||
import org.springframework.data.rest.core.RepositoryTestsConfig;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.event.BeforeSaveEvent;
|
||||
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Tests to check the {@link org.springframework.validation.Validator} integration.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class ValidatorIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ RepositoryTestsConfig.class })
|
||||
static class ValidatorTestsConfig {
|
||||
|
||||
@Bean
|
||||
public ValidatingRepositoryEventListener validatingListener() {
|
||||
return new ValidatingRepositoryEventListener();
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired ApplicationContext appCtx;
|
||||
|
||||
@Test(expected = RepositoryConstraintViolationException.class)
|
||||
public void shouldValidateLastName() throws Exception {
|
||||
appCtx.publishEvent(new BeforeSaveEvent(new Person()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleAfterSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeCreate;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkDelete;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeLinkSave;
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryEventHandler;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RepositoryEventHandler(Person.class)
|
||||
public class AnnotatedPersonEventHandler {
|
||||
@HandleAfterCreate
|
||||
@HandleAfterDelete
|
||||
@HandleAfterSave
|
||||
public void handleAfter(Person p) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@HandleAfterLinkDelete
|
||||
@HandleAfterLinkSave
|
||||
public void handleAfterLink(Person p, Object o) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@HandleBeforeCreate
|
||||
@HandleBeforeDelete
|
||||
@HandleBeforeSave
|
||||
public void handleBefore(Person p) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
@HandleBeforeLinkDelete
|
||||
@HandleBeforeLinkSave
|
||||
public void handleBeforeLink(Person p, Object o) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* A repository to manage {@link org.springframework.data.rest.core.domain.jpa.Person}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RestResource(rel = "people", exported = false)
|
||||
@NoRepositoryBean
|
||||
public interface AnnotatedPersonRepository extends CrudRepository<Person, Long> {}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
|
||||
/**
|
||||
* A repository to manage {@link Person}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface ConfiguredPersonRepository extends CrudRepository<Person, Long> {}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Entity
|
||||
public class CreditCard {
|
||||
|
||||
@Id Long id;
|
||||
String creditCardNumber;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
interface CreditCardRepository extends CrudRepository<CreditCard, Long> {
|
||||
|
||||
CreditCard findByCreditCardNumber(String creditCardNumber);
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.springframework.context.MessageSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.support.ResourceBundleMessageSource;
|
||||
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
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan
|
||||
@EnableJpaRepositories
|
||||
@EnableTransactionManagement
|
||||
public class JpaRepositoryConfig {
|
||||
|
||||
@Bean
|
||||
public MessageSource messageSource() {
|
||||
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
|
||||
ms.setBasename("org.springframework.data.rest.core.ValidationErrors");
|
||||
return ms;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "ORDERS")
|
||||
public class Order {
|
||||
|
||||
private @Id Long id;
|
||||
private @ManyToOne Person creator;
|
||||
|
||||
public Order(Person creator) {
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
protected Order() {
|
||||
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Person getCreator() {
|
||||
return creator;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface OrderRepository extends CrudRepository<Order, Long> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.PrePersist;
|
||||
|
||||
/**
|
||||
* An entity that represents a person.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Entity
|
||||
public class Person {
|
||||
|
||||
@Id @GeneratedValue private Long id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@OneToMany private List<Person> siblings = Collections.emptyList();
|
||||
private Date created;
|
||||
|
||||
public Person() {}
|
||||
|
||||
public Person(String firstName, String lastName) {
|
||||
this.firstName = firstName;
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public Person addSibling(Person p) {
|
||||
if (siblings == Collections.EMPTY_LIST) {
|
||||
siblings = new ArrayList<Person>();
|
||||
}
|
||||
siblings.add(p);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Person> getSiblings() {
|
||||
return siblings;
|
||||
}
|
||||
|
||||
public void setSiblings(List<Person> siblings) {
|
||||
this.siblings = siblings;
|
||||
}
|
||||
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
@PrePersist
|
||||
private void prePersist() {
|
||||
this.created = Calendar.getInstance().getTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.rest.core.event.AbstractRepositoryEventListener;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class PersonBeforeSaveHandler extends AbstractRepositoryEventListener<Person> {
|
||||
@Override
|
||||
protected void onBeforeSave(Person person) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import static org.springframework.util.ClassUtils.*;
|
||||
import static org.springframework.util.StringUtils.*;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.HandleBeforeSave;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
/**
|
||||
* A test {@link Validator} that checks for non-blank names.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Component
|
||||
@HandleBeforeSave
|
||||
public class PersonNameValidator implements Validator {
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return isAssignable(clazz, Person.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
Person p = (Person) target;
|
||||
if (!hasText(p.getLastName())) {
|
||||
errors.rejectValue("lastName", "blank", "Last name cannot be blank");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.annotation.DateTimeFormat.ISO;
|
||||
|
||||
/**
|
||||
* A repository to manage {@link Person}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@RestResource(rel = "people", path = "people")
|
||||
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
|
||||
|
||||
@RestResource(rel = "firstname", path = "firstname")
|
||||
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
|
||||
|
||||
Page<Person> findByCreatedGreaterThan(@Param("date") Date date, Pageable pageable);
|
||||
|
||||
@Query("select p from Person p where p.created > :date")
|
||||
Page<Person> findByCreatedUsingISO8601Date(@Param("date") @DateTimeFormat(iso = ISO.DATE_TIME) Date date,
|
||||
Pageable pageable);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.springframework.data.rest.core.domain.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
|
||||
/**
|
||||
* A repository to manage {@link Person}s.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface PlainPersonRepository extends CrudRepository<Person, Long> {}
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.springframework.data.rest.core.domain.mongodb;
|
||||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import com.mongodb.Mongo;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
|
||||
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Configuration
|
||||
@ComponentScan(basePackageClasses = { MongoDbRepositoryConfig.class })
|
||||
@EnableMongoRepositories
|
||||
public class MongoDbRepositoryConfig {
|
||||
|
||||
@Bean
|
||||
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
|
||||
return new SimpleMongoDbFactory(new Mongo("localhost"), "spring-data-rest");
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MongoTemplate mongoTemplate() throws UnknownHostException {
|
||||
return new MongoTemplate(mongoDbFactory());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.springframework.data.rest.core.domain.mongodb;
|
||||
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Document
|
||||
public class Profile {
|
||||
|
||||
@Id private String id;
|
||||
private String name;
|
||||
private String type;
|
||||
|
||||
public Profile() {}
|
||||
|
||||
public Profile(String id, String name, String type) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Profile setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Profile setType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package org.springframework.data.rest.core.domain.mongodb;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
@Component
|
||||
public class ProfileLoader implements InitializingBean {
|
||||
|
||||
@Autowired private ProfileRepository profiles;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
profiles.save(new Profile("jdoe", "jdoe", "account"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.springframework.data.rest.core.domain.mongodb;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
|
||||
/**
|
||||
* Repository for managing {@link Profile}s in MongoDB.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public interface ProfileRepository extends CrudRepository<Profile, ObjectId> {}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.AbstractIntegrationTests;
|
||||
import org.springframework.data.rest.core.domain.jpa.Order;
|
||||
import org.springframework.data.rest.core.domain.jpa.OrderRepository;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonRepository;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ReflectionRepositoryInvoker}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ReflectionRepositoryInvokerIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Autowired Repositories repositories;
|
||||
@Autowired ConversionService conversionService;
|
||||
@Autowired PersonRepository repository;
|
||||
@Autowired OrderRepository orderRepository;
|
||||
|
||||
RepositoryInformation information;
|
||||
RepositoryInvoker invoker;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
information = repositories.getRepositoryInformationFor(Person.class);
|
||||
invoker = new ReflectionRepositoryInvoker(repository, information, conversionService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesFindOneWithStringIdCorrectly() {
|
||||
|
||||
Person person = repository.findAll().iterator().next();
|
||||
assertThat(person, is(notNullValue()));
|
||||
|
||||
Object result = invoker.invokeFindOne(person.getId().toString());
|
||||
assertThat(result, is(instanceOf(Person.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesFindAllWithoutPageableCorrectly() {
|
||||
|
||||
Iterable<Object> result = invoker.invokeFindAll((Pageable) null);
|
||||
assertThat(result, is(instanceOf(Page.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesFindAllWithPageableCorrectly() {
|
||||
|
||||
Iterable<Object> result = invoker.invokeFindAll(new PageRequest(0, 10));
|
||||
assertThat(result, is(instanceOf(Page.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallsBackToPlainFindAllIfRepositoryIsNotPaging() {
|
||||
|
||||
ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(orderRepository,
|
||||
repositories.getRepositoryInformationFor(Order.class), conversionService);
|
||||
Iterable<Object> result = invoker.invokeFindAll(new PageRequest(0, 10));
|
||||
|
||||
assertThat(result, is(instanceOf(List.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokesQueryMethod() throws Exception {
|
||||
|
||||
HashMap<String, String[]> parameters = new HashMap<String, String[]>();
|
||||
parameters.put("firstName", new String[] { "John" });
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class);
|
||||
Object result = invoker.invokeQueryMethod(method, parameters, null, null);
|
||||
|
||||
assertThat(result, is(instanceOf(Page.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void considersFormattingAnnotationsOnQueryMethodParameters() throws Exception {
|
||||
|
||||
HashMap<String, String[]> parameters = new HashMap<String, String[]>();
|
||||
parameters.put("date", new String[] { "2013-07-18T10:49:00.000+02:00" });
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByCreatedUsingISO8601Date", Date.class, Pageable.class);
|
||||
Object result = invoker.invokeQueryMethod(method, parameters, null, null);
|
||||
|
||||
assertThat(result, is(instanceOf(Page.class)));
|
||||
Page<?> page = (Page<?>) result;
|
||||
assertThat(page.getNumberOfElements(), is(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package org.springframework.data.rest.core.invoke;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.rest.core.domain.jpa.PersonRepository;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryMethod;
|
||||
import org.springframework.data.rest.core.support.Methods;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Tests to verify the integrity of the {@link RepositoryMethod} abstraction.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
*/
|
||||
public class RepositoryMethodUnitTests {
|
||||
|
||||
Map<String, RepositoryMethod> methods = new HashMap<String, RepositoryMethod>();
|
||||
RepositoryMethod method;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
doWithMethods(PersonRepository.class, new ReflectionUtils.MethodCallback() {
|
||||
@Override
|
||||
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
|
||||
String name = method.getName();
|
||||
RepositoryMethod repoMethod = new RepositoryMethod(method);
|
||||
methods.put(name, repoMethod);
|
||||
}
|
||||
}, Methods.USER_METHODS);
|
||||
method = methods.get("findByFirstName");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindSimpleQueryMethods() throws Exception {
|
||||
assertThat(method, notNullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindPageableInformationOnMethod() throws Exception {
|
||||
assertThat(method, notNullValue());
|
||||
assertThat(method.isPageable(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotFindSortInformationOnMethod() throws Exception {
|
||||
assertThat(method, notNullValue());
|
||||
assertThat(method.isSortable(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProvideParameterClassTypes() throws Exception {
|
||||
assertThat(method, notNullValue());
|
||||
assertThat(method.getParameters().get(0).getParameterType(), is(typeCompatibleWith(String.class)));
|
||||
assertThat(method.getParameters().get(1).getParameterType(), is(typeCompatibleWith(Pageable.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldProvideParameterNames() throws Exception {
|
||||
assertThat(method, notNullValue());
|
||||
assertThat(method.getParameterNames(), contains("firstName", "arg1"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.mapping.CollectionResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryCollectionResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RepositoryCollectionResourceMapping}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryCollectionResourceMappingUnitTests {
|
||||
|
||||
@Test
|
||||
public void buildsDefaultMappingForRepository() {
|
||||
|
||||
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(PersonRepository.class);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("persons")));
|
||||
assertThat(mapping.getRel(), is("persons"));
|
||||
assertThat(mapping.getSingleResourceRel(), is("person"));
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void honorsAnnotatedsMapping() {
|
||||
|
||||
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(AnnotatedPersonRepository.class);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("bar")));
|
||||
assertThat(mapping.getRel(), is("foo"));
|
||||
assertThat(mapping.getSingleResourceRel(), is("annotatedPerson"));
|
||||
assertThat(mapping.isExported(), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void repositoryAnnotationTrumpsDomainTypeMapping() {
|
||||
|
||||
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(
|
||||
AnnotatedAnnotatedPersonRepository.class);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("/trumpsAll")));
|
||||
assertThat(mapping.getRel(), is("foo"));
|
||||
assertThat(mapping.getSingleResourceRel(), is("annotatedPerson"));
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExposeRepositoryForPublicDomainTypeIfRepoIsPackageProtected() {
|
||||
|
||||
ResourceMapping mapping = new RepositoryCollectionResourceMapping(PackageProtectedRepository.class);
|
||||
assertThat(mapping.isExported(), is(false));
|
||||
}
|
||||
|
||||
public static class Person {}
|
||||
|
||||
@RestResource(path = "bar", rel = "foo", exported = false)
|
||||
static class AnnotatedPerson {}
|
||||
|
||||
public interface PersonRepository extends Repository<Person, Long> {}
|
||||
|
||||
interface AnnotatedPersonRepository extends Repository<AnnotatedPerson, Long> {}
|
||||
|
||||
@RestResource(path = "trumpsAll")
|
||||
interface AnnotatedAnnotatedPersonRepository extends Repository<AnnotatedPerson, Long> {}
|
||||
|
||||
public static class PublicClass {}
|
||||
|
||||
static interface PackageProtectedRepository extends Repository<PublicClass, Long> {}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryCollectionResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryMethodResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryMethodResourceMappingUnitTests {
|
||||
|
||||
RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(PersonRepository.class);
|
||||
|
||||
@Test
|
||||
public void foo() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
|
||||
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("findByLastname")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesConfiguredNameWithLeadingSlash() throws Exception {
|
||||
|
||||
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
|
||||
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("bar")));
|
||||
}
|
||||
|
||||
static class Person {}
|
||||
|
||||
interface PersonRepository extends Repository<Person, Long> {
|
||||
|
||||
Iterable<Person> findByLastname(String lastname);
|
||||
|
||||
@RestResource(path = "/bar")
|
||||
Iterable<Person> findByFirstname(String firstname);
|
||||
|
||||
@RestResource(path = "foo")
|
||||
Iterable<Person> findByEmailAddress(String email);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.domain.jpa.CreditCard;
|
||||
import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.core.domain.jpa.Person;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link ResourceMappings}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
@Transactional
|
||||
public class ResourceMappingsIntegrationTest {
|
||||
|
||||
@Autowired ListableBeanFactory factory;
|
||||
|
||||
ResourceMappings mappings;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
Repositories repositories = new Repositories(factory);
|
||||
this.mappings = new ResourceMappings(new RepositoryRestConfiguration(), repositories);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void detectsAllMappings() {
|
||||
assertThat(mappings, is(Matchers.<ResourceMetadata> iterableWithSize(6)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void exportsResourceAndSearchesForPersons() {
|
||||
|
||||
ResourceMetadata personMappings = mappings.getMappingFor(Person.class);
|
||||
|
||||
assertThat(personMappings.isExported(), is(true));
|
||||
assertThat(personMappings.getSearchResourceMappings().isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExportAnyMappingsForHiddenRepository() {
|
||||
|
||||
ResourceMetadata creditCardMapping = mappings.getMappingFor(CreditCard.class);
|
||||
|
||||
assertThat(creditCardMapping.isExported(), is(false));
|
||||
assertThat(creditCardMapping.getSearchResourceMappings().isExported(), is(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.mapping.CollectionResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.TypeBasedCollectionResourceMapping;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link TypeBasedCollectionResourceMapping}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class TypeBasedCollectionResourceMappingUnitTest {
|
||||
|
||||
@Test
|
||||
public void defaultsMappingsByType() {
|
||||
|
||||
CollectionResourceMapping mapping = new TypeBasedCollectionResourceMapping(Sample.class);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("sample")));
|
||||
assertThat(mapping.getRel(), is("samples"));
|
||||
assertThat(mapping.getSingleResourceRel(), is("sample"));
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void usesCustomizedRel() {
|
||||
|
||||
CollectionResourceMapping mapping = new TypeBasedCollectionResourceMapping(CustomizedSample.class);
|
||||
|
||||
assertThat(mapping.getPath(), is(new Path("customizedSample")));
|
||||
assertThat(mapping.getRel(), is("myRel"));
|
||||
assertThat(mapping.getSingleResourceRel(), is("customizedSample"));
|
||||
assertThat(mapping.isExported(), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-99
|
||||
*/
|
||||
@Test
|
||||
public void doesNotExportNonPublicTypesByDefault() {
|
||||
|
||||
CollectionResourceMapping mapping = new TypeBasedCollectionResourceMapping(HiddenSample.class);
|
||||
|
||||
assertThat(mapping.isExported(), is(false));
|
||||
}
|
||||
|
||||
public interface Sample {}
|
||||
|
||||
interface HiddenSample {}
|
||||
|
||||
@RestResource(rel = "myRel")
|
||||
interface CustomizedSample {
|
||||
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user