View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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         // XXX be careful no to use shortcut eval with || might not delete
108         // second file
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                     // if it is smaller than previous this *must* be an error as
169                     // we constantly increment
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         // by contract when this method is called an f2 exists it must be valid
205         if (f2.exists()) {
206             // move previous value to backup position
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             // we do not care for encoding as we only have numbers
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 }