org.jasig.ssp.util.sort.SortingAndPaging.java Source code

Java tutorial

Introduction

Here is the source code for org.jasig.ssp.util.sort.SortingAndPaging.java

Source

/**
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you 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 the following location:
 *
 *   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.jasig.ssp.util.sort;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.StatelessSession;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.jasig.ssp.model.ObjectStatus;
import org.jasig.ssp.util.collections.Pair;

import com.google.common.collect.Lists;

/**
 * Encapsulate common filtering options for the DAO layer including
 * {@link ObjectStatus}, paging, and sorting.
 * 
 * <p>
 * Never allows more than {@link #MAXIMUM_ALLOWABLE_RESULTS} results in one
 * query. Use multiple requests with different firstResult settings to see more
 * results beyond the {@link #MAXIMUM_ALLOWABLE_RESULTS} setting.
 */
public final class SortingAndPaging { // NOPMD

    /**
     * The default maximum results if an upper limit was not placed when
     * creating the instance.
     */
    final public static Integer DEFAULT_MAXIMUM_RESULTS = 100;

    /**
     * The maximum allowable results
     */
    final public static Integer MAXIMUM_ALLOWABLE_RESULTS = 1000;

    final transient private ObjectStatus status;

    final private transient Integer firstResult;

    @JsonProperty
    final private transient Integer maxResults;

    @JsonProperty
    final private transient String defaultSortProperty;

    @JsonProperty
    final private transient SortDirection defaultSortDirection;

    @JsonProperty
    private transient List<Pair<String, SortDirection>> sortFields;

    /**
     * Construct a basic instance with only an {@link ObjectStatus} filter but
     * not paging or sorting filters.
     * 
     * <p>
     * Will automatically use {@link #DEFAULT_MAXIMUM_RESULTS} for the maximum
     * result setting.
     * 
     * @param status
     *            Object status filter
     */
    public SortingAndPaging(final ObjectStatus status) {
        this.status = status == null ? ObjectStatus.ACTIVE : status;
        firstResult = 0;
        maxResults = DEFAULT_MAXIMUM_RESULTS;
        defaultSortProperty = null;
        defaultSortDirection = null;
    }

    /**
     * Construct a full instance of these settings with a multi-column field
     * sort.
     * 
     * @param status
     *            Object status
     * @param firstResult
     *            First result to return (0-based)
     * @param maxResults
     *            Maximum total results to return.
     * 
     *            <p>
     *            Will use {@link #DEFAULT_MAXIMUM_RESULTS} if not specified
     *            here. Can not exceed {@link #MAXIMUM_ALLOWABLE_RESULTS}
     *            unless a value less than zero is specified.
     * @param sortFields
     *            Ordered list of sort fields and directions
     * @param defaultSortProperty
     *            A default sort property if the sort parameter is null.
     * @param defaultSortDirection
     *            A default sort direction if the sort parameter is null.
     */
    //@JsonCreator
    public SortingAndPaging(final ObjectStatus status, final Integer firstResult, final Integer maxResults,
            final List<Pair<String, SortDirection>> sortFields, final String defaultSortProperty,
            final SortDirection defaultSortDirection) {
        this.status = status == null ? ObjectStatus.ACTIVE : status;
        this.firstResult = (firstResult == null) || (firstResult < Integer.valueOf(0)) ? Integer.valueOf(0)
                : firstResult;
        if (maxResults == null || maxResults > MAXIMUM_ALLOWABLE_RESULTS) {
            this.maxResults = MAXIMUM_ALLOWABLE_RESULTS;
        } else if (maxResults == 0) {
            this.maxResults = DEFAULT_MAXIMUM_RESULTS;
        } else if (maxResults < 0) {
            this.maxResults = null; // inifinite. see isPaged()
        } else {
            this.maxResults = maxResults;
        }
        this.sortFields = sortFields;
        this.defaultSortProperty = defaultSortProperty;
        this.defaultSortDirection = defaultSortDirection;
    }

