Copyright (c) 2004, 2014 IBM Corporation and others.
This program and the accompanying materials
are made available under the terms of the Eclipse Public License 2.0
which accompanies this distribution, and is available at
https://www.eclipse.org/legal/epl-2.0/
SPDX-License-Identifier: EPL-2.0
Contributors:
IBM Corporation - initial API and implementation
/*******************************************************************************
* Copyright (c) 2004, 2014 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.storagemanager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SyncFailedException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.osgi.framework.internal.reliablefile.ReliableFile;
import org.eclipse.osgi.framework.internal.reliablefile.ReliableFileInputStream;
import org.eclipse.osgi.framework.internal.reliablefile.ReliableFileOutputStream;
import org.eclipse.osgi.internal.location.LocationHelper;
import org.eclipse.osgi.internal.location.Locker;
import org.eclipse.osgi.internal.messages.Msg;
Storage managers provide a facility for tracking the state of a group of files having
relationship with each others and being updated by several entities at the same time.
The typical usecase is in shared configuration data areas.
The facilities provided here are cooperative. That is, all participants must
agree to the conventions and to calling the given API. There is no capacity
to enforce these conventions or prohibit corruption.
Clients can not extend this class
Example:
//Open the storage manager
org.eclipse.osgi.storagemanager.StorageManager cacheStorageManager = new StorageManager("d:/sharedFolder/bar/", false); //$NON-NLS-1$
try {
cacheStorageManager.open(true);
} catch (IOException e) {
// Ignore the exception. The registry will be rebuilt from source.
}
//To read from a file
java.io.File fileA = cacheStorageManager.lookup("fileA", false));
java.io.File fileB = cacheStorageManager.lookup("fileB", false));
//Do the reading code
new java.io.FileOutputStream(fileA);
//To write in files
cacheStorageManager.add("fileC"); //add the file to the filemanager (in this case we assume it is not already here)
cacheStorageManager.add("fileD");
// The file is never written directly into the file name, so we create some temporary file
java.io.File fileC = cacheStorageManager.createTempFile("fileC");
java.io.File fileD = cacheStorageManager.createTempFile("fileD");
//Do the actual writing here...
//Finally update the storagemanager with the actual file to manage.
cacheStorageManager.update(new String[] {"fileC", "fileD"}, new String[] {fileC.getName(), fileD.getName()};
//Close the file manager at the end
cacheStorageManager.close();
Implementation details
The following implementation details are provided to help with understanding the
behavior of this class.
The general principle is to maintain a table which maps user-level file names
onto an actual disk files. If a file needs to be modified,
it is stored into a new file. The old content is not removed from disk until all entities
have closed there instance of the storage manager.
Once the instance has been created, open() must be called before performing any other operation.
On open the storage manager obtains a snapshot of the current managed files contents. If an
entity updates a managed file, the storage manager will save the content for that instance of the
storage manager, all other storage manager instances will still have access to that managed file's
content as it was when the instance was first opened.
Since: 3.2
/**
* Storage managers provide a facility for tracking the state of a group of files having
* relationship with each others and being updated by several entities at the same time.
* The typical usecase is in shared configuration data areas.
* <p>
* The facilities provided here are cooperative. That is, all participants must
* agree to the conventions and to calling the given API. There is no capacity
* to enforce these conventions or prohibit corruption.
* </p>
* <p>
* Clients can not extend this class
* </p>
* <p>
* Example:</p>
* <pre>
* //Open the storage manager
* org.eclipse.osgi.storagemanager.StorageManager cacheStorageManager = new StorageManager("d:/sharedFolder/bar/", false); //$NON-NLS-1$
* try {
* cacheStorageManager.open(true);
* } catch (IOException e) {
* // Ignore the exception. The registry will be rebuilt from source.
* }
*
* //To read from a file
* java.io.File fileA = cacheStorageManager.lookup("fileA", false));
* java.io.File fileB = cacheStorageManager.lookup("fileB", false));
* //Do the reading code
* new java.io.FileOutputStream(fileA);
*
* //To write in files
* cacheStorageManager.add("fileC"); //add the file to the filemanager (in this case we assume it is not already here)
* cacheStorageManager.add("fileD");
*
* // The file is never written directly into the file name, so we create some temporary file
* java.io.File fileC = cacheStorageManager.createTempFile("fileC");
* java.io.File fileD = cacheStorageManager.createTempFile("fileD");
*
* //Do the actual writing here...
*
* //Finally update the storagemanager with the actual file to manage.
* cacheStorageManager.update(new String[] {"fileC", "fileD"}, new String[] {fileC.getName(), fileD.getName()};
*
* //Close the file manager at the end
* cacheStorageManager.close();
* </pre>
* <p>
* Implementation details <br>
* The following implementation details are provided to help with understanding the
* behavior of this class.
* The general principle is to maintain a table which maps user-level file names
* onto an actual disk files. If a file needs to be modified,
* it is stored into a new file. The old content is not removed from disk until all entities
* have closed there instance of the storage manager.
* Once the instance has been created, open() must be called before performing any other operation.
* On open the storage manager obtains a snapshot of the current managed files contents. If an
* entity updates a managed file, the storage manager will save the content for that instance of the
* storage manager, all other storage manager instances will still have access to that managed file's
* content as it was when the instance was first opened.
* </p>
* @since 3.2
*/
// Note the implementation of this class originated from the following deprecated classes:
// /org.eclipse.osgi/eclipseAdaptor/src/org/eclipse/core/runtime/adaptor/FileManager.java
// /org.eclipse.osgi/eclipseAdaptor/src/org/eclipse/core/runtime/adaptor/StreamManager.java
public final class StorageManager {
private static final int FILETYPE_STANDARD = 0;
private static final int FILETYPE_RELIABLEFILE = 1;
private static final String MANAGER_FOLDER = ".manager"; //$NON-NLS-1$
private static final String TABLE_FILE = ".fileTable"; //$NON-NLS-1$
private static final String LOCK_FILE = ".fileTableLock"; //$NON-NLS-1$
private static final int MAX_LOCK_WAIT = 5000; // 5 seconds
// these should be static but the tests expect to be able to create new managers after changing this setting dynamically
private final boolean useReliableFiles = Boolean.valueOf(System.getProperty("osgi.useReliableFiles")).booleanValue(); //$NON-NLS-1$
private final boolean tempCleanup = Boolean.valueOf(System.getProperty("osgi.embedded.cleanTempFiles")).booleanValue(); //$NON-NLS-1$
private final boolean openCleanup = Boolean.valueOf(System.getProperty("osgi.embedded.cleanupOnOpen")).booleanValue(); //$NON-NLS-1$
private final boolean saveCleanup = Boolean.valueOf(System.getProperty("osgi.embedded.cleanupOnSave")).booleanValue(); //$NON-NLS-1$
private class Entry {
int readId;
int writeId;
int fileType;
Entry(int readId, int writeId, int type) {
this.readId = readId;
this.writeId = writeId;
this.fileType = type;
}
int getReadId() {
return readId;
}
int getWriteId() {
return writeId;
}
int getFileType() {
return fileType;
}
void setReadId(int value) {
readId = value;
}
void setWriteId(int value) {
writeId = value;
}
void setFileType(int type) {
fileType = type;
}
}
private final File base; //The folder managed
private final File managerRoot; //The folder that will contain all the file related to the functionning of the manager (typically a subdir of base)
private final String lockMode;
private final File tableFile;
private final File lockFile; // The lock file for the table (this file is the same for all the instances)
private Locker locker; // The locker for the lock
private File instanceFile; //The file representing the running instance. It is created when the table file is read.
private Locker instanceLocker = null; //The locker for the instance file.
private final boolean readOnly; // Whether this storage manager is in read-only mode
private boolean open; // Whether this storage manager is open for use
// locking related fields
private int tableStamp = -1;
private final Properties table = new Properties();
Returns a new storage manager for the area identified by the given base
directory.
Params: - base – the directory holding the files to be managed
- lockMode – the lockMode to use for the storage manager. It can have one the 3 values: none, java.io, java.nio
and also supports null in which case the lock strategy will be the global one.
/**
* Returns a new storage manager for the area identified by the given base
* directory.
*
* @param base the directory holding the files to be managed
* @param lockMode the lockMode to use for the storage manager. It can have one the 3 values: none, java.io, java.nio
* and also supports null in which case the lock strategy will be the global one.
*/
public StorageManager(File base, String lockMode) {
this(base, lockMode, false);
}
Returns a new storage manager for the area identified by the given base
directory.
Params: - base – the directory holding the files to be managed
- lockMode – the lockMode to use for the storage manager. It can have one the 3 values: none, java.io, java.nio
and also supports null in which case the lock strategy will be the global one.
- readOnly – true if the managed files are read-only
/**
* Returns a new storage manager for the area identified by the given base
* directory.
*
* @param base the directory holding the files to be managed
* @param lockMode the lockMode to use for the storage manager. It can have one the 3 values: none, java.io, java.nio
* and also supports null in which case the lock strategy will be the global one.
* @param readOnly true if the managed files are read-only
*/
public StorageManager(File base, String lockMode, boolean readOnly) {
this.base = base;
this.lockMode = lockMode;
this.managerRoot = new File(base, MANAGER_FOLDER);
this.tableFile = new File(managerRoot, TABLE_FILE);
this.lockFile = new File(managerRoot, LOCK_FILE);
this.readOnly = readOnly;
open = false;
}
private void initializeInstanceFile() throws IOException {
if (instanceFile != null || readOnly)
return;
this.instanceFile = File.createTempFile(".tmp", ".instance", managerRoot); //$NON-NLS-1$//$NON-NLS-2$
this.instanceFile.deleteOnExit();
instanceLocker = LocationHelper.createLocker(instanceFile, lockMode, false);
instanceLocker.lock();
}
private String getAbsolutePath(String file) {
return new File(base, file).getAbsolutePath();
}
Add the given managed file name to the list of files managed by this manager.
Params: - managedFile – name of the file to manage
Throws: - IOException – if there are any problems adding the given file name to the manager
/**
* Add the given managed file name to the list of files managed by this manager.
*
* @param managedFile name of the file to manage
* @throws IOException if there are any problems adding the given file name to the manager
*/
public void add(String managedFile) throws IOException {
add(managedFile, FILETYPE_STANDARD);
}
/* (non-Javadoc
* Add the given file name to the list of files managed by this manager.
*
* @param managedFile name of the file to manage.
* @param fileType the file type.
* @throws IOException if there are any problems adding the given file to the manager
*/
private void add(String managedFile, int fileType) throws IOException {
if (!open)
throw new IOException(Msg.fileManager_notOpen);
if (readOnly)
throw new IOException(Msg.fileManager_illegalInReadOnlyMode);
if (!lock(true))
throw new IOException(Msg.fileManager_cannotLock);
try {
updateTable();
Entry entry = (Entry) table.get(managedFile);
if (entry == null) {
entry = new Entry(0, 1, fileType);
table.put(managedFile, entry);
// if this managed file existed before, ensure there is not an old
// version on the disk to avoid name collisions. If version found,
// us the oldest generation+1 for the write ID.
int oldestGeneration = findOldestGeneration(managedFile);
if (oldestGeneration != 0)
entry.setWriteId(oldestGeneration + 1);
save();
} else {
if (entry.getFileType() != fileType) {
entry.setFileType(fileType);
updateTable();
save();
}
}
} finally {
release();
}
}
/* (non-Javadoc)
* Find the oldest generation of a file still available on disk
* @param file the file from which to obtain the oldest generation.
* @return the oldest generation of the file or 0 if the file does
* not exist.
*/
private int findOldestGeneration(String managedFile) {
String[] files = base.list();
int oldestGeneration = 0;
if (files != null) {
String name = managedFile + '.';
int len = name.length();
for (String file : files) {
if (!file.startsWith(name)) {
continue;
}
try {
int generation = Integer.parseInt(file.substring(len));
if (generation > oldestGeneration)
oldestGeneration = generation;
}catch (NumberFormatException e) {
continue;
}
}
}
return oldestGeneration;
}
Update the given managed files with the content in the given source files.
The managedFiles is a list of managed file names which are currently managed.
If a managed file name is not currently managed it will be added as a
managed file for this storage manager.
The sources are absolute (or relative to the current working directory)
file paths containing the new content for the corresponding managed files.
Params: - managedFiles – the managed files to update
- sources – the new content for the managed files
Throws: - IOException – if there are any problems updating the given managed files
/**
* Update the given managed files with the content in the given source files.
* The managedFiles is a list of managed file names which are currently managed.
* If a managed file name is not currently managed it will be added as a
* managed file for this storage manager.
* The sources are absolute (or relative to the current working directory)
* file paths containing the new content for the corresponding managed files.
*
* @param managedFiles the managed files to update
* @param sources the new content for the managed files
* @throws IOException if there are any problems updating the given managed files
*/
public void update(String[] managedFiles, String[] sources) throws IOException {
if (!open)
throw new IOException(Msg.fileManager_notOpen);
if (readOnly)
throw new IOException(Msg.fileManager_illegalInReadOnlyMode);
if (!lock(true))
throw new IOException(Msg.fileManager_cannotLock);
try {
updateTable();
int[] originalReadIDs = new int[managedFiles.length];
boolean error = false;
for (int i = 0; i < managedFiles.length; i++) {
originalReadIDs[i] = getId(managedFiles[i]);
if (!update(managedFiles[i], sources[i]))
error = true;
}
if (error) {
// restore the original readIDs to avoid inconsistency for this group
for (int i = 0; i < managedFiles.length; i++) {
Entry entry = (Entry) table.get(managedFiles[i]);
entry.setReadId(originalReadIDs[i]);
}
throw new IOException(Msg.fileManager_updateFailed);
}
save(); //save only if no errors
} finally {
release();
}
}
Returns a list of all the managed files currently being managed.
Returns: the names of the managed files
/**
* Returns a list of all the managed files currently being managed.
*
* @return the names of the managed files
*/
public String[] getManagedFiles() {
if (!open)
return null;
Set<Object> set = table.keySet();
@SuppressWarnings("cast")
String[] keys = (String[]) set.toArray(new String[set.size()]);
String[] result = new String[keys.length];
for (int i = 0; i < keys.length; i++)
result[i] = new String(keys[i]);
return result;
}
Returns the directory containing the files being managed by this storage
manager.
Returns: the directory containing the managed files
/**
* Returns the directory containing the files being managed by this storage
* manager.
*
* @return the directory containing the managed files
*/
public File getBase() {
return base;
}
Returns the current numeric id (appendage) of the given managed file.
managedFile + "." + getId(target)
. A value of -1 is returned
if the given name is not managed.
Params: - managedFile – the name of the managed file
Returns: the id of the managed file
/**
* Returns the current numeric id (appendage) of the given managed file.
* <code>managedFile + "." + getId(target)</code>. A value of -1 is returned
* if the given name is not managed.
*
* @param managedFile the name of the managed file
* @return the id of the managed file
*/
public int getId(String managedFile) {
if (!open)
return -1;
Entry entry = (Entry) table.get(managedFile);
if (entry == null)
return -1;
return entry.getReadId();
}
Returns if readOnly state this storage manager is using.
Returns: if this storage manager update state is read-only.
/**
* Returns if readOnly state this storage manager is using.
*
* @return if this storage manager update state is read-only.
*/
public boolean isReadOnly() {
return readOnly;
}
/* (non-Javadoc)
* Attempts to lock the state of this manager and returns <code>true</code>
* if the lock could be acquired.
* <p>
* Locking a manager is advisory only. That is, it does not prevent other
* applications from modifying the files managed by this manager.
* </p>
*
* @exception IOException
* if there was an unexpected problem while acquiring the
* lock.
*/
private boolean lock(boolean wait) throws IOException {
if (readOnly)
return false;
if (locker == null) {
locker = LocationHelper.createLocker(lockFile, lockMode, false);
if (locker == null)
throw new IOException(Msg.fileManager_cannotLock);
}
boolean locked = locker.lock();
if (locked || !wait)
return locked;
//Someone else must have the directory locked, but they should release it quickly
long start = System.currentTimeMillis();
while (true) {
try {
Thread.sleep(200); // 5x per second
} catch (InterruptedException e) {/*ignore*/
}
locked = locker.lock();
if (locked)
return true;
// never wait longer than 5 seconds
long time = System.currentTimeMillis() - start;
if (time > MAX_LOCK_WAIT)
return false;
}
}
Returns the actual file location to use when reading the given managed file.
A value of null
can be returned if the given managed file name is not
managed and add is set to false.
The returned file should be considered read-only. Any updates to the content of this file should be done using update(String[], String[])
.
Params: - managedFile – the managed file to lookup
- add – indicate whether the managed file name should be added to the manager if
it is not already managed.
Throws: - IOException – if the add flag is set to true and the addition of the managed file failed
Returns: the absolute file location to use for the given managed file or
null
if the given managed file is not managed
/**
* Returns the actual file location to use when reading the given managed file.
* A value of <code>null</code> can be returned if the given managed file name is not
* managed and add is set to false.
* <p>
* The returned file should be considered read-only. Any updates to the content of this
* file should be done using {@link #update(String[], String[])}.
*
* @param managedFile the managed file to lookup
* @param add indicate whether the managed file name should be added to the manager if
* it is not already managed.
* @throws IOException if the add flag is set to true and the addition of the managed file failed
* @return the absolute file location to use for the given managed file or
* <code>null</code> if the given managed file is not managed
*/
public File lookup(String managedFile, boolean add) throws IOException {
if (!open)
throw new IOException(Msg.fileManager_notOpen);
Entry entry = (Entry) table.get(managedFile);
if (entry == null) {
if (add) {
add(managedFile);
entry = (Entry) table.get(managedFile);
} else {
return null;
}
}
return new File(getAbsolutePath(managedFile + '.' + entry.getReadId()));
}
private boolean move(String source, String managedFile) {
File original = new File(source);
File targetFile = new File(managedFile);
// its ok if the original does not exist. The table entry will capture
// that fact. There is no need to put something in the filesystem.
if (!original.exists() || targetFile.exists())
return false;
return original.renameTo(targetFile);
}
Saves the state of the storage manager and releases any locks held.
/**
* Saves the state of the storage manager and releases any locks held.
*/
private void release() {
if (locker == null)
return;
locker.release();
}
Removes the given managed file from management by this storage manager.
Params: - managedFile – the managed file to remove
Throws: - IOException – if an error occured removing the managed file
/**
* Removes the given managed file from management by this storage manager.
*
* @param managedFile the managed file to remove
* @throws IOException if an error occured removing the managed file
*/
public void remove(String managedFile) throws IOException {
if (!open)
throw new IOException(Msg.fileManager_notOpen);
if (readOnly)
throw new IOException(Msg.fileManager_illegalInReadOnlyMode);
// The removal needs to be done eagerly, so the value is effectively removed from the disktable.
// Otherwise, an updateTable() caused by an update(,) could cause the file to readded to the local table.
if (!lock(true))
throw new IOException(Msg.fileManager_cannotLock);
try {
updateTable();
table.remove(managedFile);
save();
} finally {
release();
}
}
private void updateTable() throws IOException {
int stamp;
stamp = ReliableFile.lastModifiedVersion(tableFile);
if (stamp == tableStamp || stamp == -1)
return;
Properties diskTable = new Properties();
InputStream input = new ReliableFileInputStream(tableFile);
try {
diskTable.load(input);
} finally {
try {
input.close();
} catch (IOException e) {
// ignore
}
}
tableStamp = stamp;
for (Enumeration<Object> e = diskTable.keys(); e.hasMoreElements();) {
String file = (String) e.nextElement();
String value = diskTable.getProperty(file);
if (value != null) {
Entry entry = (Entry) table.get(file);
// check front of value for ReliableFile
int id;
int fileType;
int idx = value.indexOf(',');
if (idx != -1) {
id = Integer.parseInt(value.substring(0, idx));
fileType = Integer.parseInt(value.substring(idx + 1));
} else {
id = Integer.parseInt(value);
fileType = FILETYPE_STANDARD;
}
if (entry == null) {
table.put(file, new Entry(id, id + 1, fileType));
} else {
entry.setWriteId(id + 1);
//don't change type
}
}
}
}
/*
* This method should be called while the manager is locked.
*/
private void save() throws IOException {
if (readOnly)
return;
// if the table file has change on disk, update our data structures then
// rewrite the file.
updateTable();
Properties props = new Properties();
for (Enumeration<Object> e = table.keys(); e.hasMoreElements();) {
String file = (String) e.nextElement();
Entry entry = (Entry) table.get(file);
String value;
if (entry.getFileType() != FILETYPE_STANDARD) {
value = Integer.toString(entry.getWriteId() - 1) + ',' + //In the table we save the write number - 1, because the read number can be totally different.
entry.getFileType();
} else {
value = Integer.toString(entry.getWriteId() - 1); //In the table we save the write number - 1, because the read number can be totally different.
}
props.put(file, value);
}
ReliableFileOutputStream fileStream = new ReliableFileOutputStream(tableFile);
boolean error = true;
try {
props.store(fileStream, "safe table"); //$NON-NLS-1$
fileStream.close();
error = false;
} finally {
if (error)
fileStream.abort();
}
// bug 259981 we should clean up
if (saveCleanup) {
try {
cleanup(false);
} catch (IOException ex) {
// If IOException is thrown from our custom method.
// log and swallow for now.
System.out.println("Unexpected IOException is thrown inside cleanupWithLock. Please look below for stacktrace"); //$NON-NLS-1$
ex.printStackTrace(System.out);
}
}
tableStamp = ReliableFile.lastModifiedVersion(tableFile);
}
private boolean update(String managedFile, String source) throws IOException {
Entry entry = (Entry) table.get(managedFile);
if (entry == null)
add(managedFile);
int newId = entry.getWriteId();
// attempt to rename the file to the next generation
boolean success = move(getAbsolutePath(source), getAbsolutePath(managedFile) + '.' + newId);
if (!success) {
//possible the next write generation file exists? Lets determine the largest
//generation number, then use that + 1.
newId = findOldestGeneration(managedFile) + 1;
success = move(getAbsolutePath(source), getAbsolutePath(managedFile) + '.' + newId);
}
if (!success)
return false;
// update the entry. read and write ids should be the same since
// everything is in sync
entry.setReadId(newId);
entry.setWriteId(newId + 1);
return true;
}
This methods remove all the temporary files that have been created by the storage manager.
This removal is only done if the instance of eclipse calling this method is the last instance using this storage manager.
Throws: - IOException –
/**
* This methods remove all the temporary files that have been created by the storage manager.
* This removal is only done if the instance of eclipse calling this method is the last instance using this storage manager.
* @throws IOException
*/
private void cleanup(boolean doLock) throws IOException {
if (readOnly)
return;
//Lock first, so someone else can not start while we're in the middle of cleanup
if (doLock && !lock(true))
throw new IOException(Msg.fileManager_cannotLock);
try {
//Iterate through the temp files and delete them all, except the one representing this storage manager.
String[] files = managerRoot.list();
if (files != null) {
for (String file : files) {
if (file.endsWith(".instance") && (instanceFile == null || !file.equalsIgnoreCase(instanceFile.getName()))) { //$NON-NLS-1$
Locker tmpLocker = LocationHelper.createLocker(new File(managerRoot, file), lockMode, false);
if (tmpLocker.lock()) {
//If I can lock it is a file that has been left behind by a crash
tmpLocker.release();
new File(managerRoot, file).delete();
} else {
tmpLocker.release();
return; //The file is still being locked by somebody else
}
}
}
}
//If we are here it is because we are the last instance running. After locking the table and getting its latest content, remove all the backup files and change the table
updateTable();
Collection<Map.Entry<Object, Object>> managedFiles = table.entrySet();
for (Map.Entry<Object, Object> fileEntry : managedFiles) {
String fileName = (String) fileEntry.getKey();
Entry info = (Entry) fileEntry.getValue();
if (info.getFileType() == FILETYPE_RELIABLEFILE) {
ReliableFile.cleanupGenerations(new File(base, fileName));
} else {
//Because we are cleaning up, we are giving up the values from our table, and we must delete all the files that are not referenced by the table
String readId = Integer.toString(info.getWriteId() - 1);
deleteCopies(fileName, readId);
}
}
if (tempCleanup) {
files = base.list();
if (files != null) {
for (String file : files) {
if (file.endsWith(ReliableFile.tmpExt)) {
new File(base, file).delete();
}
}
}
}
} finally {
if (doLock)
release();
}
}
private void deleteCopies(String fileName, String exceptionNumber) {
String notToDelete = fileName + '.' + exceptionNumber;
String[] files = base.list();
if (files == null)
return;
for (String file : files) {
if (file.startsWith(fileName + '.') && !file.equals(notToDelete)) {
new File(base, file).delete();
}
}
}
This method declares the storage manager as closed. From thereon, the instance can no longer be used.
It is important to close the manager as it also cleans up old copies of the managed files.
/**
* This method declares the storage manager as closed. From thereon, the instance can no longer be used.
* It is important to close the manager as it also cleans up old copies of the managed files.
*/
public void close() {
if (!open)
return;
open = false;
if (readOnly)
return;
try {
cleanup(true);
} catch (IOException e) {
//Ignore and close.
}
if (instanceLocker != null)
instanceLocker.release();
if (instanceFile != null)
instanceFile.delete();
}
This methods opens the storage manager.
This method must be called before any operation on the storage manager.
Params: - wait – indicates if the open operation must wait in case of contention on the lock file.
Throws: - IOException – if an error occurred opening the storage manager
/**
* This methods opens the storage manager.
* This method must be called before any operation on the storage manager.
* @param wait indicates if the open operation must wait in case of contention on the lock file.
* @throws IOException if an error occurred opening the storage manager
*/
public void open(boolean wait) throws IOException {
if (!readOnly) {
managerRoot.mkdirs();
if (!managerRoot.isDirectory())
throw new IOException(Msg.fileManager_cannotLock);
if (openCleanup)
cleanup(true);
boolean locked = lock(wait);
if (!locked && wait)
throw new IOException(Msg.fileManager_cannotLock);
}
try {
initializeInstanceFile();
updateTable();
open = true;
} finally {
release();
}
}
Creates a new unique empty temporary-file in the storage manager base directory. The file name
must be at least 3 characters. This file can later be used to update a managed file.
Note that File.deleteOnExit()
is not called on the returned file.
Params: - file – the file name to create temporary file from.
Throws: - IOException – if the file can not be created.
See Also: Returns: the newly-created empty file.
/**
* Creates a new unique empty temporary-file in the storage manager base directory. The file name
* must be at least 3 characters. This file can later be used to update a managed file.
* <p>
* Note that {@link File#deleteOnExit()} is not called on the returned file.
* </p>
* @param file the file name to create temporary file from.
* @return the newly-created empty file.
* @throws IOException if the file can not be created.
* @see #update(String[], String[])
*/
public File createTempFile(String file) throws IOException {
if (readOnly)
throw new IOException(Msg.fileManager_illegalInReadOnlyMode);
File tmpFile = File.createTempFile(file, ReliableFile.tmpExt, base);
// bug 350106: do not use deleteOnExit() If clients really want that the
// they can call it themselves.
return tmpFile;
}
Returns a managed InputStream
for a managed file.
null
can be returned if the given name is not managed.
Params: - managedFile – the name of the managed file to open.
Throws: - IOException – if the content is missing, corrupt or an error occurs.
Returns: an input stream to the managed file or
null
if the given name is not managed.
/**
* Returns a managed <code>InputStream</code> for a managed file.
* <code>null</code> can be returned if the given name is not managed.
*
* @param managedFile the name of the managed file to open.
* @return an input stream to the managed file or
* <code>null</code> if the given name is not managed.
* @throws IOException if the content is missing, corrupt or an error occurs.
*/
public InputStream getInputStream(String managedFile) throws IOException {
return getInputStream(managedFile, ReliableFile.OPEN_BEST_AVAILABLE);
}
Returns a managed input stream set for the managed file names.
Elements of the returned set may be null
if a given name is not managed. This method should be used for managed file sets which use the output streams returned by the getOutputStreamSet(String[])
to save data. Params: - managedFiles – the names of the managed files to open.
Throws: - IOException – if the content of one of the managed files is missing, corrupt or an error occurs.
Returns: a set input streams to the given managed files.
/**
* Returns a managed input stream set for the managed file names.
* Elements of the returned set may be <code>null</code> if a given name is not managed.
* This method should be used for managed file sets which use the output streams returned
* by the {@link #getOutputStreamSet(String[])} to save data.
*
* @param managedFiles the names of the managed files to open.
* @return a set input streams to the given managed files.
* @throws IOException if the content of one of the managed files is missing, corrupt or an error occurs.
*/
public InputStream[] getInputStreamSet(String[] managedFiles) throws IOException {
InputStream[] streams = new InputStream[managedFiles.length];
for (int i = 0; i < streams.length; i++)
streams[i] = getInputStream(managedFiles[i], ReliableFile.OPEN_FAIL_ON_PRIMARY);
return streams;
}
private InputStream getInputStream(String managedFiles, int openMask) throws IOException {
if (useReliableFiles) {
int id = getId(managedFiles);
if (id == -1)
return null;
return new ReliableFileInputStream(new File(getBase(), managedFiles), id, openMask);
}
File lookup = lookup(managedFiles, false);
if (lookup == null)
return null;
return new FileInputStream(lookup);
}
Returns a ManagedOutputStream
for a managed file.
Closing the ouput stream will update the storage manager with the
new content of the managed file.
Params: - managedFile – the name of the managed file to write.
Throws: - IOException – if an error occurs opening the managed file.
Returns: a managed output stream for the managed file.
/**
* Returns a <code>ManagedOutputStream</code> for a managed file.
* Closing the ouput stream will update the storage manager with the
* new content of the managed file.
*
* @param managedFile the name of the managed file to write.
* @return a managed output stream for the managed file.
* @throws IOException if an error occurs opening the managed file.
*/
public ManagedOutputStream getOutputStream(String managedFile) throws IOException {
if (useReliableFiles) {
ReliableFileOutputStream out = new ReliableFileOutputStream(new File(getBase(), managedFile));
return new ManagedOutputStream(out, this, managedFile, null);
}
File tmpFile = createTempFile(managedFile);
return new ManagedOutputStream(new FileOutputStream(tmpFile), this, managedFile, tmpFile);
}
Returns an array of ManagedOutputStream
for a set of managed files.
When all managed output streams in the set have been closed, the storage manager
will be updated with the new content of the managed files.
Aborting any one of the streams will cause the entire content of the set to abort
and be discarded.
Params: - managedFiles – list of names of the managed file to write.
Throws: - IOException – if an error occurs opening the managed files.
Returns: an array of managed output streams respectively of managed files.
/**
* Returns an array of <code>ManagedOutputStream</code> for a set of managed files.
* When all managed output streams in the set have been closed, the storage manager
* will be updated with the new content of the managed files.
* Aborting any one of the streams will cause the entire content of the set to abort
* and be discarded.
*
* @param managedFiles list of names of the managed file to write.
* @return an array of managed output streams respectively of managed files.
* @throws IOException if an error occurs opening the managed files.
*/
public ManagedOutputStream[] getOutputStreamSet(String[] managedFiles) throws IOException {
int count = managedFiles.length;
ManagedOutputStream[] streams = new ManagedOutputStream[count];
int idx = 0;
try {
for (; idx < count; idx++) {
ManagedOutputStream newStream = getOutputStream(managedFiles[idx]);
newStream.setStreamSet(streams);
streams[idx] = newStream;
}
} catch (IOException e) {
// cleanup
for (int jdx = 0; jdx < idx; jdx++)
streams[jdx].abort();
throw e;
}
return streams;
}
/* (non-Javadoc)
* Instructs this manager to abort and discard a managed output stream.
* This method should be used if any errors occur after opening a managed
* output stream where the contents should not be saved.
* If this output stream is part of a set, all other managed output streams in this set
* will also be closed and aborted.
* @param out the managed output stream
* @see #getOutputStream(String)
* @see #getOutputStreamSet(String[])
*/
void abortOutputStream(ManagedOutputStream out) {
ManagedOutputStream[] streamset = out.getStreamSet();
if (streamset == null) {
streamset = new ManagedOutputStream[] {out};
}
synchronized (streamset) {
for (ManagedOutputStream stream : streamset) {
out = stream;
if (out.getOutputFile() == null) {
// this is a ReliableFileOutpuStream
ReliableFileOutputStream rfos = (ReliableFileOutputStream) out.getOutputStream();
rfos.abort();
} else {
// plain FileOutputStream();
if (out.getState() == ManagedOutputStream.ST_OPEN) {
try {
out.getOutputStream().close();
} catch (IOException e) {/*do nothing*/
}
}
out.getOutputFile().delete();
}
out.setState(ManagedOutputStream.ST_CLOSED);
}
}
}
/* (non-Javadoc)
* Close the managed output stream and update the new content to
* this manager. If this managed output stream is part of a set, only after closing
* all managed output streams in the set will storage manager be updated.
*
* @param smos the output stream.
* @throws IOException if an errors occur.
* @see #getOutputStream(String)
* @see #getOutputStreamSet(String[])
*/
void closeOutputStream(ManagedOutputStream smos) throws IOException {
if (smos.getState() != ManagedOutputStream.ST_OPEN)
return;
ManagedOutputStream[] streamSet = smos.getStreamSet();
if (smos.getOutputFile() == null) {
// this is a ReliableFileOutputStream
ReliableFileOutputStream rfos = (ReliableFileOutputStream) smos.getOutputStream();
// manage file deletes
File file = rfos.closeIntermediateFile();
smos.setState(ManagedOutputStream.ST_CLOSED);
String target = smos.getTarget();
if (streamSet == null) {
add(target, StorageManager.FILETYPE_RELIABLEFILE);
update(new String[] {smos.getTarget()}, new String[] {file.getName()});
ReliableFile.fileUpdated(new File(getBase(), smos.getTarget()));
}
} else {
// this is a plain old file output steam
OutputStream out = smos.getOutputStream();
out.flush();
try {
((FileOutputStream) out).getFD().sync();
} catch (SyncFailedException e) {/*ignore*/
}
out.close();
smos.setState(ManagedOutputStream.ST_CLOSED);
String target = smos.getTarget();
if (streamSet == null) {
add(target, StorageManager.FILETYPE_STANDARD);
update(new String[] {target}, new String[] {smos.getOutputFile().getName()});
}
}
if (streamSet != null) {
synchronized (streamSet) {
//check all the streams to see if there are any left open....
for (ManagedOutputStream stream : streamSet) {
if (stream.getState() == ManagedOutputStream.ST_OPEN) {
return; //done
}
}
//all streams are closed, we need to update storage manager
String[] targets = new String[streamSet.length];
String[] sources = new String[streamSet.length];
for (int idx = 0; idx < streamSet.length; idx++) {
smos = streamSet[idx];
targets[idx] = smos.getTarget();
File outputFile = smos.getOutputFile();
if (outputFile == null) {
// this is a ReliableFile
add(smos.getTarget(), StorageManager.FILETYPE_RELIABLEFILE);
ReliableFileOutputStream rfos = (ReliableFileOutputStream) smos.getOutputStream();
File file = rfos.closeIntermediateFile(); //multiple calls to close() ok
sources[idx] = file.getName();
ReliableFile.fileUpdated(new File(getBase(), smos.getTarget()));
} else {
add(smos.getTarget(), StorageManager.FILETYPE_STANDARD);
sources[idx] = outputFile.getName();
}
}
update(targets, sources);
}
}
}
}