/** * *************************************************************************** * Copyright (c) 2010 Qcadoo Limited * Project: Qcadoo Framework * Version: 1.4 * * This file is part of Qcadoo. * * Qcadoo is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * *************************************************************************** */ package com.qcadoo.view.internal.components.grid; import static com.qcadoo.view.internal.components.grid.GridComponentFilterOperator.ISNULL; import java.math.BigDecimal; import java.text.ParseException; import java.util.*; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Lists; import com.qcadoo.localization.api.utils.DateUtils; import com.qcadoo.model.api.DataDefinition; import com.qcadoo.model.api.FieldDefinition; import com.qcadoo.model.api.search.JoinType; import com.qcadoo.model.api.search.SearchCriteriaBuilder; import com.qcadoo.model.api.search.SearchCriterion; import com.qcadoo.model.api.search.SearchRestrictions; import com.qcadoo.model.api.search.SearchRestrictions.SearchMatchMode; import com.qcadoo.model.api.types.BelongsToType; import com.qcadoo.view.api.components.grid.GridComponentMultiSearchFilter; public final class GridComponentFilterUtils { private GridComponentFilterUtils() { } public static void addFilters(final Map<String, String> filters, final Map<String, GridComponentColumn> columns, final DataDefinition dataDefinition, final SearchCriteriaBuilder criteria) throws GridComponentFilterException { for (Map.Entry<String, String> filter : filters.entrySet()) { String field = getFieldNameByColumnName(columns, filter.getKey()); if (field != null) { try { FieldDefinition fieldDefinition = getFieldDefinition(dataDefinition, field); Map.Entry<GridComponentFilterOperator, String> filterValue = parseFilterValue(filter.getValue()); if ("".equals(filterValue.getValue()) && !ISNULL.equals(filterValue.getKey())) { continue; } field = addAliases(criteria, field, JoinType.LEFT); if (fieldDefinition != null && String.class.isAssignableFrom(fieldDefinition.getType().getType())) { addStringFilter(criteria, filterValue, field); } else if (fieldDefinition != null && Boolean.class.isAssignableFrom(fieldDefinition.getType().getType())) { addSimpleFilter(criteria, filterValue, field, "1".equals(filterValue.getValue())); } else if (fieldDefinition != null && Date.class.isAssignableFrom(fieldDefinition.getType().getType())) { addDateFilter(criteria, filterValue, field); } else if (fieldDefinition != null && BigDecimal.class.isAssignableFrom(fieldDefinition.getType().getType())) { addDecimalFilter(criteria, filterValue, field); } else if (fieldDefinition != null && Integer.class.isAssignableFrom(fieldDefinition.getType().getType())) { addIntegerFilter(criteria, filterValue, field); } else { addSimpleFilter(criteria, filterValue, field, filterValue.getValue()); } } catch (ParseException pe) { throw new GridComponentFilterException(filter.getValue()); } } } } public static void addMultiSearchFilter(GridComponentMultiSearchFilter multiSearchFilter, Map<String, GridComponentColumn> columns, DataDefinition dataDefinition, SearchCriteriaBuilder criteria) throws GridComponentFilterException { LinkedList<SearchCriterion> searchRules = Lists.newLinkedList(); for (GridComponentMultiSearchFilterRule rule : multiSearchFilter.getRules()) { String field = getFieldNameByColumnName(columns, rule.getField()); if (field != null) { try { FieldDefinition fieldDefinition = getFieldDefinition(dataDefinition, field); if ("".equals(rule.getData()) && !ISNULL.equals(rule.getFilterOperator())) { continue; } field = addAliases(criteria, field, JoinType.LEFT); SearchCriterion searchRule; if (fieldDefinition != null && String.class.isAssignableFrom(fieldDefinition.getType().getType())) { searchRule = createStringCriterion(rule.getFilterOperator(), rule.getData(), field); } else if (fieldDefinition != null && Boolean.class.isAssignableFrom(fieldDefinition.getType().getType())) { searchRule = createSimpleCriterion(rule.getFilterOperator(), "1".equals(rule.getData()), field); } else if (fieldDefinition != null && Date.class.isAssignableFrom(fieldDefinition.getType().getType())) { searchRule = createDateCriterion(rule.getFilterOperator(), rule.getData(), field); } else if (fieldDefinition != null && BigDecimal.class.isAssignableFrom(fieldDefinition.getType().getType())) { searchRule = createDecimalCriterion(rule.getFilterOperator(), rule.getData(), field); } else if (fieldDefinition != null && Integer.class.isAssignableFrom(fieldDefinition.getType().getType())) { searchRule = createIntegerCriterion(rule.getFilterOperator(), rule.getData(), field); } else { searchRule = createSimpleCriterion(rule.getFilterOperator(), rule.getData(), field); } searchRules.add(searchRule); } catch (Exception pe) { throw new GridComponentFilterException(rule.getData()); } } } SearchCriterion groupedRules = null; if (searchRules.size() == 1) { groupedRules = searchRules.pollFirst(); } else if (searchRules.size() == 2) { if (multiSearchFilter.getGroupOperator() == GridComponentFilterGroupOperator.AND) { groupedRules = SearchRestrictions.and(searchRules.pollFirst(), searchRules.pollFirst()); } else if (multiSearchFilter.getGroupOperator() == GridComponentFilterGroupOperator.OR) { groupedRules = SearchRestrictions.or(searchRules.pollFirst(), searchRules.pollFirst()); } } else if (searchRules.size() > 2) { SearchCriterion firstRule = searchRules.pollFirst(); SearchCriterion secondRule = searchRules.pollFirst(); SearchCriterion[] otherRules = new SearchCriterion[searchRules.size()]; searchRules.toArray(otherRules); if (multiSearchFilter.getGroupOperator() == GridComponentFilterGroupOperator.AND) { groupedRules = SearchRestrictions.and(firstRule, secondRule, otherRules); } else if (multiSearchFilter.getGroupOperator() == GridComponentFilterGroupOperator.OR) { groupedRules = SearchRestrictions.or(firstRule, secondRule, otherRules); } } if (groupedRules != null) { criteria.add(groupedRules); } } private static SearchCriterion createSimpleCriterion(GridComponentFilterOperator filterOperator, Object data, String field) { switch (filterOperator) { case EQ: case CN: case BW: case EW: return SearchRestrictions.eq(field, data); case NE: return SearchRestrictions.ne(field, data); case GT: return SearchRestrictions.gt(field, data); case GE: return SearchRestrictions.ge(field, data); case LT: return SearchRestrictions.lt(field, data); case LE: return SearchRestrictions.le(field, data); case ISNULL: return SearchRestrictions.isNull(field); case IN: if (data instanceof Collection<?>) { return SearchRestrictions.in(field, (Collection<?>) data); } else { throw new IllegalStateException("Unknown filter value, collection required"); } default: throw new IllegalStateException("Unknown filter operator"); } } private static SearchCriterion createIntegerCriterion(GridComponentFilterOperator filterOperator, String data, String field) throws GridComponentFilterException { try { final Object value; if (filterOperator == GridComponentFilterOperator.IN) { Collection<String> values = parseListValue(data); Collection<Integer> integerValues = Lists.newArrayListWithCapacity(values.size()); for (String stringValue : values) { integerValues.add(Integer.valueOf(stringValue)); } value = integerValues; } else { value = Integer.valueOf(data); } return createSimpleCriterion(filterOperator, value, field); } catch (NumberFormatException nfe) { throw new GridComponentFilterException(data, nfe); } } private static SearchCriterion createDecimalCriterion(GridComponentFilterOperator filterOperator, String data, String field) throws GridComponentFilterException { try { final Object value; if (filterOperator == GridComponentFilterOperator.IN) { Collection<String> values = parseListValue(data); Collection<BigDecimal> decimalValues = Lists.newArrayListWithCapacity(values.size()); for (String stringValue : values) { decimalValues.add(new BigDecimal(stringValue)); } value = decimalValues; } else { value = new BigDecimal(data); } return createSimpleCriterion(filterOperator, value, field); } catch (NumberFormatException nfe) { throw new GridComponentFilterException(data, nfe); } } private static SearchCriterion createDateCriterion(GridComponentFilterOperator filterOperator, String data, String field) throws ParseException { if (filterOperator == GridComponentFilterOperator.IN) { Collection<String> values = parseListValue(data); Collection<Date> dates = Lists.newArrayListWithCapacity(values.size()); for (String value : values) { dates.add(DateUtils.parseDate(value)); } return SearchRestrictions.in(field, dates); } Date minDate = null; Date maxDate = null; if(!ISNULL.equals(filterOperator)){ minDate = DateUtils.parseAndComplete(data, false); maxDate = DateUtils.parseAndComplete(data, true); } switch (filterOperator) { case EQ: case CN: case BW: case EW: return SearchRestrictions.between(field, minDate, maxDate); case NE: return SearchRestrictions.not(SearchRestrictions.between(field, minDate, maxDate)); case GT: return SearchRestrictions.gt(field, maxDate); case GE: return SearchRestrictions.ge(field, minDate); case LT: return SearchRestrictions.lt(field, minDate); case LE: return SearchRestrictions.le(field, maxDate); case ISNULL: return SearchRestrictions.isNull(field); default: throw new IllegalStateException("Unknown filter operator"); } } private static SearchCriterion createStringCriterion(GridComponentFilterOperator filterOperator, String data, String field) { switch (filterOperator) { case EQ: case LE: case GE: return SearchRestrictions.eq(field, data); case CN: return SearchRestrictions.ilike(field, data, SearchMatchMode.ANYWHERE); case BW: return SearchRestrictions.ilike(field, data, SearchMatchMode.START); case EW: return SearchRestrictions.ilike(field, data, SearchMatchMode.END); case IN: Collection<String> values = parseListValue(data); return SearchRestrictions.inIgnoringCase(field, values); case NE: case GT: case LT: return SearchRestrictions.ne(field, data); case ISNULL: return SearchRestrictions.isNull(field); default: throw new IllegalStateException("Unknown filter operator"); } } private static Collection<String> parseListValue(String data) { String[] tokens = data.split(","); Collection<String> values = Lists.newArrayListWithCapacity(tokens.length); for (int i = 0; i < tokens.length; ++i) { values.add(tokens[i].trim()); } return values; } public static String addAliases(final SearchCriteriaBuilder criteria, final String field, final JoinType joinType) { if (field == null) { return null; } String[] path = field.split("\\."); if (path.length == 1) { return field; } String lastAlias = ""; for (int i = 0; i < path.length - 1; i++) { criteria.createAlias(lastAlias + path[i], path[i] + "_a", joinType); lastAlias = path[i] + "_a."; } return lastAlias + path[path.length - 1]; } private static void addIntegerFilter(final SearchCriteriaBuilder criteria, final Entry<GridComponentFilterOperator, String> filterValue, final String field) throws GridComponentFilterException { criteria.add(createIntegerCriterion(filterValue.getKey(), filterValue.getValue(), field)); } private static void addDecimalFilter(final SearchCriteriaBuilder criteria, final Entry<GridComponentFilterOperator, String> filterValue, final String field) throws GridComponentFilterException { criteria.add(createDecimalCriterion(filterValue.getKey(), filterValue.getValue(), field)); } private static void addSimpleFilter(final SearchCriteriaBuilder criteria, final Entry<GridComponentFilterOperator, String> filterValue, final String field, final Object value) { criteria.add(createSimpleCriterion(filterValue.getKey(), value, field)); } private static void addStringFilter(final SearchCriteriaBuilder criteria, final Entry<GridComponentFilterOperator, String> filterValue, final String field) { String value = filterValue.getValue(); GridComponentFilterOperator operator = filterValue.getKey(); if (filterValue.getKey() == GridComponentFilterOperator.EQ) { operator = GridComponentFilterOperator.CN; } criteria.add(createStringCriterion(operator, value, field)); } private static void addDateFilter(final SearchCriteriaBuilder criteria, final Entry<GridComponentFilterOperator, String> filterValue, final String field) throws ParseException { criteria.add(createDateCriterion(filterValue.getKey(), filterValue.getValue(), field)); } private static Map.Entry<GridComponentFilterOperator, String> parseFilterValue(final String filterValue) { GridComponentFilterOperator operator = GridComponentFilterOperator.EQ; String value; if (filterValue.charAt(0) == '>') { if (filterValue.length() > 1 && filterValue.charAt(1) == '=') { operator = GridComponentFilterOperator.GE; value = filterValue.substring(2); } else if (filterValue.length() > 1 && filterValue.charAt(1) == '<') { operator = GridComponentFilterOperator.NE; value = filterValue.substring(2); } else { operator = GridComponentFilterOperator.GT; value = filterValue.substring(1); } } else if (filterValue.charAt(0) == '<') { if (filterValue.length() > 1 && filterValue.charAt(1) == '=') { operator = GridComponentFilterOperator.LE; value = filterValue.substring(2); } else if (filterValue.length() > 1 && filterValue.charAt(1) == '>') { operator = GridComponentFilterOperator.NE; value = filterValue.substring(2); } else { operator = GridComponentFilterOperator.LT; value = filterValue.substring(1); } } else if (filterValue.charAt(0) == '=') { if (filterValue.length() > 1 && filterValue.charAt(1) == '<') { operator = GridComponentFilterOperator.LE; value = filterValue.substring(2); } else if (filterValue.length() > 1 && filterValue.charAt(1) == '>') { operator = GridComponentFilterOperator.GE; value = filterValue.substring(2); } else if (filterValue.length() > 1 && filterValue.charAt(1) == '=') { value = filterValue.substring(2); } else { value = filterValue.substring(1); } } else if (filterValue.charAt(0) == '[' && filterValue.charAt(filterValue.length() - 1) == ']') { operator = GridComponentFilterOperator.IN; value = filterValue.substring(1, filterValue.length() - 1); } else if (ISNULL.name().equals(filterValue.toUpperCase())) { operator = GridComponentFilterOperator.ISNULL; value = ""; } else { value = filterValue; } return Collections.singletonMap(operator, value.trim()).entrySet().iterator().next(); } protected static FieldDefinition getFieldDefinition(DataDefinition dataDefinition, final String field) { String[] path = field.split("\\."); for (int i = 0; i < path.length; i++) { if (dataDefinition.getField(path[i]) == null) { return null; } FieldDefinition fieldDefinition = dataDefinition.getField(path[i]); if (i < path.length - 1) { if (fieldDefinition.getType() instanceof BelongsToType) { dataDefinition = ((BelongsToType) fieldDefinition.getType()).getDataDefinition(); continue; } else { return null; } } return fieldDefinition; } return null; } public static String getFieldNameByColumnName(final Map<String, GridComponentColumn> columns, final String columnName) { GridComponentColumn column = columns.get(columnName); if (column == null) { return null; } final String expression = column.getExpression(); if (StringUtils.isNotBlank(expression)) { return getFieldNameFromExpression(expression); } else if (column.getFields().size() == 1) { return column.getFields().get(0).getName(); } return null; } private static String getFieldNameFromExpression(final String expression) { String pattern = "#(\\w+)(\\['(\\w+)'\\])?([[?]?.[get|getStringField|getBooleanField|getDecimalField|getIntegerField|getDateField|getBelongsToField]\\('\\w+'\\)]*)"; Matcher matcher = Pattern.compile(pattern).matcher(StringUtils.trim(expression)); if (matcher.matches()) { final StringBuilder fieldNameBuilder = new StringBuilder(matcher.group(1)); if (StringUtils.isNotBlank(matcher.group(3))) { fieldNameBuilder.append("."); fieldNameBuilder.append(matcher.group(3)); } if (StringUtils.isNotBlank(matcher.group(4))) { final String[] searchList = new String[] { "get('", "?.get('", "getStringField('", "?.getStringField('", "getBooleanField('", "?.getBooleanField('", "getDecimalField('", "?.getDecimalField('", "getIntegerField('", "?.getIntegerField('", "getDateField('", "?.getDateField('", "getBelongsToField('", "?.getBelongsToField('", "')" }; final String[] replacementList = new String[] { "", ".", "", ".", "", ".", "", ".", "", ".", "", ".", "", ".", "" }; fieldNameBuilder.append(StringUtils.replaceEach(matcher.group(4), searchList, replacementList)); } return fieldNameBuilder.toString(); } return null; } }