Package org.springframework.data.rest.webmvc

Source Code of org.springframework.data.rest.webmvc.RepositorySearchController

/*
* 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.webmvc;

import static org.springframework.data.rest.webmvc.ControllerUtils.*;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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.rest.core.mapping.MethodResourceMapping;
import org.springframework.data.rest.core.mapping.ParameterMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.web.HateoasSortHandlerMethodArgumentResolver;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.hateoas.TemplateVariable;
import org.springframework.hateoas.TemplateVariable.VariableType;
import org.springframework.hateoas.TemplateVariables;
import org.springframework.hateoas.UriTemplate;
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.ClassUtils;
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.ResponseBody;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.util.UriComponentsBuilder;

/**
* Controller to lookup and execute searches on a given repository.
*
* @author Jon Brisbin
* @author Oliver Gierke
*/
@RepositoryRestController
class RepositorySearchController extends AbstractRepositoryRestController {

  private static final String SEARCH = "/search";
  private static final String BASE_MAPPING = "/{repository}" + SEARCH;

  private final EntityLinks entityLinks;
  private final ResourceMappings mappings;
  private final PagedResourcesAssembler<Object> assembler;
  private final HateoasSortHandlerMethodArgumentResolver sortResolver;

  /**
   * Creates a new {@link RepositorySearchController} using the given {@link PagedResourcesAssembler},
   * {@link EntityLinks} and {@link ResourceMappings}.
   *
   * @param assembler must not be {@literal null}.
   * @param entityLinks must not be {@literal null}.
   * @param mappings must not be {@literal null}.
   */
  @Autowired
  public RepositorySearchController(PagedResourcesAssembler<Object> assembler, EntityLinks entityLinks,
      ResourceMappings mappings, HateoasSortHandlerMethodArgumentResolver sortResolver) {

    super(assembler);

    Assert.notNull(entityLinks, "EntityLinks must not be null!");
    Assert.notNull(mappings, "ResourceMappings must not be null!");
    Assert.notNull(sortResolver, "HateoasSortHandlerMethodArgumentResolver must not be null!");

    this.entityLinks = entityLinks;
    this.mappings = mappings;
    this.assembler = assembler;
    this.sortResolver = sortResolver;
  }

  /**
   * <code>OPTIONS /{repository}/search</code>.
   *
   * @param resourceInformation
   * @return
   * @since 2.2
   */
  @RequestMapping(value = BASE_MAPPING, method = RequestMethod.OPTIONS)
  public HttpEntity<?> optionsForSearches(RootResourceInformation resourceInformation) {

    verifySearchesExposed(resourceInformation);

    HttpHeaders headers = new HttpHeaders();
    headers.setAllow(Collections.singleton(HttpMethod.GET));

    return new ResponseEntity<Object>(headers, HttpStatus.OK);
  }

  /**
   * <code>HEAD /{repository}/search</code> - Checks whether the search resource is present.
   *
   * @param resourceInformation
   * @return
   */
  @RequestMapping(value = BASE_MAPPING, method = RequestMethod.HEAD)
  public HttpEntity<?> headForSearches(RootResourceInformation resourceInformation) {

    verifySearchesExposed(resourceInformation);

    return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
  }

  /**
   * <code>GET /{repository}/search</code> - Exposes links to the individual search resources exposed by the backing
   * repository.
   *
   * @param resourceInformation
   * @return
   */
  @ResponseBody
  @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
  public ResourceSupport listSearches(RootResourceInformation resourceInformation) {

    verifySearchesExposed(resourceInformation);

    Links queryMethodLinks = getSearchLinks(resourceInformation.getDomainType());

    if (queryMethodLinks.isEmpty()) {
      throw new ResourceNotFoundException();
    }

    ResourceSupport result = new ResourceSupport();
    result.add(queryMethodLinks);

    return result;
  }

  /**
   * Executes the search with the given name.
   *
   * @param request
   * @param repository
   * @param search
   * @param pageable
   * @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) {

    Method method = checkExecutability(resourceInformation, search);
    Object resources = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable, sort, assembler);

    return new ResponseEntity<Object>(resources, HttpStatus.OK);
  }

  /**
   * Executes a query method and exposes the results in compact form.
   *
   * @param resourceInformation
   * @param repository
   * @param method
   * @param pageable
   * @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) {

    Method method = checkExecutability(resourceInformation, search);
    Object resource = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable, sort, assembler);

    List<Link> links = new ArrayList<Link>();

    if (resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {

      for (Object obj : ((Resources<?>) resource).getContent()) {
        if (null != obj && obj instanceof Resource) {
          Resource<?> res = (Resource<?>) obj;
          links.add(resourceLink(resourceInformation, res));
        }
      }

    } else if (resource instanceof Resource) {

      Resource<?> res = (Resource<?>) resource;
      links.add(resourceLink(resourceInformation, res));
    }

    return new Resources<Resource<?>>(EMPTY_RESOURCE_LIST, links);
  }

  /**
   * <code>OPTIONS /{repository}/search/{search}</code>.
   *
   * @param information
   * @param search
   * @return
   * @since 2.2
   */
  @RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.OPTIONS)
  public ResponseEntity<Object> optionsForSearch(RootResourceInformation information, @PathVariable String search) {

    checkExecutability(information, search);

    HttpHeaders headers = new HttpHeaders();
    headers.setAllow(Collections.singleton(HttpMethod.GET));

    return new ResponseEntity<Object>(headers, HttpStatus.OK);
  }

  /**
   * Handles a {@code HEAD} request for individual searches.
   *
   * @param information
   * @param search
   * @return
   * @since 2.2
   */
  @RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.HEAD)
  public ResponseEntity<Object> headForSearch(RootResourceInformation information, @PathVariable String search) {

    checkExecutability(information, search);
    return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
  }

  /**
   * Checks that the given request is actually executable. Will reject execution if we don't find a search with the
   * given name.
   *
   * @param resourceInformation
   * @param searchName
   * @return
   */
  private Method checkExecutability(RootResourceInformation resourceInformation, String searchName) {

    SearchResourceMappings searchMapping = verifySearchesExposed(resourceInformation);

    Method method = searchMapping.getMappedMethod(searchName);

    if (method == null) {
      throw new ResourceNotFoundException();
    }

    return method;
  }

  /**
   * @param invoker
   * @param request
   * @param method
   * @param pageable
   * @return
   */
  private Object executeQueryMethod(final RepositoryInvoker invoker, WebRequest request, Method method,
      DefaultedPageable pageable, Sort sort, PersistentEntityResourceAssembler assembler) {

    Map<String, String[]> parameters = request.getParameterMap();
    Object result = invoker.invokeQueryMethod(method, parameters, pageable.getPageable(), sort);

    if (ClassUtils.isPrimitiveOrWrapper(method.getReturnType())) {
      return result;
    }

    return resultToResources(result, assembler, null);
  }

  /**
   * Returns {@link Links} to the individual searches exposed.
   *
   * @param domainType the domain type we want to obtain the search links for.
   * @return
   */
  private Links getSearchLinks(Class<?> domainType) {

    List<Link> links = new ArrayList<Link>();

    SearchResourceMappings searchMappings = mappings.getSearchResourceMappings(domainType);
    LinkBuilder builder = entityLinks.linkFor(domainType).slash(searchMappings.getPath());

    for (MethodResourceMapping mapping : searchMappings) {

      if (!mapping.isExported()) {
        continue;
      }

      TemplateVariables variables = new TemplateVariables();

      for (ParameterMetadata metadata : mapping.getParametersMetadata()) {
        variables = variables.concat(new TemplateVariable(metadata.getName(), VariableType.REQUEST_PARAM));
      }

      String href = builder.slash(mapping.getPath()).toString().concat(variables.toString());

      Link link = new Link(href, mapping.getRel());

      if (mapping.isPagingResource()) {
        link = assembler.appendPaginationParameterTemplates(link);
      } else if (mapping.isSortableResource()) {

        TemplateVariables sortVariable = sortResolver.getSortTemplateVariables(null, UriComponentsBuilder
            .fromUriString(link.expand().getHref()).build());
        link = new Link(new UriTemplate(link.getHref()).with(sortVariable), link.getRel());
      }

      links.add(link);
    }

    return new Links(links);
  }

  /**
   * Verifies that the given {@link RootResourceInformation} has searches exposed.
   *
   * @param resourceInformation
   */
  private SearchResourceMappings verifySearchesExposed(RootResourceInformation resourceInformation) {

    SearchResourceMappings resourceMappings = resourceInformation.getSearchMappings();

    if (!resourceMappings.isExported()) {
      throw new ResourceNotFoundException();
    }

    return resourceMappings;
  }
}
TOP

Related Classes of org.springframework.data.rest.webmvc.RepositorySearchController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.