package org.ovirt.engine.core.bll.context; import java.io.Serializable; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.Set; import org.ovirt.engine.core.common.businessentities.BusinessEntity; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.EntityStatusSnapshot; import org.ovirt.engine.core.common.businessentities.BusinessEntitySnapshot.SnapshotType; import org.ovirt.engine.core.common.businessentities.BusinessEntityWithStatus; import org.ovirt.engine.core.common.businessentities.TransientCompensationBusinessEntity; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.dal.dbbroker.DbFacade; import org.ovirt.engine.core.dao.BusinessEntitySnapshotDao; import org.ovirt.engine.core.utils.Serializer; /** * Default context used to track entities that are changing during a command's execution and save the changes at each * state change to the DB. */ public class DefaultCompensationContext extends CompensationContextBase { /** * A set of all the entities which have been snapshotted ever in this context, since we only want to save the * initial snapshot of each entity the command has changed/created. */ private Set<CachedEntityEntry> cachedEntities = new HashSet<>(); /** * All the entities that have been changed/added since the state/command began. */ private List<BusinessEntitySnapshot> entitiesToPersist = new LinkedList<>(); /** * The serializer which is used to convert the entity to a snapshot. */ private Serializer snapshotSerializer; /** * The Dao which is used to track all the changed business entities. */ private BusinessEntitySnapshotDao businessEntitySnapshotDao; /** * The id of the command which this context is tracking. */ private Guid commandId; /** * The type of the command which this context is tracking. */ private String commandType; /** * @param snapshotSerializer * the snapshotSerializer to set */ public void setSnapshotSerializer(Serializer snapshotSerializer) { this.snapshotSerializer = snapshotSerializer; } /** * @param businessEntitySnapshotDao * the businessEntitySnapshotDao to set */ public void setBusinessEntitySnapshotDao(BusinessEntitySnapshotDao businessEntitySnapshotDao) { this.businessEntitySnapshotDao = businessEntitySnapshotDao; } /** * @param commandId * the commandId to set */ public void setCommandId(Guid commandId) { this.commandId = commandId; } /** * Return the command id for the compensation context * @return the command id */ public Guid getCommandId() { return commandId; } /** * @param commandType * the commandType to set */ public void setCommandType(String commandType) { this.commandType = commandType; } @Override public void snapshotEntity(BusinessEntity<?> entity) { snapshotEntityInMemory(entity, entity, SnapshotType.DELETED_OR_UPDATED_ENTITY); } @Override public void snapshotEntityUpdated(BusinessEntity<?> entity) { snapshotEntityInMemory(entity, entity, SnapshotType.UPDATED_ONLY_ENTITY); } @Override public void snapshotNewEntity(BusinessEntity<?> entity) { snapshotEntityInMemory(entity, entity.getId(), SnapshotType.NEW_ENTITY_ID); } @Override public <T extends Enum<?>> void snapshotEntityStatus(BusinessEntityWithStatus<?, T> entity, T status) { EntityStatusSnapshot snapshot = new EntityStatusSnapshot(); snapshot.setId(entity.getId()); snapshot.setStatus(status); snapshotEntityInMemory(entity, snapshot, SnapshotType.CHANGED_STATUS_ONLY); } @Override public void snapshotObject(TransientCompensationBusinessEntity entity) { snapshotEntityInMemory(entity, entity, SnapshotType.TRANSIENT_ENTITY); } @Override public <T extends Enum<?>> void snapshotEntityStatus(BusinessEntityWithStatus<?, T> entity) { snapshotEntityStatus(entity, entity.getStatus()); } /** * Save a snapshot of the entity but only if it is new to this context. * * @param entity * The entity to save a snapshot of. * @param payload * The payload to be serialized and saved. * @param snapshotType * The type of snapshot we're taking, so that in compensation we know what is the payload type, and how * to use it to revert the entity state. */ private void snapshotEntityInMemory(BusinessEntity<?> entity, Serializable payload, SnapshotType snapshotType) { CachedEntityEntry cachedEntityEntry = new CachedEntityEntry(entity, snapshotType); checkEntityForRollback(entity); if (!cachedEntities.contains(cachedEntityEntry)) { cachedEntities.add(cachedEntityEntry); entitiesToPersist.add(createBusinessEntitySnapshot(entity, payload, snapshotType)); } } private BusinessEntitySnapshot createBusinessEntitySnapshot(BusinessEntity<?> entity, Serializable payload, SnapshotType snapshotType) { BusinessEntitySnapshot entitySnapshot = new BusinessEntitySnapshot(); entitySnapshot.setCommandId(commandId); entitySnapshot.setCommandType(commandType); entitySnapshot.setEntityId(String.valueOf(entity.getId())); entitySnapshot.setEntityType(entity.getClass().getName()); entitySnapshot.setEntitySnapshot((String) snapshotSerializer.serialize(payload)); entitySnapshot.setSnapshotClass(payload.getClass().getName()); entitySnapshot.setSnapshotType(snapshotType); entitySnapshot.setInsertionOrder(cachedEntities.size()); return entitySnapshot; } private void checkEntityForRollback(BusinessEntity<?> entity) { if(entity == null) { throw new IllegalArgumentException("Can not create snapshot from a null entity"); } @SuppressWarnings("unchecked") Class<BusinessEntity<Serializable>> entityClass = (Class<BusinessEntity<Serializable>>) entity.getClass(); boolean verifyDaoExistence = ! (entity instanceof TransientCompensationBusinessEntity); if (verifyDaoExistence) { //callMustNotFail DbFacade.getInstance().getDaoForEntity(entityClass); } } @Override public void stateChanged() { for (BusinessEntitySnapshot snapshot : entitiesToPersist) { businessEntitySnapshotDao.save(snapshot); } entitiesToPersist.clear(); } @Override public void doAfterCompensationCleanup() { doClearCollectedCompensationData(); } @Override public void doCleanupCompensationDataAfterSuccessfulCommand() { doClearCollectedCompensationData(); } @Override public void doClearCollectedCompensationData() { businessEntitySnapshotDao.removeAllForCommandId(commandId); cachedEntities.clear(); entitiesToPersist.clear(); } /* -- Inner types -- */ /** * Represents a cached entity which is made of the snapshot type, the entity class and the id, so that we can track * which entities have already been recorded and which entities have not. */ private class CachedEntityEntry { /** * The id of the cached entity. */ private Object id; /** * The class of the cached entity. */ private Class<?> entityClass; /** * The type of snapshot that is cached. */ private SnapshotType snapshotType; /** * Construct a new cached entry for the given entity. * * @param entity * The entity to construct a cache entry for. * @param snapshotType * The type of snapshot. */ public CachedEntityEntry(BusinessEntity<?> entity, SnapshotType snapshotType) { super(); this.id = entity.getId(); this.entityClass = entity.getClass(); this.snapshotType = snapshotType; } @Override public int hashCode() { return Objects.hash( getOuterType(), entityClass, id, snapshotType ); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof CachedEntityEntry)) { return false; } CachedEntityEntry other = (CachedEntityEntry) obj; return Objects.equals(getOuterType(), other.getOuterType()) && Objects.equals(entityClass, other.entityClass) && Objects.equals(id, other.id) && Objects.equals(snapshotType, other.snapshotType); } private DefaultCompensationContext getOuterType() { return DefaultCompensationContext.this; } } @Override public void snapshotEntities(Collection<? extends BusinessEntity<?>> entities) { if (entities != null) { for (BusinessEntity<?> entity : entities) { snapshotEntity(entity); } } } @Override public void snapshotNewEntities(Collection<? extends BusinessEntity<?>> entities) { if (entities != null) { for (BusinessEntity<?> entity : entities) { snapshotNewEntity(entity); } } } @Override public boolean isCompensationEnabled() { return true; } }