package org.zstack.core.db;
import com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.zstack.core.Platform;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.TransactionalCallback.Operation;
import org.zstack.header.Component;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.APIListMessage;
import org.zstack.header.vo.EO;
import org.zstack.header.vo.SoftDeletionCascade;
import org.zstack.header.vo.SoftDeletionCascades;
import org.zstack.utils.BeanUtils;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.FieldUtils;
import org.zstack.utils.ObjectUtils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.logging.CLoggerImpl;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.util.*;
import static org.zstack.utils.CollectionDSL.list;
public class DatabaseFacadeImpl implements DatabaseFacade, Component {
private static final CLogger logger = CLoggerImpl.getLogger(DatabaseFacadeImpl.class);
@PersistenceUnit(unitName = "zstack.jpa")
private EntityManagerFactory entityManagerFactory;
@PersistenceContext(unitName = "zstack.jpa")
private EntityManager entityManager;
@Autowired
private PluginRegistry pluginRgty;
private DataSource dataSource = null;
private DataSource extraDataSource = null;
private List<TransactionalCallback> transactionAsyncCallbacks = null;
private List<TransactionalSyncCallback> transactionSyncCallbacks = null;
private Map<Class, List<SoftDeleteEntityExtensionPoint>> softDeleteExtensions = new HashMap<Class, List<SoftDeleteEntityExtensionPoint>>();
private Map<Class, List<SoftDeleteEntityByEOExtensionPoint>> softDeleteByEOExtensions = new HashMap<Class, List<SoftDeleteEntityByEOExtensionPoint>>();
private List<SoftDeleteEntityExtensionPoint> softDeleteForAllExtensions = new ArrayList<SoftDeleteEntityExtensionPoint>();
private Map<Class, List<HardDeleteEntityExtensionPoint>> hardDeleteExtensions = new HashMap<Class, List<HardDeleteEntityExtensionPoint>>();
private List<HardDeleteEntityExtensionPoint> hardDeleteForAllExtensions = new ArrayList<HardDeleteEntityExtensionPoint>();
private Map<Class, EntityInfo> entityInfoMap = new HashMap<Class, EntityInfo>();
private String dbVersion;
class EntityInfo {
Field voPrimaryKeyField;
Field eoPrimaryKeyField;
Field eoSoftDeleteColumn;
Class eoClass;
Class voClass;
Map<EntityEvent, EntityLifeCycleCallback> listeners = new HashMap<EntityEvent, EntityLifeCycleCallback>();
EntityInfo(Class voClazz) {
voClass = voClazz;
voPrimaryKeyField = FieldUtils.getAnnotatedField(Id.class, voClass);
DebugUtils.Assert(voPrimaryKeyField != null, String.format("%s has no primary key", voClass));
voPrimaryKeyField.setAccessible(true);
EO at = (EO) voClazz.getAnnotation(EO.class);
if (at != null) {
eoClass = at.EOClazz();
DebugUtils.Assert(eoClass != null, String.format("cannot find EO entity specified by VO entity[%s]", voClazz.getName()));
eoPrimaryKeyField = FieldUtils.getAnnotatedField(Id.class, eoClass);
DebugUtils.Assert(eoPrimaryKeyField != null, String.format("cannot find primary key field(@Id annotated) in EO entity[%s]", eoClass.getName()));
eoPrimaryKeyField.setAccessible(true);
eoSoftDeleteColumn = FieldUtils.getField(at.softDeletedColumn(), eoClass);
DebugUtils.Assert(eoSoftDeleteColumn != null, String.format("cannot find soft delete column[%s] in EO entity[%s]", at.softDeletedColumn(), eoClass.getName()));
eoSoftDeleteColumn.setAccessible(true);
}
buildInheritanceDeletionExtension();
buildSoftDeletionCascade();
}
private void buildSoftDeletionCascade() {
SoftDeletionCascades ats = (SoftDeletionCascades) voClass.getAnnotation(SoftDeletionCascades.class);
if (ats == null) {
return;
}
for (final SoftDeletionCascade at : ats.value()) {
final Class parent = at.parent();
if (!parent.isAnnotationPresent(Entity.class)) {
throw new CloudRuntimeException(String.format("class[%s] has annotation @SoftDeletionCascade but its parent class[%s] is not annotated by @Entity",
voClass, parent));
}
if (!parent.isAnnotationPresent(EO.class)) {
continue;
}
List<SoftDeleteEntityExtensionPoint> exts = softDeleteExtensions.get(parent);
if (exts == null) {
exts = new ArrayList<SoftDeleteEntityExtensionPoint>();
softDeleteExtensions.put(parent, exts);
}
exts.add(new SoftDeleteEntityExtensionPoint() {
@Override
public List<Class> getEntityClassForSoftDeleteEntityExtension() {
return Arrays.asList(parent);
}
@Override
@Transactional
public void postSoftDelete(Collection entityIds, Class entityClass) {
String sql = String.format("delete from %s me where me.%s in (:ids)", voClass.getSimpleName(), at.joinColumn());
Query q = getEntityManager().createQuery(sql);
q.setParameter("ids", entityIds);
q.executeUpdate();
}
});
}
}
private void buildInheritanceDeletionExtension() {
PrimaryKeyJoinColumn at = (PrimaryKeyJoinColumn) voClass.getAnnotation(PrimaryKeyJoinColumn.class);
if (at == null) {
return;
}
final Class parent = voClass.getSuperclass();
if (!parent.isAnnotationPresent(Entity.class)) {
throw new CloudRuntimeException(String.format("class[%s] has annotation @PrimaryKeyJoinColumn but its parent class[%s] is not annotated by @Entity",
voClass, parent));
}
if (!parent.isAnnotationPresent(EO.class)) {
return;
}
if (!hasEO()) {
List<SoftDeleteEntityExtensionPoint> exts = softDeleteExtensions.get(parent);
if (exts == null) {
exts = new ArrayList<SoftDeleteEntityExtensionPoint>();
softDeleteExtensions.put(parent, exts);
}
exts.add(new SoftDeleteEntityExtensionPoint() {
@Override
public List<Class> getEntityClassForSoftDeleteEntityExtension() {
return Arrays.asList(parent);
}
@Override
public void postSoftDelete(Collection entityIds, Class entityClass) {
nativeSqlDelete(entityIds);
}
});
} else {
List<SoftDeleteEntityByEOExtensionPoint> exts = softDeleteByEOExtensions.get(eoClass);
if (exts == null) {
exts = new ArrayList<SoftDeleteEntityByEOExtensionPoint>();
softDeleteByEOExtensions.put(eoClass, exts);
}
exts.add(new SoftDeleteEntityByEOExtensionPoint() {
@Override
public List<Class> getEOClassForSoftDeleteEntityExtension() {
return Arrays.asList(eoClass);
}
@Override
public void postSoftDelete(Collection entityIds, Class EOClass) {
nativeSqlDelete(entityIds);
}
});
}
}
private void fireSoftDeleteExtension(Collection ids, Class entityClass) {
List<SoftDeleteEntityExtensionPoint> exts = softDeleteExtensions.get(entityClass);
if (exts != null) {
for (SoftDeleteEntityExtensionPoint ext : exts) {
ext.postSoftDelete(ids, entityClass);
}
}
for (SoftDeleteEntityExtensionPoint ext : softDeleteForAllExtensions) {
ext.postSoftDelete(ids, entityClass);
}
}
private void fireSoftDeleteExtensionByEOClass(Collection ids, Class eoClass) {
List<SoftDeleteEntityByEOExtensionPoint> exts = softDeleteByEOExtensions.get(eoClass);
if (exts != null) {
for (SoftDeleteEntityByEOExtensionPoint ext : exts) {
ext.postSoftDelete(ids, eoClass);
}
}
}
boolean hasEO() {
return eoClass != null;
}
private Object getEOPrimaryKeyValue(Object entity) {
try {
return eoPrimaryKeyField.get(entity);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException(e);
}
}
private Object getVOPrimaryKeyValue(Object entity) {
try {
return voPrimaryKeyField.get(entity);
} catch (IllegalAccessException e) {
throw new CloudRuntimeException(e);
}
}
private void updateEO(Object entity, DataIntegrityViolationException de) {
if (!MySQLIntegrityConstraintViolationException.class.isAssignableFrom(de.getRootCause().getClass())) {
throw de;
}
MySQLIntegrityConstraintViolationException me = (MySQLIntegrityConstraintViolationException) de.getRootCause();
if (!(me.getErrorCode() == 1062 && "23000".equals(me.getSQLState()) && me.getMessage().contains("PRIMARY"))) {
throw de;
}
if (!hasEO()) {
throw de;
}
// at this point, the error is caused by a update tried on VO entity which has been soft deleted. This is mostly
// caused by a deletion cascade(e.g deleting host will cause vm running on it to be deleted, and deleting vm is trying to return capacity
// to host which has been soft deleted, because vm deletion is executed in async manner). In this case, we make the update to EO table
Object idval = getEOPrimaryKeyValue(entity);
Object eo = getEntityManager().find(eoClass, idval);
final Object deo = ObjectUtils.copy(eo, entity);
new Runnable() {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void run() {
getEntityManager().merge(deo);
}
}.run();
logger.debug(String.format("A EO[%s] update has been made", eoClass.getName()));
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private Object update(Object e, boolean refresh) {
try {
e = getEntityManager().merge(e);
if (refresh) {
getEntityManager().flush();
getEntityManager().refresh(e);
}
return e;
} catch (DataIntegrityViolationException de) {
updateEO(e, de);
}
return e;
}
@DeadlockAutoRestart
void update(Object e) {
update(e, false);
}
@DeadlockAutoRestart
Object updateAndRefresh(Object e) {
return update(e, true);
}
private void hardDelete(Object entity) {
entity = getEntityManager().merge(entity);
getEntityManager().remove(entity);
Object idval = getVOPrimaryKeyValue(entity);
fireHardDeleteExtension(list(idval));
}
private void softDelete(Object entity) {
try {
Object idval = getEOPrimaryKeyValue(entity);
if (idval == null) {
// the entity is physically deleted
return;
}
Object eo = getEntityManager().find(eoClass, idval);
eoSoftDeleteColumn.set(eo, new Timestamp(new Date().getTime()).toString());
getEntityManager().merge(eo);
fireSoftDeleteExtension(Arrays.asList(idval), voClass);
fireSoftDeleteExtensionByEOClass(Arrays.asList(idval), eoClass);
} catch (CloudRuntimeException ce) {
throw ce;
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
private void softDelete(Collection ids) {
if (ids.size() == 1) {
String sql = String.format("update %s eo set eo.%s = (:date) where eo.%s = :id", eoClass.getSimpleName(), eoSoftDeleteColumn.getName(), eoPrimaryKeyField.getName());
Query q = getEntityManager().createQuery(sql);
q.setParameter("id", ids.iterator().next());
q.setParameter("date", new Timestamp(new Date().getTime()).toString());
q.executeUpdate();
} else {
String sql = String.format("update %s eo set eo.%s = (:date) where eo.%s in (:ids)", eoClass.getSimpleName(), eoSoftDeleteColumn.getName(), eoPrimaryKeyField.getName());
Query q = getEntityManager().createQuery(sql);
q.setParameter("ids", ids);
q.setParameter("date", new Timestamp(new Date().getTime()).toString());
q.executeUpdate();
}
fireSoftDeleteExtension(ids, voClass);
fireSoftDeleteExtensionByEOClass(ids, eoClass);
}
private void fireHardDeleteExtension(Collection ids) {
List<HardDeleteEntityExtensionPoint> exts = hardDeleteExtensions.get(voClass);
if (exts != null) {
for (HardDeleteEntityExtensionPoint ext : exts) {
ext.postHardDelete(ids, voClass);
}
}
for (HardDeleteEntityExtensionPoint ext : hardDeleteForAllExtensions) {
ext.postHardDelete(ids, voClass);
}
}
private void hardDelete(Collection ids) {
String tblName = hasEO() ? eoClass.getSimpleName() : voClass.getSimpleName();
if (ids.size() == 1) {
String sql = String.format("delete from %s eo where eo.%s = :id", tblName, voPrimaryKeyField.getName());
Query q = getEntityManager().createQuery(sql);
q.setParameter("id", ids.iterator().next());
q.executeUpdate();
} else {
String sql = String.format("delete from %s eo where eo.%s in (:ids)", tblName, voPrimaryKeyField.getName());
Query q = getEntityManager().createQuery(sql);
q.setParameter("ids", ids);
q.executeUpdate();
}
fireHardDeleteExtension(ids);
}
@Transactional
private void nativeSqlDelete(Collection ids) {
// native sql can avoid JPA cascades a deletion to parent entity when deleting a child entity
String sql = String.format("delete from %s where %s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
Query q = getEntityManager().createNativeQuery(sql);
q.setParameter("ids", ids);
q.executeUpdate();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void remove(Object entity) {
if (!hasEO()) {
hardDelete(entity);
} else {
softDelete(entity);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void removeByPrimaryKey(Object id) {
if (hasEO()) {
softDelete(list(id));
} else {
hardDelete(list(id));
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void removeByPrimaryKeys(Collection ids) {
if (hasEO()) {
softDelete(ids);
} else {
hardDelete(ids);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
void removeCollection(Collection entities) {
for (Object entity : entities) {
if (!entity.getClass().isAnnotationPresent(EO.class)) {
hardDelete(entity);
} else {
softDelete(entity);
}
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
Object reload(Object entity) {
return getEntityManager().find(entity.getClass(), getVOPrimaryKeyValue(entity));
}
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
List listByPrimaryKeys(Collection ids, int offset, int length) {
String sql = null;
Query query = null;
if (ids == null || ids.isEmpty()) {
sql = String.format("select e from %s e", voClass.getSimpleName());
query = getEntityManager().createQuery(sql, voClass);
} else {
sql = String.format("select e from %s e where e.%s in (:ids)", voClass.getSimpleName(), voPrimaryKeyField.getName());
query = getEntityManager().createQuery(sql, voClass);
query.setParameter("ids", ids);
}
query.setFirstResult(offset);
query.setMaxResults(length);
return query.getResultList();
}
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
boolean isExist(Object id) {
String sql = String.format("select count(*) from %s ref where ref.%s = :id", voClass.getSimpleName(), voPrimaryKeyField.getName());
TypedQuery<Long> q = getEntityManager().createQuery(sql, Long.class);
q.setParameter("id", id);
q.setMaxResults(1);
Long count = q.getSingleResult();
return count > 0;
}
void installLifeCycleCallback(EntityEvent evt, EntityLifeCycleCallback l) {
listeners.put(evt, l);
}
void fireLifeCycleEvent(EntityEvent evt, Object o) {
EntityLifeCycleCallback cb = listeners.get(evt);
if (cb != null) {
cb.entityLifeCycleEvent(evt, o);
}
}
}
@Transactional(readOnly = true)
private void getDbVersionOnInit() {
String sql = "select version from schema_version order by version desc limit 1";
Query q = getEntityManager().createNativeQuery(sql);
dbVersion = (String) q.getSingleResult();
}
void init() {
buildEntityInfo();
getDbVersionOnInit();
}
@Override
public <T> T persist(T entity) {
return persist(entity, false);
}
EntityInfo getEntityInfo(Class clz) {
EntityInfo info = entityInfoMap.get(clz);
DebugUtils.Assert(info != null, String.format("cannot find entity info for %s", clz.getName()));
return info;
}
@Override
public <T> void update(T entity) {
getEntityInfo(entity.getClass()).update(entity);
}
@Override
public CriteriaBuilder getCriteriaBuilder() {
return entityManagerFactory.getCriteriaBuilder();
}
@Override
public <T> SimpleQuery<T> createQuery(Class<T> entityClass) {
assert entityClass.isAnnotationPresent(Entity.class) : entityClass.getName() + " is not annotated by JPA @Entity";
return new SimpleQueryImpl<T>(entityClass);
}
public EntityManager getEntityManager() {
return entityManager;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public <T> T findById(long id, Class<T> entityClass) {
return getEntityManager().find(entityClass, id);
}
@Override
@DeadlockAutoRestart
public void remove(Object entity) {
getEntityInfo(entity.getClass()).remove(entity);
}
@Override
@DeadlockAutoRestart
public void removeCollection(Collection entities, Class entityClass) {
if (entities.isEmpty()) {
return;
}
getEntityInfo(entityClass).removeCollection(entities);
}
@Override
@DeadlockAutoRestart
public void removeByPrimaryKeys(Collection priKeys, Class entityClazz) {
if (priKeys.isEmpty()) {
return;
}
getEntityInfo(entityClazz).removeByPrimaryKeys(priKeys);
}
@Override
public <T> T updateAndRefresh(T entity) {
return (T) getEntityInfo(entity.getClass()).updateAndRefresh(entity);
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public <T> T findByUuid(String uuid, Class<T> entityClass) {
return this.getEntityManager().find(entityClass, uuid);
}
@Override
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
public <T> T find(Query q) {
List<T> ret = q.getResultList();
if (ret.size() > 1) {
throw new CloudRuntimeException("more than one result found");
}
return ret.isEmpty() ? null : ret.get(0);
}
@Override
@DeadlockAutoRestart
public void removeByPrimaryKey(Object primaryKey, Class<?> entityClass) {
getEntityInfo(entityClass).removeByPrimaryKey(primaryKey);
}
@Override
@Transactional
public void hardDeleteCollectionSelectedBySQL(String sql, Class entityClass) {
EntityInfo info = getEntityInfo(entityClass);
Query q = getEntityManager().createQuery(sql);
List ids = q.getResultList();
if (ids.isEmpty()) {
return;
}
info.hardDelete(ids);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private <T> T doPersist(T entity, boolean isRefresh) {
this.entityForTranscationCallback(Operation.PERSIST, entity.getClass());
getEntityManager().persist(entity);
if (isRefresh) {
getEntityManager().flush();
getEntityManager().refresh(entity);
}
return entity;
}
@DeadlockAutoRestart
private <T> T persist(T entity, boolean isRefresh) {
return doPersist(entity, isRefresh);
}
@Override
public <T> T persistAndRefresh(T entity) {
return persist(entity, true);
}
@Override
public long count(Class<?> entityClass) {
SimpleQuery<?> query = this.createQuery(entityClass);
return query.count();
}
private List<TransactionalCallback> getTransactionAsyncCallbacks() {
if (transactionAsyncCallbacks == null) {
transactionAsyncCallbacks = new ArrayList<TransactionalCallback>();
PluginRegistry pluginRgty = Platform.getComponentLoader().getComponent(PluginRegistry.class);
transactionAsyncCallbacks = pluginRgty.getExtensionList(TransactionalCallback.class);
}
return transactionAsyncCallbacks;
}
private List<TransactionalSyncCallback> getTransactionSyncCallbacks() {
if (transactionSyncCallbacks == null) {
transactionSyncCallbacks = new ArrayList<TransactionalSyncCallback>();
PluginRegistry pluginRgty = Platform.getComponentLoader().getComponent(PluginRegistry.class);
transactionSyncCallbacks = pluginRgty.getExtensionList(TransactionalSyncCallback.class);
}
return transactionSyncCallbacks;
}
@Override
public void entityForTranscationCallback(Operation op, Class<?>... entityClass) {
if (TransactionSynchronizationManager.isActualTransactionActive()) {
for (TransactionalSyncCallback cb : getTransactionSyncCallbacks()) {
TransactionSynchronizationSyncImpl tsi = new TransactionSynchronizationSyncImpl(cb, op, entityClass);
TransactionSynchronizationManager.registerSynchronization(tsi);
}
for (TransactionalCallback cb : getTransactionAsyncCallbacks()) {
TransactionSynchronizationAsyncImpl tsi = new TransactionSynchronizationAsyncImpl(cb, op, entityClass);
TransactionSynchronizationManager.registerSynchronization(tsi);
}
} else {
StringBuilder sb = new StringBuilder();
for (Class<?> c : entityClass) {
sb.append(c.getName()).append(",");
}
String err = String.format("entityForTranscationCallback is called but transcation is not active. Did you forget adding @Transactional to method??? [operation: %s, entity classes: %s]", op, sb.toString());
logger.warn(err);
}
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public <T> T reload(T entity) {
return (T) getEntityInfo(entity.getClass()).reload(entity);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void doUpdateCollection(Collection entities) {
for (Object e : entities) {
getEntityManager().merge(e);
}
}
@Override
@DeadlockAutoRestart
public void updateCollection(Collection entities) {
doUpdateCollection(entities);
}
@Override
public long generateSequenceNumber(Class<?> seqTable) {
try {
Field id = seqTable.getDeclaredField("id");
if (id == null) {
throw new CloudRuntimeException(String.format("sequence VO[%s] must have 'id' field", seqTable.getName()));
}
Object vo = seqTable.newInstance();
vo = persistAndRefresh(vo);
id.setAccessible(true);
return (Long) id.get(vo);
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
@Override
public <T> List<T> listByApiMessage(APIListMessage msg, Class<T> clazz) {
return listByPrimaryKeys(msg.getUuids(), msg.getOffset(), msg.getLength(), clazz);
}
@Override
public <T> List<T> listAll(Class<T> clazz) {
return listAll(0, Integer.MAX_VALUE, clazz);
}
@Override
public <T> List<T> listAll(int offset, int length, Class<T> clazz) {
return listByPrimaryKeys(null, offset, length, clazz);
}
@Override
public <T> List<T> listByPrimaryKeys(Collection ids, Class<T> clazz) {
return listByPrimaryKeys(ids, 0, Integer.MAX_VALUE, clazz);
}
@Override
public <T> List<T> listByPrimaryKeys(Collection ids, int offset, int length, Class<T> clazz) {
return getEntityInfo(clazz).listByPrimaryKeys(ids, offset, length);
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void persistCollection(Collection entities) {
for (Object e : entities) {
this.entityForTranscationCallback(Operation.PERSIST, e.getClass());
this.getEntityManager().persist(e);
}
}
@Override
public boolean isExist(Object id, Class<?> clazz) {
return getEntityInfo(clazz).isExist(id);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void _eoCleanup(Class VOClazz) {
EntityInfo info = getEntityInfo(VOClazz);
if (!info.hasEO()) {
return;
}
String deleted = info.eoSoftDeleteColumn.getName();
String sql = String.format("select eo.%s from %s eo where eo.%s is not null", info.voPrimaryKeyField.getName(),
info.eoClass.getSimpleName(), deleted);
Query q = getEntityManager().createQuery(sql);
List ids = q.getResultList();
if (ids.isEmpty()) {
return;
}
info.hardDelete(ids);
}
@Override
@DeadlockAutoRestart
public void eoCleanup(Class VOClazz) {
_eoCleanup(VOClazz);
}
@Override
public DataSource getDataSource() {
return dataSource;
}
public void setExtraDataSource(DataSource extraDataSource) {
this.extraDataSource = extraDataSource;
}
@Override
public DataSource getExtraDataSource() {
return extraDataSource;
}
@Override
public boolean start() {
populateExtensions();
return true;
}
private void buildEntityInfo() {
String[] pkgs = StringUtils.split(DbGlobalProperty.ENTITY_PACKAGES, ",");
List<Class> clzs = BeanUtils.scanClass(Arrays.asList(pkgs), Entity.class);
for (Class clz : clzs) {
logger.debug(String.format("build entity info for %s", clz.getName()));
entityInfoMap.put(clz, new EntityInfo(clz));
}
}
private void populateExtensions() {
for (SoftDeleteEntityExtensionPoint ext : pluginRgty.getExtensionList(SoftDeleteEntityExtensionPoint.class)) {
if (ext.getEntityClassForSoftDeleteEntityExtension() == null) {
softDeleteForAllExtensions.add(ext);
continue;
}
for (Class eclazz : ext.getEntityClassForSoftDeleteEntityExtension()) {
List<SoftDeleteEntityExtensionPoint> exts = softDeleteExtensions.get(eclazz);
if (exts == null) {
exts = new ArrayList<SoftDeleteEntityExtensionPoint>();
softDeleteExtensions.put(eclazz, exts);
}
exts.add(ext);
}
}
for (SoftDeleteEntityByEOExtensionPoint ext : pluginRgty.getExtensionList(SoftDeleteEntityByEOExtensionPoint.class)) {
for (Class eoClass : ext.getEOClassForSoftDeleteEntityExtension()) {
List<SoftDeleteEntityByEOExtensionPoint> exts = softDeleteByEOExtensions.get(eoClass);
if (exts == null) {
exts = new ArrayList<SoftDeleteEntityByEOExtensionPoint>();
softDeleteByEOExtensions.put(eoClass, exts);
}
exts.add(ext);
}
}
for (HardDeleteEntityExtensionPoint ext : pluginRgty.getExtensionList(HardDeleteEntityExtensionPoint.class)) {
if (ext.getEntityClassForHardDeleteEntityExtension() == null) {
hardDeleteForAllExtensions.add(ext);
continue;
}
for (Class clazz : ext.getEntityClassForHardDeleteEntityExtension()) {
List<HardDeleteEntityExtensionPoint> exts = hardDeleteExtensions.get(clazz);
if (exts == null) {
exts = new ArrayList<HardDeleteEntityExtensionPoint>();
hardDeleteExtensions.put(clazz, exts);
}
exts.add(ext);
}
}
}
@Override
@Transactional(readOnly = true)
public Timestamp getCurrentSqlTime() {
Query query = getEntityManager().createNativeQuery("select current_timestamp()");
return (Timestamp) query.getSingleResult();
}
@Override
public String getDbVersion() {
return dbVersion;
}
@Override
public void installEntityLifeCycleCallback(Class clz, EntityEvent evt, EntityLifeCycleCallback cb) {
if (clz != null) {
EntityInfo info = entityInfoMap.get(clz);
DebugUtils.Assert(info != null, String.format("cannot find EntityInfo for the class[%s]", clz));
info.installLifeCycleCallback(evt, cb);
} else {
for (EntityInfo info : entityInfoMap.values()) {
info.installLifeCycleCallback(evt, cb);
}
}
}
@Override
public boolean stop() {
return true;
}
void entityEvent(EntityEvent evt, Object entity) {
EntityInfo info = entityInfoMap.get(entity.getClass());
if (info == null) {
logger.warn(String.format("cannot find EntityInfo for the class[%s], not entity events will be fired", entity.getClass()));
return;
}
info.fireLifeCycleEvent(evt, entity);
}
}