001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.core.persist;
028:
029: import java.io.DataInputStream;
030: import java.io.DataOutputStream;
031: import java.io.File;
032: import java.io.FileInputStream;
033: import java.io.FileNotFoundException;
034: import java.io.FileOutputStream;
035: import java.io.FilenameFilter;
036: import java.io.IOException;
037: import java.io.InputStream;
038: import java.io.ObjectInputStream;
039: import java.io.ObjectOutputStream;
040: import java.io.OutputStream;
041: import java.text.SimpleDateFormat;
042: import java.util.ArrayList;
043: import java.util.Arrays;
044: import java.util.Date;
045: import java.util.List;
046:
047: import org.cougaar.bootstrap.SystemProperties;
048: import org.cougaar.core.service.DataProtectionKey;
049: import org.cougaar.util.log.Logger;
050:
051: /**
052: * This {@link PersistencePlugin} abstract base class saves and
053: * restores blackboard objects in files. The actual opening of the
054: * input and output streams remains abstract.
055: * <p>
056: * There is one optional parameter naming the persistence root
057: * directory. If the parameter is omitted, the persistence root is
058: * specified by system properties.
059: *
060: * @property org.cougaar.install.path
061: * Used by FilePersistence as the
062: * parent directory for persistence snapshots when there is no
063: * directory specified in configuration parameters and
064: * org.cougaar.core.persistence.path is a relative pathname. This
065: * property is not used if the plugin is configured with a specific
066: * parameter specifying the location of the persistence root.
067: *
068: * @property org.cougaar.core.persistence.path
069: * Specifies the directory
070: * in which persistence snapshots should be saved. If this is a
071: * relative path, it the base will be the value or
072: * org.cougaar.install.path. This property is not used if the plugin
073: * is configured with a specific parameter specifying the location of
074: * the persistence root.
075: */
076: public abstract class FilePersistenceBase extends
077: PersistencePluginAdapter implements PersistencePlugin,
078: PersistenceNames {
079: private static final String NEWSEQUENCE = "newSequence";
080: private static final String SEQUENCE = "sequence";
081: private static final String MUTEX = "mutex";
082: private static final String OWNER = "owner";
083: private static final long MUTEX_TIMEOUT = 60000L;
084:
085: private static File getDefaultPersistenceRoot(String name) {
086: String installPath = SystemProperties.getProperty(
087: "org.cougaar.install.path", "/tmp");
088: File workspaceDirectory = new File(SystemProperties
089: .getProperty("org.cougaar.workspace", installPath
090: + "/workspace"));
091: return new File(workspaceDirectory, SystemProperties
092: .getProperty("org.cougaar.core.persistence.path", name));
093: }
094:
095: private File persistenceDirectory;
096: private File persistenceRoot;
097: private File ownerFile;
098: private String instanceId;
099: private FileMutex mutex;
100: private int deltaNumber; // The number of the currently open output file.
101:
102: protected void handleParameter(String param) {
103: String value;
104: if ((value = parseParamValue(param, PERSISTENCE_ROOT_PREFIX)) != null) {
105: persistenceRoot = new File(value);
106: } else {
107: if (pps.getLogger().isWarnEnabled()) {
108: pps.getLogger().warn(
109: name + ": Unrecognized parameter " + param);
110: }
111: }
112: }
113:
114: public void init(PersistencePluginSupport pps, String name,
115: String[] params, boolean deleteOldPersistence)
116: throws PersistenceException {
117: // Special case for old-style nameless first parameter (means persistenceRoot=<param>)
118: if (params.length == 1 && params[0].indexOf('=') < 0) {
119: params[0] = PERSISTENCE_ROOT_PREFIX + params[0];
120: }
121: init(pps, name, params);
122: if (persistenceRoot == null) {
123: persistenceRoot = getDefaultPersistenceRoot(name);
124: }
125: persistenceRoot.mkdirs();
126: if (!persistenceRoot.isDirectory()) {
127: pps.getLogger()
128: .fatal("Not a directory: " + persistenceRoot);
129: throw new PersistenceException(
130: "Persistence root unavailable");
131: }
132: String agentName = pps.getMessageAddress().getAddress();
133: persistenceDirectory = new File(persistenceRoot, agentName);
134: if (!persistenceDirectory.isDirectory()) {
135: if (!persistenceDirectory.mkdirs()) {
136: String msg = "FilePersistence(" + name
137: + ") not a directory: " + persistenceDirectory;
138: pps.getLogger().fatal(msg);
139: throw new PersistenceException(msg);
140: }
141: }
142: if (deleteOldPersistence)
143: deleteOldPersistence();
144: SimpleDateFormat format = new SimpleDateFormat(
145: "yyyyMMddHHmmssSSS");
146: instanceId = format.format(new Date());
147: mutex = new FileMutex(persistenceDirectory, MUTEX,
148: MUTEX_TIMEOUT);
149: lockOwnership();
150: try {
151: ownerFile = new File(persistenceDirectory, OWNER);
152: DataOutputStream o = new DataOutputStream(
153: new FileOutputStream(ownerFile));
154: o.writeUTF(instanceId);
155: o.close();
156: } catch (IOException ioe) {
157: pps.getLogger().fatal("assertOwnership exception", ioe);
158: throw new PersistenceException("assertOwnership exception",
159: ioe);
160: } finally {
161: unlockOwnership();
162: }
163: }
164:
165: public boolean checkOwnership() throws PersistenceException {
166: lockOwnership();
167: try {
168: DataInputStream i = new DataInputStream(
169: new FileInputStream(ownerFile));
170: return i.readUTF().equals(instanceId);
171: } catch (IOException ioe) {
172: throw new PersistenceException("checkOwnership exception",
173: ioe);
174: } finally {
175: unlockOwnership();
176: }
177: }
178:
179: public void lockOwnership() throws PersistenceException {
180: try {
181: mutex.lock();
182: } catch (IOException ioe) {
183: throw new PersistenceException("lockOwnership exception",
184: ioe);
185: }
186: }
187:
188: public void unlockOwnership() throws PersistenceException {
189: try {
190: mutex.unlock();
191: } catch (IOException ioe) {
192: throw new PersistenceException("unlockOwnership exception",
193: ioe);
194: }
195: }
196:
197: protected abstract InputStream openFileInputStream(File file)
198: throws FileNotFoundException;
199:
200: protected abstract OutputStream openFileOutputStream(File file)
201: throws FileNotFoundException;
202:
203: protected abstract boolean rename(File from, File to);
204:
205: private File getSequenceFile(String suffix) {
206: return new File(persistenceDirectory, SEQUENCE + suffix);
207: }
208:
209: private File getNewSequenceFile(String suffix) {
210: return new File(persistenceDirectory, NEWSEQUENCE + suffix);
211: }
212:
213: private SequenceNumbers readSequenceFile(File sequenceFile)
214: throws IOException {
215: Logger ls = pps.getLogger();
216: if (ls.isInfoEnabled()) {
217: ls.info("Reading " + sequenceFile);
218: }
219: DataInputStream sequenceStream = new DataInputStream(
220: openFileInputStream(sequenceFile));
221: try {
222: int first = sequenceStream.readInt();
223: int last = sequenceStream.readInt();
224: long timestamp = sequenceFile.lastModified();
225: return new SequenceNumbers(first, last, timestamp);
226: } finally {
227: sequenceStream.close();
228: }
229: }
230:
231: public SequenceNumbers[] readSequenceNumbers(final String suffix) {
232: FilenameFilter filter;
233: if (suffix.equals("")) {
234: filter = new FilenameFilter() {
235: public boolean accept(File dir, String path) {
236: return (path.startsWith(NEWSEQUENCE) || path
237: .startsWith(SEQUENCE));
238: }
239: };
240: } else {
241: filter = new FilenameFilter() {
242: public boolean accept(File dir, String path) {
243: return (path.endsWith(suffix) && (path
244: .startsWith(NEWSEQUENCE) || path
245: .startsWith(SEQUENCE)));
246: }
247: };
248: }
249: String[] names = persistenceDirectory.list(filter);
250: List result = new ArrayList(names.length);
251: File sequenceFile;
252: for (int i = 0; i < names.length; i++) {
253: if (names[i].startsWith(NEWSEQUENCE)) {
254: File newSequenceFile = new File(persistenceDirectory,
255: names[i]);
256: sequenceFile = new File(persistenceDirectory, SEQUENCE
257: + names[i].substring(NEWSEQUENCE.length()));
258: rename(newSequenceFile, sequenceFile);
259: } else {
260: sequenceFile = new File(persistenceDirectory, names[i]);
261: }
262: try {
263: result.add(readSequenceFile(sequenceFile));
264: } catch (IOException e) {
265: pps.getLogger().error("Error reading " + sequenceFile,
266: e);
267: }
268: }
269: return (SequenceNumbers[]) result
270: .toArray(new SequenceNumbers[result.size()]);
271: }
272:
273: private void writeSequenceNumbers(SequenceNumbers sequenceNumbers,
274: String suffix) {
275: try {
276: File sequenceFile = getSequenceFile(suffix);
277: File newSequenceFile = getNewSequenceFile(suffix);
278: DataOutputStream sequenceStream = new DataOutputStream(
279: openFileOutputStream(newSequenceFile));
280: try {
281: sequenceStream.writeInt(sequenceNumbers.first);
282: sequenceStream.writeInt(sequenceNumbers.current);
283: } finally {
284: sequenceStream.close();
285: sequenceFile.delete();
286: if (!rename(newSequenceFile, sequenceFile)) {
287: pps.getLogger().error(
288: "Failed to rename " + newSequenceFile
289: + " to " + sequenceFile);
290: }
291: }
292: } catch (Exception e) {
293: pps.getLogger().error("Exception writing sequenceFile", e);
294: }
295: }
296:
297: public void cleanupOldDeltas(SequenceNumbers cleanupNumbers) {
298: Logger ls = pps.getLogger();
299: for (int deltaNumber = cleanupNumbers.first; deltaNumber < cleanupNumbers.current; deltaNumber++) {
300: File deltaFile = getDeltaFile(deltaNumber);
301: if (deltaFile.delete()) {
302: if (ls.isInfoEnabled())
303: ls.info("Deleted " + deltaFile);
304: } else {
305: ls.error("Failed to delete " + deltaFile);
306: }
307: }
308: }
309:
310: public void cleanupArchive() {
311: Logger ls = pps.getLogger();
312: if (archiveCount < Integer.MAX_VALUE) {
313: FilenameFilter filter = new FilenameFilter() {
314: public boolean accept(File dir, String path) {
315: return path.startsWith(SEQUENCE)
316: && !path.equals(SEQUENCE);
317: }
318: };
319: String[] names = persistenceDirectory.list(filter);
320: int excess = names.length - archiveCount;
321: if (ls.isInfoEnabled()) {
322: ls.info(excess + " excess archives to delete");
323: }
324: if (excess > 0) {
325: Arrays.sort(names);
326: for (int i = 0; i < excess; i++) {
327: File sequenceFile = new File(persistenceDirectory,
328: names[i]);
329: if (ls.isInfoEnabled())
330: ls.info("Deleting " + sequenceFile);
331: try {
332: SequenceNumbers sn = readSequenceFile(sequenceFile);
333: cleanupOldDeltas(sn);
334: sequenceFile.delete();
335: } catch (IOException ioe) {
336: ls.error("cleanupArchive", ioe);
337: }
338: }
339: }
340: }
341: }
342:
343: public OutputStream openOutputStream(int deltaNumber, boolean full)
344: throws IOException {
345: File tempFile = getTempFile(deltaNumber);
346: Logger ls = pps.getLogger();
347: this .deltaNumber = deltaNumber;
348: if (ls.isInfoEnabled()) {
349: ls.info("Persist to " + tempFile);
350: }
351: return openFileOutputStream(tempFile);
352: }
353:
354: public void finishOutputStream(SequenceNumbers retainNumbers,
355: boolean full) {
356: File tempFile = getTempFile(deltaNumber);
357: File deltaFile = getDeltaFile(deltaNumber);
358: tempFile.renameTo(deltaFile);
359: writeSequenceNumbers(retainNumbers, "");
360: if (full)
361: writeSequenceNumbers(retainNumbers,
362: PersistenceServiceComponent
363: .formatDeltaNumber(retainNumbers.first));
364: }
365:
366: public void abortOutputStream(SequenceNumbers retainNumbers) {
367: getTempFile(retainNumbers.current).delete();
368: }
369:
370: public InputStream openInputStream(int deltaNumber)
371: throws IOException {
372: File deltaFile = getDeltaFile(deltaNumber);
373: Logger ls = pps.getLogger();
374: if (ls.isInfoEnabled()) {
375: ls.info("rehydrate " + deltaFile);
376: }
377: return openFileInputStream(deltaFile);
378: }
379:
380: public void finishInputStream(int deltaNumber) {
381: }
382:
383: private void deleteOldPersistence() {
384: File[] files = persistenceDirectory.listFiles();
385: for (int i = 0; i < files.length; i++) {
386: files[i].delete();
387: }
388: }
389:
390: public void storeDataProtectionKey(int deltaNumber,
391: DataProtectionKey key) throws IOException {
392: File file = getEncryptedKeyFile(deltaNumber);
393: ObjectOutputStream ois = new ObjectOutputStream(
394: openFileOutputStream(file));
395: try {
396: ois.writeObject(key);
397: } finally {
398: ois.close();
399: }
400: }
401:
402: public DataProtectionKey retrieveDataProtectionKey(int deltaNumber)
403: throws IOException {
404: File file = getEncryptedKeyFile(deltaNumber);
405: ObjectInputStream ois = new ObjectInputStream(
406: openFileInputStream(file));
407: try {
408: return (DataProtectionKey) ois.readObject();
409: } catch (ClassNotFoundException cnfe) {
410: IOException ioe = new IOException(
411: "Read DataProtectionKey failed");
412: ioe.initCause(cnfe);
413: throw ioe;
414: } finally {
415: ois.close();
416: }
417: }
418:
419: private File getTempFile(int sequence) {
420: return new File(persistenceDirectory, instanceId
421: + "_"
422: + PersistenceServiceComponent
423: .formatDeltaNumber(sequence));
424: }
425:
426: private File getDeltaFile(int sequence) {
427: return new File(persistenceDirectory, "delta"
428: + PersistenceServiceComponent
429: .formatDeltaNumber(sequence));
430: }
431:
432: private File getEncryptedKeyFile(int sequence) {
433: return new File(persistenceDirectory, "key"
434: + PersistenceServiceComponent
435: .formatDeltaNumber(sequence));
436: }
437: }
|