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