package henplus.io;
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.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Map;
import java.util.Set;
/**
* Helper class to write the configuration. Focus is to avoid half-written configuration
* files if IO-Errors occur (full harddisk ..) and to merge properties.
*
* @author hzeller
* @version $Revision: 1.1 $
*/
public final class ConfigurationContainer {
/** configuration file name */
private final File _configFile;
/** file content digest on last read. */
private byte[] _inputDigest;
/** properties read initially */
private Properties _readProperties;
public ConfigurationContainer(File file) {
_configFile = file.getAbsoluteFile();
}
public interface ReadAction {
public void readConfiguration(InputStream in) throws Exception;
}
/**
* Execute the read action with the InputStream from the corresponding
* configuration file.
*/
public void read(ReadAction action) {
try {
InputStream input = getInput();
try {
action.readConfiguration(input);
}
finally {
if (input != null) input.close();
}
}
catch (Exception e) {
}
}
/**
* get the input stream for this configuration container. If
* no configuration file exists, 'null' is returned. Remember content
* digest on close().
*/
private InputStream getInput() {
if (!_configFile.canRead()) {
return null;
}
try {
InputStream in = new FileInputStream(_configFile);
final MessageDigest inputDigest = MessageDigest.getInstance("MD5");
return new DigestInputStream(in, inputDigest) {
boolean isClosed = false;
public void close() throws IOException {
if (!isClosed) {
super.close();
_inputDigest = inputDigest.digest();
}
isClosed = true;
}
};
}
catch (Exception e) {
return null; // no input.
}
}
public interface WriteAction {
/**
* Write configuration. If any Exception is thrown, the original file
* is not overwritten.
*/
public void writeConfiguration(OutputStream out) throws Exception;
}
/**
* Write configuration. The configuration is first written to a temporary
* file. Does not overwrite the original file if any Exception
* occurs in the course of this or the resulting file is no different.
*/
public void write(WriteAction action) {
File tmpFile = null;
try {
tmpFile = File.createTempFile("config-", ".tmp", _configFile.getParentFile());
final MessageDigest outputDigest = MessageDigest.getInstance("MD5");
OutputStream out = new DigestOutputStream(new FileOutputStream(tmpFile), outputDigest);
try {
action.writeConfiguration(out);
}
finally {
out.close();
}
if (_inputDigest == null || !_configFile.exists()
|| !MessageDigest.isEqual(_inputDigest, outputDigest.digest())) {
//System.err.println("non equal.. write file " + _configFile);
tmpFile.renameTo(_configFile);
}
}
catch (Exception e) {
System.err.println("do not write config. Error occured: " + e);
}
finally {
if (tmpFile != null) {
tmpFile.delete();
}
}
}
public Map readProperties() {
return readProperties(null);
}
/**
* convenience-method to read properties. If you handle
* simple properties within your command, then use
* this method so that versioning and merging
* is handled.
*/
public Map readProperties(Map prefill) {
_readProperties = new Properties();
if (prefill != null) {
_readProperties.putAll(prefill);
}
final InputStream input = getInput();
if (input != null) {
try {
_readProperties.load(input);
input.close();
}
catch (Exception e) {
System.err.println(e); // can't help.
}
}
Map props = (Properties) _readProperties.clone();
return props;
}
/**
* convenience-method to write properties. Properties
* must have been read before.
* @param allowMerge allow merging of properties that have
* been added by another instance of henplus.
*/
public void storeProperties(Map props, boolean allowMerge, final String comment) {
if (_readProperties == null) {
throw new IllegalStateException("properties not read before");
}
/* merge if wanted */
final Properties outputProperties = new Properties();
if (allowMerge) {
// all properties, that are not present compared to last read
// should be removed after merge.
final Set locallyRemovedProperties = new HashSet();
locallyRemovedProperties.addAll(_readProperties.keySet());
locallyRemovedProperties.removeAll(props.keySet());
final InputStream input = getInput();
if (input != null) {
try {
outputProperties.load(input);
input.close();
}
catch (Exception e) {
// can't help.
}
}
final Iterator it = locallyRemovedProperties.iterator();
while (it.hasNext()) {
String key = (String) it.next();
outputProperties.remove(key);
}
}
outputProperties.putAll(props);
if (outputProperties.equals(_readProperties)) {
//System.err.println("equal properties. Do nothing " + _configFile);
return;
}
write(new WriteAction() {
public void writeConfiguration(OutputStream out)
throws Exception {
outputProperties.store(out, comment);
out.close();
}
});
}
}
|