1 5 package org.prevayler.implementation.logging; 6 7 import java.io.EOFException ; 8 import java.io.File ; 9 import java.io.FileFilter ; 10 import java.io.IOException ; 11 import java.util.Date ; 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 25 public class PersistentLogger implements FileFilter , TransactionLogger { 26 27 private final File _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 43 public PersistentLogger(String directory, long logSizeThresholdInBytes, long logAgeThresholdInMillis) throws IOException , ClassNotFoundException { 44 _directory = FileManager.produceDirectory(directory); 45 _logSizeThresholdInBytes = logSizeThresholdInBytes; 46 _logAgeThresholdInMillis = logAgeThresholdInMillis; 47 } 48 49 50 public void log(Transaction transaction, Date executionTime, Turn myTurn) { 51 if (!_nextTransactionInitialized) throw new IllegalStateException ("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 iox) { 72 handleExceptionWhileWriting(iox, _outputLog.file()); 73 } 74 75 try { 76 myTurn.start(); 77 try { 78 if (outputJournalToClose != null) outputJournalToClose.close(); 79 } catch (IOException 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 file = transactionLogFile(transactionNumber); 109 try { 110 return new DurableOutputStream(file); 111 } catch (IOException iox) { 112 handleExceptionWhileCreating(iox, file); 113 return null; 114 } 115 } 116 117 118 121 public void update(TransactionSubscriber subscriber, long initialTransactionWanted) throws IOException , ClassNotFoundException { 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) { if (transactionLogFile(initialFileCandidate).exists()) break; 139 initialFileCandidate--; 140 } 141 return initialFileCandidate; 142 } 143 144 145 private void initializeNextTransaction(long initialTransactionWanted, long nextTransaction) throws IOException { 146 if (_nextTransactionInitialized) { 147 if (_nextTransaction < initialTransactionWanted) throw new IOException ("The transaction log has not yet reached transaction " + initialTransactionWanted + ". The last logged transaction was " + (_nextTransaction - 1) + "."); 148 if (nextTransaction < _nextTransaction) throw new IOException ("Unable to find transactionLog file containing transaction " + nextTransaction + ". Might have been manually deleted."); 149 if (nextTransaction > _nextTransaction) throw new IllegalStateException (); 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 , ClassNotFoundException { 160 long recoveringTransaction = initialLogFile; 161 File logFile = transactionLogFile(recoveringTransaction); 162 SimpleInputStream inputLog = null; 163 try { 164 inputLog = new SimpleInputStream(logFile); 165 } catch (EOFException 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 eof) { 180 File nextFile = transactionLogFile(recoveringTransaction); 181 if (logFile.equals(nextFile)) renameUnusedFile(logFile); logFile = nextFile; 183 if (!logFile.exists()) break; 184 try { 185 inputLog = new SimpleInputStream(logFile); 186 } catch (EOFException e) { 187 renameUnusedFile(logFile); 188 return recoveringTransaction; 189 } 190 } 191 } 192 return recoveringTransaction; 193 } 194 195 196 private void renameUnusedFile(File logFile) { 197 logFile.renameTo(new File (logFile.getAbsolutePath() + ".unusedFile" + System.currentTimeMillis())); 198 } 199 200 201 203 public boolean accept(File file) { 204 String name = file.getName(); 205 if (!name.endsWith(".transactionLog")) return false; 206 if (name.length() != 34) return false; 207 try { number(file); } catch (RuntimeException r) { return false; } 208 return true; 209 } 210 211 private File transactionLogFile(long transaction) { 212 String fileName = "0000000000000000000" + transaction; 213 fileName = fileName.substring(fileName.length() - 19) + ".transactionLog"; 214 return new File (_directory, fileName); 215 } 216 217 static private long number(File file) { 218 return Long.parseLong(file.getName().substring(0, 19)); 219 } 220 221 222 protected void handleExceptionWhileCreating(IOException iox, File 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 iox, File 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 iox, File 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 iox, String message) { 236 iox.printStackTrace(); 237 System.out.println(message); 238 while (true) Thread.yield(); 239 } 240 241 242 public void close() throws IOException { 243 if (_outputLog != null) _outputLog.close(); 244 } 245 246 } 247 | Popular Tags |