All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.synyx.hades.extensions.web.PageableArgumentResolver Maven / Gradle / Ivy

/*
 * Copyright 2008-2010 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.synyx.hades.extensions.web;

import java.beans.PropertyEditorSupport;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.ServletRequest;

import org.springframework.beans.PropertyValue;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.MethodParameter;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.ServletRequestParameterPropertyValues;
import org.springframework.web.bind.support.WebArgumentResolver;
import org.springframework.web.context.request.NativeWebRequest;
import org.synyx.hades.domain.Order;
import org.synyx.hades.domain.PageRequest;
import org.synyx.hades.domain.Pageable;
import org.synyx.hades.domain.Sort;


/**
 * Extracts paging information from web requests and thus allows injecting
 * {@link Pageable} instances into controller methods. Request properties to be
 * parsed can be configured. Default configuration uses request properties
 * beginning with {@link #DEFAULT_PREFIX}{@link #DEFAULT_SEPARATOR}.
 * 
 * @author Oliver Gierke
 * @author Marc Kannegiesser
 */
public class PageableArgumentResolver implements WebArgumentResolver {

    private static final Pageable DEFAULT_PAGE_REQUEST = new PageRequest(0, 10);
    private static final String DEFAULT_PREFIX = "page";
    private static final String DEFAULT_SEPARATOR = ".";

    private Pageable fallbackPagable = DEFAULT_PAGE_REQUEST;
    private String prefix = DEFAULT_PREFIX;
    private String separator = DEFAULT_SEPARATOR;


    /**
     * Setter to configure a fallback instance of {@link Pageable} that is being
     * used to back missing parameters. Defaults to
     * {@value #DEFAULT_PAGE_REQUEST}.
     * 
     * @param fallbackPagable the fallbackPagable to set
     */
    public void setFallbackPagable(Pageable fallbackPagable) {

        this.fallbackPagable =
                null == fallbackPagable ? DEFAULT_PAGE_REQUEST
                        : fallbackPagable;
    }


    /**
     * Setter to configure the prefix of request parameters to be used to
     * retrieve paging information. Defaults to {@link #DEFAULT_PREFIX}.
     * 
     * @param prefix the prefix to set
     */
    public void setPrefix(String prefix) {

        this.prefix = null == prefix ? DEFAULT_PREFIX : prefix;
    }


    /**
     * Setter to configure the separator between prefix and actual property
     * value. Defaults to {@link #DEFAULT_SEPARATOR}.
     * 
     * @param separator the separator to set
     */
    public void setSeparator(String separator) {

        this.separator = null == separator ? DEFAULT_SEPARATOR : separator;
    }


    /*
     * (non-Javadoc)
     * 
     * @see
     * org.springframework.web.bind.support.WebArgumentResolver#resolveArgument
     * (org.springframework.core.MethodParameter,
     * org.springframework.web.context.request.NativeWebRequest)
     */
    public Object resolveArgument(MethodParameter methodParameter,
            NativeWebRequest webRequest) {

        if (methodParameter.getParameterType().equals(Pageable.class)) {

            assertPageableUniqueness(methodParameter);

            Pageable request =
                    getDefaultFromAnnotationOrFallback(methodParameter);

            ServletRequest servletRequest =
                    (ServletRequest) webRequest.getNativeRequest();

            PropertyValues propertyValues =
                    new ServletRequestParameterPropertyValues(servletRequest,
                            getPrefix(methodParameter), separator);

            DataBinder binder = new ServletRequestDataBinder(request);

            binder.initDirectFieldAccess();
            binder.registerCustomEditor(Sort.class, new SortPropertyEditor(
                    "sort.dir", propertyValues));
            binder.bind(propertyValues);

            if (request.getPageNumber() > 0) {

                request =
                        new PageRequest(request.getPageNumber() - 1,
                                request.getPageSize(), request.getSort());
            }

            return request;
        }

        return UNRESOLVED;
    }


    private Pageable getDefaultFromAnnotationOrFallback(
            MethodParameter methodParameter) {

        // search for PageableDefaults annotation
        for (Annotation annotation : methodParameter.getParameterAnnotations()) {
            if (annotation instanceof PageableDefaults) {
                PageableDefaults defaults = (PageableDefaults) annotation;
                // +1 is because we substract 1 later
                return new PageRequest(defaults.pageNumber() + 1,
                        defaults.value());
            }
        }

        // Construct request with fallback request to ensure sensible
        // default values. Create fresh copy as Spring will manipulate the
        // instance under the covers
        return new PageRequest(fallbackPagable.getPageNumber(),
                fallbackPagable.getPageSize(), fallbackPagable.getSort());
    }


