Skip to content

Commit

Permalink
Created base classes for bean keys to identify persistent data within…
Browse files Browse the repository at this point in the history
… a transaction. Also created generic methods based on these keys to reduce the amount of required methods for every key type. And some other small things.
  • Loading branch information
SimonDan committed Jan 11, 2020
1 parent 09ab08c commit 72f794e
Show file tree
Hide file tree
Showing 24 changed files with 412 additions and 366 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@
import de.adito.ojcms.beans.literals.fields.IField;
import de.adito.ojcms.transactions.annotations.TransactionalScoped;
import de.adito.ojcms.transactions.api.*;
import de.adito.ojcms.transactions.spi.IBeanDataLoader;
import de.adito.ojcms.transactions.spi.*;

import javax.inject.Inject;
import java.sql.*;
import java.util.*;
import java.util.function.Function;

/**
* Implementation of a managed transaction that lives in a transactional scope.
Expand All @@ -23,85 +21,70 @@ class ManagedTransaction implements ITransaction
@Inject
private IBeanDataLoader loader;
@Inject
private IBeanDataStorage storage;
@Inject
private OverallTransactionalChanges overallTransactionalChanges;
@Inject
private TransactionalChanges changes;
@Inject
private Connection connection;

private final Map<String, Integer> containerSizeCache = new HashMap<>();
private final Map<ContainerIndexKey, BeanData<ContainerIndexKey>> byIndexCache = new HashMap<>();
private final Map<ContainerIdentifierKey, BeanData<ContainerIndexKey>> byIdentifierCache = new HashMap<>();
private final Map<String, BeanData<String>> singleBeanCache = new HashMap<>();
private final Map<IBeanKey, PersistentBeanData> beanDataCache = new HashMap<>();
private final Map<String, Map<Integer, PersistentBeanData>> fullContainerLoadCache = new HashMap<>();

@Override
public int requestContainerSize(String pContainerId)
{
if (overallTransactionalChanges.isContainerDirty(pContainerId, changes))
throw new ConcurrentTransactionException(pContainerId);
overallTransactionalChanges.throwIfContainerDirty(pContainerId, changes);

return containerSizeCache.computeIfAbsent(pContainerId, loader::loadSize);
final int initialSize = containerSizeCache.computeIfAbsent(pContainerId, loader::loadContainerSize);
final int differenceByChanges = changes.getContainerSizeDifference(pContainerId);
return initialSize + differenceByChanges;
}

@Override
public BeanData<ContainerIndexKey> requestBeanDataFromContainer(ContainerIndexKey pIndexBasedKey)
public <KEY extends IBeanKey> PersistentBeanData requestBeanDataByKey(KEY pKey)
{
if (overallTransactionalChanges.isBeanInContainerDirty(pIndexBasedKey, changes))
throw new ConcurrentTransactionException(pIndexBasedKey);
overallTransactionalChanges.throwIfBeanDirty(pKey, changes);

return _retrieveFromCacheOrLoadAndCache(byIndexCache, pIndexBasedKey, loader::loadByIndex);
final PersistentBeanData initialData = beanDataCache.computeIfAbsent(pKey, loader::loadByKey);
return _integrateChanges(pKey, initialData);
}

@Override
public BeanData<ContainerIndexKey> requestBeanDataFromContainer(ContainerIdentifierKey pIdentifierKey)
public Map<Integer, PersistentBeanData> requestFullContainerLoad(String pContainerId)
{
final BeanData<ContainerIndexKey> beanData = _retrieveFromCacheOrLoadAndCache(byIdentifierCache, pIdentifierKey,
loader::loadByIdentifiers);
overallTransactionalChanges.throwIfContainerDirty(pContainerId, changes);
final Map<Integer, PersistentBeanData> fullData = fullContainerLoadCache.computeIfAbsent(pContainerId, loader::fullContainerLoad);

//If there are changes to the requested bean, it must have been cached, so no problem retrieving the index key from there
if (overallTransactionalChanges.isBeanInContainerDirty(beanData.getKey(), changes))
throw new ConcurrentTransactionException(beanData.getKey());

return beanData;
}

@Override
public BeanData<String> requestSingleBeanData(String pSingleBeanId)
{
if (overallTransactionalChanges.isSingleBeanDirty(pSingleBeanId, changes))
throw new ConcurrentTransactionException(pSingleBeanId);
for (PersistentBeanData beanData : fullData.values())
{
//There may be changes registered by index or identifiers
_integrateChanges(beanData.createIndexKey(pContainerId), beanData);
_integrateChanges(beanData.createIdentifierKey(pContainerId), beanData);
}

return singleBeanCache.computeIfAbsent(pSingleBeanId, loader::loadSingleBean);
return fullData;
}

@Override
public void registerBeanAddition(String pContainerId, int pIndex, Map<IField<?>, Object> pNewData)
{
overallTransactionalChanges.throwIfContainerDirty(pContainerId, changes);
changes.beanAdded(pContainerId, pIndex, pNewData);
}

@Override
public void registerBeanRemoval(ContainerIndexKey pIndexBasedKey)
{
changes.beanRemoved(pIndexBasedKey);
}

@Override
public void registerBeanRemoval(ContainerIdentifierKey pIdentifierKey)
{
changes.beanRemoved(pIdentifierKey);
}

@Override
public <VALUE> void registerBeanValueChange(ContainerIndexKey pContainerKey, IField<VALUE> pChangedField, VALUE pNewValue)
public <KEY extends IContainerBeanKey> void registerBeanRemoval(KEY pKey)
{
changes.beanValueChanged(pContainerKey, pChangedField, pNewValue);
overallTransactionalChanges.throwIfContainerDirty(pKey.getContainerId(), changes);
changes.beanRemoved(pKey);
}

@Override
public <VALUE> void registerSingleBeanValueChange(String pSingleBeanId, IField<VALUE> pChangedField, VALUE pNewValue)
public <KEY extends IBeanKey, VALUE> void registerBeanValueChange(KEY pKey, IField<VALUE> pChangedField, VALUE pNewValue)
{
changes.singleBeanValueChanged(pSingleBeanId, pChangedField, pNewValue);
overallTransactionalChanges.throwIfBeanDirty(pKey, changes);
changes.beanValueChanged(pKey, pChangedField, pNewValue);
}

/**
Expand All @@ -110,51 +93,28 @@ public <VALUE> void registerSingleBeanValueChange(String pSingleBeanId, IField<V
void commit()
{
changes.commitChanges();

try
{
connection.commit();
}
catch (SQLException pE)
{
throw new RuntimeException(pE);
}
storage.commitChanges();
}

/**
* Rolls back all changes made during this transaction.
*/
void rollback()
{
try
{
connection.rollback();
}
catch (SQLException pE)
{
throw new RuntimeException(pE);
}
storage.rollbackChanges();
}

