KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > store > raw > log > FlushedScan


1 /*
2
3    Derby - Class org.apache.derby.impl.store.raw.log.FlushedScan
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    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, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.store.raw.log;
23
24 import org.apache.derby.iapi.reference.SQLState;
25 import org.apache.derby.iapi.reference.MessageId;
26
27 import org.apache.derby.impl.store.raw.log.LogCounter;
28 import org.apache.derby.impl.store.raw.log.LogRecord;
29 import org.apache.derby.impl.store.raw.log.StreamLogScan;
30
31 import org.apache.derby.iapi.services.sanity.SanityManager;
32 import org.apache.derby.iapi.error.StandardException;
33 import org.apache.derby.iapi.services.i18n.MessageService;
34 import org.apache.derby.iapi.store.raw.log.LogInstant;
35 import org.apache.derby.iapi.store.raw.log.LogFactory;
36 import org.apache.derby.iapi.store.raw.xact.TransactionId;
37 import org.apache.derby.iapi.services.io.ArrayInputStream;
38
39 import org.apache.derby.io.StorageRandomAccessFile;
40
41 import java.io.IOException JavaDoc;
42
43 /**
44
45     Scan the the log which is implemented by a series of log files.n
46     This log scan knows how to move across log file if it is positioned at
47     the boundary of a log file and needs to getNextRecord.
48
49     <PRE>
50     4 bytes - length of user data, i.e. N
51     8 bytes - long representing log instant
52     N bytes of supplied data
53     4 bytes - length of user data, i.e. N
54     </PRE>
55
56 */

