1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.transaction.file;
18
19 import java.io.BufferedReader;
20 import java.io.BufferedWriter;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.InputStreamReader;
28 import java.io.OutputStream;
29 import java.io.OutputStreamWriter;
30 import java.io.UnsupportedEncodingException;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34 import org.apache.commons.transaction.util.FileHelper;
35
36 /**
37 * Fail-Safe sequence store implementation using the file system. Works by
38 * versioning values of sequences and throwing away all versions, but the
39 * current and the previous one.
40 *
41 */
42 public class FileSequence {
43
44 private Log logger = LogFactory.getLog(getClass());
45
46 protected final String storeDir;
47
48 /**
49 * Creates a new resource manager operation on the specified directories.
50 *
51 * @param storeDir
52 * directory where sequence information is stored
53 */
54 public FileSequence(String storeDir) {
55 this.storeDir = storeDir;
56 File file = new File(storeDir);
57 file.mkdirs();
58 if (!file.exists()) {
59 throw new IllegalStateException("Can not create working directory " + storeDir);
60 }
61 }
62
63 /**
64 * Checks if the sequence already exists.
65 *
66 * @param sequenceName
67 * the name of the sequence you want to check
68 * @return <code>true</code> if the sequence already exists,
69 * <code>false</code> otherwise
70 */
71 public synchronized boolean exists(String sequenceName) {
72 String pathI = getPathI(sequenceName);
73 String pathII = getPathII(sequenceName);
74
75 return (FileHelper.fileExists(pathI) || FileHelper.fileExists(pathII));
76 }
77
78 /**
79 * Creates a sequence if it does not already exist.
80 *
81 * @param sequenceName
82 * the name of the sequence you want to create
83 * @return <code>true</code> if the sequence has been created,
84 * <code>false</code> if it already existed
85 */
86 public synchronized boolean create(String sequenceName, long initialValue) {
87 if (exists(sequenceName))
88 return false;
89 write(sequenceName, initialValue);
90 return true;
91 }
92
93 /**
94 * Deletes a sequence if it exists.
95 *
96 * @param sequenceName
97 * the name of the sequence you want to delete
98 * @return <code>true</code> if the sequence has been deleted,
99 * <code>false</code> if not
100 */
101 public synchronized boolean delete(String sequenceName) {
102 if (!exists(sequenceName))
103 return false;
104 String pathI = getPathI(sequenceName);
105 String pathII = getPathII(sequenceName);
106
107
108
109 boolean res1 = FileHelper.deleteFile(pathI);
110 boolean res2 = FileHelper.deleteFile(pathII);
111
112 return (res1 || res2);
113 }
114
115 /**
116 * Gets the next value of the sequence.
117 *
118 * @param sequenceName
119 * the name of the sequence you want the next value for
120 * @param increment
121 * the increment for the sequence, i.e. how much to add to the
122 * sequence with this call
123 * @return the next value of the sequence <em>not yet incremented</em>,
124 * i.e. the increment is recorded internally, but not returned with
125 * the next call to this method
126 * @throws ResourceManagerException
127 * if anything goes wrong while accessing the sequence
128 */
129 public synchronized long nextSequenceValueBottom(String sequenceName, long increment) {
130 if (!exists(sequenceName)) {
131 throw new IllegalStateException("Sequence " + sequenceName + " does not exist");
132 }
133 if (increment <= 0) {
134 throw new IllegalArgumentException("Increment must be greater than 0, was " + increment);
135 }
136 long value = read(sequenceName);
137 long newValue = value + increment;
138 write(sequenceName, newValue);
139 return value;
140 }
141
142 protected long read(String sequenceName) {
143 String pathI = getPathI(sequenceName);
144 String pathII = getPathII(sequenceName);
145
146 long returnValue = -1;
147
148 long valueI = -1;
149 if (FileHelper.fileExists(pathI)) {
150 try {
151 valueI = readFromPath(pathI);
152 } catch (NumberFormatException e) {
153 throw new Error("Fatal internal error: Backup sequence value corrupted");
154 } catch (FileNotFoundException e) {
155 throw new Error("Fatal internal error: Backup sequence vanished");
156 } catch (IOException e) {
157 throw new Error("Fatal internal error: Backup sequence value corrupted");
158 }
159 }
160
161 long valueII = -1;
162 if (FileHelper.fileExists(pathII)) {
163 try {
164 valueII = readFromPath(pathII);
165 if (valueII > valueI) {
166 returnValue = valueII;
167 } else {
168
169
170 logger
171 .warn("Latest sequence value smaller than previous, reverting to previous");
172 FileHelper.deleteFile(pathII);
173 returnValue = valueI;
174 }
175 } catch (NumberFormatException e) {
176 logger.warn("Latest sequence value corrupted, reverting to previous");
177 FileHelper.deleteFile(pathII);
178 returnValue = valueI;
179 } catch (FileNotFoundException e) {
180 logger.warn("Can not find latest sequence value, reverting to previous");
181 FileHelper.deleteFile(pathII);
182 returnValue = valueI;
183 } catch (IOException e) {
184 logger.warn("Can not read latest sequence value, reverting to previous");
185 FileHelper.deleteFile(pathII);
186 returnValue = valueI;
187 }
188 } else {
189 logger.warn("Can not read latest sequence value, reverting to previous");
190 returnValue = valueI;
191 }
192
193 if (returnValue != -1) {
194 return returnValue;
195 } else {
196 throw new Error("Fatal internal error: Could not compute valid sequence value");
197 }
198 }
199
200 protected void write(String sequenceName, long value) {
201 String pathII = getPathII(sequenceName);
202
203 File f2 = new File(pathII);
204
205 if (f2.exists()) {
206
207 String pathI = getPathI(sequenceName);
208 File f1 = new File(pathI);
209 f1.delete();
210 if (!f2.renameTo(f1)) {
211 throw new Error("Fatal internal error: Can not create backup value at" + pathI);
212 }
213 }
214 try {
215 if (!f2.createNewFile()) {
216 throw new Error("Fatal internal error: Can not create new value at" + pathII);
217 }
218 } catch (IOException e) {
219 throw new Error("Fatal internal error: Can not create new value at" + pathII, e);
220 }
221 writeToPath(pathII, value);
222 }
223
224 protected String getPathI(String sequenceName) {
225 return storeDir + "/" + sequenceName + "_1.seq";
226 }
227
228 protected String getPathII(String sequenceName) {
229 return storeDir + "/" + sequenceName + "_2.seq";
230 }
231
232 protected long readFromPath(String path) throws NumberFormatException, FileNotFoundException,
233 IOException {
234 File file = new File(path);
235 BufferedReader reader = null;
236 try {
237 InputStream is = new FileInputStream(file);
238
239
240 reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
241 String valueString = reader.readLine();
242 long value = Long.parseLong(valueString);
243 return value;
244 } catch (UnsupportedEncodingException e) {
245 throw new Error("Fatal internal error, encoding UTF-8 unknown");
246 } finally {
247 if (reader != null) {
248 try {
249 reader.close();
250 } catch (IOException e) {
251 }
252
253 }
254 }
255 }
256
257 protected void writeToPath(String path, long value) {
258 File file = new File(path);
259 BufferedWriter writer = null;
260 try {
261 OutputStream os = new FileOutputStream(file);
262 writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
263 String valueString = Long.toString(value);
264 writer.write(valueString);
265 writer.write('\n');
266 } catch (FileNotFoundException e) {
267 throw new Error("Fatal internal error: Can not find sequence at " + path);
268 } catch (IOException e) {
269 throw new Error("Fatal internal error: Can not write to sequence at " + path);
270 } finally {
271 if (writer != null) {
272 try {
273 writer.close();
274 } catch (IOException e) {
275 }
276
277 }
278 }
279 }
280 }