package org.csc.phynixx.loggersystem.logger.channellogger;
/*
* #%L
* phynixx-common
* %%
* Copyright (C) 2014 csc
* %%
* Licensed 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.
* #L%
*/
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.concurrent.TimeoutException;
import org.csc.phynixx.common.exceptions.DelegatedRuntimeException;
import org.csc.phynixx.common.logger.IPhynixxLogger;
import org.csc.phynixx.common.logger.PhynixxLogManager;
import org.csc.phynixx.loggersystem.logger.channellogger.lock.FileChannelLockManager;
import org.csc.phynixx.loggersystem.logger.channellogger.lock.IAccessGuard;
/**
*
* A TAEnabledRandomAccessFile provides random access to the file's content and let you append data to the current.
* It provides a simple but efficient atomic write.
* The first long of the file contains the committed size. Any content beyond the file pointer is ignored.
* To write bytes needs to operations. First these bytes are appended to the file.
* What is the second operation is to update the new commit position.
*
* The committed size is the size of the written and committed data.
*
* The visible size of the file contains the written data. It starts from the end of the header data and has the size of {@link #getCommittedSize()}
* Data beyond this range cannot be read and is not available.
*
* <pre>
* |--------|----................---------|------
* 0 start of committed size
* visible data + header size
*
* </pre>
*
*
* @see java.nio.channels.FileChannel
* @see java.io.RandomAccessFile
* @see java.nio.channels.FileLock
*
*/
public class TAEnabledRandomAccessFile {
/**
* Header size. As the header contains the commited size (of type long) the header size is 8
*/
public static final int HEADER_LENGTH = (Long.SIZE / Byte.SIZE);
/**
* Groesse eines Bytes
*/
public static final int BYTE_BYTES = 1;
/**
* max. Inhalt eines Byte als int *
*/
public static final int MAX_BYTE_VALUE = Byte.MAX_VALUE;
private static final IPhynixxLogger LOG = PhynixxLogManager.getLogger(TAEnabledRandomAccessFile.class);
/**
* Das RandomAccessFile, dass zum Schreiben u. Lesen geoeffnet wird.
*/
private RandomAccessFile raf = null;
private File file;
private IAccessGuard fileLock = null;
/**
* redundant committed size to achieve perform range checks
*/
private long committedSize=0;
/**
* Initialisierungs-Methode
* @param file TODO
* @param raf - RandomAccessFile
*
* @throws IOException
*/
TAEnabledRandomAccessFile(File file, RandomAccessFile raf) {
this.file=file;
this.raf = raf;
try {
fileLock = acquireFileLock(file,raf);
this.restoreCommittedSize();
check();
} catch (Exception e) {
closeQuitely();
throw new DelegatedRuntimeException(e);
}
}
private void closeQuitely() {
try {
this.close();
} catch (Exception e) {}
}
private IAccessGuard acquireFileLock(File file, RandomAccessFile raf) throws IOException, InterruptedException, TimeoutException {
IAccessGuard lock = FileChannelLockManager.lock(file, raf);
lock.acquire();
return lock;
}
/**
* liefert die Groesse des Headerbereiches
*
* @return Groesse des Header
*/
public static int getHeaderLength() {
return HEADER_LENGTH;
}
/**
* Gibt das RandomAccessFile zurueck
*
* @return RandomAccessFile
*/
RandomAccessFile getRandomAccessFile() {
check();
return this.raf;
}
/**
* ueberpueft die Gueltigkeit eine Dateiposition bzgl. des Nutzbereichs
* wird in position gerufen.
*
* @param pos Position die auf Gueltigkeit bzgl. des Nutzbereichs
* ueberprueft werden soll
* @throws IllegalArgumentException position ist nicht zuleassig
*/
private void checkPosition(long pos) {
if (pos < 0) {
throw new IllegalArgumentException("Uebergebene Position (=" + pos + ") darf nicht kleiner als 0 sein");
}
}
/**
* @return byte available starting from the current position to the visible end of the file (==committed size)
* @throws IOException
*/
public long available() throws IOException {
check();
return this.getCommittedSize() - this.position();
}
/**
* @return current position starting from the end of the header data.
* @throws java.io.IOException , IO Error
* @see
*/
long position() throws IOException {
check();
return this.raf.getFilePointer() - getHeaderLength();
}
private void check() {
if (isClose()) {
throw new IllegalStateException("TAEnabledRandomAccessFile is closed");
}
if( this.fileLock==null || !fileLock.isValid()) {
throw new IllegalStateException("Filelock is not valid");
}
}
/**
* Setzt Bereich der Nutzdaten auf die angegebenen Position.
* Evtl. HeaderBrereiche werden ignoriert
*
* @param newPosition Position
* @throws java.io.IOException IO Error
* @author Phynixx
*/
private void position(long newPosition) throws IOException {
check();
checkPosition(newPosition);
this.getRandomAccessFile().seek(newPosition + getHeaderLength());
}
/**
* Schliesst die Datei und den FileChannel
*
* @throws java.io.IOException , wenn das Schliessen schief geht.
*/
public void close() throws IOException {
if (raf != null) {
// gibt Lock auf datei frei
try {
if (this.fileLock != null) {
if( !fileLock.release()) {
LOG.error("Filelock not release properly");
}
} else {
LOG.error("No Filelock set");
}
} finally {
// Schliessen der Daten-Datei
this.fileLock = null;
raf.close();
raf = null;
}
}
}
/**
* zeigt an, ob die Instanz geschlossen ist
*
* @return true wenn die Datei geschlossen ist
*/
public boolean isClose() {
return (this.raf == null);
}
/**
*
*
* Gibt die Groesse des Comitteten Bereichs in Byte zurueck. Da am Anfang
* der Datei die Dateigroesse geschrieben wird, ist die Mindest-Groesse 4.
* Bei 4 Byte sind also noch keine Daten in die Datei geschrieben worden.
*
* @return commited size (bytes)
* @throws java.io.IOException IO-Error
*/
public long getCommittedSize() throws IOException {
check();
return this.committedSize;
}
/**
*
* reads the next INTEGER starting form the current file position.
* If there are not enough bytes available (from the current position to the visible end of the file) to read the data an exception is thrown.
*
* @return read value
* @throws java.io.IOException IO Error
* @author Schmidt-Casdorff
*/
public int readInt() throws IOException {
check();
checkRead(4);
return raf.readInt();
}
/**
*
* reads the next SHORT starting form the current file position.
* If there are not enough bytes available (from the current position to the visible end of the file) to read the data an exception is thrown.
*
* @return read value
* @throws java.io.IOException IO Error
* @author Schmidt-Casdorff
*/
public short readShort() throws IOException {
check();
checkRead(2);
return raf.readShort();
}
/**
*
* reads the next LONG starting form the current file position.
* If there are not enough bytes available (from the current position to the visible end of the file) to read the data an exception is thrown.
*
* @return read value
* @throws java.io.IOException IO Error
* @author Schmidt-Casdorff
*/
public long readLong() throws IOException {
check();
checkRead(8);
return raf.readLong();
}
/**
* reads the next length bytes starting form the current file position.
* If there are not enough bytes available (from the current position to the visible end of the file) to read the data an exception is thrown.
*
* overflow (position is beyond committed data), is
* kopiert den Bereich zwischen startPosition und enedPosition der Datei in den ByteBuffer.
* Es wird die Read-methode des RandomAccessFiles genommen, da sich
* herausgestellt hat, dass die Methode im Batchbetrieb etwas schneller
* ist als die Channel-methode.
* Ee werden bytes gelesen fuer die gilt:
* startPosition ≤ b < endPosition
*
* @param length umber of bytes to be read
* @return content
* @throws java.io.IOException IO Error
*/
public byte[] read(int length) throws IOException {
check();
checkRead(length);
if (length >= Integer.MAX_VALUE) {
throw new IOException("Length of read area may not exceed " + Integer.MAX_VALUE);
}
if (this.position()+length > this.getCommittedSize()) {
throw new IOException("Length of read area may not exceed the committed size of " + this.getCommittedSize());
}
int intLength = Long.valueOf(length).intValue();
byte[] buffer = new byte[intLength];
if (isClose()) {
throw new IOException("TAEnabledChanneList ist closed");
}
long retVal = this.getRandomAccessFile().read(buffer, 0, intLength);
if (retVal < 0) {
throw new IOException("Channel cannot read " + (position() + intLength) );
}
return buffer;
}
private void checkRead(int length) throws IOException {
if( this.available() < length) {
throw new IOException("Cannot read "+length +" byte. Starting from current position "+position()+" there are only "+this.available()+" bytes available");
}
}
/**
* appends a byte[] to the file, but doesn't commit.
*
* In order to re-read the content it is recommended to store the size of the byte[] before writing the byte[]. This value can be used as the value of parameter length.
*
* @param buffer value to be appended
* @throws java.io.IOException IO Error
*
* @see #read(int)
*/
public void write(byte[] buffer) throws IOException {
check();
//assert buffer != null : "Der Buffer ist null";
if (isClose()) {
throw new IOException("TAEnabledChanneList is closed");
}
long currentPosition = this.position();
this.raf.write(buffer);
// this.incPosition(buffer.length);
assert this.position() - currentPosition == buffer.length : "Expected new position : " + currentPosition + buffer.length + " actual position " + this.position();
}
/**
* appends a SHORT to the file, but doesn't commit
*
* @param value value to be appended
* @throws java.io.IOException IO Error
*/
public void writeShort(short value) throws IOException {
check();
// increments the current position
getRandomAccessFile().writeShort(value);
}
/**
* appends an INT to the file, but doesn't commit
*
* @param value value to be appended
* @throws java.io.IOException IO Error
*/
public void writeInt(int value) throws IOException {
check();
// increments the current position
getRandomAccessFile().writeInt(value);
}
/**
* appends a LONG to the file, but doesn't commit
*
* @param value value to be appended
* @throws java.io.IOException IO Error
*/
public void writeLong(long value) throws IOException {
check();
// increments the current position
getRandomAccessFile().writeLong(value);
}
/**
* sets the current position to the start of the data. (position()==0)
* @throws IOException
*/
public void rewind() throws IOException {
check();
this.position(0);
}
/**
* sets the current position to end of the visible data. (position()==getCommittedSize())
* @throws IOException
*/
public void forwardWind() throws IOException {
check();
this.position(this.getCommittedSize());
}
/**
* updates the committed size with 0. All data is truncated
* @throws IOException
*/
public void reset() throws IOException {
check();
this.position(0);
this.commit();
// forget about the rest
this.getRandomAccessFile().getChannel().truncate(TAEnabledRandomAccessFile.HEADER_LENGTH);
}
/**
*
* updates the first 4 bytes of the file with the current position of the randomAccessFile
*
* @throws java.io.IOException Error updating the file
*/
public void commit() throws IOException {
check();
long currentPosition = this.position();
// go to the header in order to be prepared to update the committed size
this.getRandomAccessFile().seek(0);
// update the commiited data with the current position
getRandomAccessFile().writeLong(currentPosition);
// reset the file position to the original value
this.position(currentPosition);
// write through
this.getRandomAccessFile().getChannel().force(false);
this.committedSize= currentPosition;
}
/**
* restores the committed size recored in the first 4 bytes of the file. *
* the randomaccessFile is positioned to this position.
*
* The content beyond this new position is not destroyed but will be be overwritten,
*
* @throws java.io.IOException IO-Error
* @see
*/
private void restoreCommittedSize() throws IOException {
check();
long privCommittedSize=-1;
this.getRandomAccessFile().seek(0);
if (this.raf.length() < HEADER_LENGTH) {
this.getRandomAccessFile().writeLong(0);
privCommittedSize=0;
} else {
privCommittedSize = this.getRandomAccessFile().readLong();
}
this.position(privCommittedSize);
this.committedSize=privCommittedSize;
}
}