Clover coverage report - ActiveIO - 1.0
Coverage timestamp: Fri Apr 22 2005 14:27:22 PDT
file stats: LOC: 484   Methods: 24
NCLOC: 318   Classes: 1
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
LogFileManager.java 48.8% 66.7% 83.3% 62.8%
coverage coverage
 1   
 /**
 2   
  *
 3   
  * Copyright 2004 Hiram Chirino
 4   
  *
 5   
  * Licensed under the Apache License, Version 2.0 (the "License");
 6   
  * you may not use this file except in compliance with the License.
 7   
  * You may obtain a copy of the License at
 8   
  *
 9   
  * http://www.apache.org/licenses/LICENSE-2.0
 10   
  *
 11   
  * Unless required by applicable law or agreed to in writing, software
 12   
  * distributed under the License is distributed on an "AS IS" BASIS,
 13   
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14   
  * See the License for the specific language governing permissions and
 15   
  * limitations under the License.
 16   
  *
 17   
  **/
 18   
 package org.activeio.journal.active;
 19   
 
 20   
 import java.io.DataInput;
 21   
 import java.io.DataInputStream;
 22   
 import java.io.DataOutput;
 23   
 import java.io.DataOutputStream;
 24   
 import java.io.File;
 25   
 import java.io.IOException;
 26   
 import java.nio.ByteBuffer;
 27   
 import java.text.NumberFormat;
 28   
 
 29   
 import org.activeio.Packet;
 30   
 import org.activeio.adapter.PacketInputStream;
 31   
 import org.activeio.adapter.PacketOutputStream;
 32   
 import org.activeio.journal.InvalidRecordLocationException;
 33   
 import org.activeio.packet.ByteArrayPacket;
 34   
 import org.activeio.packet.ByteBufferPacket;
 35   
 
 36   
 import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt;
 37   
 
 38   
 /**
 39   
  * Provides a logical view of many seperate files as one single long log file.
 40   
  * The seperate files that compose the LogFile are Segements of the LogFile.
 41   
  * <p/>This class is not thread safe.
 42   
  * 
 43   
  * @version $Revision: 1.1 $
 44   
  */
 45   
 final public class LogFileManager {
 46   
 
 47   
     public static final int DEFAULT_LOGFILE_COUNT = Integer.parseInt(System.getProperty("org.activeio.journal.active.DefaultLogFileCount", ""+(2)));
 48   
     public static final int DEFAULT_LOGFILE_SIZE = Integer.parseInt(System.getProperty("org.activeio.journal.active.DefaultLogFileSize", ""+(1024*1024*20)));
 49   
 
 50   
     static final public int SERIALIZED_SIZE = 6+Location.SERIALIZED_SIZE;
 51   
 
 52   
     static final public byte DATA_RECORD_TYPE = 1;
 53   
     static final public byte MARK_RECORD_TYPE = 2;
 54   
     static final private NumberFormat onlineLogNameFormat = NumberFormat.getNumberInstance();
 55   
     static {
 56  4
         onlineLogNameFormat.setMinimumIntegerDigits(3);
 57  4
         onlineLogNameFormat.setMaximumIntegerDigits(3);
 58  4
         onlineLogNameFormat.setGroupingUsed(false);
 59  4
         onlineLogNameFormat.setParseIntegerOnly(true);
 60  4
         onlineLogNameFormat.setMaximumFractionDigits(0);
 61   
     }
 62   
 
 63   
     // Config
 64   
     private final File logDirectory;
 65   
     private final int initialLogFileSize;
 66   
     private final int onlineLogFileCount;
 67   
     private final SynchronizedInt activeLogFileCount = new SynchronizedInt(0);
 68   
     
 69   
     // Keeps track of the online log file.
 70   
     private LogFileNode firstNode;
 71   
     private LogFileNode firstActiveNode;
 72   
     private LogFileNode firstInactiveNode;
 73   
     private LogFileNode appendNode;
 74   
 
 75   
     private ControlFile controlFile;
 76   
     private int lastLogFileId = -1;
 77   
     private Location lastMark;
 78   
     private boolean disposed;
 79   
     private boolean loadedFromCleanShutDown;
 80   
 
 81   
 
 82  0
     public LogFileManager(File logDirectory) throws IOException {
 83  0
         this(logDirectory, DEFAULT_LOGFILE_COUNT, DEFAULT_LOGFILE_SIZE);
 84   
     }
 85   
 
 86  10
     public LogFileManager(File logDirectory, int onlineLogFileCount, int initialLogFileSize) throws IOException {
 87  10
         this.logDirectory = logDirectory;
 88  10
         this.onlineLogFileCount = onlineLogFileCount;
 89  10
         this.initialLogFileSize = initialLogFileSize;
 90  10
         initialize(onlineLogFileCount);
 91   
     }
 92   
 
 93  10
     void initialize(int onlineLogFileCount) throws IOException {
 94   
 
 95  10
         LogFileNode logFiles[] = new LogFileNode[onlineLogFileCount];
 96   
 
 97   
         // Create the log directory if it does not exist.
 98  10
         if (!logDirectory.exists()) {
 99  10
             if (!logDirectory.mkdirs()) {
 100  0
                 throw new IOException("Could not create directory: " + logDirectory);
 101   
             }
 102   
         }
 103   
 
 104   
         // Open the control file.        
 105  10
         int controlDataSize = SERIALIZED_SIZE + (LogFileNode.SERIALIZED_SIZE*onlineLogFileCount);
 106  10
         controlFile = new ControlFile(new File(logDirectory, "control.dat"),  controlDataSize);
 107   
         // Make sure we are the only process using the control file.
 108  10
         controlFile.lock();
 109   
         
 110   
         // Initialize the nodes.
 111  10
         for (int i = 0; i < onlineLogFileCount; i++) {
 112  40
             LogFile file = new LogFile(new File(logDirectory, "log-" + onlineLogNameFormat.format(i) + ".dat"),
 113   
                     initialLogFileSize);
 114  40
             logFiles[i] = new LogFileNode(file);
 115   
         }
 116   
 
 117   
         // Link the nodes together.
 118  10
         for (int i = 0; i < onlineLogFileCount; i++) {
 119  40
             if (i == (onlineLogFileCount - 1)) {
 120  10
                 logFiles[i].setNext(logFiles[0]);
 121   
             } else {
 122  30
                 logFiles[i].setNext(logFiles[i + 1]);
 123   
             }
 124   
         }
 125   
         
 126  10
         firstNode = logFiles[0];
 127  10
         loadState();
 128   
 
 129   
         // Find the first active node
 130  10
         for (int i = 0; i < onlineLogFileCount; i++) {
 131  40
             if( logFiles[i].isActive() ) {
 132  0
                 if( firstActiveNode == null || logFiles[i].getId() < firstActiveNode.getId() ) {
 133  0
                     firstActiveNode = logFiles[i];
 134   
                 }
 135   
             }
 136   
         }
 137   
         
 138   
         // None was active? activate one.
 139  10
         if ( firstActiveNode == null ) {
 140  10
             firstInactiveNode = logFiles[0];
 141  10
             activateNextLogFile();
 142   
         } else {            
 143   
             // Find the append log and the first inactive node
 144  0
             firstInactiveNode = null;
 145  0
             LogFileNode log = firstActiveNode;
 146  0
             do {
 147  0
                 if( !log.isActive() ) {
 148  0
                     firstInactiveNode = log;
 149  0
                     break;
 150   
                 } else {
 151  0
                     appendNode = log;
 152   
                 }
 153  0
                 log = log.getNext();
 154  0
             } while (log != firstActiveNode);
 155   
         }
 156   
         
 157   
         // If we did not have a clean shut down then we have to check the state 
 158   
         // of the append log.
 159  10
         if( !this.loadedFromCleanShutDown ) {
 160  10
             checkAppendLog();
 161   
         }
 162   
                     
 163  10
         loadedFromCleanShutDown = false;
 164  10
         storeState();
 165   
     }
 166   
 
 167  10
     private void checkAppendLog() throws IOException {
 168   
         
 169   
         // We are trying to get the true append offset and the last Mark that was written in 
 170   
         // the append log.
 171   
         
 172  10
         int offset = 0;
 173  10
         Record record = new Record();
 174  10
         LogFile logFile = appendNode.getLogFile();
 175  10
         Location markLocation=null;
 176   
         
 177  10
         while( logFile.loadAndCheckRecord(offset, record) ) {
 178   
             
 179  0
             if( record.getLocation().getLogFileId()!= appendNode.getId() || record.getLocation().getLogFileOffset()!=offset ) {
 180   
                 // We must have run past the end of the append location.
 181  0
                 break;
 182   
             }
 183   
 
 184  0
             if ( record.getRecordType()==LogFileManager.MARK_RECORD_TYPE) {
 185  0
                 markLocation = record.getLocation();
 186   
             }
 187   
             
 188  0
             offset += record.getRecordLength();            
 189   
         }
 190   
         
 191  10
         appendNode.setAppendOffset(offset);
 192   
         
 193  10
         if( markLocation!=null ) {
 194  0
             try {
 195  0
                 Packet packet = readPacket(markLocation);
 196  0
                 markLocation = Location.readFromPacket(packet);
 197   
             } catch (InvalidRecordLocationException e) {
 198  0
                 throw (IOException)new IOException(e.getMessage()).initCause(e);
 199   
             }
 200  0
             updateMark(markLocation);
 201   
         }
 202   
         
 203   
     }
 204   
 
 205  36
     private void storeState() throws IOException {
 206  36
         Packet controlData = controlFile.getControlData();
 207  36
         if( controlData.remaining() == 0 )
 208  0
             return;
 209   
         
 210  36
         DataOutput data = new DataOutputStream(new PacketOutputStream(controlData));
 211   
 
 212  36
         data.writeInt(lastLogFileId);
 213  36
         data.writeBoolean(lastMark!=null);
 214  36
         if( lastMark!=null )
 215  0
             lastMark.writeToDataOutput(data);
 216  36
         data.writeBoolean(loadedFromCleanShutDown);
 217   
         
 218   
         // Load each node's state
 219  36
         LogFileNode log = firstNode;
 220  36
         do {            
 221  144
             log.writeExternal( data );
 222  144
             log = log.getNext();
 223  144
         } while (log != firstNode);
 224   
         
 225  36
         controlFile.store();
 226   
     }
 227   
 
 228  10
     private void loadState() throws IOException {
 229  10
         if( controlFile.load() ) {
 230  0
             Packet controlData = controlFile.getControlData();
 231  0
             if( controlData.remaining() == 0 )
 232  0
                 return;
 233   
             
 234  0
             DataInput data = new DataInputStream(new PacketInputStream(controlData));
 235   
     
 236  0
             lastLogFileId =data.readInt();
 237  0
             if( data.readBoolean() )
 238  0
                 lastMark = Location.readFromDataInput(data);
 239   
             else
 240  0
                 lastMark = null;
 241  0
             loadedFromCleanShutDown = data.readBoolean();
 242   
     
 243   
             // Load each node's state
 244  0
             LogFileNode log = firstNode;
 245  0
             do {            
 246  0
                 log.readExternal( data );
 247  0
                 log = log.getNext();
 248  0
             } while (log != firstNode);
 249   
         }
 250   
     }
 251   
 
 252  10
     public void dispose() {
 253   
 
 254  10
         if (disposed)
 255  0
             return;
 256  10
         this.disposed = true;
 257   
         
 258  10
         try {
 259   
             // Close all the opened log files.
 260  10
             LogFileNode log = firstNode;
 261  10
             do {
 262  40
                 log.getLogFile().dispose();
 263  40
                 log = log.getNext();
 264  40
             } while (log != firstNode);
 265   
             
 266  10
             loadedFromCleanShutDown=true;
 267  10
             storeState();
 268  10
             controlFile.dispose();
 269   
         } catch ( IOException e ) {            
 270   
         }
 271   
         
 272   
     }
 273   
 
 274  16
     private int getNextLogFileId() {
 275  16
         return ++lastLogFileId;
 276   
     }
 277   
 
 278   
     /**
 279   
      * @param write
 280   
      * @throws IOException
 281   
      */
 282  20
     public void append(BatchedWrite write) throws IOException {
 283   
 
 284  20
         if (!appendNode.isActive())
 285  0
             throw new IllegalStateException("Log file is not active.  Writes are not allowed");
 286  20
         if (appendNode.isReadOnly())
 287  0
             throw new IllegalStateException("Log file has been marked Read Only.  Writes are not allowed");
 288   
 
 289   
         // Write and force the data to disk.
 290  20
         LogFile logFile = appendNode.getLogFile();
 291  20
         ByteBuffer buffer = ((ByteBufferPacket)write.getPacket().narrow(ByteBufferPacket.class)).getByteBuffer();
 292  20
         int size = buffer.remaining();
 293  20
         logFile.write(appendNode.getAppendOffset(), buffer);
 294  20
         if( write.getForce() )
 295  16
             logFile.force();
 296   
 
 297   
         // Update state
 298  20
         appendNode.appended(size);
 299  20
         if (write.getMark() != null) {
 300  0
             updateMark(write.getMark());
 301   
         }
 302   
     }
 303   
 
 304   
     /**
 305   
      * @param write
 306   
      * @throws IOException
 307   
      */
 308  0
     synchronized private void updateMark(Location mark) throws IOException {
 309   
         // If we wrote a mark we may need to deactivate some log files.
 310  0
         this.lastMark = mark;
 311  0
         while (firstActiveNode != appendNode) {
 312  0
             if (firstActiveNode.getId() < lastMark.getLogFileId()) {                
 313  0
                 firstActiveNode.setActive(false);
 314  0
                 activeLogFileCount.decrement();
 315  0
                 if( firstInactiveNode ==null )
 316  0
                     firstInactiveNode = firstActiveNode;
 317  0
                 firstActiveNode = firstActiveNode.getNextActive();
 318   
             } else {
 319  0
                 break;
 320   
             }
 321   
         }
 322   
     }
 323   
 
 324  30
     RecordInfo readRecordInfo(Location location) throws IOException, InvalidRecordLocationException {
 325   
 
 326  30
         LogFileNode logFileState = getLogFileWithId(location.getLogFileId());
 327   
         // There can be no record at the append offset.
 328  30
         if (logFileState.getAppendOffset() == location.getLogFileOffset()) {
 329  0
             throw new InvalidRecordLocationException("No record at (" + location
 330   
                     + ") found.  Location past end of logged data.");
 331   
         }
 332   
 
 333   
         // Is there a record header at the seeked location?
 334  30
         try {
 335  30
             LogFile logFile = logFileState.getLogFile();
 336  30
             Record header = new Record();
 337  30
             logFile.readRecordHeader(location.getLogFileOffset(), header);
 338  30
             return new RecordInfo(location, header, logFileState);
 339   
         } catch (IOException e) {
 340  0
             throw new InvalidRecordLocationException("No record at (" + location + ") found.");
 341   
         }
 342   
     }
 343   
 
 344  30
     LogFileNode getLogFileWithId(int logFileId) throws InvalidRecordLocationException {
 345  30
         for (LogFileNode lf = firstActiveNode; lf != null; lf = lf.getNextActive()) {
 346  30
             if (lf.getId() == logFileId) {
 347  30
                 return lf;
 348   
             }
 349   
 
 350   
             // Short cut since id's will only increment
 351  0
             if (logFileId < lf.getId())
 352  0
                 break;
 353   
         }
 354  0
         throw new InvalidRecordLocationException("Log file with id;" + logFileId + " is not active.");
 355   
     }
 356   
 
 357   
     /**
 358   
      * @param lastLocation
 359   
      * @return
 360   
      */
 361  10
     public Location getNextDataRecordLocation(Location lastLocation) throws IOException, InvalidRecordLocationException {
 362  10
         RecordInfo ri = readRecordInfo(lastLocation);
 363  10
         while (true) {
 364   
 
 365  10
             int logFileId = ri.getLocation().getLogFileId();
 366  10
             int offset = ri.getNextLocation();
 367   
 
 368   
             // Are we overflowing into next logFile?
 369  10
             if (offset >= ri.getLogFileState().getAppendOffset()) {
 370  2
                 LogFileNode nextActive = ri.getLogFileState().getNextActive();
 371  2
                 if (nextActive == null) {
 372  2
                     return null;
 373   
                 }
 374  0
                 logFileId = nextActive.getId();
 375  0
                 offset = 0;
 376   
             }
 377   
 
 378  8
             try {
 379  8
                 ri = readRecordInfo(new Location(logFileId, offset));
 380   
             } catch (InvalidRecordLocationException e) {
 381  0
                 return null;
 382   
             }
 383   
 
 384   
             // Is the next record the right record type?
 385  8
             if (ri.getHeader().getRecordType() == DATA_RECORD_TYPE) {
 386  8
                 return ri.getLocation();
 387   
             }
 388   
             // No? go onto the next record.
 389   
         }
 390   
     }
 391   
 
 392   
     /**
 393   
      * @param logFileIndex
 394   
      * @param logFileOffset
 395   
      * @return
 396   
      * @throws IOException
 397   
      * @throws InvalidRecordLocationException
 398   
      */
 399  12
     public Packet readPacket(Location location) throws IOException, InvalidRecordLocationException {
 400   
 
 401   
         // Is there a record header at the seeked location?
 402  12
         RecordInfo recordInfo = readRecordInfo(location);
 403   
 
 404  12
         byte data[] = new byte[recordInfo.getHeader().getPayloadLength()];
 405   
 
 406  12
         LogFile logFile = recordInfo.getLogFileState().getLogFile();
 407  12
         logFile.read(recordInfo.getDataOffset(), data);
 408   
 
 409  12
         return new ByteArrayPacket(data);
 410   
 
 411   
     }
 412   
 
 413  6
     public int getInitialLogFileSize() {
 414  6
         return initialLogFileSize;
 415   
     }
 416   
 
 417  4
     public Location getFirstActiveLogLocation() {
 418  4
         if (firstActiveNode == null)
 419  0
             return null;
 420  4
         if (firstActiveNode.getAppendOffset() == 0)
 421  2
             return null;
 422  2
         return new Location(firstActiveNode.getId(), 0);
 423   
     }
 424   
 
 425  16
     void activateNextLogFile() throws IOException {
 426   
 
 427   
         // The current append logFile becomes readonly
 428  16
         if (appendNode != null) {
 429  6
             appendNode.setReadOnly(true);
 430   
         }
 431   
 
 432  16
         LogFileNode next = firstInactiveNode;
 433  16
         synchronized (this) {
 434  16
             firstInactiveNode = firstInactiveNode.getNextInactive();
 435  16
             next.activate(getNextLogFileId());
 436  16
             if (firstActiveNode == null) {
 437  10
                 firstActiveNode = next;
 438   
             }
 439   
         }        
 440  16
         activeLogFileCount.increment();        
 441  16
         appendNode = next;
 442   
         
 443  16
         storeState();
 444   
     }
 445   
 
 446   
     /**
 447   
      * @return Returns the logDirectory.
 448   
      */
 449  2
     public File getLogDirectory() {
 450  2
         return logDirectory;
 451   
     }
 452   
 
 453   
     /**
 454   
      * @return Returns the lastMark.
 455   
      */
 456  6
     public Location getLastMarkedRecordLocation() {
 457  6
         return lastMark;
 458   
     }
 459   
 
 460  40
     public Location getNextAppendLocation() {
 461  40
         return new Location(appendNode.getId(), appendNode.getAppendOffset());
 462   
     }
 463   
 
 464   
     /**
 465   
      * @return Returns the onlineLogFileCount.
 466   
      */
 467  2
     public int getOnlineLogFileCount() {
 468  2
         return onlineLogFileCount;
 469   
     }
 470   
 
 471  0
     public boolean isPastHalfActive() {
 472  0
         return (onlineLogFileCount/2.f) < activeLogFileCount.get();
 473   
     }
 474   
 
 475  0
     synchronized  public Location getFirstRecordLocationOfSecondActiveLogFile() {
 476  0
         return firstActiveNode.getNextActive().getFirstRecordLocation();
 477   
     }
 478   
 
 479  10
     synchronized public boolean canActivateNextLogFile() {
 480  10
         return firstInactiveNode!=null;
 481   
     }
 482   
 
 483   
 }
 484