    /**
     * 
     * @param status
     * @param sortFields
     * @param defaultSortProperty
     * @param defaultSortDirection
     * 
     * Use this constructor if there is to be no paging.  firstResult and maxResults are set to null
     */

    public SortingAndPaging(final ObjectStatus status, final List<Pair<String, SortDirection>> sortFields,
            final String defaultSortProperty, final SortDirection defaultSortDirection) {
        this.status = status == null ? ObjectStatus.ACTIVE : status;
        this.firstResult = null;
        this.maxResults = null;
        this.sortFields = sortFields;
        this.defaultSortProperty = defaultSortProperty;
        this.defaultSortDirection = defaultSortDirection;
    }

    /**
     * Special {@code private} constructor for full-fidelity deserialization from JSON. Can't use existing
     * constructors because they either don't have enough fields or coerce values such that we lose fidelity, e.g.
     * a null {@code firstResult} is coerced to a default.
     *
     * <p>The extra 'foo' argument exists to avoid collisions with the other historical constructor which would
     * otherwise have exactly the same argument list.</p>
     *
     * @param status
     * @param firstResult
     * @param maxResults
     * @param sortFields
     * @param defaultSortProperty
     * @param defaultSortDirection
     * @param foo
     */
    @JsonCreator
    private SortingAndPaging(@JsonProperty("status") final ObjectStatus status,
            @JsonProperty("firstResult") final Integer firstResult,
            @JsonProperty("maxResults") final Integer maxResults,
            @JsonProperty("sortFields") final List<Pair<String, SortDirection>> sortFields,
            @JsonProperty("defaultSortProperty") final String defaultSortProperty,
            @JsonProperty("defaultSortDirection") final SortDirection defaultSortDirection,
            @JsonProperty("foo") final SortDirection foo) {
        this.status = status;
        this.firstResult = firstResult;
        this.maxResults = maxResults;
        this.sortFields = sortFields;
        this.defaultSortProperty = defaultSortProperty;
        this.defaultSortDirection = defaultSortDirection;
    }

    /**
     * Gets the object status
     * 
     * @return the object status
     */
    @JsonProperty
    public ObjectStatus getStatus() {
        return status;
    }

    /**
     * Gets the first result filter setting. 0-based index
     * 
     * @return the first result filter setting
     */
    @JsonProperty
    public Integer getFirstResult() {
        return firstResult;
    }

    /**
     * Gets the maximum results filter setting
     * 
     * @return the maximum results filter setting
     */
    @JsonProperty
    public Integer getMaxResults() {
        return maxResults;
    }

    /**
     * Gets the default sort property
     * 
     * @return the default sort property
     */
    @JsonProperty
    public String getDefaultSortProperty() {
        return defaultSortProperty;
    }

    /**
     * Gets the default sort direction
     * 
     * @return the default sort direction
     */
    @JsonProperty
    public SortDirection getDefaultSortDirection() {
        return defaultSortDirection;
    }

    /**
     * Gets the ordered list of sort fields, in a Pair with the field (property)
     * as the first argument and the sort direction as the second argument.
     * 
     * @return the ordered list of sort fields
     */
    @JsonProperty
    public List<Pair<String, SortDirection>> getSortFields() {
        return sortFields;
    }

    /**
     * True if the {@link ObjectStatus} filter is set to anything besides
     * {@link ObjectStatus#ALL}.
     * 
     * @return true if filtering by object status
     */
    @JsonIgnore
    public boolean isFilteredByStatus() {
        return (status != null && status != ObjectStatus.ALL);
    }

    /**
     * True if these filters include paging, determined by the use of a maximum
     * results and valid first result filters.
     * 
     * @return true if these filters include paging
     */
    @JsonIgnore
    public boolean isPaged() {
        return ((null != maxResults) && (maxResults > 0) && (null != firstResult) && (firstResult >= 0));
    }

    /**
     * True if these filters include any sorting fields, by determining if there
     * are any non-default, non-empty sort fields.
     * 
     * @return true if these filters include sorting fields
     */
    @JsonIgnore
    public boolean isSorted() {
        if ((sortFields == null) || sortFields.isEmpty()) {
            return false;
        } else {
            final Pair<String, SortDirection> entry = sortFields.iterator().next();
            return !StringUtils.isEmpty(entry.getFirst());
        }
    }

