/* * Copyright 2015 DiSiD Technologies S.L.L. All rights reserved. * * Project : DiSiD org.gvnix.web.datatables * SVN Id : $Id$ */ package org.gvnix.web.datatables.util.impl; import java.lang.reflect.Field; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.time.DateUtils; import org.gvnix.web.datatables.util.EntityManagerProvider; import org.gvnix.web.datatables.util.QuerydslUtilsBean; import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.TypeDescriptor; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Iterables; import com.mysema.query.BooleanBuilder; import com.mysema.query.types.Order; import com.mysema.query.types.OrderSpecifier; import com.mysema.query.types.Predicate; import com.mysema.query.types.expr.BooleanExpression; import com.mysema.query.types.path.DatePath; import com.mysema.query.types.path.NumberPath; import com.mysema.query.types.path.PathBuilder; public class QuerydslUtilsBeanImpl implements QuerydslUtilsBean { @Autowired private ConversionService conversionService; @Autowired private MessageSource messageSource; @Autowired private EntityManagerProvider entityManagerProvider; private static LoadingCache<Class<?>, BeanWrapper> beanWrappersCache = CacheBuilder .newBuilder().maximumSize(200) .build(new CacheLoader<Class<?>, BeanWrapper>() { public BeanWrapper load(Class<?> key) { return new BeanWrapperImpl(key); } }); /** * Get BeanWrapper instance for klass. <b>Warning<b>: BeanWrapper returned * is not Thread-safe!!! * * @param klass * @return */ private static BeanWrapper getBeanWrapper(Class<?> klass) { BeanWrapper beanWrapper; try { beanWrapper = beanWrappersCache.get(klass); } catch (ExecutionException e) { throw new RuntimeException(e); } return beanWrapper; } /** * {@inheritDoc} */ @Override public <T> BooleanBuilder createPredicateByAnd(PathBuilder<T> entity, Map<String, Object> searchArgs) { // Using BooleanBuilder, a cascading builder for // Predicate expressions BooleanBuilder predicate = new BooleanBuilder(); if (searchArgs == null || searchArgs.isEmpty()) { return predicate; } // Build the predicate for (Entry<String, Object> entry : searchArgs.entrySet()) { String key = entry.getKey(); // can // contain "_operator_" // entries for each // field Object valueToSearch = entry.getValue(); String operator = (String) searchArgs.get(OPERATOR_PREFIX .concat(key)); // If value to search is a collection, creates a predicate for // each object of the collection if (valueToSearch instanceof Collection) { @SuppressWarnings("unchecked") Collection<Object> valueColl = (Collection<Object>) valueToSearch; for (Object valueObj : valueColl) { predicate.and(createObjectExpression(entity, key, valueObj, operator)); } } else { predicate.and(createObjectExpression(entity, key, valueToSearch, operator)); } } return predicate; } /** * {@inheritDoc} */ @Override public <T, E> BooleanBuilder createPredicateByIn(PathBuilder<T> entity, String fieldName, Set<E> values) { // Using BooleanBuilder, a cascading builder for // Predicate expressions BooleanBuilder predicate = new BooleanBuilder(); if (StringUtils.isEmpty(fieldName) || values.isEmpty()) { return predicate; } // Build the predicate predicate.and(createCollectionExpression(entity, fieldName, values)); return predicate; } /** * {@inheritDoc} */ @Override public <T> Predicate createFilterExpression(PathBuilder<T> entityPath, String fieldName, Class<?> fieldType, String searchStr) { return createFilterExpression(entityPath, fieldName, searchStr); } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public <T> Predicate createFilterExpression(PathBuilder<T> entityPath, String fieldName, String searchStr) { TypeDescriptor descriptor = getTypeDescriptor(fieldName, entityPath); if (descriptor == null) { throw new IllegalArgumentException(String.format( "Can't found field '%s' on entity '%s'", fieldName, entityPath.getType())); } Class<?> fieldType = descriptor.getType(); // Check for field type in order to delegate in custom-by-type // create expression method if (String.class == fieldType) { return createStringExpressionWithOperators(entityPath, fieldName, searchStr); } else if (Boolean.class == fieldType || boolean.class == fieldType) { return createBooleanExpressionWithOperators(entityPath, fieldName, searchStr); } else if (Number.class.isAssignableFrom(fieldType) || NUMBER_PRIMITIVES.contains(fieldType)) { return createNumberExpressionGenericsWithOperators(entityPath, fieldName, descriptor, searchStr); } else if (Date.class.isAssignableFrom(fieldType) || Calendar.class.isAssignableFrom(fieldType)) { String datePattern = "dd/MM/yyyy"; if (messageSource != null) { datePattern = messageSource.getMessage( "global.filters.operations.date.pattern", null, LocaleContextHolder.getLocale()); } BooleanExpression expression = createDateExpressionWithOperators( entityPath, fieldName, (Class<Date>) fieldType, searchStr, datePattern); return expression; } else if (fieldType.isEnum()) { return createEnumExpression(entityPath, fieldName, searchStr, (Class<? extends Enum>) fieldType); } return null; } @Override public <T> Predicate createSearchExpression(PathBuilder<T> entityPath, String fieldName, Class<?> fieldType, String searchStr) { return createSearchExpression(entityPath, fieldName, searchStr); } @Override public <T> Predicate createSearchExpression(PathBuilder<T> entityPath, String fieldName, String searchStr) { TypeDescriptor descriptor = getTypeDescriptor(fieldName, entityPath); if (descriptor == null) { throw new IllegalArgumentException(String.format( "Can't found field '%s' on entity '%s'", fieldName, entityPath.getType())); } Class<?> fieldType = descriptor.getType(); // Check for field type in order to delegate in custom-by-type // create expression method if (String.class == fieldType) { return createStringLikeExpression(entityPath, fieldName, searchStr); } else if (Boolean.class == fieldType || boolean.class == fieldType) { return createBooleanExpression(entityPath, fieldName, searchStr); } else if (Number.class.isAssignableFrom(fieldType) || NUMBER_PRIMITIVES.contains(fieldType)) { return createNumberExpressionGenerics(entityPath, fieldName, fieldType, descriptor, searchStr); } else if (Date.class.isAssignableFrom(fieldType) || Calendar.class.isAssignableFrom(fieldType)) { BooleanExpression expression = createDateExpression(entityPath, fieldName, (Class<Date>) fieldType, searchStr); return expression; } else if (fieldType.isEnum()) { return createEnumExpression(entityPath, fieldName, searchStr, (Class<? extends Enum>) fieldType); } return null; } @Override @SuppressWarnings("unchecked") public <T> Predicate createNumberExpressionGenerics( PathBuilder<T> entityPath, String fieldName, Class<?> fieldType, TypeDescriptor descriptor, String searchStr) { Predicate numberExpression = null; if (isNumber(searchStr, descriptor)) { if (BigDecimal.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<BigDecimal>) fieldType, descriptor, searchStr); } if (BigInteger.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<BigInteger>) fieldType, descriptor, searchStr); } if (Byte.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Byte>) fieldType, descriptor, searchStr); } if (Double.class.isAssignableFrom(fieldType) || double.class == fieldType) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Double>) fieldType, descriptor, searchStr); } if (Float.class.isAssignableFrom(fieldType) || float.class == fieldType) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Float>) fieldType, descriptor, searchStr); } if (Integer.class.isAssignableFrom(fieldType) || int.class == fieldType) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Integer>) fieldType, descriptor, searchStr); } if (Long.class.isAssignableFrom(fieldType) || long.class == fieldType) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Long>) fieldType, descriptor, searchStr); } if (Short.class.isAssignableFrom(fieldType) || short.class == fieldType) { numberExpression = createNumberExpression(entityPath, fieldName, (Class<Short>) fieldType, descriptor, searchStr); } } return numberExpression; } @Override @SuppressWarnings("unchecked") public <T> Predicate createNumberExpressionGenericsWithOperators( PathBuilder<T> entityPath, String fieldName, TypeDescriptor descriptor, String searchStr) { Predicate numberExpression = null; Class<?> fieldType = descriptor.getType(); if (isNumber(searchStr, descriptor)) { if (BigDecimal.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<BigDecimal>) fieldType, descriptor, searchStr); } if (BigInteger.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<BigInteger>) fieldType, descriptor, searchStr); } if (Byte.class.isAssignableFrom(fieldType)) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Byte>) fieldType, descriptor, searchStr); } if (Double.class.isAssignableFrom(fieldType) || double.class == fieldType) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Double>) fieldType, descriptor, searchStr); } if (Float.class.isAssignableFrom(fieldType) || float.class == fieldType) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Float>) fieldType, descriptor, searchStr); } if (Integer.class.isAssignableFrom(fieldType) || int.class == fieldType) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Integer>) fieldType, descriptor, searchStr); } if (Long.class.isAssignableFrom(fieldType) || long.class == fieldType) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Long>) fieldType, descriptor, searchStr); } if (Short.class.isAssignableFrom(fieldType) || short.class == fieldType) { numberExpression = createNumberExpressionEqual(entityPath, fieldName, (Class<Short>) fieldType, descriptor, searchStr); } } else { // If is not a number, can be possible that exists a filter // expression. if (BigDecimal.class.isAssignableFrom(fieldType)) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<BigDecimal>) fieldType, descriptor, searchStr); } if (BigInteger.class.isAssignableFrom(fieldType)) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<BigInteger>) fieldType, descriptor, searchStr); } if (Byte.class.isAssignableFrom(fieldType)) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Byte>) fieldType, descriptor, searchStr); } if (Double.class.isAssignableFrom(fieldType) || double.class == fieldType) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Double>) fieldType, descriptor, searchStr); } if (Float.class.isAssignableFrom(fieldType) || float.class == fieldType) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Float>) fieldType, descriptor, searchStr); } if (Integer.class.isAssignableFrom(fieldType) || int.class == fieldType) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Integer>) fieldType, descriptor, searchStr); } if (Long.class.isAssignableFrom(fieldType) || long.class == fieldType) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Long>) fieldType, descriptor, searchStr); } if (Short.class.isAssignableFrom(fieldType) || short.class == fieldType) { numberExpression = getNumericFilterExpression(entityPath, fieldName, (Class<Short>) fieldType, descriptor, searchStr); } } return numberExpression; } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createObjectExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj) { return createObjectExpression(entityPath, fieldName, searchObj, null); } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "unchecked", "rawtypes" }) public <T> BooleanExpression createObjectExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj, String operator) { if (searchObj == null) { return null; } TypeDescriptor typeDescriptor = getTypeDescriptor(fieldName, entityPath); if (typeDescriptor == null) { throw new IllegalArgumentException(String.format( "Can't found field '%s' on entity '%s'", fieldName, entityPath.getType())); } if (StringUtils.isBlank(operator) || StringUtils.equalsIgnoreCase(operator, "eq")) { return entityPath.get(fieldName).eq(searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "in")) { return entityPath.get(fieldName).in(searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "ne")) { return entityPath.get(fieldName).ne(searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "notIn")) { return entityPath.get(fieldName).notIn(searchObj); } else if (StringUtils.equalsIgnoreCase(operator, OPERATOR_ISNULL)) { return entityPath.get(fieldName).isNull(); } else if (StringUtils.equalsIgnoreCase(operator, "isNotNull")) { return entityPath.get(fieldName).isNotNull(); } Class<?> fieldType = getFieldType(fieldName, entityPath); if (String.class == fieldType && String.class == searchObj.getClass()) { return createStringExpression(entityPath, fieldName, searchObj, operator); } else if ((Boolean.class == fieldType || boolean.class == fieldType) && String.class == searchObj.getClass()) { return createBooleanExpression(entityPath, fieldName, searchObj, operator); } else if ((Number.class.isAssignableFrom(fieldType) || NUMBER_PRIMITIVES .contains(fieldType)) && String.class == searchObj.getClass() && isValidValueFor((String) searchObj, typeDescriptor, conversionService)) { return createNumericExpression(entityPath, fieldName, searchObj, operator, fieldType); } else if ((Date.class.isAssignableFrom(fieldType) || Calendar.class .isAssignableFrom(fieldType)) && String.class == searchObj.getClass()) { return createDateExpression(entityPath, fieldName, searchObj, operator, fieldType); } else if (fieldType.isEnum() && String.class == searchObj.getClass()) { return createEnumExpression(entityPath, fieldName, (String) searchObj, (Class<? extends Enum>) fieldType); } return entityPath.get(fieldName).eq(searchObj); } /** * Check if a string is valid for a type <br/> * If conversion service is not provided try to check by apache commons * utilities. <b>TODO</b> in this (no-conversionService) case just * implemented for numerics * * @param string * @param typeDescriptor * @param conversionService (optional) * @return */ private static boolean isValidValueFor(String string, TypeDescriptor typeDescriptor, ConversionService conversionService) { if (conversionService != null) { try { conversionService.convert(string, STRING_TYPE_DESCRIPTOR, typeDescriptor); } catch (ConversionException e) { return false; } return true; } else { Class<?> fieldType = typeDescriptor.getType(); if (Number.class.isAssignableFrom(fieldType) || NUMBER_PRIMITIVES.contains(fieldType)) { return NumberUtils.isNumber(string); } // TODO implement other types return true; } } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public <T> BooleanExpression createDateExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj, String operator, Class<?> fieldType) { DatePath<Date> dateExpression = entityPath.getDate(fieldName, (Class<Date>) fieldType); try { Date value = DateUtils.parseDateStrictly((String) searchObj, FULL_DATE_PATTERNS); if (StringUtils.equalsIgnoreCase(operator, OPERATOR_GOE)) { return dateExpression.goe(value); } else if (StringUtils.equalsIgnoreCase(operator, "gt") || StringUtils.equalsIgnoreCase(operator, "after")) { return dateExpression.gt(value); } else if (StringUtils.equalsIgnoreCase(operator, OPERATOR_LOE)) { return dateExpression.loe(value); } else if (StringUtils.equalsIgnoreCase(operator, "lt") || StringUtils.equalsIgnoreCase(operator, "before")) { return dateExpression.lt(value); } } catch (ParseException e) { return entityPath.get(fieldName).eq(searchObj); } return entityPath.get(fieldName).eq(searchObj); } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public <T> BooleanExpression createNumericExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj, String operator, Class<?> fieldType) { NumberPath numberExpression = null; if (BigDecimal.class.isAssignableFrom(fieldType)) { numberExpression = entityPath.getNumber(fieldName, (Class<BigDecimal>) fieldType); } else if (BigInteger.class.isAssignableFrom(fieldType)) { numberExpression = entityPath.getNumber(fieldName, (Class<BigInteger>) fieldType); } else if (Byte.class.isAssignableFrom(fieldType)) { numberExpression = entityPath.getNumber(fieldName, (Class<Byte>) fieldType); } else if (Double.class.isAssignableFrom(fieldType) || double.class == fieldType) { numberExpression = entityPath.getNumber(fieldName, (Class<Double>) fieldType); } else if (Float.class.isAssignableFrom(fieldType) || float.class == fieldType) { numberExpression = entityPath.getNumber(fieldName, (Class<Float>) fieldType); } else if (Integer.class.isAssignableFrom(fieldType) || int.class == fieldType) { numberExpression = entityPath.getNumber(fieldName, (Class<Integer>) fieldType); } else if (Long.class.isAssignableFrom(fieldType) || long.class == fieldType) { numberExpression = entityPath.getNumber(fieldName, (Class<Long>) fieldType); } else if (Short.class.isAssignableFrom(fieldType) || short.class == fieldType) { numberExpression = entityPath.getNumber(fieldName, (Class<Short>) fieldType); } if (numberExpression != null) { Number value = NumberUtils.createNumber((String) searchObj); if (StringUtils.equalsIgnoreCase(operator, OPERATOR_GOE)) { return numberExpression.goe(value); } else if (StringUtils.equalsIgnoreCase(operator, "gt")) { return numberExpression.gt(value); } else if (StringUtils.equalsIgnoreCase(operator, "like")) { return numberExpression.like((String) searchObj); } else if (StringUtils.equalsIgnoreCase(operator, OPERATOR_LOE)) { return numberExpression.loe(value); } else if (StringUtils.equalsIgnoreCase(operator, "lt")) { return numberExpression.lt(value); } } return entityPath.get(fieldName).eq(searchObj); } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createBooleanExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj, String operator) { Boolean value = BooleanUtils.toBooleanObject((String) searchObj); if (value != null) { if (StringUtils.equalsIgnoreCase(operator, OPERATOR_GOE)) { return entityPath.getBoolean(fieldName).goe(value); } else if (StringUtils.equalsIgnoreCase(operator, "gt")) { return entityPath.getBoolean(fieldName).gt(value); } else if (StringUtils.equalsIgnoreCase(operator, OPERATOR_LOE)) { return entityPath.getBoolean(fieldName).loe(value); } else if (StringUtils.equalsIgnoreCase(operator, "lt")) { return entityPath.getBoolean(fieldName).lt(value); } } return entityPath.get(fieldName).eq(searchObj); } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createStringExpression( PathBuilder<T> entityPath, String fieldName, Object searchObj, String operator) { if (StringUtils.equalsIgnoreCase(operator, OPERATOR_GOE)) { return entityPath.getString(fieldName).goe((String) searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "gt")) { return entityPath.getString(fieldName).gt((String) searchObj); } else if (StringUtils.equalsIgnoreCase(operator, OPERATOR_LOE)) { return entityPath.getString(fieldName).loe((String) searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "lt")) { return entityPath.getString(fieldName).lt((String) searchObj); } else if (StringUtils.equalsIgnoreCase(operator, "like")) { return entityPath.getString(fieldName).like((String) searchObj); } return entityPath.get(fieldName).eq(searchObj); } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createStringExpression( PathBuilder<T> entityPath, String fieldName, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } BooleanExpression expression = entityPath.getString(fieldName).lower() .eq(searchStr.toLowerCase()); return expression; } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createStringLikeExpression( PathBuilder<T> entityPath, String fieldName, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } String str = "%".concat(searchStr.toLowerCase()).concat("%"); BooleanExpression expression = entityPath.getString(fieldName).lower() .like(str); return expression; } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createStringExpressionWithOperators( PathBuilder<T> entityPath, String fieldName, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } // All operations String endsOperation = "ENDS"; String startsOperation = "STARTS"; String containsOperation = "CONTAINS"; String isEmptyOperation = "ISEMPTY"; String isNotEmptyOperation = "ISNOTEMPTY"; String isNullOperation = OPERATOR_ISNULL; String isNotNullOperation = OPERATOR_NOTNULL; if (messageSource != null) { endsOperation = messageSource.getMessage( "global.filters.operations.string.ends", null, LocaleContextHolder.getLocale()); startsOperation = messageSource.getMessage( "global.filters.operations.string.starts", null, LocaleContextHolder.getLocale()); containsOperation = messageSource.getMessage( "global.filters.operations.string.contains", null, LocaleContextHolder.getLocale()); isEmptyOperation = messageSource.getMessage( "global.filters.operations.string.isempty", null, LocaleContextHolder.getLocale()); isNotEmptyOperation = messageSource.getMessage( "global.filters.operations.string.isnotempty", null, LocaleContextHolder.getLocale()); isNullOperation = messageSource.getMessage(G_FIL_OPE_ISNULL, null, LocaleContextHolder.getLocale()); isNotNullOperation = messageSource.getMessage(G_FIL_OPE_NOTNULL, null, LocaleContextHolder.getLocale()); } // If written expression is ENDS operation Pattern endsOperator = Pattern.compile(String.format("%s[(](.+)[)]$", endsOperation)); Matcher endsMatcher = endsOperator.matcher(searchStr); if (endsMatcher.matches()) { // Getting value String value = endsMatcher.group(1); String str = "%".concat(value.toLowerCase()); return entityPath.getString(fieldName).lower().like(str); } // If written expression is STARTS operation Pattern startsOperator = Pattern.compile(String.format("%s[(](.+)[)]$", startsOperation)); Matcher startsMatcher = startsOperator.matcher(searchStr); if (startsMatcher.matches()) { // Getting value String value = startsMatcher.group(1); String str = value.toLowerCase().concat("%"); return entityPath.getString(fieldName).lower().like(str); } // If written expression is CONTAINS operation Pattern containsOperator = Pattern.compile(String.format( "%s[(](.+)[)]$", containsOperation)); Matcher containsMatcher = containsOperator.matcher(searchStr); if (containsMatcher.matches()) { // Getting value String value = containsMatcher.group(1); String str = "%".concat(value.toLowerCase()).concat("%"); return entityPath.getString(fieldName).lower().like(str); } // If written expression is ISEMPTY operation Pattern isEmptyOperator = Pattern.compile(String.format("%s", isEmptyOperation)); Matcher isEmptyMatcher = isEmptyOperator.matcher(searchStr); if (isEmptyMatcher.matches()) { return entityPath.getString(fieldName).isEmpty() .or(entityPath.getString(fieldName).isNull()); } // If written expression is ISNOTEMPTY operation Pattern isNotEmptyOperator = Pattern.compile(String.format("%s", isNotEmptyOperation)); Matcher isNotEmptyMatcher = isNotEmptyOperator.matcher(searchStr); if (isNotEmptyMatcher.matches()) { return entityPath.getString(fieldName).isNotEmpty() .and(entityPath.getString(fieldName).isNotNull()); } // If written expression is ISNULL operation Pattern isNullOperator = Pattern.compile(String.format("%s", isNullOperation)); Matcher isNullMatcher = isNullOperator.matcher(searchStr); if (isNullMatcher.matches()) { return entityPath.getString(fieldName).isNull(); } // If written expression is ISNOTNULL operation Pattern isNotNullOperator = Pattern.compile(String.format("%s", isNotNullOperation)); Matcher isNotNullMatcher = isNotNullOperator.matcher(searchStr); if (isNotNullMatcher.matches()) { return entityPath.getString(fieldName).isNotNull(); } // If written expression is a symbol operation expression // Getting expressions with symbols Pattern symbolOperator = Pattern.compile("[=]?(.+)"); Matcher symbolMatcher = symbolOperator.matcher(searchStr); if (symbolMatcher.matches()) { String value = symbolMatcher.group(1); // operator is not necessary. Always is = return entityPath.getString(fieldName).lower() .eq(value.toLowerCase()); } return null; } /** * {@inheritDoc} */ @Override public <T, N extends Number & Comparable<?>> BooleanExpression createNumberExpression( PathBuilder<T> entityPath, String fieldName, Class<N> fieldType, TypeDescriptor descriptor, String searchStr) { if (StringUtils.isBlank(searchStr)) { return null; } NumberPath<N> numberExpression = entityPath.getNumber(fieldName, fieldType); BooleanExpression expression = null; if (conversionService != null) { try { Object number = conversionService.convert(searchStr, STRING_TYPE_DESCRIPTOR, descriptor); if (number == null) { expression = numberExpression.stringValue().like( "%".concat(searchStr).concat("%")); } else { String toSearch = number.toString(); if (number instanceof BigDecimal && ((BigDecimal) number).scale() > 1) { // For bigDecimal trim 0 in decimal part toSearch = StringUtils.stripEnd(toSearch, "0"); if (StringUtils.endsWith(toSearch, ".")) { // prevent "#." strings toSearch = toSearch.concat("0"); } } expression = numberExpression.stringValue().like( "%".concat(toSearch).concat("%")); } } catch (ConversionException e) { expression = numberExpression.stringValue().like( "%".concat(searchStr).concat("%")); } } else { expression = numberExpression.stringValue().like( "%".concat(searchStr).concat("%")); } return expression; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public <T, N extends Number & Comparable<?>> BooleanExpression createNumberExpressionEqual( PathBuilder<T> entityPath, String fieldName, Class<N> fieldType, TypeDescriptor descriptor, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } NumberPath<N> numberExpression = entityPath.getNumber(fieldName, fieldType); TypeDescriptor strDesc = STRING_TYPE_DESCRIPTOR; if (conversionService != null) { try { return numberExpression.eq((N) conversionService.convert( searchStr, strDesc, descriptor)); } catch (ConversionException ex) { return numberExpression.stringValue().like( "%".concat(searchStr).concat("%")); } } else { return numberExpression.stringValue().like( "%".concat(searchStr).concat("%")); } } /** * {@inheritDoc} */ @Override public <T, C extends Comparable<?>> BooleanExpression createDateExpression( PathBuilder<T> entityPath, String fieldName, Class<C> fieldType, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } DatePath<C> dateExpression = entityPath.getDate(fieldName, fieldType); BooleanExpression expression; // Search by full date String[] parsePatterns = null; try { parsePatterns = FULL_DATE_PATTERNS_WITH_TIME; Date searchDate = DateUtils.parseDateStrictly(searchStr, parsePatterns); Calendar searchCal = Calendar.getInstance(); searchCal.setTime(searchDate); expression = dateExpression.eq((fieldType.cast(searchCal))); } catch (Exception e) { // do nothing, and try the next parsing expression = null; } if (expression == null) { try { parsePatterns = FULL_DATE_PAT_WO_TIME; Date searchDate = DateUtils.parseDateStrictly(searchStr, parsePatterns); Calendar searchCal = Calendar.getInstance(); searchCal.setTime(searchDate); expression = dateExpression .dayOfMonth() .eq(searchCal.get(Calendar.DAY_OF_MONTH)) .and(dateExpression.month().eq( searchCal.get(Calendar.MONTH) + 1)) .and(dateExpression.year().eq( searchCal.get(Calendar.YEAR))); } catch (Exception e) { // do nothing, and try the next parsing expression = null; } } if (expression == null) { // Search by day and month parsePatterns = DAY_AND_MONTH_DATE_PATTERNS; try { Date searchDate = DateUtils.parseDateStrictly(searchStr, parsePatterns); Calendar searchCal = Calendar.getInstance(); searchCal.setTime(searchDate); expression = dateExpression .dayOfMonth() .eq(searchCal.get(Calendar.DAY_OF_MONTH)) .and(dateExpression.month().eq( searchCal.get(Calendar.MONTH) + 1)); } catch (Exception e) { // do nothing, and try the next parsing expression = null; } } // Search by month and year if (expression == null) { parsePatterns = MONTH_AND_YEAR_DATE_PATTERNS; try { Date searchDate = DateUtils.parseDateStrictly(searchStr, parsePatterns); Calendar searchCal = Calendar.getInstance(); searchCal.setTime(searchDate); // from 1st day of the month Calendar monthStartCal = Calendar.getInstance(); monthStartCal.set(searchCal.get(Calendar.YEAR), searchCal.get(Calendar.MONTH), 1, 23, 59, 59); monthStartCal.set(Calendar.MILLISECOND, 999); // to last day of the month Calendar monthEndCal = Calendar.getInstance(); monthEndCal.set(searchCal.get(Calendar.YEAR), (searchCal.get(Calendar.MONTH) + 1), 1, 23, 59, 59); monthEndCal.set(Calendar.MILLISECOND, 999); expression = dateExpression.between( fieldType.cast(monthStartCal), fieldType.cast(monthEndCal)); } catch (Exception e) { // do nothing, and try the next parsing expression = null; } } // Search by year // NOT NEEDED; JUST USE DEFAULT EXPRESSION if (expression == null) { // Default expression expression = dateExpression.stringValue().like( "%".concat(searchStr).concat("%")); } return expression; } /** * {@inheritDoc} */ @Override public <T, C extends Comparable<?>> BooleanExpression createDateExpressionWithOperators( PathBuilder<T> entityPath, String fieldName, Class<C> fieldType, String searchStr, String datePattern) { if (StringUtils.isEmpty(searchStr)) { return null; } DatePath<C> dateExpression = entityPath.getDate(fieldName, fieldType); // Getting simpleDateFormat DateFormat dateFormat = new SimpleDateFormat(datePattern); // All possible operations String date = "DATE"; String year = "YEAR"; String month = "MONTH"; String day = "DAY"; String between = "BETWEEN"; String isNullOperation = OPERATOR_ISNULL; String isNotNullOperation = OPERATOR_NOTNULL; if (messageSource != null) { date = messageSource.getMessage( "global.filters.operations.date.date", null, LocaleContextHolder.getLocale()); year = messageSource.getMessage( "global.filters.operations.date.year", null, LocaleContextHolder.getLocale()); month = messageSource.getMessage( "global.filters.operations.date.month", null, LocaleContextHolder.getLocale()); day = messageSource.getMessage( "global.filters.operations.date.day", null, LocaleContextHolder.getLocale()); between = messageSource.getMessage( "global.filters.operations.date.between", null, LocaleContextHolder.getLocale()); isNullOperation = messageSource.getMessage(G_FIL_OPE_ISNULL, null, LocaleContextHolder.getLocale()); isNotNullOperation = messageSource.getMessage(G_FIL_OPE_NOTNULL, null, LocaleContextHolder.getLocale()); } // If written expression is ISNULL operation Pattern isNullOperator = Pattern.compile(String.format("%s", isNullOperation)); Matcher isNullMatcher = isNullOperator.matcher(searchStr); if (isNullMatcher.matches()) { return dateExpression.isNull(); } // If written expression is ISNOTNULL operation Pattern isNotNullOperator = Pattern.compile(String.format("%s", isNotNullOperation)); Matcher isNotNullMatcher = isNotNullOperator.matcher(searchStr); if (isNotNullMatcher.matches()) { return dateExpression.isNotNull(); } // Creating regex to get DATE operator Pattern dateOperator = Pattern.compile(String.format( "%s[(]([\\d\\/]*)[)]", date)); Matcher dateMatcher = dateOperator.matcher(searchStr); if (dateMatcher.matches()) { try { String dateValue = dateMatcher.group(1); Date dateToFilter = dateFormat.parse(dateValue); Calendar searchCal = Calendar.getInstance(); searchCal.setTime(dateToFilter); return dateExpression.eq(conversionService.convert(searchCal, fieldType)); } catch (ParseException e) { return null; } } // Creating regex to get YEAR operator Pattern yearOperator = Pattern.compile(String.format( "%s[(]([\\d]*)[)]", year)); Matcher yearMatcher = yearOperator.matcher(searchStr); if (yearMatcher.matches()) { String value = yearMatcher.group(1); return dateExpression.year().eq(Integer.parseInt(value)); } // Creating regex to get MONTH operator Pattern monthOperator = Pattern.compile(String.format( "%s[(]([\\d]*)[)]", month)); Matcher monthMatcher = monthOperator.matcher(searchStr); if (monthMatcher.matches()) { String value = monthMatcher.group(1); return dateExpression.month().eq(Integer.parseInt(value)); } // Creating regex to get DAY operator Pattern dayOperator = Pattern.compile(String.format("%s[(]([\\d]*)[)]", day)); Matcher dayMatcher = dayOperator.matcher(searchStr); if (dayMatcher.matches()) { String value = dayMatcher.group(1); return dateExpression.dayOfMonth().eq(Integer.parseInt(value)); } // Creating regex to get BETWEEN operator Pattern betweenOperator = Pattern.compile(String.format( "%s[(]([\\d\\/]*);([\\d\\/]*)[)]", between)); Matcher betweenMatcher = betweenOperator.matcher(searchStr); if (betweenMatcher.matches()) { String valueFrom = betweenMatcher.group(1); String valueTo = betweenMatcher.group(2); if (StringUtils.isNotBlank(valueFrom) && StringUtils.isNotBlank(valueTo)) { try { Date dateFrom = dateFormat.parse(valueFrom); Date dateTo = dateFormat.parse(valueTo); Calendar dateFromCal = Calendar.getInstance(); dateFromCal.setTime(dateFrom); Calendar dateToCal = Calendar.getInstance(); dateToCal.setTime(dateTo); return dateExpression.between( conversionService.convert(dateFromCal, fieldType), conversionService.convert(dateToCal, fieldType)); } catch (Exception e) { return null; } } } return null; } /** * {@inheritDoc} */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public <T> BooleanExpression createEnumExpression( PathBuilder<T> entityPath, String fieldName, String searchStr, Class<? extends Enum> enumClass) { if (StringUtils.isEmpty(searchStr)) { return null; } // Filter string to search than cannot be a identifier if (!StringUtils.isAlphanumeric(StringUtils.lowerCase(searchStr))) { return null; } // TODO i18n of enum name // normalize search string searchStr = StringUtils.trim(searchStr).toLowerCase(); // locate enums matching by name Set matching = new HashSet(); Enum<?> enumValue; String enumStr; for (Field enumField : enumClass.getDeclaredFields()) { if (enumField.isEnumConstant()) { enumStr = enumField.getName(); enumValue = Enum.valueOf(enumClass, enumStr); // Check enum name contains string to search if (enumStr.toLowerCase().contains(searchStr)) { // Add to matching enum matching.add(enumValue); continue; } // Check using toString enumStr = enumValue.toString(); if (enumStr.toLowerCase().contains(searchStr)) { // Add to matching enum matching.add(enumValue); } } } if (matching.isEmpty()) { return null; } // create a enum in matching condition BooleanExpression expression = entityPath.get(fieldName).in(matching); return expression; } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createBooleanExpression( PathBuilder<T> entityPath, String fieldName, String searchStr) { if (StringUtils.isBlank(searchStr)) { return null; } Boolean value = null; // I18N: Spanish (normalize search value: trim start-end and lower case) if ("si".equals(StringUtils.trim(searchStr).toLowerCase())) { value = Boolean.TRUE; } else { value = BooleanUtils.toBooleanObject(searchStr); } // if cannot parse to boolean or null input if (value == null) { return null; } BooleanExpression expression = entityPath.getBoolean(fieldName).eq( value); return expression; } /** * {@inheritDoc} */ @Override public <T> BooleanExpression createBooleanExpressionWithOperators( PathBuilder<T> entityPath, String fieldName, String searchStr) { if (StringUtils.isBlank(searchStr)) { return null; } // Getting all operations String trueOperation = "TRUE"; String falseOperation = "FALSE"; String isNullOperation = OPERATOR_ISNULL; String isNotNullOperation = OPERATOR_NOTNULL; if (messageSource != null) { trueOperation = messageSource.getMessage( "global.filters.operations.boolean.true", null, LocaleContextHolder.getLocale()); falseOperation = messageSource.getMessage( "global.filters.operations.boolean.false", null, LocaleContextHolder.getLocale()); isNullOperation = messageSource.getMessage(G_FIL_OPE_ISNULL, null, LocaleContextHolder.getLocale()); isNotNullOperation = messageSource.getMessage(G_FIL_OPE_NOTNULL, null, LocaleContextHolder.getLocale()); } // If written function is TRUE Pattern trueOperator = Pattern.compile(String.format("%s", trueOperation)); Matcher trueMatcher = trueOperator.matcher(searchStr); if (trueMatcher.matches()) { return entityPath.getBoolean(fieldName).eq(Boolean.TRUE); } // If written function is FALSE Pattern falseOperator = Pattern.compile(String.format("%s", falseOperation)); Matcher falseMatcher = falseOperator.matcher(searchStr); if (falseMatcher.matches()) { return entityPath.getBoolean(fieldName).eq(Boolean.FALSE); } // If written expression is ISNULL operation Pattern isNullOperator = Pattern.compile(String.format("%s", isNullOperation)); Matcher isNullMatcher = isNullOperator.matcher(searchStr); if (isNullMatcher.matches()) { return entityPath.getBoolean(fieldName).isNull(); } // If written expression is ISNOTNULL operation Pattern isNotNullOperator = Pattern.compile(String.format("%s", isNotNullOperation)); Matcher isNotNullMatcher = isNotNullOperator.matcher(searchStr); if (isNotNullMatcher.matches()) { return entityPath.getBoolean(fieldName).isNotNull(); } return null; } /** * {@inheritDoc} */ @Override public <T, E> BooleanExpression createCollectionExpression( PathBuilder<T> entityPath, String fieldName, Collection<E> values) { if (StringUtils.isEmpty(fieldName) || values.isEmpty()) { return null; } if (values.size() > 500) { BooleanExpression expression = null; Iterable<List<E>> collectionParts = Iterables .partition(values, 500); for (List<E> part : collectionParts) { if (expression == null) { expression = doCreateCollectionExpression(entityPath, fieldName, part); } else { expression = expression.or(doCreateCollectionExpression( entityPath, fieldName, part)); } } return expression; } else { return doCreateCollectionExpression(entityPath, fieldName, values); } } @Override public <T, E> BooleanExpression doCreateCollectionExpression( PathBuilder<T> entityPath, String fieldName, Collection<E> values) { BooleanExpression expression = entityPath.get(fieldName).in(values); return expression; } /** * {@inheritDoc} */ @Override public <T, E extends Comparable<?>> OrderSpecifier<?> createOrderSpecifier( PathBuilder<T> entityPath, String fieldName, Class<E> fieldType, Order order) { OrderSpecifier<?> orderBy = null; // Get the OrderSpecifier if (order == Order.ASC) { orderBy = entityPath.getComparable(fieldName, fieldType).asc(); } else if (order == Order.DESC) { orderBy = entityPath.getComparable(fieldName, fieldType).desc(); } return orderBy; } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public <T, N extends Number & Comparable<?>> BooleanExpression getNumericFilterExpression( PathBuilder<T> entityPath, String fieldName, Class<N> fieldType, TypeDescriptor descriptor, String searchStr) { if (StringUtils.isEmpty(searchStr)) { return null; } TypeDescriptor strDesc = STRING_TYPE_DESCRIPTOR; NumberPath<N> numberExpression = entityPath.getNumber(fieldName, fieldType); // If written expression is a symbol operation expression // Getting expressions with symbols Pattern symbolOperator = Pattern.compile("([!=><][=>]?)([-]?[\\d.,]*)"); Matcher symbolMatcher = symbolOperator.matcher(searchStr); if (symbolMatcher.matches()) { String symbolExpression = symbolMatcher.group(1); String value = symbolMatcher.group(2); if (!StringUtils.isBlank(value)) { Object valueConverted = conversionService.convert(value, strDesc, descriptor); if (symbolExpression.equals("=") || symbolExpression.equals("==")) { return numberExpression.eq((N) valueConverted); } else if (symbolExpression.equals(">") || symbolExpression.equals(">>")) { return numberExpression.gt((N) valueConverted); } else if (symbolExpression.equals("<")) { return numberExpression.lt((N) valueConverted); } else if (symbolExpression.equals(">=")) { return numberExpression.goe((N) valueConverted); } else if (symbolExpression.equals("<=")) { return numberExpression.loe((N) valueConverted); } else if (symbolExpression.equals("!=") || symbolExpression.equals("<>")) { return numberExpression.ne((N) valueConverted); } } } // Get all operations String isNullOperation = OPERATOR_ISNULL; String isNotNullOperation = OPERATOR_NOTNULL; String betweenOperation = "BETWEEN"; if (messageSource != null) { isNullOperation = messageSource.getMessage(G_FIL_OPE_ISNULL, null, LocaleContextHolder.getLocale()); isNotNullOperation = messageSource.getMessage(G_FIL_OPE_NOTNULL, null, LocaleContextHolder.getLocale()); betweenOperation = messageSource.getMessage( "global.filters.operations.number.between", null, LocaleContextHolder.getLocale()); } // If written function is BETWEEN function Pattern betweenFunctionOperator = Pattern.compile(String.format( "%s[(]([-]?[\\d.,]*);([-]?[\\d.,]*)[)]", betweenOperation)); Matcher betweenFunctionMatcher = betweenFunctionOperator .matcher(searchStr); if (betweenFunctionMatcher.matches()) { // Getting valueFrom and valueTo String valueFrom = betweenFunctionMatcher.group(1); String valueTo = betweenFunctionMatcher.group(2); Object valueFromConverted = conversionService.convert(valueFrom, strDesc, descriptor); Object valueToConverted = conversionService.convert(valueTo, strDesc, descriptor); if (!StringUtils.isBlank(valueFrom) && !StringUtils.isBlank(valueTo)) { return numberExpression.between((N) valueFromConverted, (N) valueToConverted); } } // If written expression is ISNULL operation Pattern isNullOperator = Pattern.compile(String.format("%s", isNullOperation)); Matcher isNullMatcher = isNullOperator.matcher(searchStr); if (isNullMatcher.matches()) { return numberExpression.isNull(); } // If written expression is ISNOTNULL operation Pattern isNotNullOperator = Pattern.compile(String.format("%s", isNotNullOperation)); Matcher isNotNullMatcher = isNotNullOperator.matcher(searchStr); if (isNotNullMatcher.matches()) { return numberExpression.isNotNull(); } return null; } /** * {@inheritDoc} */ @Override public <T> Class<?> getFieldType(String fieldName, PathBuilder<T> entity) { TypeDescriptor descriptor = getTypeDescriptor(fieldName, entity); return descriptor.getType(); } /** * {@inheritDoc} */ @Override public <T> Class<?> getFieldType1(String fieldName, PathBuilder<T> entity) { Class<?> entityType = entity.getType(); String fieldNameToFindType = fieldName; // Makes the array of classes to find fieldName agains them Class<?>[] classArray = ArrayUtils.<Class<?>> toArray(entityType); if (fieldName.contains(SEPARATOR_FIELDS)) { String[] fieldNameSplitted = StringUtils.split(fieldName, SEPARATOR_FIELDS); for (int i = 0; i < fieldNameSplitted.length - 1; i++) { Class<?> fieldType = BeanUtils.findPropertyType( fieldNameSplitted[i], ArrayUtils.<Class<?>> toArray(entityType)); classArray = ArrayUtils.add(classArray, fieldType); entityType = fieldType; } fieldNameToFindType = fieldNameSplitted[fieldNameSplitted.length - 1]; } return BeanUtils.findPropertyType(fieldNameToFindType, classArray); } /** * {@inheritDoc} */ @Override public <T> TypeDescriptor getTypeDescriptor(String fieldName, PathBuilder<T> entity) { Class<?> entityType = entity.getType(); if (entityType == Object.class) { // Remove from path the root "entity" alias String fromRootPath = entity.toString().replaceFirst("^[^.]+[.]", ""); TypeDescriptor fromRoot = getTypeDescriptor(fromRootPath, entity .getRoot().getType()); if (fromRoot == null) { return null; } entityType = fromRoot.getType(); } return getTypeDescriptor(fieldName, entityType); } /** * {@inheritDoc} */ @Override public <T> TypeDescriptor getTypeDescriptor(String fieldName, Class<T> entityType) { String fieldNameToFindType = fieldName; BeanWrapper beanWrapper = getBeanWrapper(entityType); TypeDescriptor fieldDescriptor = null; Class<?> propType = null; // Find recursive the las beanWrapper if (fieldName.contains(SEPARATOR_FIELDS)) { String[] fieldNameSplitted = StringUtils.split(fieldName, SEPARATOR_FIELDS); for (int i = 0; i < fieldNameSplitted.length - 1; i++) { propType = beanWrapper.getPropertyType(fieldNameSplitted[i]); if (propType == null) { throw new IllegalArgumentException(String.format( "Property %s not found in %s (request %s.%s)", fieldNameSplitted[i], beanWrapper.getWrappedClass(), entityType, fieldName)); } beanWrapper = getBeanWrapper(propType); } fieldNameToFindType = fieldNameSplitted[fieldNameSplitted.length - 1]; } fieldDescriptor = beanWrapper .getPropertyTypeDescriptor(fieldNameToFindType); return fieldDescriptor; } /** * {@inheritDoc} */ @Override public boolean isNumber(String searchStr, TypeDescriptor descriptor) { return isValidValueFor(searchStr, descriptor, conversionService); } protected ConversionService getConversionService() { return conversionService; } protected MessageSource getMessageSource() { return messageSource; } protected EntityManagerProvider getEntityManagerProvider() { return entityManagerProvider; } protected static LoadingCache<Class<?>, BeanWrapper> getBeanWrappersCache() { return beanWrappersCache; } }