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