/**
* Tries to retrieve bean data from the cache or load the data if not cached yet.
* Integrates changes made within this transaction to an instance of {@link PersistentBeanData}.
*
* @param pCache the cache holding bean data by identifying keys
* @param pKey the generic key to identify the bean data
* @param loadingFunction a function to load the persistent bean data by the key
* @param <KEY> the generic type of the key identifying the bean data
* @return the cached or loaded bean data
* @param pKey the key to identify the changes
* @param pInitialData the intial bean data to integrate the changes into
* @return the persistent bean data with integrated changes
*/
private <KEY> BeanData<ContainerIndexKey> _retrieveFromCacheOrLoadAndCache(Map<KEY, BeanData<ContainerIndexKey>> pCache, KEY pKey,
Function<KEY, BeanData<ContainerIndexKey>> loadingFunction)
private <KEY extends IBeanKey> PersistentBeanData _integrateChanges(KEY pKey, PersistentBeanData pInitialData)
{
if (pCache.containsKey(pKey))
return pCache.get(pKey);

final BeanData<ContainerIndexKey> beanData = loadingFunction.apply(pKey);
byIndexCache.put(beanData.getKey(), beanData);
byIdentifierCache.put(beanData.getIdentifierKey(beanData.getKey().getContainerId()), beanData);

return beanData;
return changes.getPotentiallyChangedValues(pKey)
.map(pInitialData::integrateChanges)
.orElse(pInitialData);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.adito.ojcms.transactions;

import de.adito.ojcms.transactions.api.ContainerIndexKey;
import de.adito.ojcms.transactions.api.IBeanKey;
import de.adito.ojcms.transactions.exceptions.ConcurrentTransactionException;

import javax.enterprise.context.ApplicationScoped;
import java.util.Set;
Expand All @@ -20,39 +21,25 @@ class OverallTransactionalChanges
private final Set<TransactionalChanges> activeTransactionalChanges = ConcurrentHashMap.newKeySet();

/**
* Determines if the content of a bean container (size related) is currently changed by an other transaction.
* Throws a {@link ConcurrentTransactionException} if a bean container has been modified by another transaction.
*
* @param pContainerId the id of the container to check
* @param pSelfReference a self reference to the asking changes instance (to exclude its changes)
* @return <tt>true</tt> if size related data of the container has been changed by another transaction
*/
boolean isContainerDirty(String pContainerId, TransactionalChanges pSelfReference)
void throwIfContainerDirty(String pContainerId, TransactionalChanges pSelfReference)
{
return _checkForChange(pChanges -> pChanges.isContainerDirty(pContainerId), pSelfReference);
_throwIfChangedInOtherTransaction(pContainerId, pChanges -> pChanges.isContainerDirty(pContainerId), pSelfReference);
}

/**
* Determines if the data of a bean within a container has been changed by another transaction.
* Throws a {@link ConcurrentTransactionException} if a bean has been modified by another transaction.
*
* @param pKey the index based key identifying the bean in the container
* @param pKey the key identifying the bean
* @param pSelfReference a self reference to the asking changes instance (to exclude its changes)
* @return <tt>true</tt> the data of the bean within the container has been changed by another transaction
*/
boolean isBeanInContainerDirty(ContainerIndexKey pKey, TransactionalChanges pSelfReference)
<KEY extends IBeanKey> void throwIfBeanDirty(KEY pKey, TransactionalChanges pSelfReference)
{
return _checkForChange(pChanges -> pChanges.isBeanInContainerDirty(pKey), pSelfReference);
}

/**
* Determines if the data of a single bean has been changed by another transaction.
*
* @param pKey the id of the single bean
* @param pSelfReference a self reference to the asking changes instance (to exclude its changes)
* @return <tt>true</tt> the data of the single bean has been changed by another transaction
*/
boolean isSingleBeanDirty(String pKey, TransactionalChanges pSelfReference)
{
return _checkForChange(pChanges -> pChanges.isSingleBeanDirty(pKey), pSelfReference);
_throwIfChangedInOtherTransaction(pKey, pChanges -> pChanges.isBeanDirty(pKey), pSelfReference);
}

/**
Expand All @@ -76,16 +63,18 @@ void deregister(TransactionalChanges pChanges)
}

/**
* Checks all registered {@link TransactionalChanges} instances (apart from the self reference) for changes based on a predicate.
* Checks all registered {@link TransactionalChanges} instances (apart from the self reference) for changes based on a predicate
* and throws a {@link ConcurrentTransactionException} if changes have been found.
*
* @param pKey the key used in the predicate to identify a bean or container
* @param pPredicate the predicate to determine if requested data is changed
* @param pSelfReference a self reference to the asking changes instance (to exclude its changes)
* @return <tt>true</tt> if the requested data has been changed by another transaction
*/
private boolean _checkForChange(Predicate<TransactionalChanges> pPredicate, TransactionalChanges pSelfReference)
private void _throwIfChangedInOtherTransaction(Object pKey, Predicate<TransactionalChanges> pPredicate, TransactionalChanges pSelfReference)
{
return activeTransactionalChanges.stream()
if (activeTransactionalChanges.stream()
.filter(pChanges -> pChanges.equals(pSelfReference))
.anyMatch(pPredicate);
.anyMatch(pPredicate))
throw new ConcurrentTransactionException(pKey);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.adito.ojcms.transactions;

import de.adito.ojcms.cdi.*;
import de.adito.ojcms.cdi.context.*;
import de.adito.ojcms.transactions.annotations.TransactionalScoped;

import java.lang.annotation.Annotation;
Expand Down
Loading

0 comments on commit 72f794e

Please sign in to comment.