/*
* The MIT License
*
* Copyright 2013 Jakub Jirutka <jakub@jirutka.cz>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package cz.jirutka.rsql.hibernate;
import cz.jirutka.rsql.parser.model.Comparison;
import org.hibernate.HibernateException;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Superclass of all Criterion Builders.
*
* @author Jakub Jirutka <jakub@jirutka.cz>
*/
public abstract class AbstractCriterionBuilder {
private static final Logger LOG = LoggerFactory.getLogger(AbstractCriterionBuilder.class);
public static final Character LIKE_WILDCARD = '*';
public static final String NULL_ARGUMENT = "NULL";
/////////////// ABSTRACT METHODS ///////////////
/**
* This method is called by Criteria Builder to determine if this builder
* can handle given comparison (constraint).
*
* @param property property name or path
* @param entityClass Class of entity that holds given property.
* @param parent Reference to the parent <tt>CriteriaBuilder</tt>.
* @return <tt>true</tt> if this builder can handle given property of entity
* class, otherwise <tt>false</tt>
*/
public abstract boolean accept(String property, Class<?> entityClass, CriteriaBuilder parent);
/**
* Create <tt>Criterion</tt> for given comparison (constraint).
*
* @param property property name or path
* @param operator comparison operator
* @param argument argument
* @param entityClass Class of entity that holds given property.
* @param alias Association alias (incl. dot) which must be used to prefix
* property name!
* @param parent Reference to the parent <tt>CriteriaBuilder</tt>.
* @return Criterion
* @throws ArgumentFormatException If given argument is not in suitable
* format required by entity's property, i.e. cannot be cast to
* property's type.
* @throws UnknownSelectorException If such property does not exist.
*/
public abstract Criterion createCriterion(String property, Comparison operator, String argument,
Class<?> entityClass, String alias, CriteriaBuilder parent)
throws ArgumentFormatException, UnknownSelectorException;
/////////////// TEMPLATE METHODS ///////////////
/**
* Delegate creating of a Criterion to an appropriate method according to
* operator.
*
* Property name MUST be prefixed with an association alias!
*
* @param propertyPath property name prefixed with an association alias
* @param operator comparison operator
* @param argument argument
* @return Criterion
*/
protected Criterion createCriterion(String propertyPath, Comparison operator, Object argument) {
LOG.trace("Creating criterion: {} {} {}",
new Object[]{propertyPath, operator, argument});
switch (operator) {
case EQUAL : {
if (containWildcard(argument)) {
return createLike(propertyPath, argument);
} else if (isNullArgument(argument)) {
return createIsNull(propertyPath);
} else {
return createEqual(propertyPath, argument);
}
}
case NOT_EQUAL : {
if (containWildcard(argument)) {
return createNotLike(propertyPath, argument);
} else if (isNullArgument(argument)) {
return createIsNotNull(propertyPath);
} else {
return createNotEqual(propertyPath, argument);
}
}
case GREATER_THAN : return createGreaterThan(propertyPath, argument);
case GREATER_EQUAL : return createGreaterEqual(propertyPath, argument);
case LESS_THAN : return createLessThan(propertyPath, argument);
case LESS_EQUAL : return createLessEqual(propertyPath, argument);
}
throw new IllegalArgumentException("Unknown operator: " + operator);
}
/**
* Apply an "equal" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createEqual(String propertyPath, Object argument) {
return Restrictions.eq(propertyPath, argument);
}
/**
* Apply a case-insensitive "like" constraint to the named property. Value
* should contains wildcards "*" (% in SQL) and "_".
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createLike(String propertyPath, Object argument) {
String like = (String)argument;
like = like.replace(LIKE_WILDCARD, '%');
return Restrictions.ilike(propertyPath, like);
}
/**
* Apply an "is null" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @return Criterion
*/
protected Criterion createIsNull(String propertyPath) {
return Restrictions.isNull(propertyPath);
}
/**
* Apply a "not equal" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createNotEqual(String propertyPath, Object argument) {
return Restrictions.ne(propertyPath, argument);
}
/**
* Apply a negative case-insensitive "like" constraint to the named property.
* Value should contains wildcards "*" (% in SQL) and "_".
*
* @param propertyPath property name prefixed with an association alias
* @param argument Value with wildcards.
* @return Criterion
*/
protected Criterion createNotLike(String propertyPath, Object argument) {
return Restrictions.not(createLike(propertyPath, argument));
}
/**
* Apply an "is not null" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @return Criterion
*/
protected Criterion createIsNotNull(String propertyPath) {
return Restrictions.isNotNull(propertyPath);
}
/**
* Apply a "greater than" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createGreaterThan(String propertyPath, Object argument) {
return Restrictions.gt(propertyPath, argument);
}
/**
* Apply a "greater than or equal" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createGreaterEqual(String propertyPath, Object argument) {
return Restrictions.ge(propertyPath, argument);
}
/**
* Apply a "less than" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createLessThan(String propertyPath, Object argument) {
return Restrictions.lt(propertyPath, argument);
}
/**
* Apply a "less than or equal" constraint to the named property.
*
* @param propertyPath property name prefixed with an association alias
* @param argument value
* @return Criterion
*/
protected Criterion createLessEqual(String propertyPath, Object argument) {
return Restrictions.le(propertyPath, argument);
}
/**
* Check if given argument contains wildcard.
*
* @param argument
* @return Return <tt>true</tt> if argument contains wildcard
* {@link #LIKE_WILDCHAR}.
*/
protected boolean containWildcard(Object argument) {
if (!(argument instanceof String)) {
return false;
}
String casted = (String) argument;
if (casted.contains(LIKE_WILDCARD.toString())) {
return true;
}
return false;
}
/**
* Check if entity of specified class metadata contains given property.
*
* @param property property name
* @param classMetadata entity metadata
* @return <tt>true</tt> if specified class metadata contains given property,
* otherwise <tt>false</tt>.
*/
protected boolean isPropertyName(String property, ClassMetadata classMetadata) {
String[] names = classMetadata.getPropertyNames();
for (String name : names) {
if (name.equals(property)) return true;
}
return false;
}
/**
* Find the java type class of given named property in entity's metadata.
*
* @param property property name
* @param classMetadata entity metadata
* @return The java type class of given property.
* @throws HibernateException If entity does not contain such property.
*/
protected Class<?> findPropertyType(String property, ClassMetadata classMetadata)
throws HibernateException {
return classMetadata.getPropertyType(property).getReturnedClass();
}
/**
* @param argument
* @return <tt>true</tt> if argument is null, <tt>false</tt> otherwise
*/
protected boolean isNullArgument(Object argument) {
return NULL_ARGUMENT.equals(argument);
}
}