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) throws ResourceManagerException {
053 this.storeDir = storeDir;
054 this.logger = logger;
055 File file = new File(storeDir);
056 file.mkdirs();
057 if (!file.exists()) {
058 throw new ResourceManagerException("Can not create working directory " + storeDir);
059 }
060 }
061
062 /**
063 * Checks if the sequence already exists.
064 *
065 * @param sequenceName the name of the sequence you want to check
066 * @return <code>true</code> if the sequence already exists, <code>false</code> otherwise
067 */
068 public synchronized boolean exists(String sequenceName) {
069 String pathI = getPathI(sequenceName);
070 String pathII = getPathII(sequenceName);
071
072 return (FileHelper.fileExists(pathI) || FileHelper.fileExists(pathII));
073 }
074
075 /**
076 * Creates a sequence if it does not already exist.
077 *
078 * @param sequenceName the name of the sequence you want to create
079 * @return <code>true</code> if the sequence has been created, <code>false</code> if it already existed
080 * @throws ResourceManagerException if anything goes wrong while accessing the sequence
081 */
082 public synchronized boolean create(String sequenceName, long initialValue) throws ResourceManagerException {
083 if (exists(sequenceName))
084 return false;
085 write(sequenceName, initialValue);
086 return true;
087 }
088
089 /**
090 * Deletes a sequence if it exists.
091 *
092 * @param sequenceName the name of the sequence you want to delete
093 * @return <code>true</code> if the sequence has been deleted, <code>false</code> if not
094 */
095 public synchronized boolean delete(String sequenceName) {
096 if (!exists(sequenceName))
097 return false;
098 String pathI = getPathI(sequenceName);
099 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 }