View Javadoc

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 }