57 public class FlushedScan implements StreamLogScan {
58
59     private StorageRandomAccessFile scan; // an output stream to the log file
60
LogToFile logFactory; // log factory knows how to to skip
61
// from log file to log file
62

63     boolean open; // true if the scan is open
64

65     long currentLogFileNumber; // the log file the scan is currently on
66

67     long currentLogFileFirstUnflushedPosition;
68                                         // The length of the unflushed portion
69
// of the current log file. This is the
70
// length of the file for all but the
71
// last log file.
72

73     long currentInstant; // the log instant the scan is
74
// currently on - only valid after a
75
// successful getNextRecord
76

77     long firstUnflushed = -1; // scan until we reach the first
78
// unflushed byte in the log.
79
long firstUnflushedFileNumber;
80     long firstUnflushedFilePosition;
81
82     //RESOLVE: This belongs in a shared place.
83
static final int LOG_REC_LEN_BYTE_LENGTH = 4;
84
85     public FlushedScan(LogToFile logFactory, long startAt)
86          throws StandardException
87     {
88         if (SanityManager.DEBUG)
89         {
90             SanityManager.ASSERT(startAt != LogCounter.INVALID_LOG_INSTANT,
91                                  "cannot start scan on an invalid log instant");
92         }
93
94         try
95         {
96             currentLogFileNumber = LogCounter.getLogFileNumber(startAt);
97             this.logFactory = logFactory;
98             scan = logFactory.getLogFileAtPosition(startAt);
99             setFirstUnflushed();
100             open = true;
101             currentInstant = LogCounter.INVALID_LOG_INSTANT; // set at getNextRecord
102
}
103
104         catch (IOException JavaDoc ioe)
105         {
106             throw logFactory.markCorrupt(
107                     StandardException.newException(SQLState.LOG_IO_ERROR, ioe));
108         }
109     }
110
111     /*
112     ** Methods of LogScan
113     */

114
115     /**
116         Read a log record into the byte array provided. Resize the input
117         stream byte array if necessary.
118
119         @return the length of the data written into data, or -1 if the end of the
120         scan has been reached.
121
122         @exception StandardException Standard Cloudscape error policy
123     */

124     public LogRecord getNextRecord(ArrayInputStream input,
125                                    TransactionId tranId,
126                                    int groupmask)
127          throws StandardException
128     {
129         try
130         {
131             boolean candidate;
132             int peekAmount = LogRecord.formatOverhead() + LogRecord.maxGroupStoredSize();
133             if (tranId != null)
134                 peekAmount += LogRecord.maxTransactionIdStoredSize(tranId);
135             int readAmount; // the number of bytes actually read
136

137             LogRecord lr;
138
139             do
140             {
141                 if (!open || !positionToNextRecord())
142                     return null;
143
144                 int checkLength;
145
146                 // this log record is a candidate unless proven otherwise
147
lr = null;
148                 candidate = true;
149                 readAmount = -1;
150
151                 currentInstant = scan.readLong();
152                 byte[] data = input.getData();
153                 if (data.length < nextRecordLength)
154                 {
155                     // make a new array of sufficient size and reset the arrary
156
// in the input stream
157
data = new byte[nextRecordLength];
158                     input.setData(data);
159                 }
160
161                 if (logFactory.databaseEncrypted())
162                 {
163                     scan.readFully(data, 0, nextRecordLength);
164                     int len = logFactory.decrypt(data, 0, nextRecordLength, data, 0);
165                     if (SanityManager.DEBUG)
166                         SanityManager.ASSERT(len == nextRecordLength);
167                     input.setLimit(0, len);
168
169                 }
170                 else // no need to decrypt, only get the group and tid if we filter
171
{
172                     if (groupmask == 0 && tranId == null)
173                     {
174                         // no filter, get the whole thing
175
scan.readFully(data, 0, nextRecordLength);
176                         input.setLimit(0, nextRecordLength);
177                     }
178                     else
179                     {
180                         // Read only enough so that group and the tran id is in
181
// the data buffer. Group is stored as compressed int
182
// and tran id is stored as who knows what. read min
183
// of peekAmount or nextRecordLength
184
readAmount = (nextRecordLength > peekAmount) ?
185                             peekAmount : nextRecordLength;
186
187                         // in the data buffer, we now have enough to peek
188
scan.readFully(data, 0, readAmount);
189                         input.setLimit(0, readAmount);
190
191                     }
192                 }
193
194                 lr = (LogRecord) input.readObject();
195
196                 if (groupmask != 0 || tranId != null)
197                 {
198                     if (groupmask != 0 && (groupmask & lr.group()) == 0)
199                         candidate = false; // no match, throw this log record out
200

201                     if (candidate && tranId != null)
202                     {
203                         TransactionId tid = lr.getTransactionId();
204                         if (!tid.equals(tranId)) // nomatch
205
candidate = false; // throw this log record out
206
}
207
208                     // if this log record is not filtered out, we need to read
209
// in the rest of the log record to the input buffer.
210
// Except if it is an encrypted database, in which case the
211
// entire log record have already be read in for
212
// decryption.
213

214                     if (candidate && !logFactory.databaseEncrypted())
215                     {
216                         // read the rest of the log into the buffer
217
if (SanityManager.DEBUG)
218                             SanityManager.ASSERT(readAmount > 0);
219
220                         if (readAmount < nextRecordLength)
221                         {
222                             // Need to remember where we are because the log
223
// record may have read part of it off the input
224
// stream already and that position is lost when we
225
// set limit again.
226
int inputPosition = input.getPosition();
227
228                             scan.readFully(data, readAmount,
229                                            nextRecordLength-readAmount);
230
231                             input.setLimit(0, nextRecordLength);
232                             input.setPosition(inputPosition);
233                         }
234                     }
235                 }
236
237                 if (candidate || logFactory.databaseEncrypted())
238                 {
239                     checkLength = scan.readInt();
240
241                     if (SanityManager.DEBUG)
242                     {
243                         SanityManager.ASSERT(checkLength == nextRecordLength, "log currupted");
244                     }
245                 }
246                 else // chances are, we haven't read all of the log record, skip it
247
{
248                     // the starting record position is in the currentInstant,
249
// calculate the next record starting position using that
250
// and the nextRecordLength
251
long nextRecordStartPosition =
252                         LogCounter.getLogFilePosition(currentInstant) +
253                         nextRecordLength + LogToFile.LOG_RECORD_OVERHEAD;
254
255                     scan.seek(nextRecordStartPosition);
256                 }
257
258             } while (candidate == false);
259
260             return lr;
261         }
262         catch (ClassNotFoundException JavaDoc cnfe)
263         {
264             throw logFactory.markCorrupt(
265                 StandardException.newException(SQLState.LOG_CORRUPTED, cnfe));
266         }
267         catch (IOException JavaDoc ioe)
268         {
269             throw logFactory.markCorrupt(
270                 StandardException.newException(SQLState.LOG_IO_ERROR, ioe));
271         }
272     }
273
274     /**
275         Reset the scan to the given LogInstant.
276
277         @param instant the position to reset to
278         @exception IOException scan cannot access the log at the new position.
279     */

280     public void resetPosition(LogInstant instant) throws IOException JavaDoc
281     {
282         if (SanityManager.DEBUG)
283         {
284             SanityManager.THROWASSERT("Unsupported feature");
285         }
286     }
287
288     /**
289         Get the log instant that is right after the record just retrived
290         @return INVALID_LOG_INSTANT if this is not a FORWARD scan or, no
291         record have been returned yet or the scan has completed.
292     */

293     public long getLogRecordEnd()
294     {
295         if (SanityManager.DEBUG)
296         {
297             SanityManager.THROWASSERT("Unsupported feature");
298         }
299         return LogCounter.INVALID_LOG_INSTANT;
300     }
301
302     
303     /**
304        returns true if there is partially writen log records before the crash
305        in the last log file. Partiall wrires are identified during forward
306        scans for log recovery.
307      */

308     public boolean isLogEndFuzzy()
309     {
310         if (SanityManager.DEBUG)
311         {
312             SanityManager.THROWASSERT("Unsupported feature");
313         }
314         return false;
315     }
316
317     /**
318         Return the log instant (as an integer) the scan is currently on - this is the log
319         instant of the log record that was returned by getNextRecord.
320     */

321     public long getInstant()
322     {
323         return currentInstant;
324     }
325
326     /**
327         Return the log instant the scan is currently on - this is the log
328         instant of the log record that was returned by getNextRecord.
329     */

330     public LogInstant getLogInstant()
331     {
332         if (currentInstant == LogCounter.INVALID_LOG_INSTANT)
333             return null;
334         else
335             return new LogCounter(currentInstant);
336     }
337
338     /**
339         Close the scan.
340     */

341     public void close()
342     {
343         if (scan != null)
344         {
345             try
346             {
347                 scan.close();
348             }
349             catch (IOException JavaDoc ioe)
350             {}
351
352             scan = null;
353         }
354         currentInstant = LogCounter.INVALID_LOG_INSTANT;
355         open = false;
356     }
357
358     /*
359       Private methods.
360       */

361     private void setFirstUnflushed()
362          throws StandardException, IOException JavaDoc
363     {
364         LogInstant firstUnflushedInstant =
365             logFactory.getFirstUnflushedInstant();
366         firstUnflushed = ((LogCounter)firstUnflushedInstant).getValueAsLong();
367         firstUnflushedFileNumber = LogCounter.getLogFileNumber(firstUnflushed);
368         firstUnflushedFilePosition = LogCounter.getLogFilePosition(firstUnflushed);
369
370         setCurrentLogFileFirstUnflushedPosition();
371     }
372
373     private void setCurrentLogFileFirstUnflushedPosition()
374          throws IOException JavaDoc
375     {
376         /*
377           Note we get the currentLogFileLength without synchronization.
378           This is safe because one of the following cases apply:
379
380           <OL>
381           <LI> The end of the flushed section of the log is in another file.
382           In this case the end of the current file will not change.
383           <LI> The end of the log is in this file. In this case we
384           end our scan at the firstUnflushedInstant and do not use
385           currentLogFileLength.
386           </OL>
387           */

388         if (currentLogFileNumber == firstUnflushedFileNumber)
389             currentLogFileFirstUnflushedPosition = firstUnflushedFilePosition;
390         else if (currentLogFileNumber < firstUnflushedFileNumber)
391             currentLogFileFirstUnflushedPosition = scan.length();
392         else
393         {
394             // RESOLVE
395
throw new IOException JavaDoc(
396                 MessageService.getTextMessage(MessageId.LOG_BAD_START_INSTANT));
397         }
398     }
399
400     private void switchLogFile()
401          throws StandardException
402     {
403         try
404         {
405             readNextRecordLength = false;
406             scan.close();
407             scan = null;
408             scan = logFactory.getLogFileAtBeginning(++currentLogFileNumber);
409             setCurrentLogFileFirstUnflushedPosition();
410         }
411
412         catch (IOException JavaDoc ioe)
413         {
414             throw logFactory.markCorrupt(
415                 StandardException.newException(SQLState.LOG_IO_ERROR, ioe));
416         }
417     }
418
419     /**
420       The length of the next record. Read from scan and set by
421       currentLogFileHasUnflushedRecord. This is used to retain the length of a
422       log record in the case currentLogFileHasUnflushedRecord reads the length
423       and determines that some bytes in the log record are not yet flushed.
424       */

425     int nextRecordLength;
426
427     /**
428       Flag to indicate that the length of the next log record has been read by
429       currentLogFileHasUnflushedRecord.
430
431       This flag gets reset in two ways:
432
433       <OL>
434       <LI> currentLogFileHasUnflushedRecord determines that the entire log
435       record is flushed and returns true. In this case getNextRecord reads and
436       returns the log record.
437       <LI> we switch log files --due to a partial log record at the end of an
438       old log file.
439       </OL>
440       */

441     boolean readNextRecordLength;
442
443     private boolean currentLogFileHasUnflushedRecord()
444          throws IOException JavaDoc
445     {
446         if (SanityManager.DEBUG)
447             SanityManager.ASSERT(scan != null, "scan is null");
448         long curPos = scan.getFilePointer();
449
450         if (!readNextRecordLength)
451         {
452             if (curPos + LOG_REC_LEN_BYTE_LENGTH >
453                                  currentLogFileFirstUnflushedPosition)
454                 return false;
455
456             nextRecordLength = scan.readInt();
457             curPos+=4;
458             readNextRecordLength = true;
459         }
460
461         if (nextRecordLength==0) return false;
462
463         int bytesNeeded =
464             nextRecordLength + LOG_REC_LEN_BYTE_LENGTH;
465
466         if (curPos + bytesNeeded > currentLogFileFirstUnflushedPosition)
467         {
468             return false;
469         }
470         else
471         {
472             readNextRecordLength = false;
473             return true;
474         }
475     }
476
477     private boolean positionToNextRecord()
478          throws StandardException, IOException JavaDoc
479     {
480         //If the flushed section of the current log file contains our record we
481
//simply return.
482
if (currentLogFileHasUnflushedRecord()) return true;
483
484         //Update our cached copy of the first unflushed instant.
485
setFirstUnflushed();
486
487         //In the call to setFirstUnflushed, we may have noticed that the current
488
//log file really does contain our record. If so we simply return.
489
if (currentLogFileHasUnflushedRecord()) return true;
490
491         //Our final chance of finding a record is if we are not scanning the log
492
//file with the last flushed instant we can switch logfiles. Note that
493
//we do this in a loop to cope with empty log files.
494
while(currentLogFileNumber < firstUnflushedFileNumber)
495         {
496               switchLogFile();
497               if (currentLogFileHasUnflushedRecord()) return true;
498         }
499
500         //The log contains no more flushed log records so we return false.
501
currentInstant = LogCounter.INVALID_LOG_INSTANT;
502         return false;
503     }
504 }
505
Popular Tags