package org.deephacks.confit.internal.jpa.query;
import com.google.common.collect.Lists;
import org.deephacks.confit.admin.query.BeanQuery;
import org.deephacks.confit.admin.query.BeanQueryBuilder.BeanRestriction;
import org.deephacks.confit.admin.query.BeanQueryBuilder.Between;
import org.deephacks.confit.admin.query.BeanQueryBuilder.Equals;
import org.deephacks.confit.admin.query.BeanQueryBuilder.GreaterThan;
import org.deephacks.confit.admin.query.BeanQueryBuilder.Has;
import org.deephacks.confit.admin.query.BeanQueryBuilder.In;
import org.deephacks.confit.admin.query.BeanQueryBuilder.LessThan;
import org.deephacks.confit.admin.query.BeanQueryBuilder.LogicalRestriction;
import org.deephacks.confit.admin.query.BeanQueryBuilder.Not;
import org.deephacks.confit.admin.query.BeanQueryBuilder.PropertyRestriction;
import org.deephacks.confit.admin.query.BeanQueryBuilder.StringContains;
import org.deephacks.confit.admin.query.BeanQueryResult;
import org.deephacks.confit.internal.jpa.Jpa20BeanManager;
import org.deephacks.confit.model.Bean;
import org.deephacks.confit.model.Schema;
import org.deephacks.confit.model.ThreadLocalManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.List;
import static org.deephacks.confit.admin.query.BeanQueryBuilder.equal;
public class JpaBeanQuery implements BeanQuery {
private static Logger log = LoggerFactory.getLogger(JpaBeanQuery.class);
private static final String INNER_JOIN_PROPERTY = "INNER JOIN CONFIG_PROPERTY p%d ON b.BEAN_ID = p%d.FK_BEAN_ID ";
private static final String INNER_JOIN_REFERENCE = "INNER JOIN CONFIG_BEAN_REF r%d ON b.BEAN_ID = r%d.FK_SOURCE_BEAN_ID ";
private final EntityManager em;
private final Jpa20BeanManager manager;
private final Schema schema;
private String schemaName;
private List<String> join = new ArrayList<>();
private List<String> where = new ArrayList<>();
private int maxResults = Integer.MAX_VALUE;
private int firstResult = 0;
private boolean autoCommit;
public JpaBeanQuery(Schema schema, EntityManager em, Jpa20BeanManager manager, boolean autoCommit) {
this.schema = schema;
this.schemaName = schema.getName();
this.em = em;
this.manager = manager;
this.autoCommit = autoCommit;
}
@Override
public BeanQuery add(BeanRestriction restriction) {
if(restriction instanceof PropertyRestriction) {
PropertyRestriction propertyRestriction = ((PropertyRestriction) restriction);
String property = propertyRestriction.getProperty();
String alias = schema.isReference(property) ? String.format("r%d", where.size()) : String.format("p%d", where.size());
String column = schema.isReference(property) ? "FK_TARGET_BEAN_ID" : "PROP_VALUE";
int count = join.size();
String joinStmt = schema.isReference(property) ? String.format(INNER_JOIN_REFERENCE, count, count) : String.format(INNER_JOIN_PROPERTY, count, count);
join.add(joinStmt);
if (restriction instanceof Equals) {
Object value = ((Equals) restriction).getValue();
String query;
if (propertyRestriction.isNot()) {
query = String.format(" (%s.prop_name='%s' AND NOT %s.%s='%s') ", alias, property, alias, column, value);
} else {
query = String.format(" (%s.prop_name='%s' AND %s.%s='%s') ", alias, property, alias, column, value);
}
where.add(query);
return this;
} else if (restriction instanceof StringContains) {
Object value = ((StringContains) restriction).getValue();
String query;
if (propertyRestriction.isNot()) {
query = String.format(" (%s.prop_name='%s' AND %s.%s NOT LIKE '%%%s%%') ", alias, property, alias, column, value);
} else {
query = String.format(" (%s.prop_name='%s' AND %s.%s LIKE '%%%s%%') ", alias, property, alias, column, value);
}
where.add(query);
return this;
} else if (restriction instanceof Between) {
/*
Between between = ((Between) restriction);
Comparable lower = between.getLower();
Comparable upper = between.getUpper();
return this;
*/
throw new UnsupportedOperationException("'Between' not implemented yet");
} else if (restriction instanceof GreaterThan) {
Comparable value = ((GreaterThan) restriction).getValue();
String cast = castValue(value, where.size());
String query;
if (propertyRestriction.isNot()) {
query = String.format(" (%s.prop_name='%s' AND NOT %s > %s) ", alias, property, cast, value);
} else {
query = String.format(" (%s.prop_name='%s' AND %s > %s) ", alias, property, cast, value);
}
where.add(query);
return this;
} else if (restriction instanceof LessThan) {
Comparable value = ((LessThan) restriction).getValue();
String cast = castValue(value, where.size());
String query;
if (propertyRestriction.isNot()) {
query = String.format(" (%s.prop_name='%s' AND NOT %s < %s) ", alias, property, cast, value);
} else {
query = String.format(" (%s.prop_name='%s' AND %s < %s) ", alias, property, cast, value);
}
where.add(query);
return this;
} else if (restriction instanceof Has) {
throw new UnsupportedOperationException("'Has' not implemented yet");
} else if (restriction instanceof In) {
In in = ((In) restriction);
List<Object> values = in.getValues();
for (Object value : values) {
PropertyRestriction equal = (PropertyRestriction) equal(property, value);
if(in.isNot()) {
equal.setNot();
}
add(equal);
}
return this;
} else {
throw new IllegalArgumentException("Could not identify restriction: " + restriction);
}
} else if(restriction instanceof LogicalRestriction) {
if (restriction instanceof Not) {
// support only one logical NOT statement at the moment
PropertyRestriction not = (PropertyRestriction) ((Not) restriction).getRestrictions().get(0);
not.setNot();
add(not);
return this;
}
throw new UnsupportedOperationException("logical restriction not supported " + restriction);
}
throw new UnsupportedOperationException("Could not identify restriction: " + restriction);
}
@Override
public BeanQuery setFirstResult(String firstResult) {
try {
this.firstResult = Integer.parseInt(firstResult);
} catch (Exception e) {
throw new IllegalArgumentException("Could not parse firstResult to an int.");
}
return this;
}
@Override
public BeanQuery setMaxResults(int maxResults) {
this.maxResults = maxResults;
return this;
}
private String castValue(Comparable value, int i) {
String type;
if(value instanceof Double || value instanceof Float) {
type = "DECIMAL";
} else if(value instanceof Long ||
value instanceof Integer ||
value instanceof Short ||
value instanceof Byte) {
type = "BIGINT";
} else {
return String.format("p%d.prop_value", i);
}
return String.format("CAST(p%d.prop_value AS %s)", i, type);
}
@Override
public BeanQueryResult retrieve() {
try {
StringBuilder sb = new StringBuilder();
sb.append("SELECT b.BEAN_ID FROM CONFIG_BEAN b ");
for (int i = 0; i < join.size(); i++) {
sb.append(join.get(i));
}
sb.append(" WHERE ");
sb.append(String.format("b.BEAN_SCHEMA_NAME='%s'", schemaName));
for (int i = 0; i < where.size(); i++) {
sb.append(" AND ");
sb.append(where.get(i));
}
sb.append(" GROUP BY b.BEAN_ID ");
Query q = em.createNativeQuery(sb.toString());
q.setFirstResult(firstResult);
q.setMaxResults(maxResults);
List<String> instanceIds = q.getResultList();
final List<Bean> result = Lists.newArrayList(manager.list(schemaName, instanceIds).values());
commit(em);
return new BeanQueryResult() {
@Override
public List<Bean> get() {
return result;
}
@Override
public String nextFirstResult() {
return Integer.toString(firstResult + maxResults);
}
};
} catch (Throwable e) {
rollback(em);
throw new RuntimeException(e);
} finally {
closeEntityManager(em);
}
}
public void commit(EntityManager em) {
if (autoCommit && em.getTransaction().isActive()) {
em.getTransaction().commit();
em.clear();
closeEntityManager(em);
} else {
log.warn("Cannot rollback tx, no transaction is active.");
}
}
public void rollback(EntityManager em) {
if (em.getTransaction().isActive()) {
em.getTransaction().rollback();
em.clear();
closeEntityManager(em);
} else {
log.warn("Cannot rollback tx, no transaction is active.");
}
}
public void closeEntityManager(EntityManager manager) {
ThreadLocalManager.pop(EntityManager.class);
if (manager == null) {
log.warn("Cannot close, no EntityManager was found in thread local.");
return;
}
if (!manager.isOpen()) {
log.warn("Cannot close, EntityManager has already been closed.");
return;
}
manager.close();
}
}