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.suppressEvents();
098                tx.setShortLived(true);
099                if (stopwatch.elapsed(TimeUnit.SECONDS) > REPORTING_INTERVAL_SECS) {
100                    manager.updateComplete(completed, errors);
101                    completed = 0;
102                    errors = 0;
103                    stopwatch.reset().start();
104                }
105                try {
106                    dbTransactionExecutor.doInTxWithRetry(() -> {
107                        service.indexOcflObject(tx, id);
108                        tx.commit();
109                    });
110                    completed += 1;
111                } catch (final Exception e) {
112                    LOGGER.error("Reindexing of OCFL id {} failed", id, e);
113                    tx.rollback();
114                    errors += 1;
115                    if (failOnError) {
116                        manager.updateComplete(completed, errors);
117                        manager.stop();
118                        service.cleanupSession(tx.getId());
119                        throw e;
120                    }
121                }
122                service.cleanupSession(tx.getId());
123            }
124            manager.updateComplete(completed, errors);
125        }
126    }
127
128    /**
129     * Stop this thread from running once it has completed its current batch.
130     */
131    public void stopThread() {
132        this.running = false;
133    }
134
135}