001/* 002 * #%L 003 * Netarchivesuite - archive 004 * %% 005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library, 006 * the National Library of France and the Austrian National Library. 007 * %% 008 * This program is free software: you can redistribute it and/or modify 009 * it under the terms of the GNU Lesser General Public License as 010 * published by the Free Software Foundation, either version 2.1 of the 011 * License, or (at your option) any later version. 012 * 013 * This program is distributed in the hope that it will be useful, 014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 016 * GNU General Lesser Public License for more details. 017 * 018 * You should have received a copy of the GNU General Lesser Public 019 * License along with this program. If not, see 020 * <http://www.gnu.org/licenses/lgpl-2.1.html>. 021 * #L% 022 */ 023 024package dk.netarkivet.archive.arcrepositoryadmin; 025 026import java.io.PrintWriter; 027import java.util.Date; 028import java.util.HashMap; 029import java.util.Map; 030import java.util.Set; 031 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import dk.netarkivet.archive.arcrepository.distribute.StoreMessage; 036import dk.netarkivet.common.distribute.arcrepository.ReplicaStoreState; 037import dk.netarkivet.common.exceptions.ArgumentNotValid; 038 039/** 040 * This class contains the information that we keep about each file in the arcrepository: Checksum and the store states 041 * for all bitarchives. 042 * <p> 043 * TODO Maybe don't have the store state info for fully completed stores, or else use a slimmer map for it. 044 */ 045public class ArcRepositoryEntry { 046 047 /** The log. */ 048 private static final Logger log = LoggerFactory.getLogger(ArcRepositoryEntry.class); 049 050 /** The filename for this entry (only set in the constructor). */ 051 private String filename; 052 053 /** 054 * The checksum of this file. This field is persistent in the admin data file. This field can never be null. Note: 055 * AdminData.setChecksum() can change this value. Note: ArcRepositoryEntry.setChecksum() can change this value. 056 */ 057 private String md5sum; 058 059 /** 060 * How the upload of this file into the bitarchives went. This field is persistent in the admin data file. After 061 * constructor initialization, this field should only be set in case of a correct operation (now or earlier). Note: 062 * the value 2 below is a hint to the number of bitarchives in our system. 063 */ 064 private Map<String, ArchiveStoreState> storeStates = new HashMap<String, ArchiveStoreState>(2); 065 066 /** 067 * The information used to reply about this entry being done. Once a reply has been sent, this entry is set to null. 068 * This field is not persistent. 069 */ 070 private StoreMessage replyInfo; 071 072 /** 073 * String used to separate the different parts of the arcRepositoryEntry, when we write the entry to persistent 074 * storage. Make package private, so accessable from AdminData 075 */ 076 static final String ENTRY_COMPONENT_SEPARATOR_STRING = " , "; 077 078 /** 079 * General delimiter used several places. Used to delimite the different storestates for the entry in the output() 080 * method. 081 */ 082 private static final String GENERAL_DELIMITER = " "; 083 084 /** 085 * Create a new entry with given checksum and replyinfo. 086 * 087 * @param filename The filename for this entry 088 * @param md5sum The checksum for this entry 089 * @param replyInfo The one-use-only reply info chunk 090 */ 091 public ArcRepositoryEntry(String filename, String md5sum, StoreMessage replyInfo) { 092 this.filename = filename; 093 this.md5sum = md5sum; 094 this.replyInfo = replyInfo; 095 } 096 097 /** 098 * Get the ArchiveStoreState for the entry in general. This is computed from the ArchiveStoreStates for the 099 * bitarchives. <br> 100 * 1. If no information about the bitarchives are available, the state UPLOAD_FAILED with timestamp=NOW is returned <br> 101 * 2. If there are information about one bitarchive, the state of this bitarchive is returned. <br> 102 * 3. If there are information from more than one bitarchive, A. if the state of one of the bitarchives equals 103 * UPLOAD_FAILED, the state UPLOAD_FAILED with the latest timestamp is returned B. else, find the lowest state of 104 * the N bitarchives: return this state together with the the latest timestamp 105 * <p> 106 * Note that the storestate and the timestamp might not come from the same bitarchive. 107 * 108 * @return the current ArchiveStoreState for the entry in general 109 */ 110 public ArchiveStoreState getGeneralStoreState() { 111 Set<String> bitarchives = storeStates.keySet(); 112 // Check whether scenario 1. 113 if (bitarchives.size() == 0) { 114 return new ArchiveStoreState(ReplicaStoreState.UPLOAD_FAILED); 115 } 116 117 String[] bitarchiveNames = bitarchives.toArray(new String[bitarchives.size()]); 118 // Check whether scenario 2. 119 if (bitarchives.size() == 1) { 120 ArchiveStoreState ass = storeStates.get(bitarchiveNames[0]); 121 return new ArchiveStoreState(ass.getState(), ass.getLastChanged()); 122 } 123 124 // Scenario 3: If there are information from more than one bitarchive 125 ArchiveStoreState ass = storeStates.get(bitarchiveNames[0]); 126 Date lastChanged = ass.getLastChanged(); 127 boolean failState = false; 128 ReplicaStoreState lowestStoreState = ass.getState(); 129 if (ass.getState().equals(ReplicaStoreState.UPLOAD_FAILED)) { 130 failState = true; 131 } 132 for (int i = 1; i < bitarchiveNames.length; i++) { 133 ArchiveStoreState tmpAss = storeStates.get(bitarchiveNames[i]); 134 if (tmpAss.getState().ordinal() < lowestStoreState.ordinal()) { 135 lowestStoreState = tmpAss.getState(); 136 } 137 if (tmpAss.getState().equals(ReplicaStoreState.UPLOAD_FAILED)) { 138 failState = true; 139 } 140 if (tmpAss.getLastChanged().after(lastChanged)) { 141 lastChanged = tmpAss.getLastChanged(); 142 } 143 } 144 145 // Scenario 3A: if the state of one of the bitarchives equals 146 // UPLOAD_FAILED. 147 if (failState) { 148 return new ArchiveStoreState(ReplicaStoreState.UPLOAD_FAILED, lastChanged); 149 } 150 151 // Scenario 3B: 152 // B. else, find the lowest state of the N bitarchives: 153 // return this state together with the the latest timestamp 154 return new ArchiveStoreState(lowestStoreState, lastChanged); 155 } 156 157 /** 158 * Set the StoreState for a specific bitarchive (set timestamp for last update to NOW). 159 * 160 * @param ba a bitarchive 161 * @param state the new StoreState for this bitarchive. 162 */ 163 void setStoreState(String ba, ReplicaStoreState state) { 164 ArchiveStoreState ass = new ArchiveStoreState(state); 165 storeStates.put(ba, ass); 166 } 167 168 /** 169 * Set the StoreState for a specific bitarchive (set timestamp for last update to lastchanged). 170 * 171 * @param baId a bitarchive 172 * @param state the new StoreState for this bitarchive. 173 * @param lastchanged the time for when the state was changed 174 */ 175 void setStoreState(String baId, ReplicaStoreState state, Date lastchanged) { 176 ArchiveStoreState ass = new ArchiveStoreState(state, lastchanged); 177 storeStates.put(baId, ass); 178 } 179 180 /** 181 * Get the StoreState for this entry for a given bitarchive or null if none. 182 * 183 * @param baId a bitarchive id 184 * @return the StoreState for a given bitarchive. 185 */ 186 public ReplicaStoreState getStoreState(String baId) { 187 ArgumentNotValid.checkNotNullOrEmpty(baId, "String baId"); 188 ArchiveStoreState ass = storeStates.get(baId); 189 if (ass == null) { 190 return null; 191 } 192 return ass.getState(); 193 } 194 195 /** 196 * Get the filename for this entry. 197 * 198 * @return the filename for this entry 199 */ 200 String getFilename() { 201 return filename; 202 } 203 204 /** 205 * Set the checksum for this entry. 206 * 207 * @param checksum the new checksum for this entry 208 */ 209 void setChecksum(String checksum) { 210 ArgumentNotValid.checkNotNullOrEmpty(checksum, "String checksum"); 211 md5sum = checksum; 212 } 213 214 /** 215 * Get the checksum for this entry. 216 * 217 * @return the stored checksum for this entry 218 */ 219 public String getChecksum() { 220 return md5sum; 221 } 222 223 /** 224 * Get the reply info and remove it from the entry. 225 * 226 * @return A reply info object that nobody else has gotten or will get. 227 */ 228 StoreMessage getAndRemoveReplyInfo() { 229 StoreMessage currentReplyInfo = this.replyInfo; 230 // Reset replyInfo of this entry. 231 this.replyInfo = null; 232 return currentReplyInfo; // return value of replyInfo before being reset 233 } 234 235 /** 236 * Returns information of whether a ReplyInfo object has been stored with this entry. 237 * 238 * @return true, if replyInfo is not null. 239 */ 240 boolean hasReplyInfo() { 241 return replyInfo != null; 242 } 243 244 /** 245 * Write this object to persistent storage. 246 * 247 * @param o A stream to write to. 248 */ 249 void output(PrintWriter o) { 250 o.print(filename + GENERAL_DELIMITER); 251 o.print(md5sum); 252 o.print(GENERAL_DELIMITER + getGeneralStoreState().toString()); 253 254 for (Map.Entry<String, ArchiveStoreState> entry : storeStates.entrySet()) { 255 o.print(ENTRY_COMPONENT_SEPARATOR_STRING + entry.getKey() + GENERAL_DELIMITER + entry.getValue()); 256 } 257 } 258 259 /** 260 * Check, if a given bitArchive has a StoreState connected to it. 261 * 262 * @param bitArchive a given bitarchive 263 * @return true, if the given bitArchive has a StoreState connected to it. 264 */ 265 boolean hasStoreState(String bitArchive) { 266 return storeStates.containsKey(bitArchive); 267 } 268 269 /** 270 * Set the replyInfo instance variable. 271 * 272 * @param replyInfo The new value for the replyInfo variable. 273 */ 274 void setReplyInfo(StoreMessage replyInfo) { 275 if (this.replyInfo != null) { 276 log.warn("Overwriting replyInfo '{}' with '{}'", this.replyInfo, replyInfo); 277 } 278 this.replyInfo = replyInfo; 279 } 280 281}