001/* 002 * The contents of this file are subject to the license and copyright 003 * detailed in the LICENSE and NOTICE files at the root of the source 004 * tree. 005 */ 006package org.fcrepo.kernel.impl; 007 008import org.fcrepo.common.db.DbTransactionExecutor; 009import org.fcrepo.config.FedoraPropsConfig; 010import org.fcrepo.kernel.api.ContainmentIndex; 011import org.fcrepo.kernel.api.Transaction; 012import org.fcrepo.kernel.api.TransactionManager; 013import org.fcrepo.kernel.api.cache.UserTypesCache; 014import org.fcrepo.kernel.api.exception.TransactionClosedException; 015import org.fcrepo.kernel.api.exception.TransactionNotFoundException; 016import org.fcrepo.kernel.api.lock.ResourceLockManager; 017import org.fcrepo.kernel.api.observer.EventAccumulator; 018import org.fcrepo.kernel.api.services.MembershipService; 019import org.fcrepo.kernel.api.services.ReferenceService; 020import org.fcrepo.persistence.api.PersistentStorageSessionManager; 021import org.fcrepo.search.api.SearchIndex; 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024import org.springframework.beans.factory.annotation.Autowired; 025import org.springframework.beans.factory.annotation.Qualifier; 026import org.springframework.scheduling.annotation.Scheduled; 027import org.springframework.stereotype.Component; 028 029import javax.inject.Inject; 030import java.util.Map; 031import java.util.concurrent.ConcurrentHashMap; 032 033import static java.util.UUID.randomUUID; 034 035/** 036 * The Fedora Transaction Manager implementation 037 * 038 * @author mohideen 039 */ 040@Component 041public class TransactionManagerImpl implements TransactionManager { 042 043 private static final Logger LOGGER = LoggerFactory.getLogger(TransactionManagerImpl.class); 044 045 private final Map<String, Transaction> transactions; 046 047 @Autowired 048 @Qualifier("containmentIndex") 049 private ContainmentIndex containmentIndex; 050 051 @Inject 052 private PersistentStorageSessionManager pSessionManager; 053 054 @Inject 055 private EventAccumulator eventAccumulator; 056 057 @Autowired 058 @Qualifier("referenceService") 059 private ReferenceService referenceService; 060 061 @Inject 062 private MembershipService membershipService; 063 064 @Inject 065 @Qualifier("searchIndex") 066 private SearchIndex searchIndex; 067 068 @Inject 069 private ResourceLockManager resourceLockManager; 070 071 @Inject 072 private FedoraPropsConfig fedoraPropsConfig; 073 074 @Inject 075 private DbTransactionExecutor dbTransactionExecutor; 076 077 @Inject 078 private UserTypesCache userTypesCache; 079 080 TransactionManagerImpl() { 081 transactions = new ConcurrentHashMap<>(); 082 } 083 084 /** 085 * Periodically scan for closed transactions for cleanup 086 */ 087 @Scheduled(fixedDelayString = "#{fedoraPropsConfig.sessionTimeout}") 088 public void cleanupClosedTransactions() { 089 LOGGER.debug("Cleaning up expired transactions"); 090 091 final var txIt = transactions.entrySet().iterator(); 092 while (txIt.hasNext()) { 093 final var txEntry = txIt.next(); 094 final var tx = txEntry.getValue(); 095 096 // Cleanup if transaction is closed and past its expiration time 097 if (tx.isCommitted() || tx.isRolledBack()) { 098 if (tx.hasExpired()) { 099 txIt.remove(); 100 } 101 } else if (tx.hasExpired()) { 102 LOGGER.debug("Rolling back expired transaction {}", tx.getId()); 103 try { 104 // If the tx has expired but is not already closed, then rollback 105 // but don't immediately remove it from the list of transactions 106 // so that the rolled back status can be checked 107 tx.rollback(); 108 } catch (final RuntimeException e) { 109 LOGGER.error("Failed to rollback expired transaction {}", tx.getId(), e); 110 } 111 } 112 113 if (tx.hasExpired()) { 114 // By this point the session as already been committed or rolledback by the transaction 115 pSessionManager.removeSession(tx.getId()); 116 } 117 } 118 } 119 120 @Override 121 public synchronized Transaction create() { 122 String txId = randomUUID().toString(); 123 while (transactions.containsKey(txId)) { 124 txId = randomUUID().toString(); 125 } 126 final Transaction tx = new TransactionImpl(txId, this, fedoraPropsConfig.getSessionTimeout()); 127 transactions.put(txId, tx); 128 return tx; 129 } 130 131 @Override 132 public Transaction get(final String transactionId) { 133 if (transactions.containsKey(transactionId)) { 134 final Transaction transaction = transactions.get(transactionId); 135 if (transaction.hasExpired()) { 136 transaction.rollback(); 137 throw new TransactionClosedException("Transaction with transactionId: " + transactionId + 138 " expired at " + transaction.getExpires() + "!"); 139 } 140 if (transaction.isCommitted()) { 141 throw new TransactionClosedException("Transaction with transactionId: " + transactionId + 142 " has already been committed."); 143 } 144 if (transaction.isRolledBack()) { 145 throw new TransactionClosedException("Transaction with transactionId: " + transactionId + 146 " has already been rolled back."); 147 } 148 return transaction; 149 } else { 150 throw new TransactionNotFoundException("No Transaction found with transactionId: " + transactionId); 151 } 152 } 153 154 protected PersistentStorageSessionManager getPersistentStorageSessionManager() { 155 return pSessionManager; 156 } 157 158 protected ContainmentIndex getContainmentIndex() { 159 return containmentIndex; 160 } 161 162 protected SearchIndex getSearchIndex() { 163 return searchIndex; 164 } 165 166 167 protected EventAccumulator getEventAccumulator() { 168 return eventAccumulator; 169 } 170 171 protected ReferenceService getReferenceService() { 172 return referenceService; 173 } 174 175 protected MembershipService getMembershipService() { 176 return membershipService; 177 } 178 179 protected ResourceLockManager getResourceLockManager() { 180 return resourceLockManager; 181 } 182 183 protected UserTypesCache getUserTypesCache() { 184 return userTypesCache; 185 } 186 187 public DbTransactionExecutor getDbTransactionExecutor() { 188 return dbTransactionExecutor; 189 } 190}