DATAREST-409 - Port RepositoryInvoker API to Spring Data Commons.
Introduced SupportedHttpMethods abstraction to be able to test the exposure of HTTp methods based on a CrudMethods instance only. Moved ResourceType to the core module.
This commit is contained in:
@@ -1,71 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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 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.CrudMethods;
|
||||
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;
|
||||
private final CrudMethods crudMethods;
|
||||
|
||||
private final boolean customSaveMethod;
|
||||
private final boolean customFindOneMethod;
|
||||
private final boolean customDeleteMethod;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
this.crudMethods = information.getCrudMethods();
|
||||
|
||||
this.customSaveMethod = isRedeclaredMethod(crudMethods.getSaveMethod());
|
||||
this.customFindOneMethod = isRedeclaredMethod(crudMethods.getFindOneMethod());
|
||||
this.customDeleteMethod = isRedeclaredMethod(crudMethods.getDeleteMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T invokeFindOne(Serializable id) {
|
||||
return customFindOneMethod ? super.<T> invokeFindOne(id) : (T) repository.findOne(convertId(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public <T> T invokeSave(T entity) {
|
||||
return customSaveMethod ? super.invokeSave(entity) : repository.save(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public void invokeDelete(Serializable id) {
|
||||
|
||||
if (customDeleteMethod) {
|
||||
super.invokeDelete(id);
|
||||
} else {
|
||||
repository.delete(convertId(id));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRedeclaredMethod(Method method) {
|
||||
return !method.getDeclaringClass().equals(CrudRepository.class);
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RepositoryInvokerFactory} to inspect the requested repository type and create a
|
||||
* matching {@link RepositoryInvoker} that suits the repository best. That means, the more concrete the base interface
|
||||
* of the repository is, the more concrete will the actual invoker become - which means it will favor concrete method
|
||||
* invocations over reflection ones.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final ConversionService conversionService;
|
||||
private final Map<Class<?>, RepositoryInvoker> invokers;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories} and
|
||||
* {@link ConversionService}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param conversionService must not be {@literal null}.
|
||||
*/
|
||||
public DefaultRepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(conversionService, "ConversionService must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.conversionService = conversionService;
|
||||
this.invokers = new HashMap<Class<?>, RepositoryInvoker>();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link RepositoryInvoker} for the repository managing the given domain type.
|
||||
*
|
||||
* @param domainType
|
||||
* @return
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvokerFactory#getInvokerFor(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public RepositoryInvoker getInvokerFor(Class<?> domainType) {
|
||||
|
||||
RepositoryInvoker invoker = invokers.get(domainType);
|
||||
|
||||
if (invoker != null) {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
invoker = prepareInvokers(domainType);
|
||||
invokers.put(domainType, invoker);
|
||||
|
||||
return invoker;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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.Arrays;
|
||||
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#hasFindAllMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFindAllMethod() {
|
||||
return methods.hasFindAllMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (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#hasSaveMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSaveMethod() {
|
||||
return methods.hasSaveMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (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 <T> T invokeSave(T object) {
|
||||
return invoke(methods.getSaveMethod(), object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindOneMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFindOneMethod() {
|
||||
return methods.hasFindOneMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (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 <T> T invokeFindOne(Serializable id) {
|
||||
return invoke(methods.getFindOneMethod(), convertId(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasDeleteMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasDeleteMethod() {
|
||||
return methods.hasDelete();
|
||||
}
|
||||
|
||||
/*
|
||||
* (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
|
||||
@SuppressWarnings("unchecked")
|
||||
public void invokeDelete(Serializable id) {
|
||||
|
||||
Method method = methods.getDeleteMethod();
|
||||
Class<?> parameterType = method.getParameterTypes()[0];
|
||||
List<Class<? extends Serializable>> idTypes = Arrays.asList(information.getIdType(), Serializable.class);
|
||||
|
||||
if (idTypes.contains(parameterType)) {
|
||||
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) {
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
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());
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2014 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 {
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to save objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasSaveMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the save method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesSave();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to delete objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasDeleteMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the delete method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesDelete();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to find a single object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasFindOneMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find a single object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindOne();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to find all objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasFindAllMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find all objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindAll();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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 {
|
||||
|
||||
<T> T invokeSave(T object);
|
||||
|
||||
<T> T invokeFindOne(Serializable id);
|
||||
|
||||
Iterable<Object> invokeFindAll(Pageable pageable);
|
||||
|
||||
Iterable<Object> invokeFindAll(Sort sort);
|
||||
|
||||
void invokeDelete(Serializable serializable);
|
||||
|
||||
Object invokeQueryMethod(Method method, Map<String, String[]> parameters, Pageable pageable, Sort sort);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Interface for a factory to create {@link RepositoryInvoker} instances for repositories managing a particular domain
|
||||
* type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RepositoryInvokerFactory {
|
||||
|
||||
/**
|
||||
* Returns the {@link RepositoryInvoker} for a repository managing the given domain type.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
RepositoryInvoker getInvokerFor(Class<?> domainType);
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2014 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.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.data.repository.core.CrudMethods;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link SupportedHttpMethods} that are determined by a {@link CrudMethods} instance.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.3
|
||||
*/
|
||||
public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
|
||||
private final ExposureAwareCrudMethods exposedMethods;
|
||||
|
||||
/**
|
||||
* Creates a new {@link CrudMethodsSupportedHttpMethods} for the given {@link CrudMethods}.
|
||||
*
|
||||
* @param crudMethods must not be {@literal null}.
|
||||
*/
|
||||
public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods) {
|
||||
|
||||
Assert.notNull(crudMethods, "CrudMethods must not be null!");
|
||||
|
||||
this.exposedMethods = new DefaultExposureAwareCrudMethods(crudMethods);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#supports(org.springframework.http.HttpMethod, org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(HttpMethod method, ResourceType type) {
|
||||
|
||||
Assert.notNull(method, "HTTP method must not be null!");
|
||||
Assert.notNull(type, "Resource type must not be null!");
|
||||
|
||||
return getMethodsFor(type).contains(method);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(ResourceType resourcType) {
|
||||
|
||||
Assert.notNull(resourcType, "Resource type must not be null!");
|
||||
|
||||
Set<HttpMethod> methods = new HashSet<HttpMethod>();
|
||||
methods.add(HttpMethod.OPTIONS);
|
||||
|
||||
switch (resourcType) {
|
||||
case COLLECTION:
|
||||
|
||||
if (exposedMethods.exposesFindAll()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
methods.add(HttpMethod.HEAD);
|
||||
}
|
||||
|
||||
if (exposedMethods.exposesSave()) {
|
||||
methods.add(HttpMethod.POST);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
|
||||
if (exposedMethods.exposesDelete() && exposedMethods.exposesFindOne()) {
|
||||
methods.add(HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
if (exposedMethods.exposesFindOne()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
methods.add(HttpMethod.HEAD);
|
||||
}
|
||||
|
||||
if (exposedMethods.exposesSave()) {
|
||||
methods.add(HttpMethod.PUT);
|
||||
methods.add(HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported resource type %s!", resourcType));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class DefaultExposureAwareCrudMethods implements ExposureAwareCrudMethods {
|
||||
|
||||
private final CrudMethods crudMethods;
|
||||
|
||||
/**
|
||||
* @param exposedMethods
|
||||
*/
|
||||
public DefaultExposureAwareCrudMethods(CrudMethods crudMethods) {
|
||||
this.crudMethods = crudMethods;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesSave()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesSave() {
|
||||
return exposes(crudMethods.getSaveMethod());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesDelete()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesDelete() {
|
||||
return exposes(crudMethods.getDeleteMethod());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesFindOne()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesFindOne() {
|
||||
return exposes(crudMethods.getFindOneMethod());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesFindAll()
|
||||
*/
|
||||
@Override
|
||||
public boolean exposesFindAll() {
|
||||
return exposes(crudMethods.getFindAllMethod());
|
||||
}
|
||||
|
||||
private static boolean exposes(Method method) {
|
||||
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
|
||||
return annotation == null ? true : annotation.exported();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
interface ExposureAwareCrudMethods {
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the save method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesSave();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the delete method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesDelete();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find a single object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindOne();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find all objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindAll();
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods.NoSupportedMethods;
|
||||
|
||||
/**
|
||||
* {@link ResourceMetadata} based on a {@link PersistentEntity}.
|
||||
@@ -100,4 +101,13 @@ public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping
|
||||
public SearchResourceMappings getSearchResourceMappings() {
|
||||
return new SearchResourceMappings(Collections.<MethodResourceMapping> emptyList());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSupportedHttpMethods()
|
||||
*/
|
||||
@Override
|
||||
public SupportedHttpMethods getSupportedHttpMethods() {
|
||||
return NoSupportedMethods.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
|
||||
private final CollectionResourceMapping mapping;
|
||||
private final RepositoryResourceMappings provider;
|
||||
private final RepositoryMetadata repositoryInterface;
|
||||
private final SupportedHttpMethods crudMethodsSupportedHttpMethods;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryAwareResourceInformation} for the given {@link Repositories},
|
||||
@@ -56,6 +57,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
|
||||
this.mapping = mapping;
|
||||
this.provider = provider;
|
||||
this.repositoryInterface = repositoryMetadata;
|
||||
this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,4 +188,13 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
|
||||
public SearchResourceMappings getSearchResourceMappings() {
|
||||
return provider.getSearchResourceMappings(repositoryInterface.getRepositoryInterface());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSupportedHttpMethods()
|
||||
*/
|
||||
@Override
|
||||
public SupportedHttpMethods getSupportedHttpMethods() {
|
||||
return crudMethodsSupportedHttpMethods;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -16,9 +16,10 @@
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Interface for metadata of resources exposed throught the system.
|
||||
* Interface for metadata of resources exposed through the system.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -62,4 +63,12 @@ public interface ResourceMetadata extends CollectionResourceMapping {
|
||||
* @return
|
||||
*/
|
||||
SearchResourceMappings getSearchResourceMappings();
|
||||
|
||||
/**
|
||||
* Returns the supported {@link HttpMethod}s for the given {@link ResourceType}.
|
||||
*
|
||||
* @param resourcType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
SupportedHttpMethods getSupportedHttpMethods();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
/**
|
||||
* An enum listing all supported resource types.
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2014 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.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* An API to discover the {@link HttpMethod}s supported on a given {@link ResourceType}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface SupportedHttpMethods {
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link HttpMethod} is supported for the given {@link ResourceType}.
|
||||
*
|
||||
* @param httpMethod must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean supports(HttpMethod method, ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns the supported {@link HttpMethod}s for the given {@link ResourceType}.
|
||||
*
|
||||
* @param resourcType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
Set<HttpMethod> getMethodsFor(ResourceType resourcType);
|
||||
|
||||
/**
|
||||
* Null object to abstract the absence of any support for any HTTP method.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
enum NoSupportedMethods implements SupportedHttpMethods {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(ResourceType resourcType) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#supports(org.springframework.http.HttpMethod, org.springframework.data.rest.core.mapping.ResourceType)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(HttpMethod method, ResourceType type) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014 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 org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.junit.Test;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
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;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
|
||||
/**
|
||||
* Intgration tests for {@link CrudRepositoryInvoker}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CrudRepositoryInvokerIntegrationTests extends AbstractIntegrationTests {
|
||||
|
||||
@Autowired ApplicationContext context;
|
||||
@Autowired PersonRepository personRepository;
|
||||
@Autowired OrderRepository orderRepository;
|
||||
|
||||
/**
|
||||
* @see DATAREST-216
|
||||
*/
|
||||
@Test
|
||||
public void invokesRedeclaredSave() {
|
||||
|
||||
RepositoryInvoker invoker = getInvokerFor(orderRepository, OrderRepository.class);
|
||||
|
||||
Person person = personRepository.findOne(1L);
|
||||
invoker.invokeSave(new Order(person));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-216
|
||||
*/
|
||||
@Test
|
||||
public void invokesRedeclaredFindOne() {
|
||||
|
||||
Person person = personRepository.findOne(1L);
|
||||
Order order = orderRepository.save(new Order(person));
|
||||
|
||||
RepositoryInvoker invoker = getInvokerFor(orderRepository, OrderRepository.class);
|
||||
invoker.invokeFindOne(order.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-216
|
||||
*/
|
||||
@Test
|
||||
public void invokesDeleteOnCrudRepository() {
|
||||
|
||||
Person person = personRepository.findOne(1L);
|
||||
Order order = orderRepository.save(new Order(person));
|
||||
|
||||
RepositoryInvoker invoker = getInvokerFor(orderRepository, CrudRepository.class);
|
||||
invoker.invokeDelete(order.getId());
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private RepositoryInvoker getInvokerFor(Object repository, Class<?> expectedType) {
|
||||
|
||||
Object proxy = getVerifyingProxy(repository, expectedType);
|
||||
Repositories repositories = new Repositories(context);
|
||||
ConversionService conversionService = new DefaultFormattingConversionService();
|
||||
|
||||
return new CrudRepositoryInvoker((CrudRepository) proxy, repositories.getRepositoryInformationFor(Order.class),
|
||||
conversionService);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <T> T getVerifyingProxy(T target, Class<?> expectedType) {
|
||||
|
||||
ProxyFactory factory = new ProxyFactory();
|
||||
factory.setInterfaces(target.getClass().getInterfaces());
|
||||
factory.setTarget(target);
|
||||
factory.addAdvice(new VerifyingMethodInterceptor(expectedType));
|
||||
|
||||
return (T) factory.getProxy();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link MethodInterceptor} to verifiy the invocation was triggered on the given type.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final class VerifyingMethodInterceptor implements MethodInterceptor {
|
||||
|
||||
private final Class expectedInvocationTarget;
|
||||
|
||||
public VerifyingMethodInterceptor(Class<?> expectedInvocationTarget) {
|
||||
this.expectedInvocationTarget = expectedInvocationTarget;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(MethodInvocation invocation) throws Throwable {
|
||||
|
||||
Class<?> type = invocation.getMethod().getDeclaringClass();
|
||||
|
||||
assertThat("Expected method invocation on " + expectedInvocationTarget + " but was invoked on " + type + "!",
|
||||
type, is(equalTo(expectedInvocationTarget)));
|
||||
|
||||
return invocation.proceed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
/*
|
||||
* 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 static org.mockito.Mockito.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import org.bson.types.ObjectId;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
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.mongodb.MongoDbFactory;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
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.Author;
|
||||
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;
|
||||
@Autowired EntityManager em;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-325
|
||||
*/
|
||||
@Test
|
||||
public void invokesMethodOnPackageProtectedRepository() throws Exception {
|
||||
|
||||
Object authorRepository = repositories.getRepositoryFor(Author.class);
|
||||
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(Author.class);
|
||||
Method method = repositoryInformation.getRepositoryInterface().getMethod("findByFirstnameContaining", String.class);
|
||||
|
||||
Map<String, String[]> parameters = Collections.singletonMap("firstname", new String[] { "Oliver" });
|
||||
|
||||
ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(authorRepository, information,
|
||||
conversionService);
|
||||
invoker.invokeQueryMethod(method, parameters, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-335, DATAREST-346
|
||||
*/
|
||||
@Test
|
||||
public void invokesOverriddenDeleteMethodCorrectly() {
|
||||
|
||||
MyRepo repository = mock(MyRepo.class);
|
||||
|
||||
MongoRepositoryFactoryBean<MyRepo, Domain, ObjectId> factory = new MongoRepositoryFactoryBean<MyRepo, Domain, ObjectId>();
|
||||
factory.setMongoOperations(new MongoTemplate(mock(MongoDbFactory.class)));
|
||||
factory.setRepositoryInterface(MyRepo.class);
|
||||
factory.setLazyInit(true);
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
ObjectId id = new ObjectId();
|
||||
|
||||
ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(repository,
|
||||
factory.getRepositoryInformation(), conversionService);
|
||||
|
||||
// We must assume a non matching type here as clients might provide the raw ID value obtained from the request
|
||||
invoker.invokeDelete(id.toString());
|
||||
|
||||
verify((CustomRepo) repository, times(1)).delete(id);
|
||||
verify(repository, times(0)).findOne(Matchers.any(ObjectId.class));
|
||||
|
||||
}
|
||||
|
||||
interface MyRepo extends CustomRepo, CrudRepository<Domain, ObjectId> {}
|
||||
|
||||
interface Domain {}
|
||||
|
||||
interface CustomRepo {
|
||||
void delete(ObjectId id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2014 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 static org.springframework.data.rest.core.mapping.ResourceType.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.CrudMethods;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.DefaultCrudMethods;
|
||||
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CrudMethodsSupportedHttpMethods}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CrudMethodsSupportedHttpMethodsUnitTests {
|
||||
|
||||
/**
|
||||
* @see DATACMNS-589, DATAREST-409
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportAnyHttpMethodForEmptyRepository() {
|
||||
|
||||
SupportedHttpMethods supportedMethods = getSupportedHttpMethodsFor(RawRepository.class);
|
||||
|
||||
assertMethodsSupported(supportedMethods, COLLECTION, true, OPTIONS);
|
||||
assertMethodsSupported(supportedMethods, COLLECTION, false, GET, PUT, POST, PATCH, DELETE, HEAD);
|
||||
|
||||
assertMethodsSupported(supportedMethods, ITEM, true, OPTIONS);
|
||||
assertMethodsSupported(supportedMethods, ITEM, false, GET, PUT, POST, PATCH, DELETE, HEAD);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217, DATAREST-330, DATACMNS-589, DATAREST-409
|
||||
*/
|
||||
@Test
|
||||
public void defaultsSupportedHttpMethodsForItemResource() {
|
||||
|
||||
SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(SampleRepository.class);
|
||||
|
||||
assertMethodsSupported(supportedHttpMethods, ITEM, true, GET, PUT, PATCH, DELETE, OPTIONS, HEAD);
|
||||
assertMethodsSupported(supportedHttpMethods, ITEM, false, POST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217, DATAREST-330, DATACMNS-589, DATAREST-409
|
||||
*/
|
||||
@Test
|
||||
public void defaultsSupportedHttpMethodsForCollectionResource() {
|
||||
|
||||
SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(SampleRepository.class);
|
||||
|
||||
assertMethodsSupported(supportedHttpMethods, COLLECTION, true, GET, POST, OPTIONS, HEAD);
|
||||
assertMethodsSupported(supportedHttpMethods, COLLECTION, false, PUT, PATCH, DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-589, DATAREST-409
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportDeleteIfDeleteMethodIsNotExported() {
|
||||
|
||||
SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(HidesDelete.class);
|
||||
|
||||
assertMethodsSupported(supportedHttpMethods, ITEM, false, DELETE);
|
||||
}
|
||||
|
||||
private static SupportedHttpMethods getSupportedHttpMethodsFor(Class<?> repositoryInterface) {
|
||||
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
|
||||
CrudMethods crudMethods = new DefaultCrudMethods(metadata);
|
||||
|
||||
return new CrudMethodsSupportedHttpMethods(crudMethods);
|
||||
}
|
||||
|
||||
private static void assertMethodsSupported(SupportedHttpMethods methods, ResourceType type, boolean supported,
|
||||
HttpMethod... httpMethods) {
|
||||
|
||||
Matcher<Iterable<HttpMethod>> isSupported = supported ? hasItems(httpMethods) : not(hasItems(httpMethods));
|
||||
|
||||
assertThat(methods.getMethodsFor(type), isSupported);
|
||||
|
||||
for (HttpMethod method : httpMethods) {
|
||||
assertThat(methods.supports(method, type), is(supported));
|
||||
}
|
||||
}
|
||||
|
||||
interface RawRepository extends Repository<Object, Long> {}
|
||||
|
||||
interface SampleRepository extends CrudRepository<Object, Long> {}
|
||||
|
||||
interface HidesDelete extends CrudRepository<Object, Long> {
|
||||
|
||||
@RestResource(exported = false)
|
||||
void delete(Object id);
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.event.AfterCreateEvent;
|
||||
@@ -38,9 +39,10 @@ import org.springframework.data.rest.core.event.AfterSaveEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeCreateEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeSaveEvent;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
|
||||
import org.springframework.data.rest.webmvc.support.BackendId;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
@@ -113,7 +115,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
public ResponseEntity<?> optionsForCollectionResource(RootResourceInformation information) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(information.getSupportedMethods(ResourceType.COLLECTION));
|
||||
SupportedHttpMethods supportedMethods = information.getSupportedMethods();
|
||||
|
||||
headers.setAllow(supportedMethods.getMethodsFor(ResourceType.COLLECTION));
|
||||
|
||||
return new ResponseEntity<Object>(headers, HttpStatus.OK);
|
||||
}
|
||||
@@ -243,7 +247,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
public ResponseEntity<?> optionsForItemResource(RootResourceInformation information) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(information.getSupportedMethods(ResourceType.ITEM));
|
||||
SupportedHttpMethods supportedMethods = information.getSupportedMethods();
|
||||
|
||||
headers.setAllow(supportedMethods.getMethodsFor(ResourceType.ITEM));
|
||||
headers.put("Accept-Patch", ACCEPT_PATCH_HEADERS);
|
||||
|
||||
return new ResponseEntity<Object>(headers, HttpStatus.OK);
|
||||
@@ -449,12 +455,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM);
|
||||
|
||||
RepositoryInvoker repoMethodInvoker = resourceInformation.getInvoker();
|
||||
|
||||
if (!repoMethodInvoker.exposesFindOne()) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
return repoMethodInvoker.invokeFindOne(id);
|
||||
return resourceInformation.getInvoker().invokeFindOne(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,14 +36,15 @@ import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.model.BeanWrapper;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.event.AfterLinkDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.AfterLinkSaveEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent;
|
||||
import org.springframework.data.rest.core.event.BeforeLinkSaveEvent;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.util.Function;
|
||||
import org.springframework.data.rest.webmvc.support.BackendId;
|
||||
import org.springframework.data.web.PagedResourcesAssembler;
|
||||
@@ -155,7 +156,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
final RepositoryInvoker repoMethodInvoker = repoRequest.getInvoker();
|
||||
|
||||
if (!repoMethodInvoker.exposesDelete()) {
|
||||
// Can't delete a property if
|
||||
if (repoRequest.getSupportedMethods().supports(HttpMethod.PUT, ResourceType.ITEM)) {
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@@ -379,7 +381,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
final RepositoryInvoker invoker = repoRequest.getInvoker();
|
||||
|
||||
if (!invoker.exposesSave()) {
|
||||
// Property can't be deleted if root resource can't be updated
|
||||
if (!repoRequest.getSupportedMethods().supports(HttpMethod.PUT, ResourceType.ITEM)) {
|
||||
throw new HttpRequestMethodNotSupportedException(HttpMethod.DELETE.name());
|
||||
}
|
||||
|
||||
@@ -443,7 +446,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
RepositoryInvoker invoker = repoRequest.getInvoker();
|
||||
|
||||
if (!invoker.exposesFindOne()) {
|
||||
if (!repoRequest.getSupportedMethods().supports(method, ResourceType.ITEM)) {
|
||||
throw new HttpRequestMethodNotSupportedException(method.name());
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ParameterMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
|
||||
@@ -16,14 +16,15 @@
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
@@ -43,6 +44,7 @@ public class RootResourceInformation {
|
||||
public RootResourceInformation(ResourceMetadata metadata, PersistentEntity<?, ?> entity, RepositoryInvoker invoker) {
|
||||
|
||||
this.resourceMetadata = metadata;
|
||||
|
||||
if (resourceMetadata == null || !resourceMetadata.isExported()) {
|
||||
|
||||
this.invoker = null;
|
||||
@@ -74,75 +76,8 @@ public class RootResourceInformation {
|
||||
return persistentEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the supported {@link HttpMethod}s for the given {@link ResourceType}.
|
||||
*
|
||||
* @param resourcType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Set<HttpMethod> getSupportedMethods(ResourceType resourcType) {
|
||||
|
||||
Assert.notNull(resourcType, "Resource type must not be null!");
|
||||
|
||||
if (invoker == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<HttpMethod> methods = new HashSet<HttpMethod>();
|
||||
methods.add(HttpMethod.OPTIONS);
|
||||
|
||||
switch (resourcType) {
|
||||
case COLLECTION:
|
||||
|
||||
if (invoker.exposesFindAll()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
methods.add(HttpMethod.HEAD);
|
||||
}
|
||||
|
||||
if (invoker.exposesSave()) {
|
||||
methods.add(HttpMethod.POST);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
|
||||
if (invoker.exposesDelete() && invoker.hasFindOneMethod()) {
|
||||
methods.add(HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
if (invoker.exposesFindOne()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
methods.add(HttpMethod.HEAD);
|
||||
}
|
||||
|
||||
if (invoker.exposesSave()) {
|
||||
methods.add(HttpMethod.PUT);
|
||||
methods.add(HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported resource type %s!", resourcType));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link HttpMethod} is supported for the given {@link ResourceType}.
|
||||
*
|
||||
* @param httpMethod must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean supports(HttpMethod httpMethod, ResourceType resourceType) {
|
||||
|
||||
Assert.notNull(httpMethod, "HTTP method must not be null!");
|
||||
Assert.notNull(resourceType, "Resource type must not be null!");
|
||||
|
||||
return getSupportedMethods(resourceType).contains(httpMethod);
|
||||
public SupportedHttpMethods getSupportedMethods() {
|
||||
return resourceMetadata.getSupportedHttpMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +99,8 @@ public class RootResourceInformation {
|
||||
Assert.notNull(httpMethod, "HTTP method must not be null!");
|
||||
Assert.notNull(resourceType, "Resource type must not be null!");
|
||||
|
||||
Collection<HttpMethod> supportedMethods = getSupportedMethods(resourceType);
|
||||
SupportedHttpMethods httpMethods = resourceMetadata.getSupportedHttpMethods();
|
||||
Collection<HttpMethod> supportedMethods = httpMethods.getMethodsFor(resourceType);
|
||||
|
||||
if (!supportedMethods.contains(httpMethod)) {
|
||||
|
||||
|
||||
@@ -45,8 +45,9 @@ import org.springframework.data.rest.core.mapping.ResourceDescription;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMapping;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SimpleResourceDescription;
|
||||
import org.springframework.data.rest.webmvc.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
|
||||
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
@@ -121,14 +122,16 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
|
||||
descriptors.add(representationDescriptor);
|
||||
|
||||
for (HttpMethod method : resourceInformation.getSupportedMethods(ResourceType.COLLECTION)) {
|
||||
SupportedHttpMethods supportedHttpMethods = resourceInformation.getSupportedMethods();
|
||||
|
||||
for (HttpMethod method : supportedHttpMethods.getMethodsFor(ResourceType.COLLECTION)) {
|
||||
|
||||
if (!UNDOCUMENTED_METHODS.contains(method)) {
|
||||
descriptors.add(buildCollectionResourceDescriptor(type, resourceInformation, representationDescriptor, method));
|
||||
}
|
||||
}
|
||||
|
||||
for (HttpMethod method : resourceInformation.getSupportedMethods(ResourceType.ITEM)) {
|
||||
for (HttpMethod method : supportedHttpMethods.getMethodsFor(ResourceType.ITEM)) {
|
||||
|
||||
if (!UNDOCUMENTED_METHODS.contains(method)) {
|
||||
descriptors.add(buildItemResourceDescriptor(resourceInformation, representationDescriptor, method));
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.rest.webmvc.IncomingRequest;
|
||||
import org.springframework.data.rest.webmvc.PersistentEntityResource;
|
||||
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||
|
||||
@@ -48,6 +48,8 @@ import org.springframework.data.geo.GeoModule;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.UriToEntityConverter;
|
||||
@@ -57,8 +59,6 @@ import org.springframework.data.rest.core.config.ProjectionDefinitionConfigurati
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor;
|
||||
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
|
||||
import org.springframework.data.rest.core.invoke.DefaultRepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceDescription;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
@@ -66,8 +66,8 @@ import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
|
||||
import org.springframework.data.rest.core.support.DomainObjectMerger;
|
||||
import org.springframework.data.rest.core.support.RepositoryRelProvider;
|
||||
import org.springframework.data.rest.core.util.UUIDConverter;
|
||||
import org.springframework.data.rest.webmvc.BaseUriAwareController;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.BaseUriAwareController;
|
||||
import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
|
||||
|
||||
@@ -17,9 +17,9 @@ package org.springframework.data.rest.webmvc.config;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -21,9 +21,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
|
||||
@@ -20,6 +20,8 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.data.rest.core.mapping.ResourceType;
|
||||
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
|
||||
import org.springframework.data.rest.webmvc.jpa.Address;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -43,8 +45,8 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI
|
||||
@Test
|
||||
public void getIsNotSupportedIfFindAllIsNotExported() {
|
||||
|
||||
RootResourceInformation information = getResourceInformation(Address.class);
|
||||
assertThat(information.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false));
|
||||
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
|
||||
assertThat(supportedMethods.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,7 +55,7 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI
|
||||
@Test
|
||||
public void postIsNotSupportedIfSaveIsNotExported() {
|
||||
|
||||
RootResourceInformation information = getResourceInformation(Address.class);
|
||||
assertThat(information.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false));
|
||||
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
|
||||
assertThat(supportedMethods.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.rest.webmvc.ResourceType.*;
|
||||
import static org.springframework.data.rest.core.mapping.ResourceType.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import org.atteo.evo.inflector.English;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -31,7 +28,7 @@ import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.repository.invoker.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
|
||||
@@ -57,90 +54,6 @@ public class RootResourceInformationUnitTests {
|
||||
this.information = new RootResourceInformation(metadata, entity, invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217, DATAREST-330
|
||||
*/
|
||||
@Test
|
||||
public void defaultsSupportedHttpMethodsForItemResource() {
|
||||
|
||||
assertThat(information.getSupportedMethods(ResourceType.ITEM), hasItems(GET, PUT, PATCH, DELETE, OPTIONS));
|
||||
assertThat(information.getSupportedMethods(ResourceType.ITEM), not(hasItems(POST)));
|
||||
|
||||
assertThat(information.getSupportedMethods(COLLECTION), hasItems(GET, POST, OPTIONS));
|
||||
assertThat(information.getSupportedMethods(COLLECTION), not(hasItems(PUT, PATCH, DELETE)));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportGetOnItemResourceIfFindOneIsNotExported() {
|
||||
|
||||
when(invoker.exposesFindOne()).thenReturn(false);
|
||||
assertThat(information.supports(GET, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportDeleteOnItemResourceIfDeleteIsNotExported() {
|
||||
|
||||
when(invoker.exposesDelete()).thenReturn(false);
|
||||
assertThat(information.supports(DELETE, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportPutOnItemResourceIfSaveIsNotExported() {
|
||||
|
||||
when(invoker.exposesSave()).thenReturn(false);
|
||||
assertThat(information.supports(POST, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-330
|
||||
*/
|
||||
@Test
|
||||
public void supportsHeadIfFindAllIsExposed() {
|
||||
|
||||
when(invoker.exposesFindAll()).thenReturn(true);
|
||||
assertThat(information.supports(HEAD, COLLECTION), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-330
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportHeadIfFindAllIsNotExposed() {
|
||||
|
||||
when(invoker.exposesFindAll()).thenReturn(false);
|
||||
assertThat(information.supports(HEAD, COLLECTION), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-330
|
||||
*/
|
||||
@Test
|
||||
public void supportsHeadIfFindOneIsExposed() {
|
||||
|
||||
when(invoker.exposesFindOne()).thenReturn(true);
|
||||
assertThat(information.supports(HEAD, ITEM), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-330
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportHeadIfFindOneIsNotExposed() {
|
||||
|
||||
when(invoker.exposesFindOne()).thenReturn(false);
|
||||
assertThat(information.supports(HEAD, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-330
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user