    /**
     * True if a default sort property is available when no sort fields are
     * explicitly defined.
     * 
     * @return true if a default sort property is available
     */
    @JsonIgnore
    public boolean isDefaultSorted() {
        return (null != defaultSortProperty) && !StringUtils.isEmpty(defaultSortProperty);
    }

    /**
     * Add the current object status filter to the specified criteria.
     * 
     * @param criteria
     *            Object status filter will be added to this criteria
     */
    public void addStatusFilterToCriteria(final Criteria criteria) {
        if (isFilteredByStatus()) {
            criteria.add(Restrictions.eq("objectStatus", status));
        }
    }

    public StringBuilder addStatusFilterToQuery(final StringBuilder query, String objectName,
            Boolean isInitialRestriction) {
        if (isFilteredByStatus()) {
            if (isInitialRestriction == null || isInitialRestriction.equals(true)) {
                query.append(" where ");
            } else {
                query.append(" and ");
            }
            query.append(objectName);
            query.append(".objectStatus = :objectStatus");
        }
        return query;
    }

    /**
     * Add the current paging filter to the specified criteria.
     * 
     * @param criteria
     *            Paging filter will be added to this criteria
     */
    public void addPagingToCriteria(final Criteria criteria) {
        if (isPaged()) {
            criteria.setFirstResult(firstResult);
            criteria.setMaxResults(maxResults);
        }
    }

    /**
     * Add the current paging filter to the specified criteria.
     * 
     * @param criteria
     *            Paging filter will be added to this criteria
     */
    public Query addPagingToQuery(final Query query) {
        if (isPaged()) {
            query.setFirstResult(firstResult);
            query.setMaxResults(maxResults);
        }
        return query;
    }

    public Pair<Long, Query> applySortingAndPagingToPagedQuery(Object session, final String countColumn,
            final String hqlSelectClause, final StringBuilder hqlWithoutSelect, final boolean filterByStatus,
            String objectToAddStatusFilter, Boolean isInitialRestriction, Map<String, Object> bindParams) {

        if (filterByStatus && StringUtils.isNotBlank(objectToAddStatusFilter)) {
            addStatusFilterToQuery(hqlWithoutSelect, objectToAddStatusFilter, isInitialRestriction);
            bindParams.put("objectStatus", getStatus());
        }

        // When using HQL, subqueries can only occur in the select and the where, not in the from.
        // So we have the client explicitly tell us where the select clause ends and the from+where
        // clause begins so we can unambiguously execute the latter twice, once with our count()
        // function and once for the "real" results. (Parsing to find where the from clause starts is
        // fraught. E.g. see https://issues.jasig.org/browse/SSP-2192 and related tickets.)
        final StringBuilder rowCntHql = new StringBuilder("select ");
        if (StringUtils.isBlank(countColumn)) {
            rowCntHql.append("count(*) ");
        } else {
            rowCntHql.append("count(distinct ").append(countColumn).append(") ");
        }
        rowCntHql.append(hqlWithoutSelect);

        Query fullQuery = null;
        Query rowCntQuery = null;
        final StringBuilder fullHql = new StringBuilder(hqlSelectClause)
                .append(addSortingToQuery(hqlWithoutSelect));
        if (session instanceof Session) {
            Session thisSession = (Session) session;
            fullQuery = addPagingToQuery(thisSession.createQuery(fullHql.toString())).setProperties(bindParams);
            fullQuery = postProcessBindParams(fullQuery, bindParams);
            rowCntQuery = thisSession.createQuery(rowCntHql.toString());
        } else if (session instanceof StatelessSession) {
            StatelessSession thisStatelessSession = (StatelessSession) session;
            fullQuery = addPagingToQuery(thisStatelessSession.createQuery(fullHql.toString()))
                    .setProperties(bindParams);
            fullQuery = postProcessBindParams(fullQuery, bindParams);
            rowCntQuery = thisStatelessSession.createQuery(rowCntHql.toString());
        } else {
            throw new IllegalArgumentException(
                    "session paramter for org.jasig.ssp.util.sort.SortingAndPaging.applySortingAndPagingToPagedQuery(Object, StringBuilder, boolean, String, Boolean, Map<String, Object>) must "
                            + "must be of type Session or StatelessSession");
        }

        rowCntQuery.setProperties(bindParams);
        rowCntQuery = postProcessBindParams(rowCntQuery, bindParams);
        final Long totalRows = (Long) rowCntQuery.list().get(0);

        // Sorting not added until here b/c if it's present in the count() query
        // above, the db will usually complain about that field not being
        // present in a group by/aggr function
        return new Pair<Long, Query>(totalRows, fullQuery);
    }

