/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation, and/or the GNU Lesser
* General Public License, version 2.1, also as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.rhq.core.domain.criteria;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.util.CriteriaUtils;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageOrdering;
/**
* @author Joseph Marques
*/
@XmlAccessorType(XmlAccessType.FIELD)
public abstract class Criteria implements Serializable, BaseCriteria {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
public enum Type {
FILTER(new String[] { "filterId", "filterIds" }), FETCH(), SORT(new String[] { "sortId" });
private List<String> globalFields;
/**
* Use this to get the global fields for this Criteria field type. Don't use inspection as the field names
* for this abstract base class do not conform (for legacy reasons) to the prefix convention help by the
* subclasses.
*
* @return The set of global fields for this Criteria field type. Meaning, usable by all subclasses.
*/
public List<String> getGlobalFields() {
return globalFields;
}
private Type() {
this.globalFields = new ArrayList<String>(0);
}
private Type(String[] globalFields) {
this.globalFields = Arrays.asList(globalFields);
}
}
/**
* This is the type of a filter value when the override for that filter does not
* define any query parameter. ON means the filter is enabled and will take effect,
* OFF means the filter will not be used in the query.
* Example, from AlertDefinitionCriteria:
* private NonBindingOverrideFilter filterResourceOnly; // requires overrides - finds only those associated with a resource
* ...
* filterOverrides.put("resourceTypeOnly", "resourceType IS NOT NULL"); // notice no ? parameter
*
* Note: Typically a null value is analogous to OFF.
*/
public enum NonBindingOverrideFilter {
ON, OFF
}
/**
* Apply a restriction to reduce the cost of the {@link Criteria}-based query generation and execution routines.
*/
public enum Restriction {
/**
* This returns an empty {@link org.rhq.core.domain.util.PageList} result
* whose {@link org.rhq.core.domain.util.PageList#getTotalSize()} method otherwise
* contains the correct value.
*/
COUNT_ONLY,
/**
* This will return the {@link org.rhq.core.domain.util.PageList} result
* whose {@link org.rhq.core.domain.util.PageList#isUnbounded()} returned true, meaning
* that the value contained within {@link org.rhq.core.domain.util.PageList#getTotalSize()} is invalid/undefined.
*/
COLLECTION_ONLY
}
private static final long serialVersionUID = 2L;
private Integer pageNumber;
private Integer pageSize;
private boolean filtersOptional;
private boolean caseSensitive;
private String[] caseSensitiveFilters;
private List<Permission> requiredPermissions;
private boolean strict;
private String[] strictFilters;
private Restriction restriction = null;
private boolean supportsAddSortId = true;
protected Map<String, String> filterOverrides;
protected Map<String, String> sortOverrides;
protected PageControl pageControlOverrides;
private List<String> orderingFieldNames;
private String alias;
private String searchExpression;
// All Criteria support sorting on ID
protected PageOrdering sortId;
// All Criteria support filtering on ID
protected Integer filterId;
// All Criteria support filtering on IDs
protected List<Integer> filterIds;
/**
* This default constructor will set default paging to avoid unintended fetch of huge results. The default is:
* <pre>setPaging(0, 200);</pre>
*/
public Criteria() {
this.filterOverrides = new HashMap<String, String>();
this.filterOverrides.put("ids", "id IN ( ? )");
this.sortOverrides = new HashMap<String, String>();
this.orderingFieldNames = new ArrayList<String>();
/*
* reasonably large default, but prevent accidentally returning 100K objects
* unless you use the setPaging method to explicit denote you want that many
*/
setPaging(0, 200);
}
public abstract Class<?> getPersistentClass();
public Integer getPageNumber() {
return pageNumber;
}
public Integer getPageSize() {
return pageSize;
}
@Override
public List<String> getOrderingFieldNames() {
return orderingFieldNames;
}
public String getJPQLFilterOverride(String fieldName) {
return filterOverrides.get(fieldName);
}
public String getJPQLSortOverride(String fieldName) {
return sortOverrides.get(fieldName);
}
@Override
public PageControl getPageControlOverrides() {
return pageControlOverrides;
}
@Override
public void addSortId(PageOrdering sortId) {
if (isSupportsAddSortId()) {
addSortField("id");
this.sortId = sortId;
} else {
throw new UnsupportedOperationException("ID sort is not supported by supported by this class");
}
}
public void addFilterId(Integer filterId) {
if (isSupportsAddFilterId()) {
this.filterId = filterId;
} else {
throw new UnsupportedOperationException("ID filter is not supported by this class");
}
}
public void addFilterIds(Integer... filterIds) {
if (isSupportsAddFilterIds()) {
this.filterIds = CriteriaUtils.getListIgnoringNulls(filterIds);
} else {
throw new UnsupportedOperationException("IDS filter is not supported by this class");
}
}
/**
* By default all Criteria support sort on ID. And this sort is applied implicitly to criteria
* queries involving paging, to ensure consistent ordering of query results. If for some unlikely reason
* the caller needs to disable the implicit ID sort then call this, setting the value to false.
*
* @param supportsAddSortId
*/
public void setSupportsAddSortId(boolean supportsAddSortId) {
this.supportsAddSortId = supportsAddSortId;
}
public boolean isSupportsAddSortId() {
return supportsAddSortId;
}
public boolean isSupportsAddFilterId() {
return true;
}
public boolean isSupportsAddFilterIds() {
return true;
}
protected void addSortField(String fieldName) {
orderingFieldNames.add("sort" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1));
}
/**
* Sets the paging constraints to return items [pageNumber * pageSize , ((pageNumber+1) * pageSize) -1 ].
* For this to work correctly, you also need to set the sort
* criteria (do not rely on implicit id-sorting to work correctly.
* @param pageNumber The page to fetch. This is 0-based.
* @param pageSize The number of items to return.
*/
@Override
public void setPaging(int pageNumber, int pageSize) {
this.pageNumber = pageNumber;
this.pageSize = pageSize;
}
/**
* If the pageControl is set, then this criteria object will completely ignore any
* calls made to setPaging(pageNumber, pageSize) as well as addSortField(fieldName),
* which is useful from a server-side calling context where the PageControl object
* will already have been created for you by the extensions at the JSF layer.
*/
@Override
public void setPageControl(PageControl pageControl) {
this.pageControlOverrides = pageControl;
}
/**
* By default, the ordering fields are automatically prepend with the alias of entity that this criteria object
* wraps. However, some authors of criteria objects want full control of this alias during sort operations. if
* this method returns true, then the alias will not be prepend to the generated "order by" clause, which makes
* author responsible for constructing the fully-qualified ordering token for each sort override.
*/
public boolean hasCustomizedSorting() {
return false;
}
public void clearPaging() {
PageControl unlimited = PageControl.getUnlimitedInstance();
this.pageNumber = unlimited.getPageNumber();
this.pageSize = unlimited.getPageSize();
this.pageControlOverrides = null;
}
/**
* If set to true, then results will come back if they match ANY filter;
* Default is 'false', which means results must match all set filters.
*/
public void setFiltersOptional(boolean filtersOptional) {
this.filtersOptional = filtersOptional;
}
public boolean isFiltersOptional() {
return filtersOptional;
}
/**
* If set to true, string-based filters will use case-sensitive matching;
* Default is 'false', which means results will match case-insensitively
*/
public void setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
public boolean isCaseSensitive() {
return this.caseSensitive;
}
/**
* Used when only a subset of active string-filters should use case-sensitive matching. Specify only the filter
* names that should use case-sensitive matching, all other string-filters will use case-insensitive matching.
* This setting is ignored if {@link #setCaseSensitive(boolean)} is set to 'true'. As an example, if you want to
* get every resource with uppercase "RHQ" in its name (case-sensitive, fuzzy match the resource name) and
* with "storage" in the description (case-insensitive, fuzzy match the description):
* <pre>
* resourceCriteria.addFilterName("RHQ");
* resourceCriteria.addFilterDescription("storage");
* resourceCriteria.setCaseSensitiveFilters("name");
* </pre>
*/
public void setCaseSensitiveFilters(String... caseSensitiveFilters) {
this.caseSensitiveFilters = caseSensitiveFilters;
}
public String[] getCaseSensitiveFilters() {
if (caseSensitiveFilters == null) {
return EMPTY_STRING_ARRAY;
}
return caseSensitiveFilters;
}
/**
* If set to true, string-based filters will use exact string matches;
* Default is 'false', which means we'll fuzzy match. If 'true' this applies to all string-based
* filters and overrides {@link #setStrictFilters(String...)}.
*/
@Override
public void setStrict(boolean strict) {
this.strict = strict;
}
@Override
public boolean isStrict() {
return this.strict;
}
/**
* Used when only a subset of active string-filters should use strict equality. Specify only the filter names
* that should use strict equality, all other string-filters will use fuzzy match. This setting is ignored
* if {@link #setStrict(boolean)} is set to 'true'. As an example, if you want to get every resource with
* "rhq" in its name (fuzzy match the resource name) but you want to ensure you only get resources associated
* with a single plugin, for which you have the exact name:
* <pre>
* resourceCriteria.addFilterName("rhq");
* resourceCriteria.addFilterPluginName(someExactPluginName);
* resourceCriteria.setStrictFilters("pluginName");
* </pre>
*
*/
public void setStrictFilters(String... strictFilters) {
this.strictFilters = strictFilters;
}
public String[] getStrictFilters() {
if (strictFilters == null) {
return EMPTY_STRING_ARRAY;
}
return strictFilters;
}
/**
* By default, two queries will be generated for this Criteria: one which fetches the requested page/subset of
* entity results, and one which fetches the total cardinality of the result set. If you wish to only retrieve one
* of those pieces of data, you can do so by setting a restriction on the query generation and execution routines.
*
* The restriction, once set, can be removed by passing NULL to this method.
*
* @see Restriction
*/
public void setRestriction(Restriction restriction) {
this.restriction = restriction;
}
public Restriction getRestriction() {
return this.restriction;
}
public void setSearchExpression(String searchExpression) {
this.searchExpression = searchExpression;
}
public String getSearchExpression() {
return this.searchExpression;
}
/** subclasses should override as necessary */
public boolean isInventoryManagerRequired() {
return false;
}
/** subclasses should override as necessary */
public boolean isSecurityManagerRequired() {
return false;
}
/**
* @return the permissions required by the user on any applicable objects. Typically resource permissions
* needed by the user on returned resources or resource related data.
*/
public List<Permission> getRequiredPermissions() {
return requiredPermissions;
}
/**
* @param requiredPermissions the permissions required by the user on any applicable objects.
* Typically resource permissions needed by the user on returned resources or resource related data.
*/
// TODO (ips): This should really be renamed setRequiredPermissions()...
public void addRequiredPermissions(Permission... requiredPermissions) {
this.requiredPermissions = Arrays.asList(requiredPermissions);
}
public String getAlias() {
if (this.alias == null) {
// Base alias on persistent class's name: org.rhq.core.domain.ResourceType -> "resourcetype"
// don't use getSimpleName - not available to GWT
String className = getPersistentClass().getName();
String classSimpleName = className.substring(className.lastIndexOf(".") + 1);
this.alias = classSimpleName.toLowerCase();
}
return this.alias;
}
/**
* Somewhat analogous to JPA's Query.getSingleResult. Wrap a CriteriaQuery result with this method when
* expecting a single result from the fetch. If the result set has only one entry it is returned. Otherwise
* a RuntimeException is thrown, indicating whether no results, or multiple results were found.
*
* @param result
* @return the single result
* @throws RuntimeException In not exactly one result is found. The message will include either the String
* "NoResultException" or "NonUniqueResultException", appropriately. The JPA exceptions are not used so that there
* is no dependency on a JPA implementation jar for the caller.
*/
public static <T> T getSingleResult(List<T> result) throws RuntimeException {
if (null == result || result.isEmpty()) {
throw new RuntimeException("NoResultException: Expected exactly one result but no result was found.");
}
if (1 != result.size()) {
throw new RuntimeException(
"NonUniqueResultException: Expected exactly one result but found multiple results: " + result);
}
return result.get(0);
}
}