1 package org.apache.jcs.auxiliary.disk.indexed;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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];
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
139
140 long readPos = ded.pos;
141 long writePos = newPosition;
142
143
144 int remaining = RECORD_HEADER + length;
145
146 while ( remaining > 0 )
147 {
148
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
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 }