Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with 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.haulmont.cuba.core.sys.persistence; import com.google.common.base.Strings; import com.google.common.collect.Sets; import com.haulmont.bali.util.StackTrace; import com.haulmont.cuba.core.EntityManager; import com.haulmont.cuba.core.Persistence; import com.haulmont.cuba.core.app.FtsSender; import com.haulmont.cuba.core.app.MiddlewareStatisticsAccumulator; import com.haulmont.cuba.core.entity.*; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.FtsConfigHelper; import com.haulmont.cuba.core.global.PersistenceHelper; import com.haulmont.cuba.core.global.Stores; import com.haulmont.cuba.core.listener.AfterCompleteTransactionListener; import com.haulmont.cuba.core.listener.BeforeCommitTransactionListener; import com.haulmont.cuba.core.sys.entitycache.QueryCacheManager; import com.haulmont.cuba.core.sys.listener.EntityListenerManager; import com.haulmont.cuba.core.sys.listener.EntityListenerType; import com.haulmont.cuba.security.app.EntityLogAPI; import org.eclipse.persistence.descriptors.changetracking.ChangeTracker; import org.eclipse.persistence.internal.descriptors.changetracking.AttributeChangeListener; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.internal.sessions.ObjectChangeSet; import org.eclipse.persistence.queries.FetchGroup; import org.eclipse.persistence.queries.FetchGroupTracker; import org.eclipse.persistence.sessions.Session; import org.eclipse.persistence.sessions.UnitOfWork; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.transaction.support.ResourceHolderSupport; import org.springframework.transaction.support.ResourceHolderSynchronization; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.inject.Inject; import java.util.*; import java.util.stream.Collectors; @Component(PersistenceImplSupport.NAME) public class PersistenceImplSupport implements ApplicationContextAware { public static final String NAME = "cuba_PersistenceImplSupport"; public static final String RESOURCE_HOLDER_KEY = ContainerResourceHolder.class.getName(); public static final String PROP_NAME = "cuba.storeName"; @Inject protected Persistence persistence; @Inject protected EntityListenerManager entityListenerManager; @Inject protected QueryCacheManager queryCacheManager; @Inject protected EntityLogAPI entityLog; protected volatile FtsSender ftsSender; @Inject protected OrmCacheSupport ormCacheSupport; @Inject protected MiddlewareStatisticsAccumulator statisticsAccumulator; protected List<BeforeCommitTransactionListener> beforeCommitTxListeners; protected List<AfterCompleteTransactionListener> afterCompleteTxListeners; private static final Logger log = LoggerFactory.getLogger(PersistenceImplSupport.class.getName()); private Logger implicitFlushLog = LoggerFactory.getLogger("com.haulmont.cuba.IMPLICIT_FLUSH"); protected static Set<Entity> createEntitySet() { return Sets.newIdentityHashSet(); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { Map<String, BeforeCommitTransactionListener> beforeCommitMap = applicationContext .getBeansOfType(BeforeCommitTransactionListener.class); beforeCommitTxListeners = new ArrayList<>(beforeCommitMap.values()); beforeCommitTxListeners.sort(new OrderComparator()); Map<String, AfterCompleteTransactionListener> afterCompleteMap = applicationContext .getBeansOfType(AfterCompleteTransactionListener.class); afterCompleteTxListeners = new ArrayList<>(afterCompleteMap.values()); afterCompleteTxListeners.sort(new OrderComparator()); } public void registerInstance(Entity entity, EntityManager entityManager) { if (!TransactionSynchronizationManager.isActualTransactionActive()) throw new RuntimeException("No transaction"); UnitOfWork unitOfWork = entityManager.getDelegate().unwrap(UnitOfWork.class); getInstanceContainerResourceHolder(getStorageName(unitOfWork)).registerInstanceForUnitOfWork(entity, unitOfWork); if (entity instanceof BaseGenericIdEntity) { BaseEntityInternalAccess.setDetached((BaseGenericIdEntity) entity, false); } } public void registerInstance(Entity entity, AbstractSession session) { // Can be called outside of a transaction when fetching lazy attributes if (!TransactionSynchronizationManager.isActualTransactionActive()) return; if (!(session instanceof UnitOfWork)) throw new RuntimeException("Session is not a UnitOfWork: " + session); getInstanceContainerResourceHolder(getStorageName(session)).registerInstanceForUnitOfWork(entity, (UnitOfWork) session); } public Collection<Entity> getInstances(EntityManager entityManager) { if (!TransactionSynchronizationManager.isActualTransactionActive()) throw new RuntimeException("No transaction"); UnitOfWork unitOfWork = entityManager.getDelegate().unwrap(UnitOfWork.class); return getInstanceContainerResourceHolder(getStorageName(unitOfWork)).getInstances(unitOfWork); } public Collection<Entity> getSavedInstances(String storeName) { if (!TransactionSynchronizationManager.isActualTransactionActive()) throw new RuntimeException("No transaction"); return getInstanceContainerResourceHolder(storeName).getSavedInstances(); } public String getStorageName(Session session) { String storeName = (String) session.getProperty(PROP_NAME); return Strings.isNullOrEmpty(storeName) ? Stores.MAIN : storeName; } public ContainerResourceHolder getInstanceContainerResourceHolder(String storeName) { ContainerResourceHolder holder = (ContainerResourceHolder) TransactionSynchronizationManager .getResource(RESOURCE_HOLDER_KEY); if (holder == null) { holder = new ContainerResourceHolder(storeName); TransactionSynchronizationManager.bindResource(RESOURCE_HOLDER_KEY, holder); } else if (!storeName.equals(holder.getStoreName())) { throw new IllegalStateException("Cannot handle entity from " + storeName + " datastore because active transaction is for " + holder.getStoreName()); } if (TransactionSynchronizationManager.isSynchronizationActive() && !holder.isSynchronizedWithTransaction()) { holder.setSynchronizedWithTransaction(true); TransactionSynchronizationManager .registerSynchronization(new ContainerResourceSynchronization(holder, RESOURCE_HOLDER_KEY)); } return holder; } public void fireEntityListeners(EntityManager entityManager, boolean warnAboutImplicitFlush) { UnitOfWork unitOfWork = entityManager.getDelegate().unwrap(UnitOfWork.class); String storeName = getStorageName(unitOfWork); traverseEntities(getInstanceContainerResourceHolder(storeName), new OnFlushEntityVisitor(storeName), warnAboutImplicitFlush); } protected boolean isDeleted(BaseGenericIdEntity entity, AttributeChangeListener changeListener) { if ((entity instanceof SoftDelete)) { ObjectChangeSet changeSet = changeListener.getObjectChangeSet(); return changeSet != null && changeSet.getAttributesToChanges().containsKey("deleteTs") && ((SoftDelete) entity).isDeleted(); } else { return BaseEntityInternalAccess.isRemoved(entity); } } protected void traverseEntities(ContainerResourceHolder container, EntityVisitor visitor, boolean warnAboutImplicitFlush) { beforeStore(container, visitor, container.getAllInstances(), createEntitySet(), warnAboutImplicitFlush); } protected void beforeStore(ContainerResourceHolder container, EntityVisitor visitor, Collection<Entity> instances, Set<Entity> processed, boolean warnAboutImplicitFlush) { boolean possiblyChanged = false; Set<Entity> withoutPossibleChanges = createEntitySet(); for (Entity instance : instances) { processed.add(instance); if (!(instance instanceof ChangeTracker && instance instanceof BaseGenericIdEntity)) continue; BaseGenericIdEntity entity = (BaseGenericIdEntity) instance; boolean result = visitor.visit(entity); if (!result) { withoutPossibleChanges.add(instance); } possiblyChanged = result || possiblyChanged; } if (!possiblyChanged) return; if (warnAboutImplicitFlush) { statisticsAccumulator.incImplicitFlushCount(); if (implicitFlushLog.isTraceEnabled()) { implicitFlushLog.trace("Implicit flush due to query execution, see stack trace for the cause:\n" + StackTrace.asString()); } else { implicitFlushLog.debug("Implicit flush due to query execution"); } } Collection<Entity> afterProcessing = container.getAllInstances(); if (afterProcessing.size() > processed.size()) { afterProcessing.removeAll(processed); beforeStore(container, visitor, afterProcessing, processed, false); } if (!withoutPossibleChanges.isEmpty()) { afterProcessing = withoutPossibleChanges.stream().filter(instance -> { ChangeTracker changeTracker = (ChangeTracker) instance; AttributeChangeListener changeListener = (AttributeChangeListener) changeTracker ._persistence_getPropertyChangeListener(); return changeListener != null && changeListener.hasChanges(); }).collect(Collectors.toList()); if (!afterProcessing.isEmpty()) { beforeStore(container, visitor, afterProcessing, processed, false); } } } public interface EntityVisitor { boolean visit(BaseGenericIdEntity entity); } public static class ContainerResourceHolder extends ResourceHolderSupport { protected Map<UnitOfWork, Set<Entity>> unitOfWorkMap = new HashMap<>(); protected Set<Entity> savedInstances = createEntitySet(); protected String storeName; public ContainerResourceHolder(String storeName) { this.storeName = storeName; } public String getStoreName() { return storeName; } protected void registerInstanceForUnitOfWork(Entity instance, UnitOfWork unitOfWork) { if (log.isTraceEnabled()) log.trace("ContainerResourceHolder.registerInstanceForUnitOfWork: instance = " + instance + ", UnitOfWork = " + unitOfWork); if (instance instanceof BaseGenericIdEntity) { BaseEntityInternalAccess.setManaged((BaseGenericIdEntity) instance, true); } Set<Entity> instances = unitOfWorkMap.get(unitOfWork); if (instances == null) { instances = createEntitySet(); unitOfWorkMap.put(unitOfWork, instances); } instances.add(instance); } protected Collection<Entity> getInstances(UnitOfWork unitOfWork) { HashSet<Entity> set = new HashSet<>(); Set<Entity> entities = unitOfWorkMap.get(unitOfWork); if (entities != null) set.addAll(entities); return set; } protected Collection<Entity> getAllInstances() { Set<Entity> set = createEntitySet(); for (Set<Entity> instances : unitOfWorkMap.values()) { set.addAll(instances); } return set; } protected Collection<Entity> getSavedInstances() { return savedInstances; } @Override public String toString() { return "ContainerResourceHolder@" + Integer.toHexString(hashCode()) + "{" + "storeName='" + storeName + '\'' + '}'; } } protected class ContainerResourceSynchronization extends ResourceHolderSynchronization<ContainerResourceHolder, String> implements Ordered { protected final ContainerResourceHolder container; public ContainerResourceSynchronization(ContainerResourceHolder resourceHolder, String resourceKey) { super(resourceHolder, resourceKey); this.container = resourceHolder; } @Override protected void cleanupResource(ContainerResourceHolder resourceHolder, String resourceKey, boolean committed) { resourceHolder.unitOfWorkMap.clear(); resourceHolder.savedInstances.clear(); } @Override public void beforeCommit(boolean readOnly) { if (log.isTraceEnabled()) log.trace("ContainerResourceSynchronization.beforeCommit: instances=" + container.getAllInstances() + ", readOnly=" + readOnly); if (!readOnly) { traverseEntities(container, new OnCommitEntityVisitor(container.getStoreName()), false); entityLog.flush(); } Collection<Entity> instances = container.getAllInstances(); Set<String> typeNames = new HashSet<>(); for (Object instance : instances) { if (instance instanceof Entity) { Entity entity = (Entity) instance; if (readOnly) { AttributeChangeListener changeListener = (AttributeChangeListener) ((ChangeTracker) entity) ._persistence_getPropertyChangeListener(); if (changeListener != null && changeListener.hasChanges()) throw new IllegalStateException( "Changed instance " + entity + " in read-only transaction"); } // if cache is enabled, the entity can have EntityFetchGroup instead of CubaEntityFetchGroup if (instance instanceof FetchGroupTracker) { FetchGroupTracker fetchGroupTracker = (FetchGroupTracker) entity; FetchGroup fetchGroup = fetchGroupTracker._persistence_getFetchGroup(); if (fetchGroup != null && !(fetchGroup instanceof CubaEntityFetchGroup)) fetchGroupTracker._persistence_setFetchGroup(new CubaEntityFetchGroup(fetchGroup)); } if (PersistenceHelper.isNew(entity)) { typeNames.add(entity.getMetaClass().getName()); } entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_DETACH, container.getStoreName()); } } if (!readOnly) { Collection<Entity> allInstances = container.getAllInstances(); for (BeforeCommitTransactionListener transactionListener : beforeCommitTxListeners) { transactionListener.beforeCommit(persistence.getEntityManager(container.getStoreName()), allInstances); } queryCacheManager.invalidate(typeNames, true); } } @Override public void afterCompletion(int status) { try { Collection<Entity> instances = container.getAllInstances(); if (log.isTraceEnabled()) log.trace("ContainerResourceSynchronization.afterCompletion: instances = " + instances); for (Object instance : instances) { if (instance instanceof BaseGenericIdEntity) { BaseGenericIdEntity baseGenericIdEntity = (BaseGenericIdEntity) instance; BaseEntityInternalAccess.setManaged(baseGenericIdEntity, false); if (BaseEntityInternalAccess.isNew(baseGenericIdEntity)) { // new instances become not new and detached only if the transaction was committed if (status == TransactionSynchronization.STATUS_COMMITTED) { BaseEntityInternalAccess.setNew(baseGenericIdEntity, false); BaseEntityInternalAccess.setDetached(baseGenericIdEntity, true); } } else { BaseEntityInternalAccess.setDetached(baseGenericIdEntity, true); } } if (instance instanceof FetchGroupTracker) { ((FetchGroupTracker) instance)._persistence_setSession(null); } if (instance instanceof ChangeTracker) { ((ChangeTracker) instance)._persistence_setPropertyChangeListener(null); } } for (AfterCompleteTransactionListener listener : afterCompleteTxListeners) { listener.afterComplete(status == TransactionSynchronization.STATUS_COMMITTED, instances); } } finally { super.afterCompletion(status); } } @Override public int getOrder() { return 100; } } protected class OnCommitEntityVisitor implements EntityVisitor { private String storeName; public OnCommitEntityVisitor(String storeName) { this.storeName = storeName; } @Override public boolean visit(BaseGenericIdEntity entity) { if (BaseEntityInternalAccess.isNew(entity) && !getSavedInstances(storeName).contains(entity)) { entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_INSERT, storeName); entityLog.registerCreate(entity, true); enqueueForFts(entity, FtsChangeType.INSERT); ormCacheSupport.evictMasterEntity(entity, null); return true; } AttributeChangeListener changeListener = (AttributeChangeListener) ((ChangeTracker) entity) ._persistence_getPropertyChangeListener(); if (changeListener == null) return false; if (isDeleted(entity, changeListener)) { entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_DELETE, storeName); entityLog.registerDelete(entity, true); if ((entity instanceof SoftDelete)) processDeletePolicy(entity); enqueueForFts(entity, FtsChangeType.DELETE); ormCacheSupport.evictMasterEntity(entity, null); return true; } else if (changeListener.hasChanges()) { EntityAttributeChanges changes = new EntityAttributeChanges(); // add changes before listener changes.addChanges(changeListener.getObjectChangeSet()); entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_UPDATE, storeName); // add changes after listener changes.addChanges(changeListener.getObjectChangeSet()); if (BaseEntityInternalAccess.isNew(entity)) { // it can happen if flush was performed, so the entity is still New but was saved entityLog.registerCreate(entity, true); enqueueForFts(entity, FtsChangeType.INSERT); } else { entityLog.registerModify(entity, true, changes); enqueueForFts(entity, FtsChangeType.UPDATE); } ormCacheSupport.evictMasterEntity(entity, changes); return true; } return false; } protected void enqueueForFts(Entity entity, FtsChangeType changeType) { if (!FtsConfigHelper.getEnabled()) return; try { if (ftsSender == null) { if (AppBeans.containsBean(FtsSender.NAME)) { ftsSender = AppBeans.get(FtsSender.NAME); } else { log.error("Error enqueueing changes for FTS: " + FtsSender.NAME + " bean not found"); } } if (ftsSender != null) ftsSender.enqueue(entity, changeType); } catch (Exception e) { log.error("Error enqueueing changes for FTS", e); } } protected void processDeletePolicy(Entity entity) { DeletePolicyProcessor processor = AppBeans.get(DeletePolicyProcessor.NAME); // prototype processor.setEntity(entity); processor.process(); } } protected class OnFlushEntityVisitor implements EntityVisitor { private String storeName; public OnFlushEntityVisitor(String storeName) { this.storeName = storeName; } @Override public boolean visit(BaseGenericIdEntity entity) { if (BaseEntityInternalAccess.isNew(entity) && !getSavedInstances(storeName).contains(entity)) { entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_INSERT, storeName); entityLog.registerCreate(entity, true); return true; } AttributeChangeListener changeListener = (AttributeChangeListener) ((ChangeTracker) entity) ._persistence_getPropertyChangeListener(); if (changeListener == null) return false; if (isDeleted(entity, changeListener)) { entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_DELETE, storeName); entityLog.registerDelete(entity, true); return true; } else if (changeListener.hasChanges()) { entityListenerManager.fireListener(entity, EntityListenerType.BEFORE_UPDATE, storeName); if (BaseEntityInternalAccess.isNew(entity)) { // it can happen if flush has already happened, so the entity is still New but was saved entityLog.registerCreate(entity, true); } else { EntityAttributeChanges changes = new EntityAttributeChanges(); changes.addChanges(changeListener.getObjectChangeSet()); entityLog.registerModify(entity, true, changes); } return true; } return false; } } }