/*
* Copyright 2008-2016 the original author or authors.
*
* 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.github.geequery.springdata.repository.query;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import jef.common.PairIO;
import jef.common.wrapper.IntRange;
import jef.database.Condition;
import jef.database.Condition.Operator;
import jef.database.DbUtils;
import jef.database.Field;
import jef.database.IConditionField.And;
import jef.database.IConditionField.Or;
import jef.database.QB;
import jef.database.dialect.type.ColumnMapping;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.query.ConditionQuery;
import jef.database.query.Query;
import jef.database.query.SqlExpression;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.repository.query.parser.PartTree.OrPart;
import org.springframework.orm.jpa.EntityManagerProxy;
import com.github.geequery.springdata.annotation.IgnoreIf;
import com.github.geequery.springdata.repository.query.GqParameters.GqParameter;
import com.github.geequery.springdata.repository.query.GqQueryExecution.CountExecution;
import com.github.geequery.springdata.repository.query.GqQueryExecution.DeleteExecution;
/**
* A {@link AbstractJpaQuery} implementation based on a {@link PartTree}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class GqPartTreeQuery extends AbstractGqQuery {
private final ITableMetadata metadata;
private final PartTree tree;
private final GqParameters parameters;
private final EntityManagerProxy em;
/**
* Creates a new {@link PartTreeJpaQuery}.
*
* @param method
* must not be {@literal null}.
* @param factory
* must not be {@literal null}.
* @param em
* must not be {@literal null}.
*/
public GqPartTreeQuery(GqQueryMethod method, EntityManagerProxy em) {
super(method, em);
this.em = em;
this.metadata = MetaHolder.getMeta(method.getEntityInformation()
.getJavaType());
this.tree = new PartTree(method.getName(), metadata.getThisType());
this.parameters = method.getParameters();
// boolean recreationRequired = parameters.hasDynamicProjection() ||
// parameters.potentiallySortsDynamically();
}
@Override
protected GqQueryExecution getExecution() {
if(tree.isDelete()){
return new DeleteExecution();
}
if(tree.isCountProjection()){
return new CountExecution();
}
return super.getExecution();
}
private Query<?> createQuery(Object[] values, boolean withPageSort) {
Query<?> q = QB.create(metadata);
ParametersParameterAccessor accessor = new ParametersParameterAccessor(
parameters, values);
Or or = new Or();
int index = 0;
for (OrPart node : tree) {
And and = new And();
for (Part part : node) {
PropertyPath path = part.getProperty();
if (path.getOwningType().getType() != metadata.getThisType()) {
throw new IllegalArgumentException("PathType:"
+ path.getOwningType().getType() + " metadata:"
+ metadata.getThisType());
}
String fieldName = path.getSegment();
ColumnMapping field = metadata.findField(fieldName);
PairIO<GqParameter> paramInfo = getBindParamIndex(index++,
fieldName);
Object obj = accessor.getBindableValue(paramInfo.first);
if (field != null) {
IgnoreIf ignore = paramInfo.second.getIgnoreIf();
if (ignore == null || !QueryUtils.isIgnore(ignore, obj)) {
add(and, part, field.field(), obj);
}
}
}
or.addCondition(and);
}
q.addCondition(or);
if (withPageSort) {
Sort sort = tree.getSort();
if (accessor.getSort() != null) {
sort = accessor.getSort();
}
Pageable page = accessor.getPageable();
if (page != null && page.getSort() != null) {
sort = page.getSort();
}
if (sort != null)
setSortToSpec(q, sort, metadata);
}
return q;
}
// FIXME use Binder to optmize.
private PairIO<GqParameter> getBindParamIndex(int index, String fieldName) {
int i = 0;
for (GqParameter param : this.parameters) {
if (param.getName() == null) {
if (index == param.getIndex()) {
return new PairIO<GqParameter>(i, param);
}
} else {
if (fieldName.equals(param.getName())) {
return new PairIO<GqParameter>(i, param);
}
}
i++;
}
throw new NoSuchElementException("Can not found bind parameter '"
+ fieldName + "' in method " + this.getQueryMethod().getName());
}
private void add(And and, Part part, Field field, Object value) {
switch (part.getType()) {
case SIMPLE_PROPERTY:
and.addCondition(QB.eq(field, value));
break;
case BETWEEN:
if (value instanceof Collection<?>) {
Object[] objs = ((Collection<?>) value).toArray();
assertTwObjects(objs.length);
and.addCondition(Condition.get(field, Operator.BETWEEN_L_L,
objs));
} else if (value instanceof int[]) {
int[] objs = (int[]) value;
assertTwObjects(objs.length);
and.addCondition(Condition.get(field, Operator.BETWEEN_L_L,
new Object[] { objs[0], objs[1] }));
} else if (value instanceof long[]) {
long[] objs = (long[]) value;
assertTwObjects(objs.length);
and.addCondition(Condition.get(field, Operator.BETWEEN_L_L,
new Object[] { objs[0], objs[1] }));
} else if (value instanceof Object[]) {
Object[] objs = (Object[]) value;
assertTwObjects(objs.length);
and.addCondition(Condition.get(field, Operator.BETWEEN_L_L,
objs));
} else {
throw new IllegalArgumentException(
"The condition value of IN must be a 'Collection' or 'Object[]'");
}
case ENDING_WITH:
and.addCondition(QB.matchEnd(field, String.valueOf(value)));
break;
case STARTING_WITH:
and.addCondition(QB.not(QB.matchStart(field, String.valueOf(value))));
break;
case CONTAINING:
and.addCondition(QB.matchAny(field, String.valueOf(value)));
break;
case GREATER_THAN:
and.addCondition(QB.gt(field, value));
break;
case GREATER_THAN_EQUAL:
and.addCondition(QB.ge(field, value));
break;
case IN:
if (value instanceof Collection<?>) {
and.addCondition(QB.in(field, (Collection<?>) value));
} else if (value instanceof int[]) {
and.addCondition(QB.in(field, (int[]) value));
} else if (value instanceof long[]) {
and.addCondition(QB.in(field, (long[]) value));
} else if (value instanceof Object[]) {
and.addCondition(QB.in(field, (Object[]) value));
} else {
throw new IllegalArgumentException(
"The condition value of IN must be a 'Collection' or 'Object[]'");
}
break;
case IS_NOT_NULL:
and.addCondition(QB.notNull(field));
break;
case IS_NULL:
and.addCondition(QB.isNull(field));
break;
case LESS_THAN:
and.addCondition(QB.lt(field, value));
break;
case LESS_THAN_EQUAL:
and.addCondition(QB.le(field, value));
break;
case LIKE:
and.addCondition(QB.like(field, String.valueOf(value)));
break;
case NOT_CONTAINING:
and.addCondition(QB.not(QB.matchAny(field, String.valueOf(value))));
break;
case NOT_IN:
and.addCondition(QB.not(QB.notin(field, (Object[]) value)));
break;
case NOT_LIKE:
and.addCondition(QB.not(QB.like(field, String.valueOf(value))));
break;
case NEAR:
throw new UnsupportedOperationException();
case NEGATING_SIMPLE_PROPERTY:
and.addCondition(QB.not(QB.eq(field, value)));
break;
case AFTER:
throw new UnsupportedOperationException();
case BEFORE:
throw new UnsupportedOperationException();
case TRUE:
throw new UnsupportedOperationException();
case WITHIN:
throw new UnsupportedOperationException();
case REGEX:
throw new UnsupportedOperationException();
case EXISTS:
throw new UnsupportedOperationException();
case FALSE:
throw new UnsupportedOperationException();
}
}
private void assertTwObjects(int length) {
if (length != 2) {
throw new IllegalArgumentException(
"The condition of BETWEEN must be 2 elements. but ["
+ length + "]");
}
}
@Override
protected List<?> getResultList(Object[] values, Pageable page) {
Query<?> q = createQuery(values, true);
IntRange range = (page == null) ? null : toRange(page);
try {
return getSession().select(q, range);
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
private IntRange toRange(Pageable pageable) {
return new IntRange(pageable.getOffset() + 1, pageable.getOffset()
+ pageable.getPageSize());
}
private void setSortToSpec(ConditionQuery spec, Sort sort,
ITableMetadata meta) {
for (Order order : sort) {
Field field;
ColumnMapping column = meta.findField(order.getProperty());
if (column == null) {
field = new SqlExpression(order.getProperty());
} else {
field = column.field();
}
spec.addOrderBy(order.isAscending(), field);
}
}
@Override
protected Object getSingleResult(Object[] values) {
Query<?> q = createQuery(values, true);
try {
return getSession().load(q, false);
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
@Override
protected int executeUpdate(Object[] values) {
Query<?> q = createQuery(values, false);
try {
return getSession().update(q.getInstance());
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
@Override
protected int executeDelete(Object[] values) {
Query<?> q = createQuery(values, false);
try {
return getSession().delete(q);
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
@Override
protected long getResultCount(Object[] values) {
Query<?> q = createQuery(values, false);
try {
return getSession().countLong(q);
} catch (SQLException e) {
throw DbUtils.toRuntimeException(e);
}
}
}