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 */ 017package org.apache.commons.transaction.file; 018 019import java.io.BufferedReader; 020import java.io.BufferedWriter; 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.FileNotFoundException; 024import java.io.FileOutputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.OutputStream; 029import java.io.OutputStreamWriter; 030import java.io.UnsupportedEncodingException; 031 032import org.apache.commons.transaction.util.FileHelper; 033import 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 */ 041public 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}