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