View Javadoc

1   package org.apache.jcs.auxiliary.disk.indexed;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.RandomAccessFile;
26  import java.io.Serializable;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.jcs.utils.serialization.StandardSerializer;
31  
32  /***
33   * Provides thread safe access to the underlying random access file.
34   */
35  class IndexedDisk
36  {
37      /***
38       * The size of the header in bytes. The header describes the length of the entry.
39       */
40      public static final int RECORD_HEADER = 4;
41  
42      private static final StandardSerializer SERIALIZER = new StandardSerializer();
43  
44      private static final Log log = LogFactory.getLog( IndexedDisk.class );
45  
46      private final String filepath;
47  
48      private RandomAccessFile raf;
49  
50      private final byte[] buffer = new byte[16384]; // 16K
51  
52      /***
53       * Constructor for the Disk object
54       * <p>
55       * @param file
56       * @exception FileNotFoundException
57       */
58      public IndexedDisk( File file )
59          throws FileNotFoundException
60      {
61          this.filepath = file.getAbsolutePath();
62          raf = new RandomAccessFile( filepath, "rw" );
63      }
64  
65      /***
66       * This reads an object from the given starting position on the file.
67       * <p>
68       * The first four bytes of the record should tell us how long it is. The data is read into a byte
69       * array and then an object is constructed from the byte array.
70       * <p>
71       * @return Serializable
72       * @param ded
73       * @throws IOException
74       * @throws ClassNotFoundException
75       */
76      protected Serializable readObject( IndexedDiskElementDescriptor ded )
77          throws IOException, ClassNotFoundException
78      {
79          byte[] data = null;
80          synchronized ( this )
81          {
82              String message = null;
83              boolean corrupted = false;
84              long fileLength = raf.length();
85              if ( ded.pos > fileLength )
86              {
87                  corrupted = true;
88                  message = "Record " + ded + " starts past EOF.";
89              }
90              else
91              {
92                  raf.seek( ded.pos );
93                  int datalen = raf.readInt();
94                  if ( ded.len != datalen )
95                  {
96                      corrupted = true;
97                      message = "Record " + ded + " does not match data length on disk (" + datalen + ")";
98                  }
99                  else if ( ded.pos + ded.len > fileLength )
100                 {
101                     corrupted = true;
102                     message = "Record " + ded + " exceeds file length.";
103                 }
104             }
105 
106             if ( corrupted )
107             {
108                 log.warn( "\n The file is corrupt: " + "\n " + message );
109                 throw new IOException( "The File Is Corrupt, need to reset" );
110             }
111 
112             raf.readFully( data = new byte[ded.len] );
113         }
114 
115         return (Serializable) SERIALIZER.deSerialize( data );
116     }
117 
118     /***
119      * Moves the data stored from one position to another. The descriptor's position is updated.
120      * <p>
121      * @param ded
122      * @param newPosition
123      * @throws IOException
124      */
125     protected void move( final IndexedDiskElementDescriptor ded, final long newPosition )
126         throws IOException
127     {
128         synchronized ( this )
129         {
130             raf.seek( ded.pos );
131             int length = raf.readInt();
132 
133             if ( length != ded.len )
134             {
135                 throw new IOException( "Mismatched memory and disk length (" + length + ") for " + ded );
136             }
137 
138             // TODO: more checks?
139 
140             long readPos = ded.pos;
141             long writePos = newPosition;
142 
143             // header len + data len
144             int remaining = RECORD_HEADER + length;
145 
146             while ( remaining > 0 )
147             {
148                 // chunk it
149                 int chunkSize = Math.min( remaining, buffer.length );
150                 raf.seek( readPos );
151                 raf.readFully( buffer, 0, chunkSize );
152 
153                 raf.seek( writePos );
154                 raf.write( buffer, 0, chunkSize );
155 
156                 writePos += chunkSize;
157                 readPos += chunkSize;
158                 remaining -= chunkSize;
159             }
160 
161             ded.pos = newPosition;
162         }
163     }
164 
165     /***
166      * Writes the given byte array to the Disk at the specified position.
167      * <p>
168      * @param data
169      * @param ded
170      * @return true if we wrote successfully
171      * @throws IOException
172      */
173     protected boolean write( IndexedDiskElementDescriptor ded, byte[] data )
174         throws IOException
175     {
176         long pos = ded.pos;
177         if ( log.isTraceEnabled() )
178         {
179             log.trace( "write> pos=" + pos );
180             log.trace( raf + " -- data.length = " + data.length );
181         }
182 
183         if ( data.length != ded.len )
184         {
185             throw new IOException( "Mismatched descriptor and data lengths" );
186         }
187 
188         synchronized ( this )
189         {
190             raf.seek( pos );
191             raf.writeInt( data.length );
192             raf.write( data, 0, ded.len );
193         }
194         return true;
195     }
196 
197     /***
198      * Serializes the object and write it out to the given position.
199      * <p>
200      * TODO: make this take a ded as well.
201      * @return
202      * @param obj
203      * @param pos
204      * @throws IOException
205      */
206     protected boolean writeObject( Serializable obj, long pos )
207         throws IOException
208     {
209         byte[] data = SERIALIZER.serialize( obj );
210         write( new IndexedDiskElementDescriptor( pos, data.length ), data );
211         return true;
212     }
213 
214     /***
215      * Returns the raf length.
216      * <p>
217      * @return
218      * @exception IOException
219      */
220     protected long length()
221         throws IOException
222     {
223         synchronized ( this )
224         {
225             return raf.length();
226         }
227     }
228 
229     /***
230      * Closes the raf.
231      * <p>
232      * @exception IOException
233      */
234     protected synchronized void close()
235         throws IOException
236     {
237         raf.close();
238     }
239 
240     /***
241      * Sets the raf to empty.
242      * <p>
243      * @exception IOException
244      */
245     protected synchronized void reset()
246         throws IOException
247     {
248         if ( log.isDebugEnabled() )
249         {
250             log.debug( "Resetting Indexed File [" + filepath + "]" );
251         }
252         raf.close();
253         File f = new File( filepath );
254         int i = 0;
255         for ( ; i < 10 && !f.delete(); i++ )
256         {
257             try
258             {
259                 Thread.sleep( 1000 );
260             }
261             catch ( InterruptedException ex )
262             {
263                 // swallow
264             }
265             log.warn( "Failed to delete " + f.getName() + " " + i );
266         }
267         if ( i == 10 )
268         {
269             IllegalStateException ex = new IllegalStateException( "Failed to delete " + f.getName() );
270             log.error( ex );
271             throw ex;
272         }
273         raf = new RandomAccessFile( filepath, "rw" );
274     }
275 
276     /***
277      * Returns the serialized form of the given object in a byte array.
278      * <p>
279      * Use the Serilizer abstraction layer.
280      * <p>
281      * @return a byte array of the serialized object.
282      * @param obj
283      * @exception IOException
284      */
285     protected static byte[] serialize( Serializable obj )
286         throws IOException
287     {
288         return SERIALIZER.serialize( obj );
289     }
290 
291     /***
292      * Truncates the file to a given length.
293      * <p>
294      * @param length the new length of the file
295      * @throws IOException
296      */
297     protected void truncate( long length )
298         throws IOException
299     {
300         if ( log.isInfoEnabled() )
301         {
302             log.info( "Trucating file [" + filepath + "] to " + length );
303         }
304         raf.setLength( length );
305     }
306 }