    /**
     * Resolves the prefix to use to bind properties from. Will prepend a
     * possible {@link Qualifier} if available or return the configured prefix
     * otherwise.
     * 
     * @param parameter
     * @return
     */
    private String getPrefix(MethodParameter parameter) {

        for (Annotation annotation : parameter.getParameterAnnotations()) {
            if (annotation instanceof Qualifier) {
                return new StringBuilder(((Qualifier) annotation).value())
                        .append("_").append(prefix).toString();
            }
        }

        return prefix;
    }


    /**
     * Asserts uniqueness of all {@link Pageable} parameters of the method of
     * the given {@link MethodParameter}.
     * 
     * @param parameter
     */
    private void assertPageableUniqueness(MethodParameter parameter) {

        Method method = parameter.getMethod();

        if (containsMoreThanOnePageableParameter(method)) {
            Annotation[][] annotations = method.getParameterAnnotations();
            assertQualifiersFor(method.getParameterTypes(), annotations);
        }
    }


    /**
     * Returns whether the given {@link Method} has more than one
     * {@link Pageable} parameter.
     * 
     * @param method
     * @return
     */
    private boolean containsMoreThanOnePageableParameter(Method method) {

        boolean pageableFound = false;

        for (Class type : method.getParameterTypes()) {

            if (pageableFound && type.equals(Pageable.class)) {
                return true;
            }

            if (type.equals(Pageable.class)) {
                pageableFound = true;
            }
        }

        return false;
    }


    /**
     * Asserts that every {@link Pageable} parameter of the given parameters
     * carries an {@link Qualifier} annotation to distinguish them from each
     * other.
     * 
     * @param parameterTypes
     * @param annotations
     */
    private void assertQualifiersFor(Class[] parameterTypes,
            Annotation[][] annotations) {

        Set values = new HashSet();

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

            if (Pageable.class.equals(parameterTypes[i])) {

                Qualifier qualifier = findAnnotation(annotations[i]);

                if (null == qualifier) {
                    throw new IllegalStateException(
                            "Ambiguous Pageable arguments in handler method. If you use multiple parameters of type Pageable you need to qualify them with @Qualifier");
                }

                if (values.contains(qualifier.value())) {
                    throw new IllegalStateException(
                            "Values of the user Qualifiers must be unique!");
                }

                values.add(qualifier.value());
            }
        }
    }


    /**
     * Returns a {@link Qualifier} annotation from the given array of
     * {@link Annotation}s. Returns {@literal null} if the array does not
     * contain a {@link Qualifier} annotation.
     * 
     * @param annotations
     * @return
     */
    private Qualifier findAnnotation(Annotation[] annotations) {

        for (Annotation annotation : annotations) {
            if (annotation instanceof Qualifier) {
                return (Qualifier) annotation;
            }
        }

        return null;
    }

    /**
     * {@link java.beans.PropertyEditor} to create {@link Sort} instances from
     * textual representations. The implementation interprets the string as a
     * comma separated list where the first entry is the sort direction (
     * {@code asc}, {@code desc}) followed by the properties to sort by.
     * 
     * @author Oliver Gierke - [email protected]
     */
    private static class SortPropertyEditor extends PropertyEditorSupport {

        private final String orderProperty;
        private final PropertyValues values;


        /**
         * Creates a new {@link SortPropertyEditor}.
         * 
         * @param orderProperty
         * @param values
         */
        public SortPropertyEditor(String orderProperty, PropertyValues values) {

            this.orderProperty = orderProperty;
            this.values = values;
        }


        /*
         * (non-Javadoc)
         * 
         * @see java.beans.PropertyEditorSupport#setAsText(java.lang.String)
         */
        @Override
        public void setAsText(String text) throws IllegalArgumentException {

            PropertyValue rawOrder = values.getPropertyValue(orderProperty);
            Order order =
                    null == rawOrder ? Order.ASCENDING : Order
                            .fromJpaValue(rawOrder.getValue().toString());

            setValue(new Sort(order, text));
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy