Package org.springframework.data.web

Source Code of org.springframework.data.web.SortHandlerMethodArgumentResolver

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.web.SortDefault.SortDefaults;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
* {@link HandlerMethodArgumentResolver} to automatically create {@link Sort} instances from request parameters or
* {@link SortDefault} annotations.
*
* @since 1.6
* @author Oliver Gierke
* @author Thomas Darimont
* @author Nick Williams
*/
public class SortHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {

  private static final String DEFAULT_PARAMETER = "sort";
  private static final String DEFAULT_PROPERTY_DELIMITER = ",";
  private static final String DEFAULT_QUALIFIER_DELIMITER = "_";
  private static final Sort DEFAULT_SORT = null;

  private static final String SORT_DEFAULTS_NAME = SortDefaults.class.getSimpleName();
  private static final String SORT_DEFAULT_NAME = SortDefault.class.getSimpleName();

  private Sort fallbackSort = DEFAULT_SORT;
  private String sortParameter = DEFAULT_PARAMETER;
  private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;
  private String qualifierDelimiter = DEFAULT_QUALIFIER_DELIMITER;

  /**
   * Configure the request parameter to lookup sort information from. Defaults to {@code sort}.
   *
   * @param sortParameter must not be {@literal null} or empty.
   */
  public void setSortParameter(String sortParameter) {

    Assert.hasText(sortParameter);
    this.sortParameter = sortParameter;
  }

  /**
   * Configures the delimiter used to separate property references and the direction to be sorted by. Defaults to
   * {@code}, which means sort values look like this: {@code firstname,lastname,asc}.
   *
   * @param propertyDelimiter must not be {@literal null} or empty.
   */
  public void setPropertyDelimiter(String propertyDelimiter) {

    Assert.hasText(propertyDelimiter, "Property delimiter must not be null or empty!");
    this.propertyDelimiter = propertyDelimiter;
  }

  /**
   * Configures the delimiter used to separate the qualifier from the sort parameter. Defaults to {@code _}, so a
   * qualified sort property would look like {@code qualifier_sort}.
   *
   * @param qualifierDelimiter the qualifier delimiter to be used or {@literal null} to reset to the default.
   */
  public void setQualifierDelimiter(String qualifierDelimiter) {
    this.qualifierDelimiter = qualifierDelimiter == null ? DEFAULT_QUALIFIER_DELIMITER : qualifierDelimiter;
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
   */
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return Sort.class.equals(parameter.getParameterType());
  }

  /*
   * (non-Javadoc)
   * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
   */
  @Override
  public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {

    String[] directionParameter = webRequest.getParameterValues(getSortParameter(parameter));

    if (directionParameter != null && directionParameter.length != 0) {
      return parseParameterIntoSort(directionParameter, propertyDelimiter);
    } else {
      return getDefaultFromAnnotationOrFallback(parameter);
    }
  }

  /**
   * Reads the default {@link Sort} to be used from the given {@link MethodParameter}. Rejects the parameter if both an
   * {@link SortDefaults} and {@link SortDefault} annotation is found as we cannot build a reliable {@link Sort}
   * instance then (property ordering).
   *
   * @param parameter will never be {@literal null}.
   * @return the default {@link Sort} instance derived from the parameter annotations or the configured fallback-sort
   *         {@link #setFallbackSort(Sort)}.
   */
  private Sort getDefaultFromAnnotationOrFallback(MethodParameter parameter) {

    SortDefaults annotatedDefaults = parameter.getParameterAnnotation(SortDefaults.class);
    SortDefault annotatedDefault = parameter.getParameterAnnotation(SortDefault.class);

    if (annotatedDefault != null && annotatedDefaults != null) {
      throw new IllegalArgumentException(String.format(
          "Cannot use both @%s and @%s on parameter %s! Move %s into %s to define sorting order!", SORT_DEFAULTS_NAME,
          SORT_DEFAULT_NAME, parameter.toString(), SORT_DEFAULT_NAME, SORT_DEFAULTS_NAME));
    }

    if (annotatedDefault != null) {
      return appendOrCreateSortTo(annotatedDefault, null);
    }

    if (annotatedDefaults != null) {
      Sort sort = null;
      for (SortDefault currentAnnotatedDefault : annotatedDefaults.value()) {
        sort = appendOrCreateSortTo(currentAnnotatedDefault, sort);
      }
      return sort;
    }

    return fallbackSort;
  }

  /**
   * Creates a new {@link Sort} instance from the given {@link SortDefault} or appends it to the given {@link Sort}
   * instance if it's not {@literal null}.
   *
   * @param sortDefault
   * @param sortOrNull
   * @return
   */
  private Sort appendOrCreateSortTo(SortDefault sortDefault, Sort sortOrNull) {

    String[] fields = SpringDataAnnotationUtils.getSpecificPropertyOrDefaultFromValue(sortDefault, "sort");

    if (fields.length == 0) {
      return null;
    }

    Sort sort = new Sort(sortDefault.direction(), fields);
    return sortOrNull == null ? sort : sortOrNull.and(sort);
  }

  /**
   * Returns the sort parameter to be looked up from the request. Potentially applies qualifiers to it.
   *
   * @param parameter will never be {@literal null}.
   * @return
   */
  protected String getSortParameter(MethodParameter parameter) {

    StringBuilder builder = new StringBuilder();

    if (parameter != null && parameter.hasParameterAnnotation(Qualifier.class)) {
      builder.append(parameter.getParameterAnnotation(Qualifier.class).value()).append(qualifierDelimiter);
    }

    return builder.append(sortParameter).toString();
  }

