mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
408 lines
15 KiB
Java
408 lines
15 KiB
Java
/**
|
|
* Copyright (C) 2010 Cloud.com, Inc. All rights reserved.
|
|
*
|
|
* This software is licensed under the GNU General Public License v3 or later.
|
|
*
|
|
* It is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or any later version.
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
package com.cloud.utils.db;
|
|
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import net.sf.cglib.proxy.Factory;
|
|
import net.sf.cglib.proxy.MethodInterceptor;
|
|
import net.sf.cglib.proxy.MethodProxy;
|
|
|
|
import com.cloud.utils.Ternary;
|
|
import com.cloud.utils.db.SearchCriteria.Func;
|
|
import com.cloud.utils.db.SearchCriteria.Op;
|
|
import com.cloud.utils.db.SearchCriteria.SelectType;
|
|
import com.cloud.utils.exception.CloudRuntimeException;
|
|
|
|
/**
|
|
* GenericSearchBuilder is used to build a search based on a VO object
|
|
* and return it in an object specified by the caller. Note that the
|
|
* VO object and the return object can be the same type. There is
|
|
* a convenience class provided called SearchBuilder that provides
|
|
* exactly that functionality.
|
|
*
|
|
* @param <T> VO object this Search is build for.
|
|
* @param <K> Result object that should contain the results.
|
|
*/
|
|
public class GenericSearchBuilder<T, K> implements MethodInterceptor {
|
|
final protected Map<String, Attribute> _attrs;
|
|
|
|
protected ArrayList<Condition> _conditions;
|
|
protected HashMap<String, Ternary<GenericSearchBuilder<?, ?>, Attribute, Attribute>> _joins;
|
|
protected ArrayList<Select> _selects;
|
|
protected ArrayList<Attribute> _groupBys;
|
|
protected Class<T> _entityBeanType;
|
|
protected Class<K> _resultType;
|
|
protected SelectType _selectType;
|
|
|
|
protected T _entity;
|
|
protected ArrayList<Attribute> _specifiedAttrs;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
protected GenericSearchBuilder(T entity, Class<K> clazz, Map<String, Attribute> attrs) {
|
|
_entityBeanType = (Class<T>)entity.getClass();
|
|
_resultType = clazz;
|
|
|
|
_attrs = attrs;
|
|
_entity = entity;
|
|
_conditions = new ArrayList<Condition>();
|
|
_joins = null;
|
|
_specifiedAttrs = new ArrayList<Attribute>();
|
|
}
|
|
|
|
public T entity() {
|
|
return _entity;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> selectField(Object... useless) {
|
|
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
|
|
assert _specifiedAttrs.size() > 0 : "You didn't specify any attributes";
|
|
|
|
if (_selects == null) {
|
|
_selects = new ArrayList<Select>();
|
|
}
|
|
|
|
for (Attribute attr : _specifiedAttrs) {
|
|
Field field = null;
|
|
try {
|
|
field = _resultType.getDeclaredField(attr.field.getName());
|
|
field.setAccessible(true);
|
|
} catch (SecurityException e) {
|
|
} catch (NoSuchFieldException e) {
|
|
}
|
|
_selects.add(new Select(Func.NATIVE, attr, field, null));
|
|
}
|
|
|
|
_specifiedAttrs.clear();
|
|
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> select(String fieldName, Func func, Object useless, Object... params) {
|
|
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
|
|
assert _specifiedAttrs.size() <= 1 : "You can't specify more than one field to search on";
|
|
assert func.getCount() == -1 || (func.getCount() == (params.length + 1)) : "The number of parameters does not match the function param count for " + func;
|
|
|
|
if (_selects == null) {
|
|
_selects = new ArrayList<Select>();
|
|
}
|
|
|
|
Field field = null;
|
|
if (fieldName != null) {
|
|
try {
|
|
field = _resultType.getDeclaredField(fieldName);
|
|
field.setAccessible(true);
|
|
} catch (SecurityException e) {
|
|
throw new CloudRuntimeException("Unable to find " + fieldName, e);
|
|
} catch (NoSuchFieldException e) {
|
|
throw new CloudRuntimeException("Unable to find " + fieldName, e);
|
|
}
|
|
} else {
|
|
assert _selects.size() == 0 : "You're selecting more than one item and yet is not providing a container class to put these items in. So what do you expect me to do. Spin magic?";
|
|
}
|
|
|
|
Select select = new Select(func, _specifiedAttrs.size() == 0 ? null : _specifiedAttrs.get(0), field, params);
|
|
_selects.add(select);
|
|
|
|
_specifiedAttrs.clear();
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
|
|
String name = method.getName();
|
|
if (name.startsWith("get")) {
|
|
String fieldName = Character.toLowerCase(name.charAt(3)) + name.substring(4);
|
|
set(fieldName);
|
|
} else if (name.startsWith("is")) {
|
|
String fieldName = Character.toLowerCase(name.charAt(2)) + name.substring(3);
|
|
set(fieldName);
|
|
} else {
|
|
assert false : "Perhaps you need to make the method start with get or is?";
|
|
}
|
|
return methodProxy.invokeSuper(object, args);
|
|
}
|
|
|
|
protected void set(String name) {
|
|
Attribute attr = _attrs.get(name);
|
|
assert (attr != null) : "Searching for a field that's not there: " + name;
|
|
_specifiedAttrs.add(attr);
|
|
}
|
|
|
|
/**
|
|
* Adds an AND condition to the SearchBuilder.
|
|
*
|
|
* @param name param name you will use later to set the values in this search condition.
|
|
* @param useless SearchBuilder.entity().get*() which refers to the field that you're searching on.
|
|
* @param op operation to apply to the field.
|
|
* @return this
|
|
*/
|
|
public GenericSearchBuilder<T, K> and(String name, Object useless, Op op) {
|
|
constructCondition(name, " AND ", _specifiedAttrs.get(0), op);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> and() {
|
|
constructCondition(null, " AND ", null, null);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> where() {
|
|
return and();
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> or() {
|
|
constructCondition(null, " OR ", null, null);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> where(String name, Object useless, Op op) {
|
|
return and(name, useless, op);
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> left(String name, Object useless, Op op) {
|
|
constructCondition(name, " ( ", _specifiedAttrs.get(0), op);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> op(String name, Object useless, Op op) {
|
|
return left(name, useless, op);
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> openParen(String name, Object useless, Op op) {
|
|
return left(name, useless, op);
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> groupBy(Object... useless) {
|
|
if(_groupBys == null) {
|
|
_groupBys = new ArrayList<Attribute>();
|
|
}
|
|
|
|
Attribute[] attrs = _specifiedAttrs.toArray(new Attribute[_specifiedAttrs.size()]);
|
|
for(Attribute attr : attrs)
|
|
_groupBys.add(attr);
|
|
|
|
_specifiedAttrs.clear();
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Adds an OR condition to the SearchBuilder.
|
|
*
|
|
* @param name param name you will use later to set the values in this search condition.
|
|
* @param useless SearchBuilder.entity().get*() which refers to the field that you're searching on.
|
|
* @param op operation to apply to the field.
|
|
* @return this
|
|
*/
|
|
public GenericSearchBuilder<T, K> or(String name, Object useless, Op op) {
|
|
constructCondition(name, " OR ", _specifiedAttrs.get(0), op);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> join(String name, GenericSearchBuilder<?, ?> builder, Object useless, Object useless2) {
|
|
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
|
|
assert _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
|
|
assert builder._entity != null : "SearchBuilder cannot be modified once it has been setup";
|
|
assert builder._specifiedAttrs.size() == 1 : "You didn't select the attribute.";
|
|
assert builder != this : "You can't add yourself, can you? Really think about it!";
|
|
|
|
Ternary<GenericSearchBuilder<?, ?>, Attribute, Attribute> t = new Ternary<GenericSearchBuilder<?, ?>, Attribute, Attribute>(builder, _specifiedAttrs.get(0), builder._specifiedAttrs.get(0));
|
|
if (_joins == null) {
|
|
_joins = new HashMap<String, Ternary<GenericSearchBuilder<?, ?>, Attribute, Attribute>>();
|
|
}
|
|
_joins.put(name, t);
|
|
|
|
builder._specifiedAttrs.clear();
|
|
_specifiedAttrs.clear();
|
|
return this;
|
|
}
|
|
|
|
protected void constructCondition(String conditionName, String cond, Attribute attr, Op op) {
|
|
assert _entity != null : "SearchBuilder cannot be modified once it has been setup";
|
|
assert op == null || _specifiedAttrs.size() == 1 : "You didn't select the attribute.";
|
|
assert op != Op.SC : "Call join";
|
|
|
|
Condition condition = new Condition(conditionName, cond, attr, op);
|
|
_conditions.add(condition);
|
|
_specifiedAttrs.clear();
|
|
}
|
|
|
|
/**
|
|
* creates the SearchCriteria so the actual values can be filled in.
|
|
*
|
|
* @return SearchCriteria
|
|
*/
|
|
public SearchCriteria<K> create() {
|
|
if (_entity != null) {
|
|
done();
|
|
}
|
|
return new SearchCriteria<K>(this);
|
|
}
|
|
|
|
public SearchCriteria<K> create(String name, Object... values) {
|
|
SearchCriteria<K> sc = create();
|
|
sc.setParameters(name, values);
|
|
return sc;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> right() {
|
|
Condition condition = new Condition("rp", " ) ", null, Op.RP);
|
|
_conditions.add(condition);
|
|
return this;
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> cp() {
|
|
return right();
|
|
}
|
|
|
|
public GenericSearchBuilder<T, K> closeParen() {
|
|
return right();
|
|
}
|
|
|
|
public SelectType getSelectType() {
|
|
return _selectType;
|
|
}
|
|
|
|
/**
|
|
* Marks the SearchBuilder as completed in building the search conditions.
|
|
*/
|
|
public synchronized void done() {
|
|
if (_entity != null) {
|
|
Factory factory = (Factory)_entity;
|
|
factory.setCallback(0, null);
|
|
_entity = null;
|
|
}
|
|
|
|
if (_joins != null) {
|
|
for (Ternary<GenericSearchBuilder<?, ?>, Attribute, Attribute> join : _joins.values()) {
|
|
join.first().done();
|
|
}
|
|
}
|
|
|
|
if (_selects == null || _selects.size() == 0) {
|
|
_selectType = SelectType.Entity;
|
|
assert _entityBeanType.equals(_resultType) : "Expecting " + _entityBeanType + " because you didn't specify any selects but instead got " + _resultType;
|
|
return;
|
|
}
|
|
|
|
for (Select select : _selects) {
|
|
if (select.field == null) {
|
|
assert (_selects.size() == 1) : "You didn't specify any fields to put the result in but you're specifying more than one select so where should I put the selects?";
|
|
_selectType = SelectType.Single;
|
|
return;
|
|
}
|
|
if (select.func != null) {
|
|
_selectType = SelectType.Result;
|
|
return;
|
|
}
|
|
}
|
|
|
|
_selectType = SelectType.Fields;
|
|
}
|
|
|
|
protected static class Condition {
|
|
protected final String name;
|
|
protected final String cond;
|
|
protected final Op op;
|
|
protected final Attribute attr;
|
|
|
|
protected Condition(String name) {
|
|
this(name, null, null, null);
|
|
}
|
|
|
|
public Condition(String name, String cond, Attribute attr, Op op) {
|
|
this.name = name;
|
|
this.attr = attr;
|
|
this.cond = cond;
|
|
this.op = op;
|
|
}
|
|
|
|
public void toSql(StringBuilder sql, Object[] params, int count) {
|
|
if (count > 0) {
|
|
sql.append(cond);
|
|
}
|
|
|
|
if (op == null) {
|
|
return;
|
|
}
|
|
|
|
if (op == Op.SC) {
|
|
sql.append(" (").append(((SearchCriteria)params[0]).getWhereClause()).append(") ");
|
|
return;
|
|
}
|
|
|
|
if (attr == null) {
|
|
return;
|
|
}
|
|
|
|
sql.append(attr.table).append(".").append(attr.columnName).append(op.toString());
|
|
if (op.getParams() == -1) {
|
|
for (int i = 0; i < params.length; i++) {
|
|
sql.insert(sql.length() - 2, "?,");
|
|
}
|
|
sql.delete(sql.length() - 3, sql.length() - 2); // remove the last ,
|
|
} else if (op == Op.EQ && (params == null || params.length == 0 || params[0] == null)) {
|
|
sql.delete(sql.length() - 4, sql.length());
|
|
sql.append(" IS NULL ");
|
|
} else if (op == Op.NEQ && (params == null || params.length == 0 || params[0] == null)) {
|
|
sql.delete(sql.length() - 5, sql.length());
|
|
sql.append(" IS NOT NULL ");
|
|
} else {
|
|
assert((op.getParams() == 0 && params == null) || (params.length == op.getParams())) : "Problem with condition: " + name;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return name.hashCode();
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj) {
|
|
if (!(obj instanceof Condition)) {
|
|
return false;
|
|
}
|
|
|
|
Condition condition = (Condition)obj;
|
|
return name.equals(condition.name);
|
|
}
|
|
}
|
|
|
|
protected static class Select {
|
|
public Func func;
|
|
public Attribute attr;
|
|
public Object[] params;
|
|
public Field field;
|
|
|
|
protected Select() {
|
|
}
|
|
|
|
public Select(Func func, Attribute attr, Field field, Object[] params) {
|
|
this.func = func;
|
|
this.attr = attr;
|
|
this.params = params;
|
|
this.field = field;
|
|
}
|
|
}
|
|
}
|