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 *
006 */
007package org.fcrepo.migration.pidlist;
008
009import org.slf4j.Logger;
010
011import java.io.BufferedReader;
012import java.io.File;
013import java.io.FileReader;
014import java.io.FileWriter;
015import java.io.IOException;
016import java.io.PrintWriter;
017
018import static org.slf4j.LoggerFactory.getLogger;
019
020/**
021 * This class "accepts" PIDs that have not already been migrated.
022 * <p>
023 * The approach taking by this implementation is to record:
024 * - the number of objects that have been migrated, and
025 * - the PID of the last migrated object
026 * <p>
027 * The assumption is that the order of processed PIDs/Objects is deterministic
028 *
029 * @author awoods
030 * @since 2019-11-08
031 */
032public class ResumePidListManager implements PidListManager {
033
034    private static final Logger LOGGER = getLogger(ResumePidListManager.class);
035
036    private File resumeFile;
037
038    // Accept all PIDs, even if they have been processed before
039    private boolean acceptAll;
040
041    // Position of the last processed PID/Object (assuming deterministic ordering)
042    private int pidResumeIndex;
043
044    // Value of the last processed PID/Object
045    private String pidResumeValue;
046
047    // Number of times "accept" has been called
048    private int index = 0;
049
050    // Last value of current PID
051    private String value = "foo";
052
053
054    /**
055     * Constructor
056     *
057     * @param pidDir where resume file will be read/created
058     * @param acceptAll whether to process all pids even if they've been processed before.
059     */
060    public ResumePidListManager(final File pidDir, final boolean acceptAll) {
061        if (!pidDir.exists()) {
062            pidDir.mkdirs();
063        }
064
065        if (!pidDir.isDirectory()) {
066            throw new IllegalArgumentException("Arg must be a directory: " + pidDir.getAbsolutePath());
067        }
068
069        this.acceptAll = acceptAll;
070        this.resumeFile = new File(pidDir, "resume.txt");
071        LOGGER.debug("Resume pid file: {}, accept all? {}", resumeFile.getAbsolutePath(), acceptAll);
072
073        try {
074            // Load pidResumeIndex and pidResumeValue
075            loadResumeFile();
076
077        } catch (IOException e) {
078            throw new RuntimeException(e);
079        }
080    }
081
082    private void loadResumeFile() throws IOException {
083
084        // First run? file does not yet exist?
085        if (!resumeFile.exists() || resumeFile.length() == 0) {
086            updateResumeFile(value, index);
087        }
088
089        try (final BufferedReader reader = new BufferedReader(new FileReader(resumeFile))) {
090
091            // First line contains PID
092            pidResumeValue = reader.readLine();
093
094            // Second line contains index
095            pidResumeIndex = Integer.parseInt(reader.readLine());
096        }
097    }
098
099
100    /**
101     * This method
102     * - returns false if "accept" has been called less than pidResumeIndex times
103     * - returns true if "accept" has been called
104     */
105    @Override
106    public boolean accept(final String pid) {
107        final String logMsg = "PID: " + pid + ", accept? ";
108
109        final String previousValue = value;
110        value = pid;
111        index++;
112
113        // Do not accept.. the previous run index is higher
114        if (index - 1 < pidResumeIndex) {
115
116            // Are we accepting all?
117            LOGGER.debug(logMsg + acceptAll);
118            return acceptAll;
119        }
120
121        // We are at the first PID that has not previously been processed
122        if (index - 1 == pidResumeIndex) {
123
124            // index matches, but value DOES NOT match the last state of previous run!
125            if (!previousValue.equalsIgnoreCase(pidResumeValue)) {
126                final String msg = "Number of accept requests does not align with expected PID value! " +
127                        "index: " + index + ", " +
128                        "pid: " + pid + ", " +
129                        "expected pid: " + pidResumeValue;
130                throw new IllegalStateException(msg);
131            }
132        }
133
134        // New "accept" requests
135        updateResumeFile(value, index);
136
137        LOGGER.debug(logMsg + true);
138        return true;
139    }
140
141    /**
142     * This method resets the current index and value, and resets the resume file
143     * -- Used for test --
144     */
145    void reset() {
146        index = 0;
147        value = "foo";
148        updateResumeFile(value, index);
149    }
150
151    private void updateResumeFile(final String pid, final int index) {
152        // Create writer of resumeFile (expense to do everytime... but need to overwrite file)
153        PrintWriter resumeFileWriter = null;
154        try {
155            resumeFileWriter = new PrintWriter(new FileWriter(resumeFile, false));
156
157        } catch (IOException e) {
158            throw new RuntimeException(e);
159        }
160
161        resumeFileWriter.write(pid);
162        resumeFileWriter.write(System.getProperty("line.separator"));
163        resumeFileWriter.write(Integer.toString(index));
164
165        resumeFileWriter.flush();
166        resumeFileWriter.close();
167    }
168}