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