View Javadoc
1   package org.apache.commons.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  import java.nio.ByteBuffer;
28  import java.nio.channels.FileChannel;
29  
30  import org.apache.commons.jcs.engine.behavior.IElementSerializer;
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  
34  /** Provides thread safe access to the underlying random access file. */
35  class IndexedDisk
36  {
37      /** The size of the header that indicates the amount of data stored in an occupied block. */
38      public static final byte HEADER_SIZE_BYTES = 4;
39  
40      /** The serializer. */
41      private final IElementSerializer elementSerializer;
42  
43      /** The logger */
44      private static final Log log = LogFactory.getLog( IndexedDisk.class );
45  
46      /** The path to the log directory. */
47      private final String filepath;
48  
49      /** The data file. */
50      private final FileChannel fc;
51  
52      /**
53       * Constructor for the Disk object
54       * <p>
55       * @param file
56       * @param elementSerializer
57       * @throws FileNotFoundException
58       */
59      public IndexedDisk( File file, IElementSerializer elementSerializer )
60          throws FileNotFoundException
61      {
62          this.filepath = file.getAbsolutePath();
63          this.elementSerializer = elementSerializer;
64          RandomAccessFile raf = new RandomAccessFile( filepath, "rw" );
65          this.fc = raf.getChannel();
66      }
67  
68      /**
69       * This reads an object from the given starting position on the file.
70       * <p>
71       * The first four bytes of the record should tell us how long it is. The data is read into a byte
72       * array and then an object is constructed from the byte array.
73       * <p>
74       * @return Serializable
75       * @param ded
76       * @throws IOException
77       * @throws ClassNotFoundException
78       */
79      protected <T extends Serializable> T readObject( IndexedDiskElementDescriptor ded )
80          throws IOException, ClassNotFoundException
81      {
82          String message = null;
83          boolean corrupted = false;
84          long fileLength = fc.size();
85          if ( ded.pos > fileLength )
86          {
87              corrupted = true;
88              message = "Record " + ded + " starts past EOF.";
89          }
90          else
91          {
92              ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
93              fc.read(datalength, ded.pos);
94              datalength.flip();
95              int datalen = datalength.getInt();
96              if ( ded.len != datalen )
97              {
98                  corrupted = true;
99                  message = "Record " + ded + " does not match data length on disk (" + datalen + ")";
100             }
101             else if ( ded.pos + ded.len > fileLength )
102             {
103                 corrupted = true;
104                 message = "Record " + ded + " exceeds file length.";
105             }
106         }
107 
108         if ( corrupted )
109         {
110             log.warn( "\n The file is corrupt: " + "\n " + message );
111             throw new IOException( "The File Is Corrupt, need to reset" );
112         }
113 
114         ByteBuffer data = ByteBuffer.allocate(ded.len);
115         fc.read(data, ded.pos + HEADER_SIZE_BYTES);
116         data.flip();
117 
118         return elementSerializer.deSerialize( data.array(), null );
119     }
120 
121     /**
122      * Moves the data stored from one position to another. The descriptor's position is updated.
123      * <p>
124      * @param ded
125      * @param newPosition
126      * @throws IOException
127      */
128     protected void move( final IndexedDiskElementDescriptor ded, final long newPosition )
129         throws IOException
130     {
131         ByteBuffer datalength = ByteBuffer.allocate(HEADER_SIZE_BYTES);
132         fc.read(datalength, ded.pos);
133         datalength.flip();
134         int length = datalength.getInt();
135 
136         if ( length != ded.len )
137         {
138             throw new IOException( "Mismatched memory and disk length (" + length + ") for " + ded );
139         }
140 
141         // TODO: more checks?
142 
143         long readPos = ded.pos;
144         long writePos = newPosition;
145 
146         // header len + data len
147         int remaining = HEADER_SIZE_BYTES + length;
148         ByteBuffer buffer = ByteBuffer.allocate(16384);
149 
150         while ( remaining > 0 )
151         {
152             // chunk it
153             int chunkSize = Math.min( remaining, buffer.capacity() );
154             buffer.limit(chunkSize);
155             fc.read(buffer, readPos);
156             buffer.flip();
157             fc.write(buffer, writePos);
158             buffer.clear();
159 
160             writePos += chunkSize;
161             readPos += chunkSize;
162             remaining -= chunkSize;
163         }
164 
165         ded.pos = newPosition;
166     }
167 
168     /**
169      * Writes the given byte array to the Disk at the specified position.
170      * <p>
171      * @param data
172      * @param ded
173      * @return true if we wrote successfully
174      * @throws IOException
175      */
176     protected boolean write( IndexedDiskElementDescriptor ded, byte[] data )
177         throws IOException
178     {
179         long pos = ded.pos;
180         if ( log.isTraceEnabled() )
181         {
182             log.trace( "write> pos=" + pos );
183             log.trace( fc + " -- data.length = " + data.length );
184         }
185 
186         if ( data.length != ded.len )
187         {
188             throw new IOException( "Mismatched descriptor and data lengths" );
189         }
190 
191         ByteBuffer buffer = ByteBuffer.allocate(HEADER_SIZE_BYTES + data.length);
192         buffer.putInt(data.length);
193         buffer.put(data);
194         buffer.flip();
195         int written = fc.write(buffer, pos);
196         //fc.force(true);
197 
198         return written == data.length;
199     }
200 
201     /**
202      * Serializes the object and write it out to the given position.
203      * <p>
204      * TODO: make this take a ded as well.
205      * @return true unless error
206      * @param obj
207      * @param pos
208      * @throws IOException
209      */
210     protected boolean writeObject( Serializable obj, long pos )
211         throws IOException
212     {
213         byte[] data = elementSerializer.serialize( obj );
214         write( new IndexedDiskElementDescriptor( pos, data.length ), data );
215         return true;
216     }
217 
218     /**
219      * Returns the raf length.
220      * <p>
221      * @return the length of the file.
222      * @throws IOException
223      */
224     protected long length()
225         throws IOException
226     {
227         return fc.size();
228     }
229 
230     /**
231      * Closes the raf.
232      * <p>
233      * @throws IOException
234      */
235     protected void close()
236         throws IOException
237     {
238         fc.close();
239     }
240 
241     /**
242      * Sets the raf to empty.
243      * <p>
244      * @throws IOException
245      */
246     protected synchronized void reset()
247         throws IOException
248     {
249         if ( log.isDebugEnabled() )
250         {
251             log.debug( "Resetting Indexed File [" + filepath + "]" );
252         }
253         fc.truncate(0);
254         fc.force(true);
255     }
256 
257     /**
258      * Truncates the file to a given length.
259      * <p>
260      * @param length the new length of the file
261      * @throws IOException
262      */
263     protected void truncate( long length )
264         throws IOException
265     {
266         if ( log.isInfoEnabled() )
267         {
268             log.info( "Truncating file [" + filepath + "] to " + length );
269         }
270         fc.truncate( length );
271     }
272 
273     /**
274      * This is used for debugging.
275      * <p>
276      * @return the file path.
277      */
278     protected String getFilePath()
279     {
280         return filepath;
281     }
282 }