KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > prevayler > implementation > logging > PersistentLogger


1 //Prevayler(TM) - The Free-Software Prevalence Layer.
2
//Copyright (C) 2001-2003 Klaus Wuestefeld
3
//This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
4

5 package org.prevayler.implementation.logging;
6
7 import java.io.EOFException JavaDoc;
8 import java.io.File JavaDoc;
9 import java.io.FileFilter JavaDoc;
10 import java.io.IOException JavaDoc;
11 import java.util.Date JavaDoc;
12
13 import org.prevayler.Transaction;
14 import org.prevayler.foundation.FileManager;
15 import org.prevayler.foundation.SimpleInputStream;
16 import org.prevayler.foundation.DurableOutputStream;
17 import org.prevayler.foundation.StopWatch;
18 import org.prevayler.foundation.Turn;
19 import org.prevayler.implementation.*;
20 import org.prevayler.implementation.publishing.TransactionSubscriber;
21
22
23 /** A TransactionLogger that will write all transactions to .transactionLog files.
24  */

25 public class PersistentLogger implements FileFilter JavaDoc, TransactionLogger {
26
27     private final File JavaDoc _directory;
28     private DurableOutputStream _outputLog;
29
30     private final long _logSizeThresholdInBytes;
31     private final long _logAgeThresholdInMillis;
32     private StopWatch _logAgeTimer;
33     
34     private long _nextTransaction;
35     private boolean _nextTransactionInitialized = false;
36
37
38     /**
39      * @param directory Where transactionLog files will be read and written.
40      * @param logSizeThresholdInBytes Size of the current transactionLog file beyond which it is closed and a new one started. Zero indicates no size threshold. This is useful transactionLog backup purposes.
41      * @param logAgeThresholdInMillis Age of the current transactionLog file beyond which it is closed and a new one started. Zero indicates no age threshold. This is useful transactionLog backup purposes.
42      */

43     public PersistentLogger(String JavaDoc directory, long logSizeThresholdInBytes, long logAgeThresholdInMillis) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
44         _directory = FileManager.produceDirectory(directory);
45         _logSizeThresholdInBytes = logSizeThresholdInBytes;
46         _logAgeThresholdInMillis = logAgeThresholdInMillis;
47     }
48
49
50     public void log(Transaction transaction, Date JavaDoc executionTime, Turn myTurn) {
51         if (!_nextTransactionInitialized) throw new IllegalStateException JavaDoc("TransactionLogger.update() has to be called at least once before TransactionLogger.log().");
52
53         DurableOutputStream myOutputJournal;
54         DurableOutputStream outputJournalToClose = null;
55         
56         try {
57             myTurn.start();
58             if (!isOutputLogValid()) {
59                 outputJournalToClose = _outputLog;
60                 _outputLog = createNewOutputLog(_nextTransaction);
61                 _logAgeTimer = StopWatch.start();
62             }
63             _nextTransaction++;
64             myOutputJournal = _outputLog;
65         } finally {
66             myTurn.end();
67         }
68
69         try {
70             myOutputJournal.sync(new TransactionTimestamp(transaction, executionTime), myTurn);
71         } catch (IOException JavaDoc iox) {
72             handleExceptionWhileWriting(iox, _outputLog.file());
73         }
74
75         try {
76             myTurn.start();
77             try {
78                 if (outputJournalToClose != null) outputJournalToClose.close();
79             } catch (IOException JavaDoc iox) {
80                 handleExceptionWhileClosing(iox, outputJournalToClose.file());
81             }
82         } finally {
83             myTurn.end();
84         }
85     }
86
87
88     private boolean isOutputLogValid() {
89         return _outputLog != null
90             && !isOutputLogTooBig()
91             && !isOutputLogTooOld();
92     }
93
94
95     private boolean isOutputLogTooOld() {
96         return _logAgeThresholdInMillis != 0
97             && _logAgeTimer.millisEllapsed() >= _logAgeThresholdInMillis;
98     }
99
100
101     private boolean isOutputLogTooBig() {
102         return _logSizeThresholdInBytes != 0
103             && _outputLog.file().length() >= _logSizeThresholdInBytes;
104     }
105
106
107     private DurableOutputStream createNewOutputLog(long transactionNumber) {
108         File JavaDoc file = transactionLogFile(transactionNumber);
109         try {
110             return new DurableOutputStream(file);
111         } catch (IOException JavaDoc iox) {
112             handleExceptionWhileCreating(iox, file);
113             return null;
114         }
115     }
116
117
118     /** IMPORTANT: This method cannot be called while the log() method is being called in another thread.
119      * If there are no log files in the directory (when a snapshot is taken and all log files are manually deleted, for example), the initialTransaction parameter in the first call to this method will define what the next transaction number will be. We have to find clearer/simpler semantics.
120      */

