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    }