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.persistence.ocfl.impl;
007
008import static org.slf4j.LoggerFactory.getLogger;
009
010import java.util.List;
011import java.util.concurrent.TimeUnit;
012
013import org.fcrepo.common.db.DbTransactionExecutor;
014import org.fcrepo.kernel.api.Transaction;
015import org.fcrepo.kernel.api.TransactionManager;
016
017import org.slf4j.Logger;
018
019import com.google.common.base.Stopwatch;
020
021/**
022 * A reindexing worker thread.
023 * @author whikloj
024 */
025public class ReindexWorker implements Runnable {
026
027    private static final Logger LOGGER = getLogger(ReindexWorker.class);
028
029    private static final long REPORTING_INTERVAL_SECS = 30;
030
031    private Thread t;
032    private ReindexManager manager;
033    private ReindexService service;
034    private boolean running = true;
035    private boolean failOnError;
036    private TransactionManager txManager;
037    private DbTransactionExecutor dbTransactionExecutor;
038
039    /**
040     * Basic Constructor
041     * @param name the name of the worker -- used in logging
042     * @param reindexManager the manager service.
043     * @param reindexService the reindexing service.
044     * @param transactionManager a transaction manager to generate
045     * @param dbTransactionExecutor manages db transactions
046     * @param failOnError whether the thread should fail on an error or log and continue.
047     */
048    public ReindexWorker(final String name,
049                         final ReindexManager reindexManager,
050                         final ReindexService reindexService,
051                         final TransactionManager transactionManager,
052                         final DbTransactionExecutor dbTransactionExecutor,
053                         final boolean failOnError) {
054        manager = reindexManager;
055        service = reindexService;
056        txManager = transactionManager;
057        this.dbTransactionExecutor = dbTransactionExecutor;
058        this.failOnError = failOnError;
059        t = new Thread(this, name);
060    }
061
062    /**
063     * Join the thread.
064     * @throws InterruptedException if the current thread is interrupted.
065     */
066    public void join() throws InterruptedException {
067        t.join();
068    }
069
070    /**
071     * Start the thread with this Runnable
072     */
073    public void start() {
074        t.start();
075    }
076
077    @Override
078    public void run() {
079        final var stopwatch = Stopwatch.createStarted();
080        while (running) {
081            final List<String> ids = manager.getIds();
082            if (ids.isEmpty()) {
083                LOGGER.debug("No more objects found to process. Stopping...");
084                stopThread();
085                break;
086            }
087
088            int completed = 0;
089            int errors = 0;
090
091            for (final var id : ids) {
092                if (!running) {
093                    break;
094                }
095
096                final Transaction tx = txManager.create();
097                tx.setShortLived(true);
098                if (stopwatch.elapsed(TimeUnit.SECONDS) > REPORTING_INTERVAL_SECS) {
099                    manager.updateComplete(completed, errors);
100                    completed = 0;
101                    errors = 0;
102                    stopwatch.reset().start();
103                }
104                try {
105                    dbTransactionExecutor.doInTxWithRetry(() -> {
106                        service.indexOcflObject(tx, id);
107                        tx.commit();
108                    });
109                    completed += 1;
110                } catch (final Exception e) {
111                    LOGGER.error("Reindexing of OCFL id {} failed", id, e);
112                    tx.rollback();
113                    errors += 1;
114                    if (failOnError) {
115                        manager.updateComplete(completed, errors);
116                        manager.stop();
117                        service.cleanupSession(tx.getId());
118                        throw e;
119                    }
120                }
121                service.cleanupSession(tx.getId());
122            }
123            manager.updateComplete(completed, errors);
124        }
125    }
126
127    /**
128     * Stop this thread from running once it has completed its current batch.
129     */
130    public void stopThread() {
131        this.running = false;
132    }
133
134}