121     public void update(TransactionSubscriber subscriber, long initialTransactionWanted) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
122         long initialLogFile = findInitialLogFile(initialTransactionWanted);
123         
124         if (initialLogFile == 0) {
125             initializeNextTransaction(initialTransactionWanted, 1);
126             return;
127         }
128
129         long nextTransaction = recoverPendingTransactions(subscriber, initialTransactionWanted, initialLogFile);
130         
131         initializeNextTransaction(initialTransactionWanted, nextTransaction);
132     }
133
134
135     private long findInitialLogFile(long initialTransactionWanted) {
136         long initialFileCandidate = initialTransactionWanted;
137         while (initialFileCandidate != 0) { //TODO Optimize.
138
if (transactionLogFile(initialFileCandidate).exists()) break;
139             initialFileCandidate--;
140         }
141         return initialFileCandidate;
142     }
143
144
145     private void initializeNextTransaction(long initialTransactionWanted, long nextTransaction) throws IOException JavaDoc {
146         if (_nextTransactionInitialized) {
147             if (_nextTransaction < initialTransactionWanted) throw new IOException JavaDoc("The transaction log has not yet reached transaction " + initialTransactionWanted + ". The last logged transaction was " + (_nextTransaction - 1) + ".");
148             if (nextTransaction < _nextTransaction) throw new IOException JavaDoc("Unable to find transactionLog file containing transaction " + nextTransaction + ". Might have been manually deleted.");
149             if (nextTransaction > _nextTransaction) throw new IllegalStateException JavaDoc();
150             return;
151         }
152         _nextTransactionInitialized = true;
153         _nextTransaction = initialTransactionWanted > nextTransaction
154             ? initialTransactionWanted
155             : nextTransaction;
156     }
157
158
159     private long recoverPendingTransactions(TransactionSubscriber subscriber, long initialTransaction, long initialLogFile) throws IOException JavaDoc, ClassNotFoundException JavaDoc {
160         long recoveringTransaction = initialLogFile;
161         File JavaDoc logFile = transactionLogFile(recoveringTransaction);
162         SimpleInputStream inputLog = null;
163         try {
164             inputLog = new SimpleInputStream(logFile);
165         } catch (EOFException JavaDoc e) {
166             renameUnusedFile(logFile);
167             return recoveringTransaction;
168         }
169
170         while(true) {
171             try {
172                 TransactionTimestamp entry = (TransactionTimestamp)inputLog.readObject();
173         
174                 if (recoveringTransaction >= initialTransaction)
175                     subscriber.receive(entry.transaction(), entry.timestamp());
176         
177                 recoveringTransaction++;
178         
179             } catch (EOFException JavaDoc eof) {
180                 File JavaDoc nextFile = transactionLogFile(recoveringTransaction);
181                 if (logFile.equals(nextFile)) renameUnusedFile(logFile); //The first transaction in this log file is incomplete. We need to reuse this file name.
182
logFile = nextFile;
183                 if (!logFile.exists()) break;
184                 try {
185                     inputLog = new SimpleInputStream(logFile);
186                 } catch (EOFException JavaDoc e) {
187                     renameUnusedFile(logFile);
188                     return recoveringTransaction;
189                 }
190             }
191         }
192         return recoveringTransaction;
193     }
194
195
196     private void renameUnusedFile(File JavaDoc logFile) {
197         logFile.renameTo(new File JavaDoc(logFile.getAbsolutePath() + ".unusedFile" + System.currentTimeMillis()));
198     }
199
200
201     /** Implementing FileFilter. 0000000000000000000.transactionLog is the format of the transaction log filename. The long number (19 digits) is the number of the next transaction to be written at the moment the file is created. All transactions written to a file, therefore, have a sequence number greater or equal to the number in its filename.
202      */

203     public boolean accept(File JavaDoc file) {
204         String JavaDoc name = file.getName();
205         if (!name.endsWith(".transactionLog")) return false;
206         if (name.length() != 34) return false;
207         try { number(file); } catch (RuntimeException JavaDoc r) { return false; }
208         return true;
209     }
210
211     private File JavaDoc transactionLogFile(long transaction) {
212         String JavaDoc fileName = "0000000000000000000" + transaction;
213         fileName = fileName.substring(fileName.length() - 19) + ".transactionLog";
214         return new File JavaDoc(_directory, fileName);
215     }
216
217     static private long number(File JavaDoc file) {
218         return Long.parseLong(file.getName().substring(0, 19));
219     }
220
221
222     protected void handleExceptionWhileCreating(IOException JavaDoc iox, File JavaDoc logFile) {
223         hang(iox, "\nThe exception above was thrown while trying to create file " + logFile + " . Prevayler's default behavior is to display this message and block all transactions. You can change this behavior by extending the PersistentLogger class and overriding the method called: handleExceptionWhileCreating(IOException iox, File logFile).");
224     }
225
226
227     protected void handleExceptionWhileWriting(IOException JavaDoc iox, File JavaDoc logFile) {
228         hang(iox, "\nThe exception above was thrown while trying to write to file " + logFile + " . Prevayler's default behavior is to display this message and block all transactions. You can change this behavior by extending the PersistentLogger class and overriding the method called: handleExceptionWhileWriting(IOException iox, File logFile).");
229     }
230     
231     protected void handleExceptionWhileClosing(IOException JavaDoc iox, File JavaDoc logFile) {
232         hang(iox, "\nThe exception above was thrown while trying to close file " + logFile + " . Prevayler's default behavior is to display this message and block all transactions. You can change this behavior by extending the PersistentLogger class and overriding the method called: handleExceptionWhileClosing(IOException iox, File logFile).");
233     }
234
235     static private void hang(IOException JavaDoc iox, String JavaDoc message) {
236         iox.printStackTrace();
237         System.out.println(message);
238         while (true) Thread.yield();
239     }
240
241
242     public void close() throws IOException JavaDoc {
243         if (_outputLog != null) _outputLog.close();
244     }
245
246 }
247
Popular Tags