package org.zstack.core.keyvalue;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.Bucket;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.serializable.SerializableHelper;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class KeyValueQuery<T> {
private static CLogger logger = Utils.getLogger(KeyValueQuery.class);
private class Condition {
String condName;
Object value;
String sql;
String entityName;
}
@Autowired
private DatabaseFacade dbf;
private T entityProxy;
private KeyValueEntityProxy<T> proxy;
private List<Bucket> opAndVals = new ArrayList<Bucket>();
private Class entityClass;
private int counter;
private int entityNameCounter;
private List<Condition> conditions = new ArrayList<Condition>();
private String sql;
public KeyValueQuery(Class<T> clz) {
proxy = new KeyValueEntityProxy<T>(clz);
entityProxy = proxy.getProxyEntity();
entityClass = clz;
}
private String makeValueName() {
return String.format("value%s", counter++);
}
private String makeEntityName() {
return String.format("e%s", entityNameCounter++);
}
public T entity() {
return entityProxy;
}
public void and(Object _, Op op, Object...vals) {
opAndVals.add(Bucket.newBucket(op, vals));
}
private void done() {
for (int i=0; i<proxy.getPaths().size(); i++) {
String key = proxy.getPaths().get(i);
Bucket b = opAndVals.get(i);
Op op = b.get(0);
Object[] vals = b.safeGet(1);
if (op == Op.IN) {
DebugUtils.Assert(vals.length > 0, String.format("condition[%s] requires at least one parameter", op));
condIn(key, vals);
} else if (op == Op.NOT_IN) {
DebugUtils.Assert(vals.length > 0, String.format("condition[%s] requires at least one parameter", op));
condNotIn(key, vals);
} else if (op == Op.NULL) {
condNull(key);
} else if (op == Op.NOT_NULL) {
condNotNull(key);
} else {
DebugUtils.Assert(vals.length == 1, String.format("condition[%s] requires one parameter", op));
Condition c = new Condition();
c.condName = makeValueName();
c.entityName = makeEntityName();
c.sql = String.format("(%s.entityKey like '%s' and %s.entityValue %s :%s)", c.entityName, key, c.entityName, op, c.condName);
DebugUtils.Assert(vals[0] != null, String.format("condition[%s] doesn't support NULL value", op));
c.value = vals[0].toString();
conditions.add(c);
}
}
List<String> conds = new ArrayList<String>(opAndVals.size());
List<String> entityNameList = new ArrayList<String>();
List<String> joinConds = new ArrayList<String>();
for (Condition c : conditions) {
conds.add(c.sql);
entityNameList.add(String.format("KeyValueVO %s", c.entityName));
joinConds.add(String.format("e.uuid = %s.uuid", c.entityName));
}
sql = "select distinct e.uuid from KeyValueVO e, %s where e.className = :clz and %s and %s";
sql = String.format(sql, StringUtils.join(entityNameList, ","), StringUtils.join(joinConds, " and "), StringUtils.join(conds, " and "));
if (logger.isTraceEnabled()) {
logger.trace(sql);
}
}
@Transactional(readOnly = true)
private List<KeyValueBinaryVO> listBinaryVO() {
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("clz", entityClass.getName());
for (Condition c : conditions) {
if (c.condName != null) {
q.setParameter(c.condName, c.value);
}
}
List<String> uuids = q.getResultList();
if (uuids.isEmpty()) {
return new ArrayList<KeyValueBinaryVO>();
}
TypedQuery<KeyValueBinaryVO> bq = dbf.getEntityManager().createQuery("select e from KeyValueBinaryVO e where e.uuid in (:uuids)", KeyValueBinaryVO.class);
bq.setParameter("uuids", uuids);
return bq.getResultList();
}
private List<T> listObject() {
done();
List<KeyValueBinaryVO> vos = listBinaryVO();
if (vos.isEmpty()) {
return new ArrayList<T>();
}
return CollectionUtils.transformToList(vos, new Function<T, KeyValueBinaryVO>() {
@Override
public T call(KeyValueBinaryVO arg) {
try {
return SerializableHelper.readObject(arg.getContents());
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
});
}
private void condNotNull(String key) {
Condition c = new Condition();
c.entityName = makeEntityName();
c.sql = String.format("(%s.entityKey like '%s')", c.entityName, key);
conditions.add(c);
}
private void condNull(String key) {
Condition c = new Condition();
c.entityName = makeEntityName();
c.sql = String.format("(%s.uuid not in (select %s_.uuid from KeyValueVO %s_ where %s_.entityKey like '%s'))",
c.entityName, c.entityName, c.entityName, c.entityName, key);
conditions.add(c);
}
private void condNotIn(String key, Object[] vals) {
List<String> values = new ArrayList<String>(vals.length);
for (Object v : vals) {
DebugUtils.Assert(v!=null, "values for condition 'NOT IN' cannot be null");
values.add(v.toString());
}
Condition c = new Condition();
c.condName = makeValueName();
c.value = values;
c.entityName = makeEntityName();
c.sql = String.format("(%s.entityKey like '%s' and %s.entityValue not in (:%s))", c.entityName, key, c.entityName, c.condName);
conditions.add(c);
}
private void condIn(String key, Object[] vals) {
List<String> values = new ArrayList<String>(vals.length);
for (Object v : vals) {
DebugUtils.Assert(v!=null, "values for condition 'IN' cannot be null");
values.add(v.toString());
}
Condition c = new Condition();
c.condName = makeValueName();
c.entityName = makeEntityName();
c.value = values;
c.sql = String.format("(%s.entityKey like '%s' and %s.entityValue in (:%s))", c.entityName, key, c.entityName, c.condName);
conditions.add(c);
}
public List<T> list() {
return listObject();
}
public T find() {
List<T> lst = listObject();
if (lst.isEmpty()) {
return null;
}
DebugUtils.Assert(lst.size() == 1, String.format("find expects only one result, but %s got", lst.size()));
return lst.get(0);
}
}