DATAREST-502 - Prepare request parameters to enable URI-to-entity resolution for executing searches.
RepositorySearchController now takes all request parameters as MultiValueMap to execute query methods. For parameters mapping to a managed resource we try to interpret the given parameter value as URI to actually trigger the URI-to-entity resolution through the newly registered UriToEntityConverter. The latter is now registered with the default ConversionService registered so that it's A lot of cleanups in RepositorySearchController. Removed some obsolete prints to the console to reduce log output during test execution. Related ticket: DATACMNS-678.
This commit is contained in:
@@ -81,7 +81,7 @@ public class UriToEntityConverter implements ConditionalGenericConverter {
|
||||
*/
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return conversionService.canConvert(STRING_TYPE, targetType);
|
||||
return !sourceType.equals(URI_TYPE) ? false : conversionService.canConvert(STRING_TYPE, targetType);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,16 +18,21 @@ package org.springframework.data.rest.webmvc;
|
||||
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.auditing.AuditableBeanWrapperFactory;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.repository.support.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
@@ -39,17 +44,21 @@ import org.springframework.hateoas.Links;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.hateoas.core.AnnotationAttribute;
|
||||
import org.springframework.hateoas.core.MethodParameters;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
/**
|
||||
* Controller to lookup and execute searches on a given repository.
|
||||
@@ -153,20 +162,23 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
/**
|
||||
* Executes the search with the given name.
|
||||
*
|
||||
* @param request
|
||||
* @param repository
|
||||
* @param resourceInformation
|
||||
* @param parameters
|
||||
* @param search
|
||||
* @param pageable
|
||||
* @param sort
|
||||
* @param assembler
|
||||
* @return
|
||||
* @throws ResourceNotFoundException
|
||||
*/
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Object> executeSearch(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String search, DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) {
|
||||
public ResponseEntity<Object> executeSearch(RootResourceInformation resourceInformation,
|
||||
@RequestParam MultiValueMap<String, Object> parameters, @PathVariable String search, DefaultedPageable pageable,
|
||||
Sort sort, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
Object result = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable, sort, assembler);
|
||||
Object result = executeQueryMethod(resourceInformation.getInvoker(), parameters, method, pageable, sort, assembler);
|
||||
|
||||
return new ResponseEntity<Object>(toResource(result, assembler, null), HttpStatus.OK);
|
||||
}
|
||||
@@ -175,20 +187,23 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
* Executes a query method and exposes the results in compact form.
|
||||
*
|
||||
* @param resourceInformation
|
||||
* @param parameters
|
||||
* @param repository
|
||||
* @param method
|
||||
* @param searcg
|
||||
* @param pageable
|
||||
* @param sort
|
||||
* @param assembler
|
||||
* @return
|
||||
*/
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.GET, //
|
||||
produces = { "application/x-spring-data-compact+json" })
|
||||
public ResourceSupport executeSearchCompact(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String repository, @PathVariable String search, DefaultedPageable pageable, Sort sort,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
public ResourceSupport executeSearchCompact(RootResourceInformation resourceInformation,
|
||||
@RequestParam MultiValueMap<String, Object> parameters, @PathVariable String repository,
|
||||
@PathVariable String search, DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
Object result = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable, sort, assembler);
|
||||
Object result = executeQueryMethod(resourceInformation.getInvoker(), parameters, method, pageable, sort, assembler);
|
||||
Object resource = toResource(result, assembler, null);
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
@@ -272,11 +287,29 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
private Object executeQueryMethod(final RepositoryInvoker invoker, WebRequest request, Method method,
|
||||
DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) {
|
||||
private Object executeQueryMethod(final RepositoryInvoker invoker,
|
||||
@RequestParam MultiValueMap<String, Object> parameters, Method method, DefaultedPageable pageable, Sort sort,
|
||||
PersistentEntityResourceAssembler assembler) {
|
||||
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
return invoker.invokeQueryMethod(method, parameters, pageable.getPageable(), sort);
|
||||
MultiValueMap<String, Object> result = new LinkedMultiValueMap<String, Object>(parameters);
|
||||
MethodParameters methodParameters = new MethodParameters(method, new AnnotationAttribute(Param.class));
|
||||
|
||||
for (Entry<String, List<Object>> entry : parameters.entrySet()) {
|
||||
|
||||
MethodParameter parameter = methodParameters.getParameter(entry.getKey());
|
||||
|
||||
if (parameter == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceMetadata metadata = mappings.getMappingFor(parameter.getParameterType());
|
||||
|
||||
if (metadata != null && metadata.isExported()) {
|
||||
result.put(parameter.getParameterName(), prepareUris(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return invoker.invokeQueryMethod(method, result, pageable.getPageable(), sort);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -284,7 +317,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
*
|
||||
* @param resourceInformation
|
||||
*/
|
||||
private SearchResourceMappings verifySearchesExposed(RootResourceInformation resourceInformation) {
|
||||
private static SearchResourceMappings verifySearchesExposed(RootResourceInformation resourceInformation) {
|
||||
|
||||
SearchResourceMappings resourceMappings = resourceInformation.getSearchMappings();
|
||||
|
||||
@@ -294,4 +327,31 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
|
||||
return resourceMappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to turn all elements of the given {@link List} into URIs and falls back to keeping the original element if
|
||||
* the conversion fails.
|
||||
*
|
||||
* @param source can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static List<Object> prepareUris(List<Object> source) {
|
||||
|
||||
if (source == null || source.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> result = new ArrayList<Object>(source.size());
|
||||
|
||||
for (Object element : source) {
|
||||
|
||||
try {
|
||||
result.add(new URI(element.toString()));
|
||||
} catch (URISyntaxException o_O) {
|
||||
result.add(element);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.ConfigurableConversionService;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.auditing.AuditableBeanWrapperFactory;
|
||||
@@ -181,8 +182,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
public DefaultFormattingConversionService defaultConversionService() {
|
||||
|
||||
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
|
||||
|
||||
// Add Spring Data Commons formatters
|
||||
conversionService.addConverter(uriToEntityConverter(conversionService));
|
||||
addFormatters(conversionService);
|
||||
|
||||
configureConversionService(conversionService);
|
||||
@@ -540,8 +541,16 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
|
||||
PersistentEntities entities = persistentEntities();
|
||||
|
||||
return new PersistentEntityJackson2Module(resourceMappings(), entities, config(), new UriToEntityConverter(
|
||||
entities, defaultConversionService()));
|
||||
return new PersistentEntityJackson2Module(resourceMappings(), entities, config(),
|
||||
uriToEntityConverter(defaultConversionService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entities
|
||||
* @return
|
||||
*/
|
||||
protected UriToEntityConverter uriToEntityConverter(ConversionService conversionService) {
|
||||
return new UriToEntityConverter(persistentEntities(), conversionService);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.ResourceTester.HasSelfLink;
|
||||
import org.springframework.data.rest.webmvc.jpa.Address;
|
||||
import org.springframework.data.rest.webmvc.jpa.Author;
|
||||
import org.springframework.data.rest.webmvc.jpa.Book;
|
||||
import org.springframework.data.rest.webmvc.jpa.CreditCard;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.webmvc.jpa.Person;
|
||||
@@ -34,11 +35,14 @@ import org.springframework.data.rest.webmvc.jpa.TestDataPopulator;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.hateoas.PagedResources;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
/**
|
||||
* Integration tests for the {@link RepositorySearchController}.
|
||||
@@ -49,6 +53,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
@Transactional
|
||||
public class RepositorySearchControllerIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
|
||||
static final DefaultedPageable PAGEABLE = new DefaultedPageable(new PageRequest(0, 10), true);
|
||||
|
||||
@Autowired TestDataPopulator loader;
|
||||
@Autowired RepositorySearchController controller;
|
||||
@Autowired PersistentEntityResourceAssembler assembler;
|
||||
@@ -88,11 +94,12 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
@Test
|
||||
public void executesSearchAgainstRepository() {
|
||||
|
||||
RequestParameters parameters = new RequestParameters("firstname", "John");
|
||||
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
|
||||
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
|
||||
parameters.add("firstname", "John");
|
||||
|
||||
ResponseEntity<Object> response = controller.executeSearch(resourceInformation, getRequest(parameters),
|
||||
"firstname", new DefaultedPageable(new PageRequest(0, 10), true), null, assembler);
|
||||
ResponseEntity<Object> response = controller.executeSearch(resourceInformation, parameters, "firstname", PAGEABLE,
|
||||
null, assembler);
|
||||
|
||||
ResourceTester tester = ResourceTester.of(response.getBody());
|
||||
PagedResources<Object> pagedResources = tester.assertIsPage();
|
||||
@@ -169,4 +176,21 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
|
||||
assertAllowHeaders(response, HttpMethod.GET);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-502
|
||||
*/
|
||||
@Test
|
||||
public void interpretsUriAsReferenceToRelatedEntity() {
|
||||
|
||||
MultiValueMap<String, Object> parameters = new LinkedMultiValueMap<String, Object>(1);
|
||||
parameters.add("author", "/author/1");
|
||||
|
||||
RootResourceInformation resourceInformation = getResourceInformation(Book.class);
|
||||
|
||||
ResponseEntity<Object> result = controller.executeSearch(resourceInformation, parameters, "findByAuthorsContains",
|
||||
PAGEABLE, null, assembler);
|
||||
|
||||
assertThat(result.getBody(), is(instanceOf(Resources.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
@@ -138,8 +137,8 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio
|
||||
|
||||
assertThat(usersLink, is(notNullValue()));
|
||||
|
||||
mvc.perform(get(usersLink.getHref())).andDo(MockMvcResultHandlers.print())
|
||||
.andExpect(jsonPath("$.descriptors[?(@.id == 'item-representation')].href", is(notNullValue())));
|
||||
mvc.perform(get(usersLink.getHref())).//
|
||||
andExpect(jsonPath("$.descriptors[?(@.id == 'item-representation')].href", is(notNullValue())));
|
||||
}
|
||||
|
||||
private Link discoverUnique(String href, String rel) throws Exception {
|
||||
|
||||
@@ -18,7 +18,9 @@ package org.springframework.data.rest.webmvc.jpa;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
|
||||
@@ -30,4 +32,7 @@ public interface BookRepository extends CrudRepository<Book, Long> {
|
||||
|
||||
@RestResource(rel = "find-by-sorted")
|
||||
List<Book> findBy(Sort sort);
|
||||
|
||||
@Query("select b from Book b where :author member of b.authors")
|
||||
List<Book> findByAuthorsContains(@Param("author") Author author);
|
||||
}
|
||||
|
||||
@@ -132,8 +132,6 @@ public class PersistentEntityToJsonSchemaConverterUnitTests {
|
||||
|
||||
String writeSchemaFor = writeSchemaFor(type);
|
||||
|
||||
System.out.println(writeSchemaFor);
|
||||
|
||||
for (Constraint constraint : constraints) {
|
||||
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user