package org.dcache.pool.repository.v3.entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dcache.pool.repository.ReplicaState; import org.dcache.pool.repository.StickyRecord; import org.dcache.pool.repository.v3.entry.state.Sticky; public class CacheRepositoryEntryState { private static final Logger _log = LoggerFactory.getLogger(CacheRepositoryEntryState.class); private static final Pattern VERSION_PATTERN = Pattern.compile("#\\s+version\\s+[0-9]\\.[0-9]"); // format version private static final int FORMAT_VERSION_MAJOR = 3; private static final int FORMAT_VERSION_MINOR = 0; // possible states of entry in the repository private final Sticky _sticky = new Sticky(); private ReplicaState _state; // data, control and SI- files locations private final Path _controlFile; public CacheRepositoryEntryState(Path controlFile) throws IOException { _controlFile = controlFile; _state = ReplicaState.NEW; // read state from file try { loadState(); }catch( FileNotFoundException | NoSuchFileException fnf) { /* * it's not an error state. */ } } public List<StickyRecord> removeExpiredStickyFlags() throws IOException { List<StickyRecord> removed = _sticky.removeExpired(); if (!removed.isEmpty()) { makeStatePersistent(); } return removed; } public void setState(ReplicaState state) throws IOException { if (state == _state) { return; } switch (state) { case NEW: throw new IllegalStateException("Entry is " + _state); case FROM_CLIENT: if (_state != ReplicaState.NEW) { throw new IllegalStateException("Entry is " + _state); } break; case FROM_STORE: if (_state != ReplicaState.NEW) { throw new IllegalStateException("Entry is " + _state); } break; case FROM_POOL: if (_state != ReplicaState.NEW) { throw new IllegalStateException("Entry is " + _state); } break; case CACHED: if (_state == ReplicaState.REMOVED || _state == ReplicaState.DESTROYED) { throw new IllegalStateException("Entry is " + _state); } break; case PRECIOUS: if (_state == ReplicaState.REMOVED || _state == ReplicaState.DESTROYED) { throw new IllegalStateException("Entry is " + _state); } break; case BROKEN: if (_state == ReplicaState.REMOVED || _state == ReplicaState.DESTROYED) { throw new IllegalStateException("Entry is " + _state); } break; case REMOVED: if (_state == ReplicaState.DESTROYED) { throw new IllegalStateException("Entry is " + _state); } break; case DESTROYED: if (_state != ReplicaState.REMOVED) { throw new IllegalStateException("Entry is " + _state); } } _state = state; makeStatePersistent(); } public ReplicaState getState() { return _state; } /* * * State transitions * */ public boolean setSticky(String owner, long expire, boolean overwrite) throws IllegalStateException, IOException { if (_state == ReplicaState.REMOVED || _state == ReplicaState.DESTROYED) { throw new IllegalStateException("Entry in removed state"); } // if sticky flag modified, make changes persistent if (_sticky.addRecord(owner, expire, overwrite)) { makeStatePersistent(); return true; } return false; } public boolean isSticky() { return _sticky.isSet(); } /** * store state in control file * @throws IOException */ private void makeStatePersistent() throws IOException { //BufferedReader in = new BufferedReader( new FileReader(_controlFile) ); try (BufferedWriter out = Files.newBufferedWriter(_controlFile)) { // write repository version number out.write("# version 3.0"); out.newLine(); switch (_state) { case PRECIOUS: out.write("precious"); out.newLine(); break; case CACHED: out.write("cached"); out.newLine(); break; case FROM_CLIENT: out.write("from_client"); out.newLine(); break; case FROM_STORE: case FROM_POOL: out.write("from_store"); out.newLine(); break; } String state = _sticky.stringValue(); if (state != null && !state.isEmpty()) { out.write(state); out.newLine(); } out.flush(); } } private void loadState() throws IOException { try (BufferedReader in = Files.newBufferedReader(_controlFile)) { _state = ReplicaState.BROKEN; String line; while ((line = in.readLine()) != null) { // ignore empty lines line = line.trim(); if (line.isEmpty()) { continue; } // a comment or version string if (line.startsWith("#")) { Matcher m = VERSION_PATTERN.matcher(line); // it's the version string if (m.matches()) { String[] versionLine = line.split("\\s"); String[] versionNumber = versionLine[2].split("\\."); int major = Integer.parseInt(versionNumber[0]); int minor = Integer.parseInt(versionNumber[1]); if (major > FORMAT_VERSION_MAJOR || minor != FORMAT_VERSION_MINOR) { throw new IOException("control file format mismatch: supported <= " + FORMAT_VERSION_MAJOR + "." + FORMAT_VERSION_MINOR + " found: " + versionLine[2]); } } continue; } if (line.equals("precious")) { _state = ReplicaState.PRECIOUS; continue; } if (line.equals("cached")) { _state = ReplicaState.CACHED; continue; } if (line.equals("from_client")) { _state = ReplicaState.FROM_CLIENT; continue; } if (line.equals("from_store")) { _state = ReplicaState.FROM_STORE; continue; } /* * backward compatibility */ if (line.equals("receiving.store")) { _state = ReplicaState.FROM_STORE; continue; } if (line.equals("receiving.cient")) { _state = ReplicaState.FROM_CLIENT; continue; } // in case of some one fixed the spelling if (line.equals("receiving.client")) { _state = ReplicaState.FROM_CLIENT; continue; } // FORMAT: sticky:owner:exipire if (line.startsWith("sticky")) { String[] stickyOptions = line.split(":"); String owner; long expire; switch (stickyOptions.length) { case 1: // old style owner = "system"; expire = -1; break; case 2: // only owner defined owner = stickyOptions[1]; expire = -1; break; case 3: owner = stickyOptions[1]; try { expire = Long.parseLong(stickyOptions[2]); } catch (NumberFormatException nfe) { // bad number _state = ReplicaState.BROKEN; return; } break; default: _log.info("Unknow number of arguments in {} [{}]", _controlFile, line); _state = ReplicaState.BROKEN; return; } _sticky.addRecord(owner, expire, true); continue; } // if none of knows states, then it's BAD state _log.error("Invalid state [{}] for entry {}", line, _controlFile); break; } } } public Collection<StickyRecord> stickyRecords() { return _sticky.records(); } }