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}