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