001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.commons.transaction.file;
018:
019: import java.io.BufferedReader;
020: import java.io.BufferedWriter;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.InputStreamReader;
028: import java.io.OutputStream;
029: import java.io.OutputStreamWriter;
030: import java.io.UnsupportedEncodingException;
031:
032: import org.apache.commons.transaction.util.FileHelper;
033: import org.apache.commons.transaction.util.LoggerFacade;
034:
035: /**
036: * Fail-Safe sequence store implementation using the file system. Works by versioning
037: * values of sequences and throwing away all versions, but the current and the previous one.
038: *
039: * @version $Id: FileSequence.java 493628 2007-01-07 01:42:48Z joerg $
040: */
041: public class FileSequence {
042:
043: protected final String storeDir;
044: protected final LoggerFacade logger;
045:
046: /**
047: * Creates a new resouce manager operation on the specified directories.
048: *
049: * @param storeDir directory where sequence information is stored
050: * @param logger logger used for warnings only
051: */
052: public FileSequence(String storeDir, LoggerFacade logger)
053: throws ResourceManagerException {
054: this .storeDir = storeDir;
055: this .logger = logger;
056: File file = new File(storeDir);
057: file.mkdirs();
058: if (!file.exists()) {
059: throw new ResourceManagerException(
060: "Can not create working directory " + storeDir);
061: }
062: }
063:
064: /**
065: * Checks if the sequence already exists.
066: *
067: * @param sequenceName the name of the sequence you want to check
068: * @return <code>true</code> if the sequence already exists, <code>false</code> otherwise
069: */
070: public synchronized boolean exists(String sequenceName) {
071: String pathI = getPathI(sequenceName);
072: String pathII = getPathII(sequenceName);
073:
074: return (FileHelper.fileExists(pathI) || FileHelper
075: .fileExists(pathII));
076: }
077:
078: /**
079: * Creates a sequence if it does not already exist.
080: *
081: * @param sequenceName the name of the sequence you want to create
082: * @return <code>true</code> if the sequence has been created, <code>false</code> if it already existed
083: * @throws ResourceManagerException if anything goes wrong while accessing the sequence
084: */
085: public synchronized boolean create(String sequenceName,
086: long initialValue) throws ResourceManagerException {
087: if (exists(sequenceName))
088: return false;
089: write(sequenceName, initialValue);
090: return true;
091: }
092:
093: /**
094: * Deletes a sequence if it exists.
095: *
096: * @param sequenceName the name of the sequence you want to delete
097: * @return <code>true</code> if the sequence has been deleted, <code>false</code> if not
098: */
099: public synchronized boolean delete(String sequenceName) {
100: if (!exists(sequenceName))
101: return false;
102: String pathI = getPathI(sequenceName);
103: String pathII = getPathII(sequenceName);
104:
105: // XXX be careful no to use shortcut eval with || might not delete second file
106: boolean res1 = FileHelper.deleteFile(pathI);
107: boolean res2 = FileHelper.deleteFile(pathII);
108:
109: return (res1 || res2);
110: }
111:
112: /**
113: * Gets the next value of the sequence.
114: *
115: * @param sequenceName the name of the sequence you want the next value for
116: * @param increment the increment for the sequence, i.e. how much to add to the sequence with this call
117: * @return the next value of the sequence <em>not yet incremented</em>, i.e. the increment is recorded
118: * internally, but not returned with the next call to this method
119: * @throws ResourceManagerException if anything goes wrong while accessing the sequence
120: */
121: public synchronized long nextSequenceValueBottom(
122: String sequenceName, long increment)
123: throws ResourceManagerException {
124: if (!exists(sequenceName)) {
125: throw new ResourceManagerException("Sequence "
126: + sequenceName + " does not exist");
127: }
128: if (increment <= 0) {
129: throw new IllegalArgumentException(
130: "Increment must be greater than 0, was "
131: + increment);
132: }
133: long value = read(sequenceName);
134: long newValue = value + increment;
135: write(sequenceName, newValue);
136: return value;
137: }
138:
139: protected long read(String sequenceName)
140: throws ResourceManagerException {
141: String pathI = getPathI(sequenceName);
142: String pathII = getPathII(sequenceName);
143:
144: long returnValue = -1;
145:
146: long valueI = -1;
147: if (FileHelper.fileExists(pathI)) {
148: try {
149: valueI = readFromPath(pathI);
150: } catch (NumberFormatException e) {
151: throw new ResourceManagerException(
152: "Fatal internal error: Backup sequence value corrupted");
153: } catch (FileNotFoundException e) {
154: throw new ResourceManagerException(
155: "Fatal internal error: Backup sequence vanished");
156: } catch (IOException e) {
157: throw new ResourceManagerException(
158: "Fatal internal error: Backup sequence value corrupted");
159: }
160: }
161:
162: long valueII = -1;
163: if (FileHelper.fileExists(pathII)) {
164: try {
165: valueII = readFromPath(pathII);
166: if (valueII > valueI) {
167: returnValue = valueII;
168: } else {
169: // if it is smaller than previous this *must* be an error as we constantly increment
170: logger
171: .logWarning("Latest sequence value smaller than previous, reverting to previous");
172: FileHelper.deleteFile(pathII);
173: returnValue = valueI;
174: }
175: } catch (NumberFormatException e) {
176: logger
177: .logWarning("Latest sequence value corrupted, reverting to previous");
178: FileHelper.deleteFile(pathII);
179: returnValue = valueI;
180: } catch (FileNotFoundException e) {
181: logger
182: .logWarning("Can not find latest sequence value, reverting to previous");
183: FileHelper.deleteFile(pathII);
184: returnValue = valueI;
185: } catch (IOException e) {
186: logger
187: .logWarning("Can not read latest sequence value, reverting to previous");
188: FileHelper.deleteFile(pathII);
189: returnValue = valueI;
190: }
191: } else {
192: logger
193: .logWarning("Can not read latest sequence value, reverting to previous");
194: returnValue = valueI;
195: }
196:
197: if (returnValue != -1) {
198: return returnValue;
199: } else {
200: throw new ResourceManagerException(
201: "Fatal internal error: Could not compute valid sequence value");
202: }
203: }
204:
205: protected void write(String sequenceName, long value)
206: throws ResourceManagerException {
207: String pathII = getPathII(sequenceName);
208:
209: File f2 = new File(pathII);
210: // by contract when this method is called an f2 exists it must be valid
211: if (f2.exists()) {
212: // move previous value to backup position
213: String pathI = getPathI(sequenceName);
214: File f1 = new File(pathI);
215: f1.delete();
216: if (!f2.renameTo(f1)) {
217: throw new ResourceManagerException(
218: "Fatal internal error: Can not create backup value at"
219: + pathI);
220: }
221: }
222: try {
223: if (!f2.createNewFile()) {
224: throw new ResourceManagerException(
225: "Fatal internal error: Can not create new value at"
226: + pathII);
227: }
228: } catch (IOException e) {
229: throw new ResourceManagerException(
230: "Fatal internal error: Can not create new value at"
231: + pathII, e);
232: }
233: writeToPath(pathII, value);
234: }
235:
236: protected String getPathI(String sequenceName) {
237: return storeDir + "/" + sequenceName + "_1.seq";
238: }
239:
240: protected String getPathII(String sequenceName) {
241: return storeDir + "/" + sequenceName + "_2.seq";
242: }
243:
244: protected long readFromPath(String path)
245: throws ResourceManagerException, NumberFormatException,
246: FileNotFoundException, IOException {
247: File file = new File(path);
248: BufferedReader reader = null;
249: try {
250: InputStream is = new FileInputStream(file);
251:
252: // we do not care for encoding as we only have numbers
253: reader = new BufferedReader(new InputStreamReader(is,
254: "UTF-8"));
255: String valueString = reader.readLine();
256: long value = Long.parseLong(valueString);
257: return value;
258: } catch (UnsupportedEncodingException e) {
259: throw new ResourceManagerException(
260: "Fatal internal error, encoding UTF-8 unknown");
261: } finally {
262: if (reader != null) {
263: try {
264: reader.close();
265: } catch (IOException e) {
266: }
267:
268: }
269: }
270: }
271:
272: protected void writeToPath(String path, long value)
273: throws ResourceManagerException {
274: File file = new File(path);
275: BufferedWriter writer = null;
276: try {
277: OutputStream os = new FileOutputStream(file);
278: writer = new BufferedWriter(new OutputStreamWriter(os,
279: "UTF-8"));
280: String valueString = Long.toString(value);
281: writer.write(valueString);
282: writer.write('\n');
283: } catch (FileNotFoundException e) {
284: throw new ResourceManagerException(
285: "Fatal internal error: Can not find sequence at "
286: + path);
287: } catch (IOException e) {
288: throw new ResourceManagerException(
289: "Fatal internal error: Can not write to sequence at "
290: + path);
291: } finally {
292: if (writer != null) {
293: try {
294: writer.close();
295: } catch (IOException e) {
296: }
297:
298: }
299: }
300: }
301: }
|