blob: 0eb45439769e944ab16b4a08886499f4900400e5 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// 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.cloud.utils.db;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import javax.naming.ConfigurationException;
import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.EntityExistsException;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import com.amazonaws.util.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.log4j.Logger;
import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.component.ComponentLifecycle;
import com.cloud.utils.component.ComponentLifecycleBase;
import com.cloud.utils.component.ComponentMethodInterceptable;
import com.cloud.utils.crypt.DBEncryptionUtil;
import com.cloud.utils.db.SearchCriteria.SelectType;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.Ip;
import com.cloud.utils.net.NetUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.NoOp;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
/**
* GenericDaoBase is a simple way to implement DAOs. It DOES NOT
* support the full EJB3 spec. It borrows some of the annotations from
* the EJB3 spec to produce a set of SQLs so developers don't have to
* copy and paste the same code over and over again. Of course,
* GenericDaoBase is completely at the mercy of the annotations you add
* to your entity bean. If GenericDaoBase does not fit your needs, then
* don't extend from it.
*
* GenericDaoBase attempts to achieve the following:
* 1. If you use _allFieldsStr in your SQL statement and use to() to convert
* the result to the entity bean, you don't ever have to worry about
* missing fields because its automatically taken from the entity bean's
* annotations.
* 2. You don't have to rewrite the same insert and select query strings
* in all of your DAOs.
* 3. You don't have to match the '?' (you know what I'm talking about) to
* the fields in the insert statement as that's taken care of for you.
*
* GenericDaoBase looks at the following annotations:
* 1. Table - just name
* 2. Column - just name
* 3. GeneratedValue - any field with this annotation is not inserted.
* 4. SequenceGenerator - sequence generator
* 5. Id
* 6. SecondaryTable
*
* Sometime later, I might look into injecting the SQLs as needed but right
* now we have to construct them at construction time. The good thing is that
* the DAOs are suppose to be one per jvm so the time is all during the
* initial load.
*
**/
@DB
public abstract class GenericDaoBase<T, ID extends Serializable> extends ComponentLifecycleBase implements GenericDao<T, ID>, ComponentMethodInterceptable {
private final static Logger s_logger = Logger.getLogger(GenericDaoBase.class);
protected final static TimeZone s_gmtTimeZone = TimeZone.getTimeZone("GMT");
protected final static Map<Class<?>, GenericDao<?, ? extends Serializable>> s_daoMaps = new ConcurrentHashMap<Class<?>, GenericDao<?, ? extends Serializable>>(71);
protected Class<T> _entityBeanType;
protected String _table;
protected String _tables;
protected Field[] _embeddedFields;
// This is private on purpose. Everyone should use createPartialSelectSql()
private final Pair<StringBuilder, Attribute[]> _partialSelectSql;
private final Pair<StringBuilder, Attribute[]> _partialQueryCacheSelectSql;
protected StringBuilder _discriminatorClause;
protected Map<String, Object> _discriminatorValues;
protected String _selectByIdSql;
protected String _count;
protected String _distinctIdSql;
protected Field _idField;
protected List<Pair<String, Attribute[]>> _insertSqls;
protected Pair<String, Attribute> _removed;
protected Pair<String, Attribute[]> _removeSql;
protected List<Pair<String, Attribute[]>> _deleteSqls;
protected Map<String, Attribute[]> _idAttributes;
protected Map<String, TableGenerator> _tgs;
protected Map<String, Attribute> _allAttributes;
protected List<Attribute> _ecAttributes;
protected Map<Pair<String, String>, Attribute> _allColumns;
protected Enhancer _enhancer;
protected Factory _factory;
protected Enhancer _searchEnhancer;
protected int _timeoutSeconds;
protected final static CallbackFilter s_callbackFilter = new UpdateFilter();
protected static final String FOR_UPDATE_CLAUSE = " FOR UPDATE ";
protected static final String SHARE_MODE_CLAUSE = " LOCK IN SHARE MODE";
protected static final String SELECT_LAST_INSERT_ID_SQL = "SELECT LAST_INSERT_ID()";
public static final Date DATE_TO_NULL = new Date(Long.MIN_VALUE);
private static final String INTEGRITY_CONSTRAINT_VIOLATION = "23000";
private static final int DUPLICATE_ENTRY_ERRO_CODE = 1062;
protected static final SequenceFetcher s_seqFetcher = SequenceFetcher.getInstance();
public static <J> GenericDao<? extends J, ? extends Serializable> getDao(Class<J> entityType) {
@SuppressWarnings("unchecked")
GenericDao<? extends J, ? extends Serializable> dao = (GenericDao<? extends J, ? extends Serializable>)s_daoMaps.get(entityType);
assert dao != null : "Unable to find DAO for " + entityType + ". Are you sure you waited for the DAO to be initialized before asking for it?";
return dao;
}
@Override
@SuppressWarnings("unchecked")
@DB()
public <J> GenericSearchBuilder<T, J> createSearchBuilder(Class<J> resultType) {
return new GenericSearchBuilder<T, J>(_entityBeanType, resultType);
}
@Override
public Map<String, Attribute> getAllAttributes() {
return _allAttributes;
}
@SuppressWarnings("unchecked")
public T createSearchEntity(MethodInterceptor interceptor) {
T entity = (T)_searchEnhancer.create();
final Factory factory = (Factory)entity;
factory.setCallback(0, interceptor);
return entity;
}
@SuppressWarnings("unchecked")
protected GenericDaoBase() {
super();
Type t = getClass().getGenericSuperclass();
if (t instanceof ParameterizedType) {
_entityBeanType = (Class<T>)((ParameterizedType)t).getActualTypeArguments()[0];
} else if (((Class<?>)t).getGenericSuperclass() instanceof ParameterizedType) {
_entityBeanType = (Class<T>)((ParameterizedType)((Class<?>)t).getGenericSuperclass()).getActualTypeArguments()[0];
} else {
_entityBeanType = (Class<T>)((ParameterizedType)((Class<?>)((Class<?>)t).getGenericSuperclass()).getGenericSuperclass()).getActualTypeArguments()[0];
}
s_daoMaps.put(_entityBeanType, this);
Class<?>[] interphaces = _entityBeanType.getInterfaces();
if (interphaces != null) {
for (Class<?> interphace : interphaces) {
s_daoMaps.put(interphace, this);
}
}
_table = DbUtil.getTableName(_entityBeanType);
final SqlGenerator generator = new SqlGenerator(_entityBeanType);
_partialSelectSql = generator.buildSelectSql(false);
_count = generator.buildCountSql();
_distinctIdSql= generator.buildDistinctIdSql();
_partialQueryCacheSelectSql = generator.buildSelectSql(true);
_embeddedFields = generator.getEmbeddedFields();
_insertSqls = generator.buildInsertSqls();
final Pair<StringBuilder, Map<String, Object>> dc = generator.buildDiscriminatorClause();
_discriminatorClause = dc.first().length() == 0 ? null : dc.first();
_discriminatorValues = dc.second();
_idAttributes = generator.getIdAttributes();
_idField = _idAttributes.get(_table).length > 0 ? _idAttributes.get(_table)[0].field : null;
_tables = generator.buildTableReferences();
_allAttributes = generator.getAllAttributes();
_allColumns = generator.getAllColumns();
_selectByIdSql = buildSelectByIdSql(createPartialSelectSql(null, true));
_removeSql = generator.buildRemoveSql();
_deleteSqls = generator.buildDeleteSqls();
_removed = generator.getRemovedAttribute();
_tgs = generator.getTableGenerators();
_ecAttributes = generator.getElementCollectionAttributes();
TableGenerator tg = this.getClass().getAnnotation(TableGenerator.class);
if (tg != null) {
_tgs.put(tg.name(), tg);
}
tg = this.getClass().getSuperclass().getAnnotation(TableGenerator.class);
if (tg != null) {
_tgs.put(tg.name(), tg);
}
Callback[] callbacks = new Callback[] {NoOp.INSTANCE, new UpdateBuilder(this)};
_enhancer = new Enhancer();
_enhancer.setSuperclass(_entityBeanType);
_enhancer.setCallbackFilter(s_callbackFilter);
_enhancer.setCallbacks(callbacks);
_factory = (Factory)_enhancer.create();
_searchEnhancer = new Enhancer();
_searchEnhancer.setSuperclass(_entityBeanType);
_searchEnhancer.setCallback(new UpdateBuilder(this));
if (s_logger.isTraceEnabled()) {
s_logger.trace("Select SQL: " + _partialSelectSql.first().toString());
s_logger.trace("Remove SQL: " + (_removeSql != null ? _removeSql.first() : "No remove sql"));
s_logger.trace("Select by Id SQL: " + _selectByIdSql);
s_logger.trace("Table References: " + _tables);
s_logger.trace("Insert SQLs:");
for (final Pair<String, Attribute[]> insertSql : _insertSqls) {
s_logger.trace(insertSql.first());
}
s_logger.trace("Delete SQLs");
for (final Pair<String, Attribute[]> deletSql : _deleteSqls) {
s_logger.trace(deletSql.first());
}
s_logger.trace("Collection SQLs");
for (Attribute attr : _ecAttributes) {
EcInfo info = (EcInfo)attr.attache;
s_logger.trace(info.insertSql);
s_logger.trace(info.selectSql);
}
}
setRunLevel(ComponentLifecycle.RUN_LEVEL_SYSTEM);
}
@Override
@DB()
@SuppressWarnings("unchecked")
public T createForUpdate(final ID id) {
final T entity = (T)_factory.newInstance(new Callback[] {NoOp.INSTANCE, new UpdateBuilder(this)});
if (id != null) {
try {
_idField.set(entity, id);
} catch (final IllegalArgumentException e) {
} catch (final IllegalAccessException e) {
}
}
return entity;
}
@Override
@DB()
public T createForUpdate() {
return createForUpdate(null);
}
@Override
@DB()
public <K> K getNextInSequence(final Class<K> clazz, final String name) {
final TableGenerator tg = _tgs.get(name);
assert (tg != null) : "Couldn't find Table generator using " + name;
return s_seqFetcher.getNextSequence(clazz, tg);
}
@Override
@DB()
public <K> K getRandomlyIncreasingNextInSequence(final Class<K> clazz, final String name) {
final TableGenerator tg = _tgs.get(name);
assert (tg != null) : "Couldn't find Table generator using " + name;
return s_seqFetcher.getRandomNextSequence(clazz, tg);
}
@Override
@DB()
public List<T> lockRows(final SearchCriteria<T> sc, final Filter filter, final boolean exclusive) {
return search(sc, filter, exclusive, false);
}
@Override
@DB()
public T lockOneRandomRow(final SearchCriteria<T> sc, final boolean exclusive) {
final Filter filter = new Filter(1);
final List<T> beans = search(sc, filter, exclusive, true);
return beans.isEmpty() ? null : beans.get(0);
}
@DB()
protected List<T> search(SearchCriteria<T> sc, final Filter filter, final Boolean lock, final boolean cache) {
sc = checkAndSetRemovedIsNull(sc);
return searchIncludingRemoved(sc, filter, lock, cache);
}
@DB()
protected List<T> search(SearchCriteria<T> sc, final Filter filter, final Boolean lock, final boolean cache, final boolean enableQueryCache) {
sc = checkAndSetRemovedIsNull(sc);
return searchIncludingRemoved(sc, filter, lock, cache, enableQueryCache);
}
@Override
public List<T> searchIncludingRemoved(SearchCriteria<T> sc, final Filter filter, final Boolean lock, final boolean cache) {
return searchIncludingRemoved(sc, filter, lock, cache, false);
}
@Override
public List<T> searchIncludingRemoved(SearchCriteria<T> sc, final Filter filter, final Boolean lock, final boolean cache, final boolean enableQueryCache) {
String clause = sc != null ? sc.getWhereClause() : null;
if (clause != null && clause.length() == 0) {
clause = null;
}
final StringBuilder str = createPartialSelectSql(sc, clause != null, enableQueryCache);
if (clause != null) {
str.append(clause);
}
Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
List<Attribute> joinAttrList = null;
if (sc != null) {
joins = sc.getJoins();
if (joins != null) {
joinAttrList = addJoins(str, joins);
}
}
List<Object> groupByValues = addGroupBy(str, sc);
addFilter(str, filter);
final TransactionLegacy txn = TransactionLegacy.currentTxn();
if (lock != null) {
assert (txn.dbTxnStarted() == true) : "As nice as I can here now....how do you lock when there's no DB transaction? Review your db 101 course from college.";
str.append(lock ? FOR_UPDATE_CLAUSE : SHARE_MODE_CLAUSE);
}
final String sql = str.toString();
PreparedStatement pstmt = null;
final List<T> result = new ArrayList<T>();
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 1;
if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
for (Attribute attr : joinAttrList) {
prepareAttribute(i++, pstmt, attr, null);
}
}
if (clause != null) {
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
}
if (joins != null) {
i = addJoinAttributes(i, pstmt, joins);
}
if (groupByValues != null) {
for (Object value : groupByValues) {
pstmt.setObject(i++, value);
}
}
if (s_logger.isDebugEnabled() && lock != null) {
txn.registerLock(pstmt.toString());
}
final ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
result.add(toEntityBean(rs, cache));
}
return result;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
@Override
@SuppressWarnings("unchecked")
public <M> List<M> customSearchIncludingRemoved(SearchCriteria<M> sc, final Filter filter) {
if (sc == null) {
throw new CloudRuntimeException("Call to customSearchIncludingRemoved with null search Criteria");
}
if (sc.isSelectAll()) {
return (List<M>)searchIncludingRemoved((SearchCriteria<T>)sc, filter, null, false);
}
String clause = sc.getWhereClause();
if (clause != null && clause.length() == 0) {
clause = null;
}
final StringBuilder str = createPartialSelectSql(sc, clause != null);
if (clause != null) {
str.append(clause);
}
Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
joins = sc.getJoins();
List<Attribute> joinAttrList = null;
if (joins != null) {
joinAttrList = addJoins(str, joins);
}
List<Object> groupByValues = addGroupBy(str, sc);
addFilter(str, filter);
final String sql = str.toString();
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 1;
if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
for (Attribute attr : joinAttrList) {
prepareAttribute(i++, pstmt, attr, null);
}
}
if (clause != null) {
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
}
if (joins != null) {
i = addJoinAttributes(i, pstmt, joins);
}
if (groupByValues != null) {
for (Object value : groupByValues) {
pstmt.setObject(i++, value);
}
}
ResultSet rs = pstmt.executeQuery();
SelectType st = sc.getSelectType();
ArrayList<M> results = new ArrayList<M>();
List<Field> fields = sc.getSelectFields();
while (rs.next()) {
if (st == SelectType.Entity) {
results.add((M)toEntityBean(rs, false));
} else if (st == SelectType.Fields || st == SelectType.Result) {
M m = sc.getResultType().newInstance();
for (int j = 1; j <= fields.size(); j++) {
setField(m, fields.get(j - 1), rs, j);
}
results.add(m);
} else if (st == SelectType.Single) {
results.add(getObject(sc.getResultType(), rs, 1));
}
}
return results;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
@Override
@DB()
public <M> List<M> customSearch(SearchCriteria<M> sc, final Filter filter) {
if (_removed != null) {
sc.addAnd(_removed.second().field.getName(), SearchCriteria.Op.NULL);
}
return customSearchIncludingRemoved(sc, filter);
}
@DB()
protected void setField(Object entity, Field field, ResultSet rs, int index) throws SQLException {
try {
final Class<?> type = field.getType();
if (type == String.class) {
byte[] bytes = rs.getBytes(index);
if (bytes != null) {
try {
Encrypt encrypt = field.getAnnotation(Encrypt.class);
if (encrypt != null && encrypt.encrypt()) {
field.set(entity, DBEncryptionUtil.decrypt(new String(bytes, "UTF-8")));
} else {
field.set(entity, new String(bytes, "UTF-8"));
}
} catch (IllegalArgumentException e) {
assert (false);
throw new CloudRuntimeException("IllegalArgumentException when converting UTF-8 data");
} catch (UnsupportedEncodingException e) {
assert (false);
throw new CloudRuntimeException("UnsupportedEncodingException when converting UTF-8 data");
}
} else {
field.set(entity, null);
}
} else if (type == long.class) {
field.setLong(entity, rs.getLong(index));
} else if (type == Long.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getLong(index));
}
} else if (type.isEnum()) {
final Enumerated enumerated = field.getAnnotation(Enumerated.class);
final EnumType enumType = (enumerated == null) ? EnumType.STRING : enumerated.value();
final Enum<?>[] enums = (Enum<?>[])field.getType().getEnumConstants();
for (final Enum<?> e : enums) {
if ((enumType == EnumType.STRING && e.name().equalsIgnoreCase(rs.getString(index))) ||
(enumType == EnumType.ORDINAL && e.ordinal() == rs.getInt(index))) {
field.set(entity, e);
return;
}
}
} else if (type == int.class) {
field.set(entity, rs.getInt(index));
} else if (type == Integer.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getInt(index));
}
} else if (type == Date.class) {
final Object data = rs.getDate(index);
if (data == null) {
field.set(entity, null);
return;
}
field.set(entity, DateUtil.parseDateString(s_gmtTimeZone, rs.getString(index)));
} else if (type == Calendar.class) {
final Object data = rs.getDate(index);
if (data == null) {
field.set(entity, null);
return;
}
final Calendar cal = Calendar.getInstance();
cal.setTime(DateUtil.parseDateString(s_gmtTimeZone, rs.getString(index)));
field.set(entity, cal);
} else if (type == boolean.class) {
field.setBoolean(entity, rs.getBoolean(index));
} else if (type == Boolean.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getBoolean(index));
}
} else if (type == URI.class) {
try {
String str = rs.getString(index);
field.set(entity, str == null ? null : new URI(str));
} catch (URISyntaxException e) {
throw new CloudRuntimeException("Invalid URI: " + rs.getString(index), e);
}
} else if (type == URL.class) {
try {
String str = rs.getString(index);
field.set(entity, str != null ? new URL(str) : null);
} catch (MalformedURLException e) {
throw new CloudRuntimeException("Invalid URL: " + rs.getString(index), e);
}
} else if (type == Ip.class) {
final Enumerated enumerated = field.getAnnotation(Enumerated.class);
final EnumType enumType = (enumerated == null) ? EnumType.STRING : enumerated.value();
Ip ip = null;
if (enumType == EnumType.STRING) {
String s = rs.getString(index);
ip = s == null ? null : new Ip(NetUtils.ip2Long(s));
} else {
ip = new Ip(rs.getLong(index));
}
field.set(entity, ip);
} else if (type == short.class) {
field.setShort(entity, rs.getShort(index));
} else if (type == Short.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getShort(index));
}
} else if (type == float.class) {
field.setFloat(entity, rs.getFloat(index));
} else if (type == Float.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getFloat(index));
}
} else if (type == double.class) {
field.setDouble(entity, rs.getDouble(index));
} else if (type == Double.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getDouble(index));
}
} else if (type == byte.class) {
field.setByte(entity, rs.getByte(index));
} else if (type == Byte.class) {
if (rs.getObject(index) == null) {
field.set(entity, null);
} else {
field.set(entity, rs.getByte(index));
}
} else if (type == byte[].class) {
field.set(entity, rs.getBytes(index));
} else {
field.set(entity, rs.getObject(index));
}
} catch (final IllegalAccessException e) {
throw new CloudRuntimeException("Yikes! ", e);
}
}
/**
* Get a value from a result set.
*
* @param type
* the expected type of the result
* @param rs
* the result set
* @param index
* the index of the column
* @return the result in the requested type
* @throws SQLException
*/
@DB()
@SuppressWarnings("unchecked")
protected static <M> M getObject(Class<M> type, ResultSet rs, int index) throws SQLException {
if (type == String.class) {
byte[] bytes = rs.getBytes(index);
if (bytes != null) {
try {
return (M)new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new CloudRuntimeException("UnsupportedEncodingException exception while converting UTF-8 data");
}
} else {
return null;
}
} else if (type == int.class) {
return (M) (Integer) rs.getInt(index);
} else if (type == Integer.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Integer) rs.getInt(index);
}
} else if (type == long.class) {
return (M) (Long) rs.getLong(index);
} else if (type == Long.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Long) rs.getLong(index);
}
} else if (type == Date.class) {
final Object data = rs.getDate(index);
if (data == null) {
return null;
} else {
return (M)DateUtil.parseDateString(s_gmtTimeZone, rs.getString(index));
}
} else if (type == short.class) {
return (M) (Short) rs.getShort(index);
} else if (type == Short.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Short) rs.getShort(index);
}
} else if (type == boolean.class) {
return (M) (Boolean) rs.getBoolean(index);
} else if (type == Boolean.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Boolean) rs.getBoolean(index);
}
} else if (type == float.class) {
return (M) (Float) rs.getFloat(index);
} else if (type == Float.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Float) rs.getFloat(index);
}
} else if (type == double.class) {
return (M) (Double) rs.getDouble(index);
} else if (type == Double.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Double) rs.getDouble(index);
}
} else if (type == byte.class) {
return (M) (Byte) rs.getByte(index);
} else if (type == Byte.class) {
if (rs.getObject(index) == null) {
return null;
} else {
return (M) (Byte) rs.getByte(index);
}
} else if (type == Calendar.class) {
final Object data = rs.getDate(index);
if (data == null) {
return null;
} else {
final Calendar cal = Calendar.getInstance();
cal.setTime(DateUtil.parseDateString(s_gmtTimeZone, rs.getString(index)));
return (M)cal;
}
} else if (type == byte[].class) {
return (M)rs.getBytes(index);
} else {
return (M)rs.getObject(index);
}
}
@DB()
protected int addJoinAttributes(int count, PreparedStatement pstmt, Collection<JoinBuilder<SearchCriteria<?>>> joins) throws SQLException {
for (JoinBuilder<SearchCriteria<?>> join : joins) {
for (final Pair<Attribute, Object> value : join.getT().getValues()) {
prepareAttribute(count++, pstmt, value.first(), value.second());
}
}
for (JoinBuilder<SearchCriteria<?>> join : joins) {
if (join.getT().getJoins() != null) {
count = addJoinAttributes(count, pstmt, join.getT().getJoins());
}
}
if (s_logger.isTraceEnabled()) {
s_logger.trace("join search statement is " + pstmt);
}
return count;
}
protected int update(ID id, UpdateBuilder ub, T entity) {
if (_cache != null) {
_cache.remove(id);
}
SearchCriteria<T> sc = createSearchCriteria();
sc.addAnd(_idAttributes.get(_table)[0], SearchCriteria.Op.EQ, id);
TransactionLegacy txn = TransactionLegacy.currentTxn();
txn.start();
try {
if (ub.getCollectionChanges() != null) {
insertElementCollection(entity, _idAttributes.get(_table)[0], id, ub.getCollectionChanges());
}
} catch (SQLException e) {
throw new CloudRuntimeException("Unable to persist element collection", e);
}
int rowsUpdated = update(ub, sc, null);
txn.commit();
return rowsUpdated;
}
public int update(UpdateBuilder ub, final SearchCriteria<?> sc, Integer rows) {
StringBuilder sql = null;
PreparedStatement pstmt = null;
final TransactionLegacy txn = TransactionLegacy.currentTxn();
try {
final String searchClause = sc.getWhereClause();
sql = ub.toSql(_tables);
if (sql == null) {
return 0;
}
sql.append(searchClause);
if (rows != null) {
sql.append(" LIMIT ").append(rows);
}
txn.start();
pstmt = txn.prepareAutoCloseStatement(sql.toString());
Collection<Ternary<Attribute, Boolean, Object>> changes = ub.getChanges();
int i = 1;
for (final Ternary<Attribute, Boolean, Object> value : changes) {
prepareAttribute(i++, pstmt, value.first(), value.third());
}
for (Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
int result = pstmt.executeUpdate();
txn.commit();
ub.clear();
return result;
} catch (final SQLException e) {
handleEntityExistsException(e);
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
}
}
/**
* If the SQLException.getSQLState is of 23000 (Integrity Constraint Violation), and the Error Code is 1062 (Duplicate Entry), throws EntityExistsException.
* @throws EntityExistsException
*/
protected static void handleEntityExistsException(SQLException e) throws EntityExistsException {
boolean isIntegrityConstantViolation = INTEGRITY_CONSTRAINT_VIOLATION.equals(e.getSQLState());
boolean isErrorCodeOfDuplicateEntry = e.getErrorCode() == DUPLICATE_ENTRY_ERRO_CODE;
if (isIntegrityConstantViolation && isErrorCodeOfDuplicateEntry) {
throw new EntityExistsException("Entity already exists ", e);
}
}
@DB()
protected Attribute findAttributeByFieldName(String name) {
return _allAttributes.get(name);
}
@DB()
protected String buildSelectByIdSql(final StringBuilder sql) {
if (_idField == null) {
return null;
}
if (_idField.getAnnotation(EmbeddedId.class) == null) {
sql.append(_table).append(".").append(DbUtil.getColumnName(_idField, null)).append(" = ? ");
} else {
s_logger.debug(String.format("field type vs declarator : %s vs %s", _idField.getType(), _idField.getDeclaringClass()));
final Class<?> clazz = _idField.getType();
final AttributeOverride[] overrides = DbUtil.getAttributeOverrides(_idField);
for (final Field field : clazz.getDeclaredFields()) {
sql.append(_table).append(".").append(DbUtil.getColumnName(field, overrides)).append(" = ? AND ");
}
removeAndClause(sql);
}
return sql.toString();
}
@DB()
@Override
public Class<T> getEntityBeanType() {
return _entityBeanType;
}
@DB()
protected T findOneIncludingRemovedBy(final SearchCriteria<T> sc) {
Filter filter = new Filter(1);
List<T> results = searchIncludingRemoved(sc, filter, null, false);
assert results.size() <= 1 : "Didn't the limiting worked?";
return results.size() == 0 ? null : results.get(0);
}
@Override
@DB()
public T findOneBy(SearchCriteria<T> sc) {
sc = checkAndSetRemovedIsNull(sc);
return findOneIncludingRemovedBy(sc);
}
@Override
@DB()
public T findOneBy(SearchCriteria<T> sc, final Filter filter) {
sc = checkAndSetRemovedIsNull(sc);
filter.setLimit(1L);
List<T> results = searchIncludingRemoved(sc, filter, null, false);
return results.isEmpty() ? null : results.get(0);
}
@DB()
protected List<T> listBy(SearchCriteria<T> sc, final Filter filter) {
sc = checkAndSetRemovedIsNull(sc);
return listIncludingRemovedBy(sc, filter);
}
@DB()
protected List<T> listBy(SearchCriteria<T> sc, final Filter filter, final boolean enableQueryCache) {
sc = checkAndSetRemovedIsNull(sc);
return listIncludingRemovedBy(sc, filter, enableQueryCache);
}
@DB()
public List<T> listBy(final SearchCriteria<T> sc) {
return listBy(sc, null);
}
@DB()
protected List<T> listIncludingRemovedBy(final SearchCriteria<T> sc, final Filter filter, final boolean enableQueryCache) {
return searchIncludingRemoved(sc, filter, null, false, enableQueryCache);
}
@DB()
protected List<T> listIncludingRemovedBy(final SearchCriteria<T> sc, final Filter filter) {
return searchIncludingRemoved(sc, filter, null, false);
}
@DB()
protected List<T> listIncludingRemovedBy(final SearchCriteria<T> sc) {
return listIncludingRemovedBy(sc, null);
}
@Override
@DB()
@SuppressWarnings("unchecked")
public T findById(final ID id) {
T result = null;
if (_cache != null) {
final Element element = _cache.get(id);
if (element == null) {
result = lockRow(id, null);
} else {
result = (T)element.getObjectValue();
}
} else {
result = lockRow(id, null);
}
return result;
}
@Override
@DB()
public T findByUuid(final String uuid) {
SearchCriteria<T> sc = createSearchCriteria();
sc.addAnd("uuid", SearchCriteria.Op.EQ, uuid);
return findOneBy(sc);
}
@Override
@DB()
public T findByUuidIncludingRemoved(final String uuid) {
SearchCriteria<T> sc = createSearchCriteria();
sc.addAnd("uuid", SearchCriteria.Op.EQ, uuid);
return findOneIncludingRemovedBy(sc);
}
@Override
@DB()
public T findByIdIncludingRemoved(final ID id) {
T result = null;
if (_cache != null) {
final Element element = _cache.get(id);
if (element == null) {
result = findById(id, true, null);
} else {
result = (T)element.getObjectValue();
}
} else {
result = findById(id, true, null);
}
return result;
}
@Override
@DB()
public T findById(final ID id, boolean fresh) {
if (!fresh) {
return findById(id);
}
if (_cache != null) {
_cache.remove(id);
}
return lockRow(id, null);
}
@Override
@DB()
public T lockRow(ID id, Boolean lock) {
return findById(id, false, lock);
}
protected T findById(ID id, boolean removed, Boolean lock) {
StringBuilder sql = new StringBuilder(_selectByIdSql);
if (!removed && _removed != null) {
sql.append(" AND ").append(_removed.first());
}
if (lock != null) {
sql.append(lock ? FOR_UPDATE_CLAUSE : SHARE_MODE_CLAUSE);
}
TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
pstmt = txn.prepareAutoCloseStatement(sql.toString());
if (_idField.getAnnotation(EmbeddedId.class) == null) {
prepareAttribute(1, pstmt, _idAttributes.get(_table)[0], id);
}
ResultSet rs = pstmt.executeQuery();
return rs.next() ? toEntityBean(rs, true) : null;
} catch (SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
}
}
@Override
@DB()
public T acquireInLockTable(ID id) {
return acquireInLockTable(id, _timeoutSeconds);
}
@Override
public T acquireInLockTable(final ID id, int seconds) {
TransactionLegacy txn = TransactionLegacy.currentTxn();
T t = null;
boolean locked = false;
try {
if (!txn.lock(_table + id.toString(), seconds)) {
return null;
}
locked = true;
t = findById(id);
return t;
} finally {
if (t == null && locked) {
txn.release(_table + id.toString());
}
}
}
@Override
public boolean releaseFromLockTable(final ID id) {
final TransactionLegacy txn = TransactionLegacy.currentTxn();
return txn.release(_table + id);
}
@Override
@DB()
public boolean lockInLockTable(final String id) {
return lockInLockTable(id, _timeoutSeconds);
}
@Override
public boolean lockInLockTable(final String id, int seconds) {
TransactionLegacy txn = TransactionLegacy.currentTxn();
return txn.lock(_table + id, seconds);
}
@Override
public boolean unlockFromLockTable(final String id) {
final TransactionLegacy txn = TransactionLegacy.currentTxn();
return txn.release(_table + id);
}
@Override
@DB()
public List<T> listAllIncludingRemoved() {
return listAllIncludingRemoved(null);
}
@DB()
protected List<Object> addGroupBy(final StringBuilder sql, SearchCriteria<?> sc) {
if (sc == null)
return null;
Pair<GroupBy<?, ?, ?>, List<Object>> groupBys = sc.getGroupBy();
if (groupBys != null) {
groupBys.first().toSql(sql);
return groupBys.second();
} else {
return null;
}
}
@DB()
protected void addFilter(final StringBuilder sql, final Filter filter) {
if (filter != null) {
if (filter.getOrderBy() != null) {
sql.append(filter.getOrderBy());
}
if (filter.getOffset() != null) {
sql.append(" LIMIT ");
sql.append(filter.getOffset());
if (filter.getLimit() != null) {
sql.append(", ").append(filter.getLimit());
}
}
}
}
@Override
@DB()
public List<T> listAllIncludingRemoved(final Filter filter) {
final StringBuilder sql = createPartialSelectSql(null, false);
addFilter(sql, filter);
return executeList(sql.toString());
}
protected List<T> executeList(final String sql, final Object... params) {
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
final List<T> result = new ArrayList<T>();
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 0;
for (final Object param : params) {
pstmt.setObject(++i, param);
}
final ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
result.add(toEntityBean(rs, true));
}
return result;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
@Override
@DB()
public List<T> listAll() {
return listAll(null);
}
@Override
@DB()
public List<T> listAll(final Filter filter) {
if (_removed == null) {
return listAllIncludingRemoved(filter);
}
final StringBuilder sql = createPartialSelectSql(null, true);
sql.append(_removed.first());
addFilter(sql, filter);
return executeList(sql.toString());
}
@Override
public boolean expunge(final ID id) {
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
String sql = null;
try {
txn.start();
for (final Pair<String, Attribute[]> deletSql : _deleteSqls) {
sql = deletSql.first();
final Attribute[] attrs = deletSql.second();
pstmt = txn.prepareAutoCloseStatement(sql);
for (int i = 0; i < attrs.length; i++) {
prepareAttribute(i + 1, pstmt, attrs[i], id);
}
pstmt.executeUpdate();
}
txn.commit();
if (_cache != null) {
_cache.remove(id);
}
return true;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
}
}
// FIXME: Does not work for joins.
@Override
public int expunge(final SearchCriteria<T> sc) {
if (sc == null) {
throw new CloudRuntimeException("Call to throw new expunge with null search Criteria");
}
final StringBuilder str = new StringBuilder("DELETE FROM ");
str.append(_table);
str.append(" WHERE ");
if (sc != null && sc.getWhereClause().length() > 0) {
str.append(sc.getWhereClause());
}
final String sql = str.toString();
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 0;
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(++i, pstmt, value.first(), value.second());
}
return pstmt.executeUpdate();
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
@DB()
protected StringBuilder createPartialSelectSql(SearchCriteria<?> sc, final boolean whereClause, final boolean enableQueryCache) {
StringBuilder sql = new StringBuilder(enableQueryCache ? _partialQueryCacheSelectSql.first() : _partialSelectSql.first());
if (sc != null && !sc.isSelectAll()) {
sql.delete(7, sql.indexOf(" FROM"));
sc.getSelect(sql, 7);
}
if (!whereClause) {
sql.delete(sql.length() - (_discriminatorClause == null ? 6 : 4), sql.length());
}
return sql;
}
@DB()
protected StringBuilder createPartialSelectSql(SearchCriteria<?> sc, final boolean whereClause) {
StringBuilder sql = new StringBuilder(_partialSelectSql.first());
if (sc != null && !sc.isSelectAll()) {
sql.delete(7, sql.indexOf(" FROM"));
sc.getSelect(sql, 7);
}
if (!whereClause) {
sql.delete(sql.length() - (_discriminatorClause == null ? 6 : 4), sql.length());
}
return sql;
}
@DB()
protected List<Attribute> addJoins(StringBuilder str, Collection<JoinBuilder<SearchCriteria<?>>> joins) {
return addJoins(str, joins, new HashMap<>());
}
@DB()
protected List<Attribute> addJoins(StringBuilder str, Collection<JoinBuilder<SearchCriteria<?>>> joins, Map<String, String> joinedTableNames) {
List<Attribute> joinAttrList = new ArrayList<>();
boolean hasWhereClause = true;
int fromIndex = str.lastIndexOf("WHERE");
if (fromIndex == -1) {
fromIndex = str.length();
hasWhereClause = false;
} else {
str.append(" AND ");
}
for (JoinBuilder<SearchCriteria<?>> join : joins) {
String joinTableName = join.getSecondAttribute()[0].table;
String joinTableAlias;
if (StringUtils.isNotEmpty(join.getName())) {
joinTableAlias = join.getName();
joinedTableNames.put(joinTableName, joinTableAlias);
} else {
joinTableAlias = joinedTableNames.getOrDefault(joinTableName, joinTableName);
}
StringBuilder onClause = new StringBuilder();
onClause.append(" ")
.append(join.getType().getName())
.append(" ")
.append(joinTableName);
if (!joinTableAlias.equals(joinTableName)) {
onClause.append(" ").append(joinTableAlias);
}
onClause.append(" ON ");
for (int i = 0; i < join.getFirstAttributes().length; i++) {
if (i > 0) {
onClause.append(join.getCondition().getName());
}
if (join.getFirstAttributes()[i].getValue() != null) {
onClause.append("?");
joinAttrList.add(join.getFirstAttributes()[i]);
} else {
onClause.append(joinedTableNames.getOrDefault(join.getFirstAttributes()[i].table, join.getFirstAttributes()[i].table))
.append(".")
.append(join.getFirstAttributes()[i].columnName);
}
onClause.append("=");
if (join.getSecondAttribute()[i].getValue() != null) {
onClause.append("?");
joinAttrList.add(join.getSecondAttribute()[i]);
} else {
if(!joinTableAlias.equals(joinTableName)) {
onClause.append(joinTableAlias);
} else {
onClause.append(joinTableName);
}
onClause.append(".")
.append(join.getSecondAttribute()[i].columnName);
}
}
onClause.append(" ");
str.insert(fromIndex, onClause);
String whereClause = join.getT().getWhereClause(joinTableAlias);
if (StringUtils.isNotEmpty(whereClause)) {
if (!hasWhereClause) {
str.append(" WHERE ");
hasWhereClause = true;
}
str.append(" (").append(whereClause).append(") AND");
}
fromIndex += onClause.length();
}
if (hasWhereClause) {
removeAndClause(str);
}
for (JoinBuilder<SearchCriteria<?>> join : joins) {
if (join.getT().getJoins() != null) {
joinAttrList.addAll(addJoins(str, join.getT().getJoins(), joinedTableNames));
}
}
return joinAttrList;
}
private void removeAndClause(StringBuilder sql) {
sql.delete(sql.length() - 4, sql.length());
}
@Override
@DB()
public List<T> search(final SearchCriteria<T> sc, final Filter filter) {
return search(sc, filter, null, false);
}
@Override
public Pair<List<T>, Integer> searchAndCount(final SearchCriteria<T> sc, final Filter filter) {
return searchAndCount(sc, filter, false);
}
@Override
@DB()
public Pair<List<T>, Integer> searchAndCount(SearchCriteria<T> sc, final Filter filter, boolean includeRemoved) {
if (!includeRemoved) {
sc = checkAndSetRemovedIsNull(sc);
}
List<T> objects = searchIncludingRemoved(sc, filter, null, false);
int count = getCountIncludingRemoved(sc);
count = checkCountOfRecordsAgainstTheResultSetSize(count, objects.size());
return new Pair<List<T>, Integer>(objects, count);
}
/**
* Validates if the count of records is higher or equal to the result set's size.<br/><br/>
* Count cannot be less than the result set, however, it can be higher due to pagination (see CLOUDSTACK-10320).
* @return Count if it is higher or equal to the result set's size, otherwise the result set's size.
*/
protected int checkCountOfRecordsAgainstTheResultSetSize(int count, int resultSetSize) {
if (count >= resultSetSize) {
return count;
}
String stackTrace = ExceptionUtils.getStackTrace(new CloudRuntimeException(String.format("The query to count all the records of [%s] resulted in a value smaller than"
+ " the result set's size [count of records: %s, result set's size: %s]. Using the result set's size instead.", _entityBeanType,
count, resultSetSize)));
s_logger.warn(stackTrace);
return resultSetSize;
}
@Override
@DB()
public Pair<List<T>, Integer> searchAndDistinctCount(final SearchCriteria<T> sc, final Filter filter) {
List<T> objects = search(sc, filter, null, false);
Integer count = getDistinctCount(sc);
// Count cannot be 0 if there is at least a result in the list, see CLOUDSTACK-10320
if (count == 0 && !objects.isEmpty()) {
// Cannot assume if it's more than one since the count is distinct vs search
count = 1;
}
return new Pair<List<T>, Integer>(objects, count);
}
@Override
@DB()
public Pair<List<T>, Integer> searchAndDistinctCount(final SearchCriteria<T> sc, final Filter filter, final String[] distinctColumns) {
List<T> objects = search(sc, filter, null, false);
Integer count = getDistinctCount(sc, distinctColumns);
// Count cannot be 0 if there is at least a result in the list, see CLOUDSTACK-10320
if (count == 0 && !objects.isEmpty()) {
// Cannot assume if it's more than one since the count is distinct vs search
count = 1;
}
return new Pair<List<T>, Integer>(objects, count);
}
@Override
@DB()
public List<T> search(final SearchCriteria<T> sc, final Filter filter, final boolean enableQueryCache) {
return search(sc, filter, null, false, enableQueryCache);
}
@Override
@DB()
public boolean update(ID id, T entity) {
assert Enhancer.isEnhanced(entity.getClass()) : "Entity is not generated by this dao";
UpdateBuilder ub = getUpdateBuilder(entity);
boolean result = update(id, ub, entity) != 0;
return result;
}
@DB()
public int update(final T entity, final SearchCriteria<T> sc, Integer rows) {
final UpdateBuilder ub = getUpdateBuilder(entity);
return update(ub, sc, rows);
}
@Override
@DB()
public int update(final T entity, final SearchCriteria<T> sc) {
final UpdateBuilder ub = getUpdateBuilder(entity);
return update(ub, sc, null);
}
@Override
@SuppressWarnings("unchecked")
public T persist(final T entity) {
if (Enhancer.isEnhanced(entity.getClass())) {
if (_idField != null) {
ID id;
try {
id = (ID)_idField.get(entity);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException("How can it be illegal access...come on", e);
}
update(id, entity);
return entity;
}
assert false : "Can't call persit if you don't have primary key";
}
ID id = null;
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
String sql = null;
try {
txn.start();
for (final Pair<String, Attribute[]> pair : _insertSqls) {
sql = pair.first();
final Attribute[] attrs = pair.second();
pstmt = txn.prepareAutoCloseStatement(sql, Statement.RETURN_GENERATED_KEYS);
int index = 1;
index = prepareAttributes(pstmt, entity, attrs, index);
pstmt.executeUpdate();
final ResultSet rs = pstmt.getGeneratedKeys();
if (id == null) {
if (rs != null && rs.next()) {
id = (ID)rs.getObject(1);
}
try {
if (_idField != null) {
if (id != null) {
if (id instanceof BigInteger) {
_idField.set(entity, ((BigInteger) id).longValue());
} else {
_idField.set(entity, id);
}
}
id = (ID)_idField.get(entity);
}
} catch (final IllegalAccessException e) {
throw new CloudRuntimeException("Yikes! ", e);
}
}
}
if (_ecAttributes != null && _ecAttributes.size() > 0) {
HashMap<Attribute, Object> ecAttributes = new HashMap<Attribute, Object>();
for (Attribute attr : _ecAttributes) {
Object ec = attr.field.get(entity);
if (ec != null) {
ecAttributes.put(attr, ec);
}
}
insertElementCollection(entity, _idAttributes.get(_table)[0], id, ecAttributes);
}
txn.commit();
} catch (final SQLException e) {
handleEntityExistsException(e);
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (IllegalArgumentException e) {
throw new CloudRuntimeException("Problem with getting the ec attribute ", e);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException("Problem with getting the ec attribute ", e);
}
return _idField != null ? findByIdIncludingRemoved(id) : null;
}
protected void insertElementCollection(T entity, Attribute idAttribute, ID id, Map<Attribute, Object> ecAttributes) throws SQLException {
TransactionLegacy txn = TransactionLegacy.currentTxn();
txn.start();
for (Map.Entry<Attribute, Object> entry : ecAttributes.entrySet()) {
Attribute attr = entry.getKey();
Object obj = entry.getValue();
EcInfo ec = (EcInfo)attr.attache;
Enumeration<?> en = null;
if (ec.rawClass == null) {
en = Collections.enumeration(Arrays.asList((Object[])obj));
} else {
en = Collections.enumeration((Collection)obj);
}
PreparedStatement pstmt = txn.prepareAutoCloseStatement(ec.clearSql);
prepareAttribute(1, pstmt, idAttribute, id);
pstmt.executeUpdate();
while (en.hasMoreElements()) {
pstmt = txn.prepareAutoCloseStatement(ec.insertSql);
if (ec.targetClass == Date.class) {
pstmt.setString(1, DateUtil.getDateDisplayString(s_gmtTimeZone, (Date)en.nextElement()));
} else {
pstmt.setObject(1, en.nextElement());
}
prepareAttribute(2, pstmt, idAttribute, id);
pstmt.executeUpdate();
}
}
txn.commit();
}
@DB()
protected Object generateValue(final Attribute attr) {
if (attr.is(Attribute.Flag.Created) || attr.is(Attribute.Flag.Removed)) {
return new Date();
} else if (attr.is(Attribute.Flag.TableGV)) {
return null;
// Not sure what to do here.
} else if (attr.is(Attribute.Flag.AutoGV)) {
if (attr.columnName.equals(GenericDao.XID_COLUMN)) {
return UUID.randomUUID().toString();
}
assert (false) : "Auto generation is not supported.";
return null;
} else if (attr.is(Attribute.Flag.SequenceGV)) {
assert (false) : "Sequence generation is not supported.";
return null;
} else if (attr.is(Attribute.Flag.DC)) {
return _discriminatorValues.get(attr.columnName);
} else {
assert (false) : "Attribute can't be auto generated: " + attr.columnName;
return null;
}
}
@DB()
protected void prepareAttribute(final int j, final PreparedStatement pstmt, final Attribute attr, Object value) throws SQLException {
if (attr.is(Attribute.Flag.DaoGenerated) && value == null) {
value = generateValue(attr);
if (attr.field == null) {
pstmt.setObject(j, value);
return;
}
}
if (attr.getValue() != null && attr.getValue() instanceof String) {
pstmt.setString(j, (String)attr.getValue());
} else if (attr.getValue() != null && attr.getValue() instanceof Long) {
pstmt.setLong(j, (Long)attr.getValue());
} else if (attr.field.getType() == String.class) {
final String str;
try {
str = (String) value;
if (str == null) {
pstmt.setString(j, null);
return;
}
} catch (ClassCastException ex) {
// This happens when we pass in an integer, long or any other object which can't be cast to String.
// Converting to string in case of integer or long can result in different results. Required specifically for details tables.
// So, we set the value for the object directly.
s_logger.debug("ClassCastException when casting value to String. Setting the value of the object directly.");
pstmt.setObject(j, value);
return;
}
final Column column = attr.field.getAnnotation(Column.class);
final int length = column != null ? column.length() : 255;
// to support generic localization, utilize MySql UTF-8 support
if (length < str.length()) {
try {
if (attr.is(Attribute.Flag.Encrypted)) {
pstmt.setBytes(j, DBEncryptionUtil.encrypt(str.substring(0, length)).getBytes("UTF-8"));
} else {
pstmt.setBytes(j, str.substring(0, length).getBytes("UTF-8"));
}
} catch (UnsupportedEncodingException e) {
// no-way it can't support UTF-8 encoding
assert (false);
throw new CloudRuntimeException("UnsupportedEncodingException when saving string as UTF-8 data");
}
} else {
try {
if (attr.is(Attribute.Flag.Encrypted)) {
pstmt.setBytes(j, DBEncryptionUtil.encrypt(str).getBytes("UTF-8"));
} else {
pstmt.setBytes(j, str.getBytes("UTF-8"));
}
} catch (UnsupportedEncodingException e) {
// no-way it can't support UTF-8 encoding
assert (false);
throw new CloudRuntimeException("UnsupportedEncodingException when saving string as UTF-8 data");
}
}
} else if (attr.field.getType() == Date.class) {
final Date date = (Date)value;
if (date == null || date.equals(DATE_TO_NULL)) {
pstmt.setObject(j, null);
return;
}
if (attr.is(Attribute.Flag.Date)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, date));
} else if (attr.is(Attribute.Flag.TimeStamp)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, date));
} else if (attr.is(Attribute.Flag.Time)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, date));
}
} else if (attr.field.getType() == Calendar.class) {
final Calendar cal = (Calendar)value;
if (cal == null) {
pstmt.setObject(j, null);
return;
}
if (attr.is(Attribute.Flag.Date)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, cal.getTime()));
} else if (attr.is(Attribute.Flag.TimeStamp)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, cal.getTime()));
} else if (attr.is(Attribute.Flag.Time)) {
pstmt.setString(j, DateUtil.getDateDisplayString(s_gmtTimeZone, cal.getTime()));
}
} else if (attr.field.getType().isEnum()) {
final Enumerated enumerated = attr.field.getAnnotation(Enumerated.class);
final EnumType type = (enumerated == null) ? EnumType.STRING : enumerated.value();
if (type == EnumType.STRING) {
pstmt.setString(j, value == null ? null : value.toString());
} else if (type == EnumType.ORDINAL) {
if (value == null) {
pstmt.setObject(j, null);
} else {
pstmt.setInt(j, ((Enum<?>)value).ordinal());
}
}
} else if (attr.field.getType() == URI.class) {
pstmt.setString(j, value == null ? null : value.toString());
} else if (attr.field.getType() == URL.class) {
pstmt.setURL(j, (URL)value);
} else if (attr.field.getType() == byte[].class) {
pstmt.setBytes(j, (byte[])value);
} else if (attr.field.getType() == Ip.class) {
final Enumerated enumerated = attr.field.getAnnotation(Enumerated.class);
final EnumType type = (enumerated == null) ? EnumType.ORDINAL : enumerated.value();
if (type == EnumType.STRING) {
pstmt.setString(j, value == null ? null : value.toString());
} else if (type == EnumType.ORDINAL) {
if (value == null) {
pstmt.setObject(j, null);
} else {
pstmt.setLong(j, (value instanceof Ip) ? ((Ip)value).longValue() : NetUtils.ip2Long((String)value));
}
}
} else {
pstmt.setObject(j, value);
}
}
@DB()
protected int prepareAttributes(final PreparedStatement pstmt, final Object entity, final Attribute[] attrs, final int index) throws SQLException {
int j = 0;
for (int i = 0; i < attrs.length; i++) {
j = i + index;
try {
prepareAttribute(j, pstmt, attrs[i], attrs[i].field != null ? attrs[i].field.get(entity) : null);
} catch (final IllegalArgumentException e) {
throw new CloudRuntimeException("IllegalArgumentException", e);
} catch (final IllegalAccessException e) {
throw new CloudRuntimeException("IllegalArgumentException", e);
}
}
return j;
}
@SuppressWarnings("unchecked")
@DB()
protected T toEntityBean(final ResultSet result, final boolean cache) throws SQLException {
final T entity = (T)_factory.newInstance(new Callback[] {NoOp.INSTANCE, new UpdateBuilder(this)});
toEntityBean(result, entity);
if (cache && _cache != null) {
try {
_cache.put(new Element(_idField.get(entity), entity));
} catch (final Exception e) {
s_logger.debug("Can't put it in the cache", e);
}
}
return entity;
}
@DB()
protected T toVO(ResultSet result, boolean cache) throws SQLException {
T entity;
try {
entity = _entityBeanType.newInstance();
} catch (InstantiationException e1) {
throw new CloudRuntimeException("Unable to instantiate entity", e1);
} catch (IllegalAccessException e1) {
throw new CloudRuntimeException("Illegal Access", e1);
}
toEntityBean(result, entity);
if (cache && _cache != null) {
try {
_cache.put(new Element(_idField.get(entity), entity));
} catch (final Exception e) {
s_logger.debug("Can't put it in the cache", e);
}
}
return entity;
}
@DB()
protected void toEntityBean(final ResultSet result, final T entity) throws SQLException {
ResultSetMetaData meta = result.getMetaData();
for (int index = 1, max = meta.getColumnCount(); index <= max; index++) {
setField(entity, result, meta, index);
}
for (Attribute attr : _ecAttributes) {
loadCollection(entity, attr);
}
}
@DB()
@SuppressWarnings("unchecked")
protected void loadCollection(T entity, Attribute attr) {
EcInfo ec = (EcInfo)attr.attache;
TransactionLegacy txn = TransactionLegacy.currentTxn();
try(PreparedStatement pstmt = txn.prepareStatement(ec.selectSql);)
{
pstmt.setObject(1, _idField.get(entity));
try(ResultSet rs = pstmt.executeQuery();)
{
ArrayList lst = new ArrayList();
if (ec.targetClass == Integer.class) {
while (rs.next()) {
lst.add(rs.getInt(1));
}
} else if (ec.targetClass == Long.class) {
while (rs.next()) {
lst.add(rs.getLong(1));
}
} else if (ec.targetClass == String.class) {
while (rs.next()) {
lst.add(rs.getString(1));
}
} else if (ec.targetClass == Short.class) {
while (rs.next()) {
lst.add(rs.getShort(1));
}
} else if (ec.targetClass == Date.class) {
while (rs.next()) {
lst.add(DateUtil.parseDateString(s_gmtTimeZone, rs.getString(1)));
}
} else if (ec.targetClass == Boolean.class) {
while (rs.next()) {
lst.add(rs.getBoolean(1));
}
} else {
assert (false) : "You'll need to add more classeses";
}
if (ec.rawClass == null) {
Object[] array = (Object[]) Array.newInstance(ec.targetClass);
lst.toArray(array);
try {
attr.field.set(entity, array);
} catch (IllegalArgumentException e) {
throw new CloudRuntimeException("Come on we screen for this stuff, don't we?", e);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException("Come on we screen for this stuff, don't we?", e);
}
} else {
try {
Collection coll = (Collection) ec.rawClass.newInstance();
coll.addAll(lst);
attr.field.set(entity, coll);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException("Come on we screen for this stuff, don't we?", e);
} catch (InstantiationException e) {
throw new CloudRuntimeException("Never should happen", e);
}
}
}
catch (SQLException e) {
throw new CloudRuntimeException("loadCollection: Exception : " +e.getMessage(), e);
}
} catch (SQLException e) {
throw new CloudRuntimeException("loadCollection: Exception : " +e.getMessage(), e);
} catch (IllegalArgumentException e) {
throw new CloudRuntimeException("loadCollection: Exception : " +e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException("loadCollection: Exception : " +e.getMessage(), e);
}
}
@Override
public void expunge() {
if (_removed == null) {
return;
}
final StringBuilder sql = new StringBuilder("DELETE FROM ");
sql.append(_table).append(" WHERE ").append(_removed.first()).append(" IS NOT NULL");
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
txn.start();
pstmt = txn.prepareAutoCloseStatement(sql.toString());
pstmt.executeUpdate();
txn.commit();
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on " + pstmt, e);
}
}
@Override
public boolean unremove(ID id) {
if (_removed == null) {
return false;
}
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
txn.start();
pstmt = txn.prepareAutoCloseStatement(_removeSql.first());
final Attribute[] attrs = _removeSql.second();
pstmt.setObject(1, null);
for (int i = 0; i < attrs.length - 1; i++) {
prepareAttribute(i + 2, pstmt, attrs[i], id);
}
final int result = pstmt.executeUpdate();
txn.commit();
if (_cache != null) {
_cache.remove(id);
}
return result > 0;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
}
}
@DB()
protected void setField(final Object entity, final ResultSet rs, ResultSetMetaData meta, final int index) throws SQLException {
Attribute attr = _allColumns.get(new Pair<String, String>(meta.getTableName(index), meta.getColumnName(index)));
if (attr == null) {
// work around for mysql bug to return original table name instead of view name in db view case
Table tbl = entity.getClass().getSuperclass().getAnnotation(Table.class);
if (tbl != null) {
attr = _allColumns.get(new Pair<String, String>(tbl.name(), meta.getColumnLabel(index)));
}
}
assert (attr != null) : "How come I can't find " + meta.getCatalogName(index) + "." + meta.getColumnName(index);
setField(entity, attr.field, rs, index);
}
@Override
public boolean remove(final ID id) {
if (_removeSql == null) {
return expunge(id);
}
final TransactionLegacy txn = TransactionLegacy.currentTxn();
PreparedStatement pstmt = null;
try {
txn.start();
pstmt = txn.prepareAutoCloseStatement(_removeSql.first());
final Attribute[] attrs = _removeSql.second();
prepareAttribute(1, pstmt, attrs[attrs.length - 1], null);
for (int i = 0; i < attrs.length - 1; i++) {
prepareAttribute(i + 2, pstmt, attrs[i], id);
}
final int result = pstmt.executeUpdate();
txn.commit();
if (_cache != null) {
_cache.remove(id);
}
return result > 0;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
}
}
@Override
public int remove(SearchCriteria<T> sc) {
if (_removeSql == null) {
return expunge(sc);
}
T vo = createForUpdate();
UpdateBuilder ub = getUpdateBuilder(vo);
ub.set(vo, _removed.second(), new Date());
return update(ub, sc, null);
}
protected Cache _cache;
@DB()
protected void createCache(final Map<String, ? extends Object> params) {
final String value = (String)params.get("cache.size");
if (value != null) {
final CacheManager cm = CacheManager.create();
final int maxElements = NumbersUtil.parseInt(value, 0);
final int live = NumbersUtil.parseInt((String)params.get("cache.time.to.live"), 300);
final int idle = NumbersUtil.parseInt((String)params.get("cache.time.to.idle"), 300);
_cache = new Cache(getName(), maxElements, false, live == -1, live == -1 ? Integer.MAX_VALUE : live, idle);
cm.addCache(_cache);
s_logger.info("Cache created: " + _cache.toString());
} else {
_cache = null;
}
}
@Override
@DB()
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
_name = name;
final String value = (String)params.get("lock.timeout");
_timeoutSeconds = NumbersUtil.parseInt(value, 300);
createCache(params);
final boolean load = Boolean.parseBoolean((String)params.get("cache.preload"));
if (load) {
listAll();
}
return true;
}
@DB()
public static <T> UpdateBuilder getUpdateBuilder(final T entityObject) {
final Factory factory = (Factory)entityObject;
assert (factory != null);
return (UpdateBuilder)factory.getCallback(1);
}
@Override
@DB()
public SearchBuilder<T> createSearchBuilder() {
return new SearchBuilder<T>(_entityBeanType);
}
@Override
@DB()
public SearchCriteria<T> createSearchCriteria() {
SearchBuilder<T> builder = createSearchBuilder();
return builder.create();
}
private SearchCriteria<T> checkAndSetRemovedIsNull(SearchCriteria<T> sc) {
if (_removed != null) {
if (sc == null) {
sc = createSearchCriteria();
}
sc.addAnd(_removed.second().field.getName(), SearchCriteria.Op.NULL);
}
return sc;
}
public Integer getDistinctCount(SearchCriteria<T> sc) {
sc = checkAndSetRemovedIsNull(sc);
return getDistinctCountIncludingRemoved(sc);
}
public Integer getDistinctCountIncludingRemoved(SearchCriteria<T> sc) {
String clause = sc != null ? sc.getWhereClause() : null;
if (clause != null && clause.length() == 0) {
clause = null;
}
final StringBuilder str = createDistinctIdSelect(sc, clause != null);
if (clause != null) {
str.append(clause);
}
Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
List<Attribute> joinAttrList = null;
if (sc != null) {
joins = sc.getJoins();
if (joins != null) {
joinAttrList = addJoins(str, joins);
}
}
// we have to disable group by in getting count, since count for groupBy clause will be different.
//List<Object> groupByValues = addGroupBy(str, sc);
final TransactionLegacy txn = TransactionLegacy.currentTxn();
final String sql = "SELECT COUNT(*) FROM (" + str.toString() + ") AS tmp";
PreparedStatement pstmt = null;
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 1;
if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
for (Attribute attr : joinAttrList) {
prepareAttribute(i++, pstmt, attr, null);
}
}
if (clause != null) {
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
}
if (joins != null) {
i = addJoinAttributes(i, pstmt, joins);
}
/*
if (groupByValues != null) {
for (Object value : groupByValues) {
pstmt.setObject(i++, value);
}
}
*/
final ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
return rs.getInt(1);
}
return 0;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
public Integer getDistinctCount(SearchCriteria<T> sc, String[] distinctColumns) {
sc = checkAndSetRemovedIsNull(sc);
return getDistinctCountIncludingRemoved(sc, distinctColumns);
}
public Integer getDistinctCountIncludingRemoved(SearchCriteria<T> sc, String[] distinctColumns) {
String clause = sc != null ? sc.getWhereClause() : null;
if (StringUtils.isEmpty(clause)) {
clause = null;
}
final StringBuilder str = createDistinctSelect(sc, clause != null, distinctColumns);
if (clause != null) {
str.append(clause);
}
Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
List<Attribute> joinAttrList = null;
if (sc != null) {
joins = sc.getJoins();
if (joins != null) {
joinAttrList = addJoins(str, joins);
}
}
final TransactionLegacy txn = TransactionLegacy.currentTxn();
final String sql = "SELECT COUNT(*) FROM (" + str.toString() + ") AS tmp";
try (PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql)) {
int i = 1;
if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
for (Attribute attr : joinAttrList) {
prepareAttribute(i++, pstmt, attr, null);
}
}
if (clause != null) {
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
}
if (joins != null) {
i = addJoinAttributes(i, pstmt, joins);
}
final ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
return rs.getInt(1);
}
return 0;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception in executing: " + sql, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught exception in : " + sql, e);
}
}
public Integer countAll() {
return getCount(null);
}
@Override
public List<T> findByUuids(String... uuidArray) {
if (ArrayUtils.isEmpty(uuidArray)) {
return new ArrayList<T>();
}
SearchCriteria<T> sc = createSearchCriteria();
sc.addAnd("uuid", SearchCriteria.Op.IN, uuidArray);
return search(sc, null);
}
public Integer getCount(SearchCriteria<T> sc) {
sc = checkAndSetRemovedIsNull(sc);
return getCountIncludingRemoved(sc);
}
public Integer getCountIncludingRemoved(SearchCriteria<T> sc) {
String clause = sc != null ? sc.getWhereClause() : null;
if (clause != null && clause.length() == 0) {
clause = null;
}
final StringBuilder str = createCountSelect(sc, clause != null);
if (clause != null) {
str.append(clause);
}
Collection<JoinBuilder<SearchCriteria<?>>> joins = null;
List<Attribute> joinAttrList = null;
if (sc != null) {
joins = sc.getJoins();
if (joins != null) {
joinAttrList = addJoins(str, joins);
}
}
final TransactionLegacy txn = TransactionLegacy.currentTxn();
final String sql = str.toString();
PreparedStatement pstmt = null;
try {
pstmt = txn.prepareAutoCloseStatement(sql);
int i = 1;
if (!CollectionUtils.isNullOrEmpty(joinAttrList)) {
for (Attribute attr : joinAttrList) {
prepareAttribute(i++, pstmt, attr, null);
}
}
if (clause != null) {
for (final Pair<Attribute, Object> value : sc.getValues()) {
prepareAttribute(i++, pstmt, value.first(), value.second());
}
}
if (joins != null) {
i = addJoinAttributes(i, pstmt, joins);
}
final ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
return rs.getInt(1);
}
return 0;
} catch (final SQLException e) {
throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
} catch (final Exception e) {
throw new CloudRuntimeException("Caught: " + pstmt, e);
}
}
@DB()
protected StringBuilder createCountSelect(SearchCriteria<?> sc, final boolean whereClause) {
StringBuilder sql = new StringBuilder(_count);
if (sc != null) {
Pair<GroupBy<?, ?, ?>, List<Object>> groupBys = sc.getGroupBy();
if (groupBys != null) {
final SqlGenerator generator = new SqlGenerator(_entityBeanType);
sql = new StringBuilder(generator.buildCountSqlWithGroupBy(groupBys.first()));
}
}
if (!whereClause) {
sql.delete(sql.length() - (_discriminatorClause == null ? 6 : 4), sql.length());
}
return sql;
}
@DB()
protected StringBuilder createDistinctIdSelect(SearchCriteria<?> sc, final boolean whereClause) {
StringBuilder sql = new StringBuilder(_distinctIdSql);
if (!whereClause) {
sql.delete(sql.length() - (_discriminatorClause == null ? 6 : 4), sql.length());
}
return sql;
}
@DB()
protected Pair<List<T>, Integer> listAndCountIncludingRemovedBy(final SearchCriteria<T> sc, final Filter filter) {
List<T> objects = searchIncludingRemoved(sc, filter, null, false);
Integer count = getCount(sc);
return new Pair<List<T>, Integer>(objects, count);
}
@DB()
protected StringBuilder createDistinctSelect(SearchCriteria<?> sc, final boolean whereClause, String[] distinctColumns) {
final SqlGenerator generator = new SqlGenerator(_entityBeanType);
String distinctSql = generator.buildDistinctSql(distinctColumns);
StringBuilder sql = new StringBuilder(distinctSql);
if (!whereClause) {
sql.delete(sql.length() - (_discriminatorClause == null ? 6 : 4), sql.length());
}
return sql;
}
}