/* * Copyright 2010, Andrew M Gibson * * www.andygibson.net * * This file is part of DataValve. * * DataValve is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DataValve 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 Lesser General Public License for more details. * * * You should have received a copy of the GNU Lesser General Public License * along with DataValve. If not, see <http://www.gnu.org/licenses/>. * */ package org.fluttercode.datavalve.provider; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.fluttercode.datavalve.Paginator; import org.fluttercode.datavalve.provider.util.DataQuery; import org.fluttercode.datavalve.provider.util.DataQueryBuilder; /** * Abstract class for Query driven datasets that implements most of the methods * in the {@link QueryDataProvider} interface. It adds features for defining * select/count statements, and translating orderKey values into other * representations. It also defines common methods for defining an order clause * and building statements based on the Sql/Ejbql structure. * <p> * Typically, the orderKey translates to an order value through the OrderKeyMap. * However, we wrap this behaviour in the {@link #translateOrderKey(String)} * method. This can be overridden if you want to change how we translate * orderKey values. * * @author Andy Gibson * * @param <T> * Type of object this dataset contains. */ public abstract class AbstractQueryDataProvider<T> extends AbstractQLDataProvider<T> implements QueryDataProvider<T>, Serializable { private static final long serialVersionUID = 1L; private int paramId; private Map<String, String> orderKeyMap = new HashMap<String, String>(); private List<String> restrictions = new ArrayList<String>(); public Map<String, String> getOrderKeyMap() { return orderKeyMap; } public void setOrderKeyMap(Map<String, String> orderKeyMap) { this.orderKeyMap = orderKeyMap; } public List<String> getRestrictions() { return restrictions; } public void setRestrictions(List<String> restrictions) { this.restrictions = restrictions; } public void init(Class<? extends Object> clazz, String prefix) { setCountStatement(String.format("select count(%s) from %s %s ", prefix, clazz.getSimpleName(), prefix)); setSelectStatement(String.format("select %s from %s %s ", prefix, clazz.getSimpleName(), prefix)); } /** * Returns the order fields for a given order key pair. Default * implementation works off the * {@link AbstractQueryDataProvider#orderKeyMap} {@link Map} property. * <p/> * Can be overridden to handle custom specific mappings. * * @param orderKeyValue * @return */ protected String translateOrderKey(String orderKeyValue) { return getOrderKeyMap().get(orderKeyValue); } protected String getNextParamName() { return "_param_" + String.valueOf(paramId++); } public void addRestriction(String restriction) { getRestrictions().add(restriction); } public boolean addRestriction(String syntax, Object value) { return addRestriction(syntax, value, value); } public boolean addRestrictionStr(String syntax, String value) { return addRestrictionStr(syntax, value, value); } public boolean addRestrictionStr(String syntax, String paramValue, String testValue) { if (isValidTestValue(testValue, true)) { return addRestriction(syntax, paramValue, testValue); } return false; } public boolean addRestriction(String syntax, Object paramValue, Object testValue) { if (isValidTestValue(testValue, false)) { if (paramValue instanceof Collection<?>) { addCollectionRestriction(syntax,(Collection<?>) paramValue); } else { addRestrictionForDefault(syntax, paramValue); } return true; } return false; } /** * Adds a restriction based on a collection. It is assumed that the syntax * used is acceptable for a collection, for example, using * <code> field in (:param)</code> and passing in a collection. * <p/> * For each item in the collection, see if it is valid and if so, add it to * the list of parameters to be used in the statement. * * @param syntax * syntax to use in the restriction * @param paramValue */ private void addCollectionRestriction(String syntax, Collection<?> paramValues) { String paramList = ""; for (Object o : paramValues) { if (isValidTestValue(o, false)) { //prefix with comma if its not the first if (paramList.length() != 0) { paramList = paramList + ","; } String name = getNextParamName(); paramList = paramList + ":" + name; getParameters().put(name, o); } } syntax = syntax.replace(":param", paramList); addRestriction(syntax); } /** * Adds a restriction based on a parameter value that is expected to be a * typical value (String, Long, int, boolean, etc) instead of a more exotic * parameter type such as as collection. This method assumes any pre-add * checks have been done for null params or test params etc, * * @param syntax * Syntax of the restriction * @param paramValue * Value to use, */ private void addRestrictionForDefault(String syntax, Object paramValue) { String name = getNextParamName(); syntax = syntax.replace(":param", ":" + name); addRestriction(syntax); getParameters().put(name, paramValue); } protected boolean isValidTestValue(Object obj, boolean testStrings) { if (obj == null) { return false; } if (obj instanceof String && testStrings) { String s = (String) obj; return s.length() != 0; } if (obj instanceof Collection<?>) { if (((Collection<?>) obj).size() == 0) { return false; } // check it contains a valid test value for (Object o : (Collection<?>) obj) { // avoid recursion by checking for self if (o != obj) { if (isValidTestValue(o, testStrings)) { return true; } } } } return true; } /** * Constructs and returns a {@link DataQueryBuilder} instance. Override this * to return alternative query builders that have different functions or to * change attributes in the one returned by default. * * @return A DataQueryBuilder to use for creating the {@link DataQuery} */ protected DataQueryBuilder createDataQueryBuilder() { return new DataQueryBuilder(); } protected DataQuery buildDataQuery(String baseStatement, boolean includeOrdering, Paginator paginator) { DataQueryBuilder builder = new DataQueryBuilder(); builder.setProvider(this); builder.setBaseStatement(baseStatement); if (includeOrdering) { builder.setOrderBy(translateOrderKey(paginator.getOrderKey())); builder.setOrderAscending(paginator.isOrderAscending()); } return builder.build(); } }