package org.apache.commons.jcs.auxiliary.disk.indexed; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import org.apache.commons.jcs.engine.behavior.IElementSerializer; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** Provides thread safe access to the underlying random access file. */ class IndexedDisk { /** The size of the header that indicates the amount of data stored in an occupied block. */ public static final byte HEADER_SIZE_BYTES = 4; /** The serializer. */ private final IElementSerializer elementSerializer; /** The logger */ private static final Log log = LogFactory.getLog( IndexedDisk.class ); /** The path to the log directory. */ private final String filepath; /** The data file. */ private final FileChannel fc; /** * Constructor for the Disk object * <p> * @param file * @param elementSerializer * @throws FileNotFoundException */ public IndexedDisk( File file, IElementSerializer elementSerializer ) throws FileNotFoundException { this.filepath = file.getAbsolutePath(); this.elementSerializer = elementSerializer; RandomAccessFile raf = new RandomAccessFile( filepath, "rw" ); this.fc = raf.getChannel(); } /** * This reads an object from the given starting position on the file. * <p> * The first four bytes of the record should tell us how long it is. The data is read into a byte * array and then an object is constructed from the byte array. * <p> * @return Serializable * @param ded * @throws IOException * @throws ClassNotFoundException */ protected <T extends Serializable> T readObject( IndexedDiskElementDescriptor ded ) throws IOException, ClassNotFoundException { String message = null; boolean corrupted = false; long fileLength = fc.size(); if ( ded.pos > fileLength ) { corrupted = true; message = "Record " + ded + " starts past EOF."; } else { ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES); fc.read(datalength, ded.pos); datalength.flip(); int datalen = datalength.getInt(); if ( ded.len != datalen ) { corrupted = true; message = "Record " + ded + " does not match data length on disk (" + datalen + ")"; } else if ( ded.pos + ded.len > fileLength ) { corrupted = true; message = "Record " + ded + " exceeds file length."; } } if ( corrupted ) { log.warn( "\n The file is corrupt: " + "\n " + message ); throw new IOException( "The File Is Corrupt, need to reset" ); } ByteBuffer data = ByteBuffer.allocate(ded.len); fc.read(data, ded.pos + HEADER_SIZE_BYTES); data.flip(); return elementSerializer.deSerialize( data.array(), null ); } /** * Moves the data stored from one position to another. The descriptor's position is updated. * <p> * @param ded * @param newPosition * @throws IOException */ protected void move( final IndexedDiskElementDescriptor ded, final long newPosition ) throws IOException { ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES); fc.read(datalength, ded.pos); datalength.flip(); int length = datalength.getInt(); if ( length != ded.len ) { throw new IOException( "Mismatched memory and disk length (" + length + ") for " + ded ); } // TODO: more checks? long readPos = ded.pos; long writePos = newPosition; // header len + data len int remaining = HEADER_SIZE_BYTES + length; ByteBuffer buffer = ByteBuffer.allocate(16384); while ( remaining > 0 ) { // chunk it int chunkSize = Math.min( remaining, buffer.capacity() ); buffer.limit(chunkSize); fc.read(buffer, readPos); buffer.flip(); fc.write(buffer, writePos); buffer.clear(); writePos += chunkSize; readPos += chunkSize; remaining -= chunkSize; } ded.pos = newPosition; } /** * Writes the given byte array to the Disk at the specified position. * <p> * @param data * @param ded * @return true if we wrote successfully * @throws IOException */ protected boolean write( IndexedDiskElementDescriptor ded, byte[] data ) throws IOException { long pos = ded.pos; if ( log.isTraceEnabled() ) { log.trace( "write> pos=" + pos ); log.trace( fc + " -- data.length = " + data.length ); } if ( data.length != ded.len ) { throw new IOException( "Mismatched descriptor and data lengths" ); } ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE_BYTES + data.length); buffer.putInt(data.length); buffer.put(data); buffer.flip(); int written = fc.write(buffer, pos); //fc.force(true); return written == data.length; } /** * Serializes the object and write it out to the given position. * <p> * TODO: make this take a ded as well. * @return true unless error * @param obj * @param pos * @throws IOException */ protected boolean writeObject( Serializable obj, long pos ) throws IOException { byte[] data = elementSerializer.serialize( obj ); write( new IndexedDiskElementDescriptor( pos, data.length ), data ); return true; } /** * Returns the raf length. * <p> * @return the length of the file. * @throws IOException */ protected long length() throws IOException { return fc.size(); } /** * Closes the raf. * <p> * @throws IOException */ protected void close() throws IOException { fc.close(); } /** * Sets the raf to empty. * <p> * @throws IOException */ protected synchronized void reset() throws IOException { if ( log.isDebugEnabled() ) { log.debug( "Resetting Indexed File [" + filepath + "]" ); } fc.truncate(0); fc.force(true); } /** * Truncates the file to a given length. * <p> * @param length the new length of the file * @throws IOException */ protected void truncate( long length ) throws IOException { if ( log.isInfoEnabled() ) { log.info( "Truncating file [" + filepath + "] to " + length ); } fc.truncate( length ); } /** * This is used for debugging. * <p> * @return the file path. */ protected String getFilePath() { return filepath; } }