  /**
   * Parses the given sort expressions into a {@link Sort} instance. The implementation expects the sources to be a
   * concatenation of Strings using the given delimiter. If the last element can be parsed into a {@link Direction} it's
   * considered a {@link Direction} and a simple property otherwise.
   *
   * @param source will never be {@literal null}.
   * @param delimiter the delimiter to be used to split up the source elements, will never be {@literal null}.
   * @return
   */
  Sort parseParameterIntoSort(String[] source, String delimiter) {

    List<Order> allOrders = new ArrayList<Sort.Order>();

    for (String part : source) {

      if (part == null) {
        continue;
      }

      String[] elements = part.split(delimiter);
      Direction direction = elements.length == 0 ? null : Direction.fromStringOrNull(elements[elements.length - 1]);

      for (int i = 0; i < elements.length; i++) {

        if (i == elements.length - 1 && direction != null) {
          continue;
        }

        String property = elements[i];

        if (!StringUtils.hasText(property)) {
          continue;
        }

        allOrders.add(new Order(direction, property));
      }
    }

    return allOrders.isEmpty() ? null : new Sort(allOrders);
  }

  /**
   * Folds the given {@link Sort} instance into a {@link List} of sort expressions, accumulating {@link Order} instances
   * of the same direction into a single expression if they are in order.
   *
   * @param sort must not be {@literal null}.
   * @return
   */
  protected List<String> foldIntoExpressions(Sort sort) {

    List<String> expressions = new ArrayList<String>();
    ExpressionBuilder builder = null;

    for (Order order : sort) {

      Direction direction = order.getDirection();

      if (builder == null) {
        builder = new ExpressionBuilder(direction);
      } else if (!builder.hasSameDirectionAs(order)) {
        builder.dumpExpressionIfPresentInto(expressions);
        builder = new ExpressionBuilder(direction);
      }

      builder.add(order.getProperty());
    }

    return builder == null ? Collections.<String> emptyList() : builder.dumpExpressionIfPresentInto(expressions);
  }

  /**
   * Folds the given {@link Sort} instance into two expressions. The first being the property list, the second being the
   * direction.
   *
   * @throws IllegalArgumentException if a {@link Sort} with multiple {@link Direction}s has been handed in.
   * @param sort must not be {@literal null}.
   * @return
   */
  protected List<String> legacyFoldExpressions(Sort sort) {

    List<String> expressions = new ArrayList<String>();
    ExpressionBuilder builder = null;

    for (Order order : sort) {

      Direction direction = order.getDirection();

      if (builder == null) {
        builder = new ExpressionBuilder(direction);
      } else if (!builder.hasSameDirectionAs(order)) {
        throw new IllegalArgumentException(String.format(
            "%s in legacy configuration only supports a single direction to sort by!", getClass().getSimpleName()));
      }

      builder.add(order.getProperty());
    }

    return builder == null ? Collections.<String> emptyList() : builder.dumpExpressionIfPresentInto(expressions);
  }

  /**
   * Helper to easily build request parameter expressions for {@link Sort} instances.
   *
   * @author Oliver Gierke
   */
  class ExpressionBuilder {

    private final List<String> elements = new ArrayList<String>();
    private final Direction direction;

    /**
     * Sets up a new {@link ExpressionBuilder} for properties to be sorted in the given {@link Direction}.
     *
     * @param direction must not be {@literal null}.
     */
    public ExpressionBuilder(Direction direction) {

      Assert.notNull(direction, "Direction must not be null!");
      this.direction = direction;
    }

    /**
     * Returns whether the given {@link Order} has the same direction as the current {@link ExpressionBuilder}.
     *
     * @param order must not be {@literal null}.
     * @return
     */
    public boolean hasSameDirectionAs(Order order) {
      return this.direction == order.getDirection();
    }

    /**
     * Adds the given property to the expression to be built.
     *
     * @param property
     */
    public void add(String property) {
      this.elements.add(property);
    }

    /**
     * Dumps the expression currently in build into the given {@link List} of {@link String}s. Will only dump it in case
     * there are properties piled up currently.
     *
     * @param expressions
     * @return
     */
    public List<String> dumpExpressionIfPresentInto(List<String> expressions) {

      if (elements.isEmpty()) {
        return expressions;
      }

      elements.add(direction.name().toLowerCase());
      expressions.add(StringUtils.collectionToDelimitedString(elements, propertyDelimiter));

      return expressions;
    }
  }

  /**
   * Configures the {@link Sort} to be used as fallback in case no {@link SortDefault} or {@link SortDefaults} (the
   * latter only supported in legacy mode) can be found at the method parameter to be resolved.
   * <p>
   * If you set this to {@literal null}, be aware that you controller methods will get {@literal null} handed into them
   * in case no {@link Sort} data can be found in the request.
   *
   * @param fallbackSort the {@link Sort} to be used as general fallback.
   */
  public void setFallbackSort(Sort fallbackSort) {
    this.fallbackSort = fallbackSort;
  }
}
TOP

Related Classes of org.springframework.data.web.SortHandlerMethodArgumentResolver

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.