    /**
     * Workaround for <a href="https://issues.jasig.org/browse/SSP-2981">SSP-2981</a> /
     * <a href="https://hibernate.atlassian.net/browse/HHH-7705">HHH-7705</a>.
     *
     * @param query
     * @param bindParams
     * @return
     */
    private Query postProcessBindParams(Query query, Map<String, Object> bindParams) {
        if (bindParams == null || bindParams.isEmpty()) {
            return query;
        }
        for (Map.Entry<String, Object> entry : bindParams.entrySet()) {
            if (entry.getValue() == null) {
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }
        return query;
    }

    public Long applySortingAndPagingToPagedQuery(final Criteria query, final boolean filterByStatus) {

        if (filterByStatus) {
            addStatusFilterToCriteria(query);
        }

        Long totalRows = null;
        // Only query for total count if query is paged or filtered
        if (isPaged() || (filterByStatus && isFilteredByStatus())) {
            totalRows = (Long) query.setProjection(Projections.rowCount()).uniqueResult();

            // clear the count projection from the query
            query.setProjection(null);
        }

        // Add Sorting and Paging
        addPagingToCriteria(query);
        addSortingToCriteria(query);

        return totalRows;
    }

    /**
     * Add the current sorting filter to the specified criteria.
     * 
     * @param criteria
     *            Paging filter will be added to this criteria
     */
    public void addSortingToCriteria(final Criteria criteria) {
        if (isSorted()) {
            // sort by each entry in the map
            for (final Pair<String, SortDirection> entry : sortFields) {
                addSortToCriteria(criteria, entry.getFirst(), entry.getSecond());
            }
        } else if (isDefaultSorted()) {
            // sort by the default property
            addSortToCriteria(criteria, defaultSortProperty, defaultSortDirection);
        }
    }

    /**
     * Add the current sorting filter to the specified criteria.
     * 
     * @param criteria
     *            Paging filter will be added to this criteria
     */
    public StringBuilder addSortingToQuery(final StringBuilder query) {
        if (isSorted()) {
            query.append(" Order By ");
            String seperator = "";
            for (final Pair<String, SortDirection> entry : sortFields) {
                query.append(seperator);
                addSortToQuery(query, entry.getFirst(), entry.getSecond());
                seperator = ", ";
            }
        } else if (isDefaultSorted()) {
            query.append(" Order By ");
            addSortToQuery(query, defaultSortProperty, defaultSortDirection);
        }
        return query;
    }

    public List<Object> sortAndPageList(List<Object> list)
            throws NoSuchFieldException, SecurityException, ClassNotFoundException {
        final List<Object> sortedList = sortList(list);
        return pageList(sortedList);
    }

    private List<Object> pageList(final List<Object> list) {
        if (isPaged()) {
            List<Object> uniques;
            Integer start = getFirstResult();
            Integer max = getMaxResults();
            if (start >= list.size()) {
                uniques = new ArrayList<Object>();
            } else {
                max = start + max > list.size() || max < 0 ? list.size() : start + max;
                uniques = list.subList(start, max);
            }
            return uniques;
        }
        return list;
    }

    @SuppressWarnings("unchecked")
    private List<Object> sortList(List<Object> results)
            throws NoSuchFieldException, SecurityException, ClassNotFoundException {
        if (results == null || results.isEmpty())
            return results;

        if (isSorted()) {
            Collections.sort(results, new GenericComparator<Object>(results.get(0).getClass(), getSortFields()));
        } else if (isDefaultSorted()) {
            Collections.sort(results, new GenericComparator<Object>(results.get(0).getClass(),
                    Arrays.asList(new Pair<String, SortDirection>(getDefaultSortProperty(), SortDirection.ASC))));
        }
        return results;
    }

    /**
     * Add all the current filters to the specified criteria
     * 
     * @param criteria
     *            All the current filters will be added to this criteria
     */
    public void addAll(final Criteria criteria) {
        addPagingToCriteria(criteria);
        addSortingToCriteria(criteria);
        addStatusFilterToCriteria(criteria);
    }

    private void addSortToCriteria(final Criteria criteria, final String sort, final SortDirection sortDirection) {
        if (sortDirection.equals(SortDirection.ASC)) {
            criteria.addOrder(Order.asc(sort));
        } else {
            criteria.addOrder(Order.desc(sort));
        }
    }

    private void addSortToQuery(final StringBuilder query, final String sort, final SortDirection sortDirection) {
        if (sortDirection.equals(SortDirection.ASC)) {
            query.append(sort);
            query.append(" asc ");
        } else {
            query.append(sort);
            query.append(" desc ");
        }
    }

    public static class GenericComparator<T> implements Comparator<T> {

        private List<Pair<Field, SortDirection>> sorters = new ArrayList<Pair<Field, SortDirection>>();

        public GenericComparator(Class listObjClass, List<Pair<String, SortDirection>> sorters)
                throws NoSuchFieldException, SecurityException, ClassNotFoundException {
            setSorters(Class.forName(listObjClass.getName()), sorters);
        }

        private void setSorters(Class cls, List<Pair<String, SortDirection>> sorters) {
            for (Pair<String, SortDirection> sorter : sorters) {
                Field field = getField(cls, sorter.getFirst());
                if (field != null)
                    this.sorters.add(new Pair<Field, SortDirection>(field, sorter.getSecond()));
            }
        }

        private Field getField(Class cls, String propertyName) {
            Field field = null;
            while (field == null && cls != null) {
                try {
                    field = cls.getDeclaredField(propertyName);
                } catch (Exception exp) {

                }
                if (field == null) {
                    cls = cls.getSuperclass();
                } else {
                    field.setAccessible(true);
                    return field;
                }
            }
            return null;
        }

        @Override
        public int compare(T o1, T o2) {
            int compared = 0;
            for (Pair<Field, SortDirection> sorter : sorters) {
                Comparable<Object> prop1 = getComparableProperty(o1, sorter.getFirst());
                Comparable<Object> prop2 = getComparableProperty(o2, sorter.getFirst());

                if (prop1 == null && prop2 != null) {
                    if (sorter.getSecond().equals(SortDirection.ASC))
                        return -1;
                    else
                        return 1;
                }

                if (prop2 == null && prop1 != null) {
                    if (sorter.getSecond().equals(SortDirection.ASC))
                        return 1;
                    else
                        return -1;
                }

                if (prop2 == null && prop1 == null) {
                    return 0;
                }

                if (sorter.getSecond().equals(SortDirection.ASC)) {
                    compared = prop1.compareTo(prop2);
                } else {
                    compared = prop2.compareTo(prop1);
                }
                if (compared != 0)
                    return compared;
            }
            return compared;
        }

        private Comparable<Object> getComparableProperty(T obj, Field field) {
            try {
                return (Comparable) field.get(obj);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }

    }

    /**
     * Append a sort field to the end of the current sort order list.
     * 
     * @param fieldname
     *            Field (property) name
     * @param direction
     *            Sort direction
     */
    public void appendSortField(final String fieldname, final SortDirection direction) {
        if (sortFields == null) {
            sortFields = new ArrayList<Pair<String, SortDirection>>();
        }

        sortFields.add(new Pair<String, SortDirection>(fieldname, direction));
    }

    /**
     * Prepend (insert) a sort field to the beginning of the current sort order
     * list.
     * 
     * @param fieldname
     *            Field (property) name
     * @param direction
     *            Sort direction
     */
    public void prependSortField(final String fieldname, final SortDirection direction) {
        if (sortFields == null) {
            sortFields = new ArrayList<Pair<String, SortDirection>>();
            sortFields.add(new Pair<String, SortDirection>(fieldname, direction));
        } else {
            final List<Pair<String, SortDirection>> newOrdering = Lists.newArrayList();
            newOrdering.add(new Pair<String, SortDirection>(fieldname, direction));
            newOrdering.addAll(sortFields);
            sortFields = newOrdering;
        }
    }

    /**
     * Construct a full instance of these settings with a single field sort.
     * 
     * @param status
     *            Object status
     * @param firstResult
     *            First result to return (0-based)
     * @param maxResults
     *            Maximum total results to return.
     * 
     *            <p>
     *            Will use {@link #DEFAULT_MAXIMUM_RESULTS} if not specified
     *            here. Can not exceed {@link #MAXIMUM_ALLOWABLE_RESULTS}.
     * @param sort
     *            Sort field (property)
     * @param sortDirection
     *            Sort direction
     * @param defaultSortProperty
     *            A default sort property if the sort parameter is null.
     * @return An instance with the specified filters.
     */
    public static SortingAndPaging createForSingleSortWithPaging(final ObjectStatus status,
            final Integer firstResult, final Integer maxResults, final String sort, final String sortDirection,
            final String defaultSortProperty) {

        List<Pair<String, SortDirection>> sortFields;
        SortDirection defaultSortDirection;

        // use sort parameter if available, otherwise use the default
        if (sort == null) {
            sortFields = null; // NOPMD
            defaultSortDirection = SortDirection.getSortDirection(sortDirection);
        } else {
            sortFields = Lists.newArrayList();
            sortFields.add(new Pair<String, SortDirection>(sort, SortDirection.getSortDirection(sortDirection)));
            defaultSortDirection = null; // NOPMD
        }

        final SortingAndPaging sAndP = new SortingAndPaging(status == null ? ObjectStatus.ACTIVE : status,
                firstResult, maxResults, sortFields, defaultSortProperty, defaultSortDirection);

        return sAndP;
    }

    /**
     * Construct a full instance of these settings with a single field sort.
     * 
     * @param status
     *            Object status
     * @param firstResult
     *            First result to return (0-based)
     * @param maxResults
     *            Maximum total results to return.
     * 
     *            <p>
     *            Will use {@link #DEFAULT_MAXIMUM_RESULTS} if not specified
     *            here. Can not exceed {@link #MAXIMUM_ALLOWABLE_RESULTS}.
     * @param sort
     *            Sort field (property)
     * @param sortDirection
     *            Sort direction
     * @param defaultSortProperty
     *            A default sort property if the sort parameter is null.
     * @return An instance with the specified filters.
     */
    public static SortingAndPaging createForSingleSortAll(final ObjectStatus status, final String sortField,
            final String sortDirection) {

        List<Pair<String, SortDirection>> sortFields;
        SortDirection defaultSortDirection;

        // use sort parameter if available, otherwise use the default
        if (sortField == null) {
            sortFields = null; // NOPMD
            defaultSortDirection = SortDirection.getSortDirection(sortDirection);
        } else {
            sortFields = Lists.newArrayList();
            sortFields
                    .add(new Pair<String, SortDirection>(sortField, SortDirection.getSortDirection(sortDirection)));
            defaultSortDirection = null; // NOPMD
        }

        final SortingAndPaging sAndP = new SortingAndPaging(status == null ? ObjectStatus.ACTIVE : status,
                sortFields, null, defaultSortDirection);

        return sAndP;
    }

    public static SortingAndPaging allActive() {
        return SortingAndPaging.allActiveSorted(null);
    }

    public static SortingAndPaging allActiveSorted(String sortOn) {
        return SortingAndPaging.createForSingleSortWithPaging(ObjectStatus.ACTIVE, 0, -1, sortOn, null, null);
    }

}