1 package org.csc.phynixx.loggersystem.logger.channellogger;
2
3 /*
4 * #%L
5 * phynixx-common
6 * %%
7 * Copyright (C) 2014 csc
8 * %%
9 * Licensed under the Apache License, Version 2.0 (the "License");
10 * you may not use this file except in compliance with the License.
11 * You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing, software
16 * distributed under the License is distributed on an "AS IS" BASIS,
17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 * See the License for the specific language governing permissions and
19 * limitations under the License.
20 * #L%
21 */
22
23
24 import org.csc.phynixx.common.logger.IPhynixxLogger;
25 import org.csc.phynixx.common.logger.PhynixxLogManager;
26
27 import java.io.IOException;
28 import java.io.RandomAccessFile;
29 import java.nio.channels.FileLock;
30
31 /**
32 *
33 * A TAEnabledRandomAccessFile provides random access to the file's content and let you append data to the current.
34 * It provides a simple but efficient atomic write.
35 * The first long of the file contains the committed size. Any content beyond the file pointer is ignored.
36 * To write bytes needs to operations. First these bytes are appended to the file.
37 * What is the second operation is to update the new commit position.
38 *
39 * The committed size is the size of the written and committed data.
40 *
41 * 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()}
42 * Data beyond this range cannot be read and is not available.
43 *
44 * <pre>
45 * |--------|----................---------|------
46 * 0 start of committed size
47 * visible data + header size
48 *
49 * </pre>
50 *
51 *
52 * @see java.nio.channels.FileChannel
53 * @see java.io.RandomAccessFile
54 * @see java.nio.channels.FileLock
55 *
56 */
57 class TAEnabledRandomAccessFile {
58
59
60 /**
61 * Header size. As the header contains the commited size (of type long) the header size is 8
62 */
63 public static final int HEADER_LENGTH = (Long.SIZE / Byte.SIZE);
64 /**
65 * Groesse eines Bytes
66 */
67
68 public static final int BYTE_BYTES = 1;
69
70 /**
71 * max. Inhalt eines Byte als int *
72 */
73 public static final int MAX_BYTE_VALUE = (int) Byte.MAX_VALUE;
74
75 private static final IPhynixxLogger LOG = PhynixxLogManager.getLogger(TAEnabledRandomAccessFile.class);
76 /**
77 * Das RandomAccessFile, dass zum Schreiben u. Lesen geoeffnet wird.
78 */
79 private RandomAccessFile raf = null;
80
81
82 private FileLock fileLock = null;
83
84
85 /**
86 * redundant committed size to achieve perform range checks
87 */
88 private long committedSize=0;
89
90
91 /**
92 * Initialisierungs-Methode
93 *
94 * @param raf - RandomAccessFile
95 * @throws IOException
96 */
97 TAEnabledRandomAccessFile(RandomAccessFile raf) throws IOException {
98 this.raf = raf;
99 fileLock = acquireFileLock(raf);
100 this.restoreCommittedSize();
101 check();
102 }
103
104 private FileLock acquireFileLock(RandomAccessFile raf) throws IOException {
105 return raf.getChannel().lock(0,HEADER_LENGTH, false);
106 }
107
108 /**
109 * liefert die Groesse des Headerbereiches
110 *
111 * @return Groesse des Header
112 */
113 public static int getHeaderLength() {
114 return HEADER_LENGTH;
115 }
116
117 /**
118 * Gibt das RandomAccessFile zurueck
119 *
120 * @return RandomAccessFile
121 */
122 RandomAccessFile getRandomAccessFile() {
123 check();
124 return this.raf;
125 }
126
127 /**
128 * ueberpueft die Gueltigkeit eine Dateiposition bzgl. des Nutzbereichs
129 * wird in position gerufen.
130 *
131 * @param pos Position die auf Gueltigkeit bzgl. des Nutzbereichs
132 * ueberprueft werden soll
133 * @throws IllegalArgumentException position ist nicht zuleassig
134 */
135 private void checkPosition(long pos) {
136 if (pos < 0) {
137 throw new IllegalArgumentException("Uebergebene Position (=" + pos + ") darf nicht kleiner als 0 sein");
138 }
139 }
140
141
142 /**
143 * @return byte available starting from the current position to the visible end of the file (==committed size)
144 * @throws IOException
145 */
146 public long available() throws IOException {
147 check();
148 return this.getCommittedSize() - this.position();
149 }
150
151
152 /**
153 * @return current position starting from the end of the header data.
154 * @throws java.io.IOException , IO Error
155 * @see
156 */
157 long position() throws IOException {
158 check();
159 return this.raf.getFilePointer() - getHeaderLength();
160 }
161
162 private void check() {
163 if (isClose()) {
164 throw new IllegalStateException("TAEnabledRandomAccessFile is closed");
165 }
166
167 if( this.fileLock==null || !fileLock.isValid()) {
168 throw new IllegalStateException("Filelock is not valid");
169 }
170 }
171
172
173 /**
174 * Setzt Bereich der Nutzdaten auf die angegebenen Position.
175 * Evtl. HeaderBrereiche werden ignoriert
176 *
177 * @param newPosition Position
178 * @throws java.io.IOException IO Error
179 * @author Phynixx
180 */
181 private void position(long newPosition) throws IOException {
182
183 check();
184 checkPosition(newPosition);
185 this.getRandomAccessFile().seek(newPosition + getHeaderLength());
186 }
187
188 /**
189 * Schliesst die Datei und den FileChannel
190 *
191 * @throws java.io.IOException , wenn das Schliessen schief geht.
192 */
193 public void close() throws IOException {
194 if (raf != null) {
195 // gibt Lock auf datei frei
196 try {
197 if (this.fileLock != null) {
198 if( fileLock.isValid()) {
199 this.fileLock.release();
200 } else {
201 LOG.error("Filelock not valid");
202 }
203 } else {
204 LOG.error("Kein Filelock gesetzt");
205 }
206 } finally {
207 // Schliessen der Daten-Datei
208 this.fileLock = null;
209 raf.close();
210 raf = null;
211 }
212 }
213 }
214
215 /**
216 * zeigt an, ob die Instanz geschlossen ist
217 *
218 * @return true wenn die Datei geschlossen ist
219 */
220 public boolean isClose() {
221 return (this.raf == null);
222 }
223
224 /**
225 *
226 *
227 * Gibt die Groesse des Comitteten Bereichs in Byte zurueck. Da am Anfang
228 * der Datei die Dateigroesse geschrieben wird, ist die Mindest-Groesse 4.
229 * Bei 4 Byte sind also noch keine Daten in die Datei geschrieben worden.
230 *
231 * @return commited size (bytes)
232 * @throws java.io.IOException IO-Error
233 */
234 public long getCommittedSize() throws IOException {
235 check();
236
237 return this.committedSize;
238
239 }
240
241
242 /**
243 *
244 * reads the next INTEGER starting form the current file position.
245 * 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.
246 *
247 * @return read value
248 * @throws java.io.IOException IO Error
249 * @author Schmidt-Casdorff
250 */
251 public int readInt() throws IOException {
252 check();
253 checkRead(4);
254 return raf.readInt();
255 }
256
257 /**
258 *
259 * reads the next SHORT starting form the current file position.
260 * 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.
261 *
262 * @return read value
263 * @throws java.io.IOException IO Error
264 * @author Schmidt-Casdorff
265 */
266 public short readShort() throws IOException {
267 check();
268 checkRead(2);
269 return raf.readShort();
270 }
271
272 /**
273 *
274 * reads the next LONG starting form the current file position.
275 * 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.
276 *
277 * @return read value
278 * @throws java.io.IOException IO Error
279 * @author Schmidt-Casdorff
280 */
281 public long readLong() throws IOException {
282 check();
283 checkRead(8);
284 return raf.readLong();
285 }
286
287 /**
288 * reads the next length bytes starting form the current file position.
289 * 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.
290 *
291 * overflow (position is beyond committed data), is
292 * kopiert den Bereich zwischen startPosition und enedPosition der Datei in den ByteBuffer.
293 * Es wird die Read-methode des RandomAccessFiles genommen, da sich
294 * herausgestellt hat, dass die Methode im Batchbetrieb etwas schneller
295 * ist als die Channel-methode.
296 * Ee werden bytes gelesen fuer die gilt: <br>
297 * startPosition <= b < endPosition
298 *
299 * @param length umber of bytes to be read
300 * @return content
301 * @throws java.io.IOException IO Error
302 */
303 public byte[] read(int length) throws IOException {
304 check();
305
306 checkRead(length);
307
308 if (length >= Integer.MAX_VALUE) {
309 throw new IOException("Length of read area may not exceed " + Integer.MAX_VALUE);
310 }
311
312 if (this.position()+length > this.getCommittedSize()) {
313 throw new IOException("Length of read area may not exceed the committed size of " + this.getCommittedSize());
314 }
315
316 int intLength = Long.valueOf(length).intValue();
317
318 byte[] buffer = new byte[intLength];
319
320 if (isClose()) {
321 throw new IOException("TAEnabledChanneList ist closed");
322 }
323
324 long retVal = this.getRandomAccessFile().read(buffer, 0, intLength);
325
326 if (retVal < 0) {
327 throw new IOException("Channel cannot read " + (position() + intLength) );
328 }
329 return buffer;
330 }
331
332 private void checkRead(int length) throws IOException {
333 if( this.available() < length) {
334 throw new IOException("Cannot read "+length +" byte. Starting from current position "+position()+" there are only "+this.available()+" bytes available");
335 }
336 }
337
338
339 /**
340 * appends a byte[] to the file, but doesn't commit.
341 *
342 * 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.
343 *
344 * @param buffer value to be appended
345 * @throws java.io.IOException IO Error
346 *
347 * @see #read(int)
348 */
349 public void write(byte[] buffer) throws IOException {
350 check();
351
352 //assert buffer != null : "Der Buffer ist null";
353 if (isClose()) {
354 throw new IOException("TAEnabledChanneList is closed");
355 }
356 long currentPosition = this.position();
357 this.raf.write(buffer);
358 // this.incPosition(buffer.length);
359
360 assert this.position() - currentPosition == buffer.length : "Expected new position : " + currentPosition + buffer.length + " actual position " + this.position();
361
362
363 }
364
365 /**
366 * appends a SHORT to the file, but doesn't commit
367 *
368 * @param value value to be appended
369 * @throws java.io.IOException IO Error
370 */
371 public void writeShort(short value) throws IOException {
372 check();
373 // increments the current position
374 getRandomAccessFile().writeShort(value);
375 }
376
377 /**
378 * appends an INT to the file, but doesn't commit
379 *
380 * @param value value to be appended
381 * @throws java.io.IOException IO Error
382 */
383 public void writeInt(int value) throws IOException {
384 check();
385 // increments the current position
386 getRandomAccessFile().writeInt(value);
387
388 }
389
390
391 /**
392 * appends a LONG to the file, but doesn't commit
393 *
394 * @param value value to be appended
395 * @throws java.io.IOException IO Error
396 */
397 public void writeLong(long value) throws IOException {
398 check();
399 // increments the current position
400 getRandomAccessFile().writeLong(value);
401
402 }
403
404 /**
405 * sets the current position to the start of the data. (position()==0)
406 * @throws IOException
407 */
408 public void rewind() throws IOException {
409 check();
410 this.position(0);
411
412 }
413
414 /**
415 * sets the current position to end of the visible data. (position()==getCommittedSize())
416 * @throws IOException
417 */
418 public void forwardWind() throws IOException {
419 check();
420 this.position(this.getCommittedSize());
421
422 }
423
424
425
426 /**
427 * updates the committed size with 0. All data is truncated
428 * @throws IOException
429 */
430 public void reset() throws IOException {
431 check();
432
433 this.position(0);
434
435 this.commit();
436
437 // forget about the rest
438 this.getRandomAccessFile().getChannel().truncate(TAEnabledRandomAccessFile.HEADER_LENGTH);
439
440 }
441
442
443 /**
444 *
445 * updates the first 4 bytes of the file with the current position of the randomAccessFile
446 *
447 * @throws java.io.IOException Error updating the file
448 */
449 public void commit() throws IOException {
450 check();
451 long currentPosition = this.position();
452
453 // go to the header in order to be prepared to update the committed size
454 this.getRandomAccessFile().seek(0);
455
456 // update the commiited data with the current position
457 getRandomAccessFile().writeLong(currentPosition);
458
459
460 // reset the file position to the original value
461 this.position(currentPosition);
462
463 // write through
464 this.getRandomAccessFile().getChannel().force(false);
465
466 this.committedSize= currentPosition;
467
468 }
469
470 /**
471 * restores the committed size recored in the first 4 bytes of the file. *
472 * the randomaccessFile is positioned to this position.
473 *
474 * The content beyond this new position is not destroyed but will be be overwritten,
475 *
476 * @throws java.io.IOException IO-Error
477 * @see
478 */
479 private void restoreCommittedSize() throws IOException {
480
481 check();
482 long privCommittedSize=-1;
483 this.getRandomAccessFile().seek(0);
484 if (this.raf.length() < HEADER_LENGTH) {
485 this.getRandomAccessFile().writeLong(0);
486 privCommittedSize=0;
487 } else {
488 privCommittedSize = this.getRandomAccessFile().readLong();
489 }
490 this.position(privCommittedSize);
491 this.committedSize=privCommittedSize;
492 }
493
494
495
496
497 }