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}