/*
* Copyright 2012 Adaptrex, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.adaptrex.core.persistence.cayenne;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.access.DataContext;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.query.EJBQLQuery;
import org.apache.cayenne.query.Ordering;
import org.apache.cayenne.query.SelectQuery;
import org.apache.cayenne.query.SortOrder;
import org.apache.log4j.Logger;
import com.adaptrex.core.ext.ExtConfig;
import com.adaptrex.core.ext.ExtTypeFormatter;
import com.adaptrex.core.ext.Filter;
import com.adaptrex.core.ext.ModelInstance;
import com.adaptrex.core.ext.Sorter;
import com.adaptrex.core.persistence.api.AdaptrexEntityType;
import com.adaptrex.core.persistence.api.BaseStoreData;
import com.adaptrex.core.persistence.api.AdaptrexStoreData;
import com.adaptrex.core.utilities.StringUtilities;
public class CayenneStoreData extends BaseStoreData implements AdaptrexStoreData {
private ExtConfig extConfig;
private static Logger log = Logger.getLogger(CayenneStoreData.class);
public CayenneStoreData(ExtConfig extConfig) {
this.extConfig = extConfig;
}
@Override
public List<Map<String, Object>> getData() throws Exception {
List<Map<String, Object>> data = new ArrayList<Map<String, Object>>();
Class<?> clazz = extConfig.getEntityClass();
CayennePersistenceManager cayenne = (CayennePersistenceManager) extConfig.getORMPersistenceManager();
ObjectContext objectContext = cayenne.getReadOnlyObjectContext();
AdaptrexEntityType adaptrexEntity = cayenne.getAdaptrexEntity(clazz.getSimpleName());
/*
* If we have a where clause, fall back to JPQL
*/
if (this.getWhere() != null && !this.getWhere().isEmpty()) {
return getWhereFallback(cayenne, objectContext, clazz, data);
}
try {
/*
* Create the expressions
*/
Expression expression = null;
for (Filter filter : this.getFilters()) {
Object val = filter.getValue();
if (val == null || String.valueOf(val).isEmpty()) continue;
String key = filter.getProperty();
String extFieldType = adaptrexEntity.getField(key).getFieldDefinition().getType();
Expression newExpression = null;
/*
* String filter
*
* Should be consistent with ExtJS filtering
* By default, the settings are: exactMatch:false, caseSensitive:false, anyMatch:false
*/
if (extFieldType.equals(ExtTypeFormatter.STRING)) {
if (filter.getCaseSensitive()) {
if (filter.getExactMatch()) {
newExpression = ExpressionFactory.matchExp(key, val);
} else if (filter.getAnyMatch()) {
newExpression = ExpressionFactory.likeExp(key, "%" + val + "%");
} else {
newExpression = ExpressionFactory.likeExp(key, val + "%");
}
} else {
if (filter.getExactMatch()) {
newExpression = ExpressionFactory.likeIgnoreCaseExp(key, val);
} else if (filter.getAnyMatch()) {
newExpression = ExpressionFactory.likeIgnoreCaseExp(key, "%" + val + "%");
} else {
newExpression = ExpressionFactory.likeIgnoreCaseExp(key, val + "%");
}
}
/*
* Boolean filter
*/
} else if (extFieldType.equals(ExtTypeFormatter.BOOLEAN)) {
newExpression = ExpressionFactory.matchExp(key, String.valueOf(val).toLowerCase().equals("true"));
/*
* Integer filter
*
* Inline stores allow flexibility not directly available in Ext. Since we
* control the component tags, we can test for various additional conditions.
* Since this is server side only, local filtering should not also be applied
*/
} else if (extFieldType.equals(ExtTypeFormatter.INT)) {
Integer numVal = null;
try {
numVal = (val instanceof Integer)
? (Integer) val
: Integer.valueOf(String.valueOf(val));
} catch (Exception e) {
log.warn("Adaptrex Store Error: Filter: Couldn't parse integer: " + val);
continue;
}
/*
* Filter based on various conditions
*/
Integer t = filter.getFilterType();
if (t == Filter.EQUAL) {
newExpression = ExpressionFactory.matchExp(key, numVal);
} else if (t == Filter.NOT_EQUAL) {
newExpression = ExpressionFactory.noMatchExp(key, numVal);
} else if (t == Filter.LESS_THAN) {
newExpression = ExpressionFactory.lessExp(key, numVal);
} else if (t == Filter.LESS_THAN_EQUAL) {
newExpression = ExpressionFactory.lessOrEqualExp(key, numVal);
} else if (t == Filter.GREATER_THAN) {
newExpression = ExpressionFactory.greaterExp(key, numVal);
} else if (t == Filter.GREATER_THAN_EQUAL) {
newExpression = ExpressionFactory.greaterOrEqualExp(key, numVal);
}
/*
* Float filter
*
* Similar to integer filter in that we can test for additional conditions
* not avaialable in standard Ext filtering. Do not rely on these filters
* to work as local filters. Also, since we can't currently test for float
* equality, we should only be checking for less than or greater than at
* this time.
*
* In the future, we may add additional filter settings which would allow
* us to test for float equality up to a certain precision.
*/
} else if (extFieldType.equals(ExtTypeFormatter.FLOAT)) {
Double numVal = null;
try {
numVal = (val instanceof Double)
? (Double) val
: Double.valueOf(String.valueOf(val));
} catch (Exception e) {
log.warn("Adaptrex Store Error: Filter: Couldn't parse float: " + val);
continue;
}
Integer t = filter.getFilterType();
if (t == Filter.EQUAL) {
log.warn("Adaptrex Store Error: Filter: Float equality not implemented: " + val);
continue;
} else if (t == Filter.NOT_EQUAL) {
log.warn("Adaptrex Store Error: Filter: Float inequality not implemented: " + val);
continue;
} else if (t == Filter.LESS_THAN || t == Filter.LESS_THAN_EQUAL) {
newExpression = ExpressionFactory.lessOrEqualExp(key, numVal);
} else if (t == Filter.GREATER_THAN || t == Filter.GREATER_THAN_EQUAL) {
newExpression = ExpressionFactory.greaterOrEqualExp(key, numVal);
}
/*
* Date filter
*
* Also simililar to integer. We can test for several date comparison
* conditions not available in standard Ext filtering.
*/
} else if (extFieldType.equals(ExtTypeFormatter.DATE)) {
Date dateVal = (Date) ExtTypeFormatter.parse(val, Date.class);
Integer t = filter.getFilterType();
if (t == Filter.EQUAL) {
newExpression = ExpressionFactory.matchExp(key, dateVal);
} else if (t == Filter.NOT_EQUAL) {
newExpression = ExpressionFactory.noMatchExp(key, dateVal);
} else if (t == Filter.LESS_THAN) {
newExpression = ExpressionFactory.lessExp(key, dateVal);
} else if (t == Filter.LESS_THAN_EQUAL) {
newExpression = ExpressionFactory.lessOrEqualExp(key, dateVal);
} else if (t == Filter.GREATER_THAN) {
newExpression = ExpressionFactory.greaterExp(key, dateVal);
} else if (t == Filter.GREATER_THAN_EQUAL) {
newExpression = ExpressionFactory.greaterOrEqualExp(key, dateVal);
}
}
if (expression == null) {
expression = newExpression;
} else {
expression = expression.andExp(newExpression);
}
}
/*
* Create query
*/
SelectQuery query = new SelectQuery(clazz, expression);
SelectQuery countQuery = new SelectQuery(clazz, expression);
/*
* Add sorters
*/
List<String> spentSort = new ArrayList<String>();
this.getGroupers().addAll(this.getSorters());
for (Sorter sorter : this.getGroupers()) {
String prop = sorter.getProperty();
if (spentSort.contains(prop)) continue;
spentSort.add(prop);
String d = sorter.getDirection();
boolean isDesc = d != null && d.toLowerCase().equals("desc");
Ordering ordering = new Ordering(prop, isDesc ? SortOrder.DESCENDING : SortOrder.ASCENDING);
query.addOrdering(ordering);
}
/*
* Start and limit
*/
if (this.getLimit() != null) query.setFetchLimit(this.getLimit());
if (this.getStart() != null) query.setFetchOffset(this.getStart().intValue());
for (Object entity : objectContext.performQuery(query)) {
data.add(new ModelInstance(extConfig, entity).getData());
}
/*
* Total Count / Inspired by http://goo.gl/W6rhG
*/
this.setTotalCount(CayenneTools.count(
(DataContext) objectContext, countQuery));
} catch (Exception e) {
throw new RuntimeException(e);
}
return data;
}
private List<Map<String, Object>> getWhereFallback(
CayennePersistenceManager cayenne, ObjectContext objectContext, Class<?> clazz, List<Map<String, Object>> data) throws Exception {
String className = clazz.getSimpleName();
String alias = className.toLowerCase().substring(0, 1);
/*
* Start the JPQL
*/
StringBuffer jpql = new StringBuffer();
jpql.append("SELECT " + alias + " FROM " + className + " " + alias + " ");
jpql.append("WHERE " + this.getWhere());
StringBuffer jpqlCount = new StringBuffer();
jpqlCount.append("SELECT COUNT (" + alias + ") FROM " + className + " " + alias + " ");
jpqlCount.append("WHERE " + this.getWhere());
/*
* ExtJS sends sorting parameters along with grouping parameters
* for the same grouper. Unsure if this behavior makes sense or
* may change in the future. Instead, we should filter out the
* duplicate sort.
*/
List<String> spentSort = new ArrayList<String>();
String sortClause = "";
this.getGroupers().addAll(this.getSorters());
if (this.getGroupers().size() > 0) {
for (Sorter sorter : this.getGroupers()) {
String prop = sorter.getProperty();
if (spentSort.contains(prop)) continue;
spentSort.add(prop);
if (!sortClause.isEmpty()) sortClause += ",";
sortClause += alias + "." + prop + " " + sorter.getDirection();
}
}
if (!sortClause.isEmpty()) {
jpql.append(" ORDER BY " + sortClause);
}
/*
* Create EJBQL queries
*/
EJBQLQuery selectQuery = new EJBQLQuery(jpql.toString());
EJBQLQuery countQuery = new EJBQLQuery(jpqlCount.toString());
/*
* Add named parameters
*/
Map<String,Object> params = this.getParams();
for (String key : params.keySet()) {
String capKey = StringUtilities.capitalize(key);
/*
* Make sure we have a field for this parameter
*/
Method paramGetter = null;
try {
paramGetter = clazz.getMethod("get" + capKey);
} catch (Exception e) {}
if (paramGetter == null) {
try {
paramGetter = clazz.getMethod("is" + capKey);
} catch (Exception e) {}
}
if (paramGetter == null) continue;
Object paramObj = ExtTypeFormatter.parse(params.get(key), paramGetter.getReturnType());
selectQuery.setParameter(key, paramObj);
countQuery.setParameter(key, paramObj);
}
this.setTotalCount((Long) objectContext.performQuery(countQuery).get(0));
if (this.getLimit() != null) selectQuery.setFetchLimit(this.getLimit());
if (this.getStart() != null) selectQuery.setFetchOffset(this.getStart().intValue());
for (Object entity : objectContext.performQuery(selectQuery)) {
data.add(new ModelInstance(extConfig, entity).getData());
}
return data;
}
}