/*
* ModeShape (http://www.modeshape.org)
*
* 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 org.modeshape.jcr.cache.document;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import org.modeshape.common.logging.Logger;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.JcrI18n;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.txn.Transactions;
/**
* A manager for keeping track of transaction-specific WorkspaceCache instances.
*/
public class TransactionalWorkspaceCaches {
private static final Logger LOGGER = Logger.getLogger(TransactionalWorkspaceCache.class);
private final TransactionManager txnMgr;
private final Map<Transaction, Map<String, TransactionalWorkspaceCache>> transactionalCachesByTransaction = new ConcurrentHashMap<>();
public TransactionalWorkspaceCaches( Transactions transactions ) {
CheckArg.isNotNull(transactions, "transactions");
this.txnMgr = transactions.getTransactionManager();
}
protected WorkspaceCache getTransactionalCache(WorkspaceCache globalWorkspaceCache) throws SystemException {
// Get the current transaction ...
Transaction txn = txnMgr.getTransaction();
if (txn == null || txn.getStatus() != Status.STATUS_ACTIVE) return globalWorkspaceCache;
String workspaceName = globalWorkspaceCache.getWorkspaceName();
return transactionalCachesByTransaction.computeIfAbsent(txn, this::newCacheMapForTransaction)
.computeIfAbsent(workspaceName, wsName -> new TransactionalWorkspaceCache(globalWorkspaceCache, this, txn));
}
private Map<String, TransactionalWorkspaceCache> newCacheMapForTransaction(final Transaction txn) {
try {
txn.registerSynchronization(new Synchronization() {
@Override
public void beforeCompletion() {
// do nothing ...
}
@Override
public void afterCompletion(int status) {
// No matter what, remove this transactional cache from the maps ...
Map<String, TransactionalWorkspaceCache> cachesByWsName = transactionalCachesByTransaction.remove(txn);
cachesByWsName.clear();
}
});
} catch (RollbackException | SystemException e) {
throw new RuntimeException(e);
}
return new ConcurrentHashMap<>();
}
public synchronized void rollbackActiveTransactionsForWorkspace(String workspaceName) {
List<Transaction> toRemove = new ArrayList<>();
// first rollback all active transactions and collect them at the same time...
transactionalCachesByTransaction.entrySet()
.stream()
.filter(entry -> entry.getValue().containsKey(workspaceName))
.forEach(entry -> {
Transaction tx = entry.getKey();
toRemove.add(tx);
try {
tx.rollback();
} catch (SystemException e) {
LOGGER.error(e,
JcrI18n.errorWhileRollingBackActiveTransactionUsingWorkspaceThatIsBeingDeleted,
workspaceName, e.getMessage());
}
});
// then remove them from the map
transactionalCachesByTransaction.keySet().removeAll(toRemove);
}
protected void clearAllCachesForTransaction(final Transaction txn) {
transactionalCachesByTransaction.getOrDefault(txn, Collections.emptyMap())
.forEach((wsName, txWsCache) -> txWsCache.internalClear());
}
protected void dispatchChangesForTransaction(final Transaction txn, final ChangeSet changes) {
transactionalCachesByTransaction.getOrDefault(txn, Collections.emptyMap())
.forEach((wsName, txWsCache) -> txWsCache.internalChangedWithinTransaction(changes));
}
}