001: package henplus.io;
002:
003: import java.io.File;
004: import java.io.FileInputStream;
005: import java.io.FileOutputStream;
006: import java.io.IOException;
007: import java.io.InputStream;
008: import java.io.OutputStream;
009: import java.security.DigestInputStream;
010: import java.security.DigestOutputStream;
011: import java.security.MessageDigest;
012: import java.util.HashSet;
013: import java.util.Iterator;
014: import java.util.Properties;
015: import java.util.Map;
016: import java.util.Set;
017:
018: /**
019: * Helper class to write the configuration. Focus is to avoid half-written configuration
020: * files if IO-Errors occur (full harddisk ..) and to merge properties.
021: *
022: * @author hzeller
023: * @version $Revision: 1.1 $
024: */
025: public final class ConfigurationContainer {
026: /** configuration file name */
027: private final File _configFile;
028:
029: /** file content digest on last read. */
030: private byte[] _inputDigest;
031:
032: /** properties read initially */
033: private Properties _readProperties;
034:
035: public ConfigurationContainer(File file) {
036: _configFile = file.getAbsoluteFile();
037: }
038:
039: public interface ReadAction {
040: public void readConfiguration(InputStream in) throws Exception;
041: }
042:
043: /**
044: * Execute the read action with the InputStream from the corresponding
045: * configuration file.
046: */
047: public void read(ReadAction action) {
048: try {
049: InputStream input = getInput();
050: try {
051: action.readConfiguration(input);
052: } finally {
053: if (input != null)
054: input.close();
055: }
056: } catch (Exception e) {
057: }
058: }
059:
060: /**
061: * get the input stream for this configuration container. If
062: * no configuration file exists, 'null' is returned. Remember content
063: * digest on close().
064: */
065: private InputStream getInput() {
066: if (!_configFile.canRead()) {
067: return null;
068: }
069: try {
070: InputStream in = new FileInputStream(_configFile);
071: final MessageDigest inputDigest = MessageDigest
072: .getInstance("MD5");
073: return new DigestInputStream(in, inputDigest) {
074: boolean isClosed = false;
075:
076: public void close() throws IOException {
077: if (!isClosed) {
078: super .close();
079: _inputDigest = inputDigest.digest();
080: }
081: isClosed = true;
082: }
083: };
084: } catch (Exception e) {
085: return null; // no input.
086: }
087: }
088:
089: public interface WriteAction {
090: /**
091: * Write configuration. If any Exception is thrown, the original file
092: * is not overwritten.
093: */
094: public void writeConfiguration(OutputStream out)
095: throws Exception;
096: }
097:
098: /**
099: * Write configuration. The configuration is first written to a temporary
100: * file. Does not overwrite the original file if any Exception
101: * occurs in the course of this or the resulting file is no different.
102: */
103: public void write(WriteAction action) {
104: File tmpFile = null;
105: try {
106: tmpFile = File.createTempFile("config-", ".tmp",
107: _configFile.getParentFile());
108: final MessageDigest outputDigest = MessageDigest
109: .getInstance("MD5");
110: OutputStream out = new DigestOutputStream(
111: new FileOutputStream(tmpFile), outputDigest);
112: try {
113: action.writeConfiguration(out);
114: } finally {
115: out.close();
116: }
117: if (_inputDigest == null
118: || !_configFile.exists()
119: || !MessageDigest.isEqual(_inputDigest,
120: outputDigest.digest())) {
121: //System.err.println("non equal.. write file " + _configFile);
122: tmpFile.renameTo(_configFile);
123: }
124: } catch (Exception e) {
125: System.err.println("do not write config. Error occured: "
126: + e);
127: } finally {
128: if (tmpFile != null) {
129: tmpFile.delete();
130: }
131: }
132: }
133:
134: public Map readProperties() {
135: return readProperties(null);
136: }
137:
138: /**
139: * convenience-method to read properties. If you handle
140: * simple properties within your command, then use
141: * this method so that versioning and merging
142: * is handled.
143: */
144: public Map readProperties(Map prefill) {
145: _readProperties = new Properties();
146: if (prefill != null) {
147: _readProperties.putAll(prefill);
148: }
149: final InputStream input = getInput();
150: if (input != null) {
151: try {
152: _readProperties.load(input);
153: input.close();
154: } catch (Exception e) {
155: System.err.println(e); // can't help.
156: }
157: }
158: Map props = (Properties) _readProperties.clone();
159: return props;
160: }
161:
162: /**
163: * convenience-method to write properties. Properties
164: * must have been read before.
165: * @param allowMerge allow merging of properties that have
166: * been added by another instance of henplus.
167: */
168: public void storeProperties(Map props, boolean allowMerge,
169: final String comment) {
170: if (_readProperties == null) {
171: throw new IllegalStateException(
172: "properties not read before");
173: }
174:
175: /* merge if wanted */
176: final Properties outputProperties = new Properties();
177: if (allowMerge) {
178: // all properties, that are not present compared to last read
179: // should be removed after merge.
180: final Set locallyRemovedProperties = new HashSet();
181: locallyRemovedProperties.addAll(_readProperties.keySet());
182: locallyRemovedProperties.removeAll(props.keySet());
183:
184: final InputStream input = getInput();
185: if (input != null) {
186: try {
187: outputProperties.load(input);
188: input.close();
189: } catch (Exception e) {
190: // can't help.
191: }
192: }
193:
194: final Iterator it = locallyRemovedProperties.iterator();
195: while (it.hasNext()) {
196: String key = (String) it.next();
197: outputProperties.remove(key);
198: }
199: }
200:
201: outputProperties.putAll(props);
202:
203: if (outputProperties.equals(_readProperties)) {
204: //System.err.println("equal properties. Do nothing " + _configFile);
205: return;
206: }
207:
208: write(new WriteAction() {
209: public void writeConfiguration(OutputStream out)
210: throws Exception {
211: outputProperties.store(out, comment);
212: out.close();
213: }
214: });
215: }
216: }
|