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