001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: LastFileReader.java,v 1.48.2.3 2008/01/07 15:14:13 cwl Exp $
007: */
008:
009: package com.sleepycat.je.log;
010:
011: import java.io.File;
012: import java.io.IOException;
013: import java.nio.ByteBuffer;
014: import java.util.HashMap;
015: import java.util.HashSet;
016: import java.util.Map;
017: import java.util.Set;
018: import java.util.logging.Level;
019:
020: import com.sleepycat.je.DatabaseException;
021: import com.sleepycat.je.dbi.EnvironmentImpl;
022: import com.sleepycat.je.utilint.DbLsn;
023: import com.sleepycat.je.utilint.Tracer;
024:
025: /**
026: * LastFileReader traverses the last log file, doing checksums and looking for
027: * the end of the log. Different log types can be registered with it and it
028: * will remember the last occurrence of targetted entry types.
029: */
030: public class LastFileReader extends FileReader {
031:
032: /* Log entry types to track. */
033: private Set trackableEntries;
034:
035: private long nextUnprovenOffset;
036: private long lastValidOffset;
037: private LogEntryType entryType;
038:
039: /*
040: * Last lsn seen for tracked types. Key = LogEntryType, data is the offset
041: * (Long).
042: */
043: private Map lastOffsetSeen;
044:
045: /**
046: * This file reader is always positioned at the last file.
047: */
048: public LastFileReader(EnvironmentImpl env, int readBufferSize)
049: throws IOException, DatabaseException {
050:
051: super (env, readBufferSize, true, DbLsn.NULL_LSN, new Long(-1),
052: DbLsn.NULL_LSN, DbLsn.NULL_LSN);
053:
054: trackableEntries = new HashSet();
055: lastOffsetSeen = new HashMap();
056:
057: lastValidOffset = 0;
058: anticipateChecksumErrors = true;
059: nextUnprovenOffset = nextEntryOffset;
060: }
061:
062: /**
063: * Ctor which allows passing in the file number we want to read to the end
064: * of. This is used by the ScavengerFileReader when it encounters a bad
065: * log record in the middle of a file.
066: */
067: public LastFileReader(EnvironmentImpl env, int readBufferSize,
068: Long specificFileNumber) throws IOException,
069: DatabaseException {
070:
071: super (env, readBufferSize, true, DbLsn.NULL_LSN,
072: specificFileNumber, DbLsn.NULL_LSN, DbLsn.NULL_LSN);
073:
074: trackableEntries = new HashSet();
075: lastOffsetSeen = new HashMap();
076:
077: lastValidOffset = 0;
078: anticipateChecksumErrors = true;
079: nextUnprovenOffset = nextEntryOffset;
080: }
081:
082: /**
083: * Override so that we always start at the last file.
084: */
085: protected void initStartingPosition(long endOfFileLsn,
086: Long singleFileNum) throws IOException, DatabaseException {
087:
088: eof = false;
089:
090: /*
091: * Start at what seems like the last file. If it doesn't exist, we're
092: * done.
093: */
094: Long lastNum = ((singleFileNum != null) && (singleFileNum
095: .longValue() >= 0)) ? singleFileNum : fileManager
096: .getLastFileNum();
097: FileHandle fileHandle = null;
098: readBufferFileEnd = 0;
099:
100: long fileLen = 0;
101: while ((fileHandle == null) && !eof) {
102: if (lastNum == null) {
103: eof = true;
104: } else {
105: try {
106: readBufferFileNum = lastNum.longValue();
107: fileHandle = fileManager
108: .getFileHandle(readBufferFileNum);
109:
110: /*
111: * Check the size of this file. If it opened successfully
112: * but only held a header or is 0 length, backup to the
113: * next "last" file unless this is the only file in the
114: * log. Note that an incomplete header will end up throwing
115: * a checksum exception, but a 0 length file will open
116: * successfully in read only mode.
117: */
118: fileLen = fileHandle.getFile().length();
119: if (fileLen <= FileManager.firstLogEntryOffset()) {
120: lastNum = fileManager.getFollowingFileNum(
121: lastNum.longValue(), false);
122: if (lastNum != null) {
123: fileHandle.release();
124: fileHandle = null;
125: }
126: }
127: } catch (DatabaseException e) {
128: lastNum = attemptToMoveBadFile(e);
129: fileHandle = null;
130: } finally {
131: if (fileHandle != null) {
132: fileHandle.release();
133: }
134: }
135: }
136: }
137:
138: nextEntryOffset = 0;
139: }
140:
141: /**
142: * Something is wrong with this file. If there is no data in this file (the
143: * header is <= the file header size) then move this last file aside and
144: * search the next "last" file. If the last file does have data in it,
145: * throw an exception back to the application, since we're not sure what to
146: * do now.
147: */
148: private Long attemptToMoveBadFile(DatabaseException origException)
149: throws DatabaseException, IOException {
150:
151: String fileName = fileManager
152: .getFullFileNames(readBufferFileNum)[0];
153: File problemFile = new File(fileName);
154: Long lastNum = null;
155:
156: if (problemFile.length() <= FileManager.firstLogEntryOffset()) {
157: fileManager.clear(); // close all existing files
158: /* Move this file aside. */
159: lastNum = fileManager.getFollowingFileNum(
160: readBufferFileNum, false);
161: fileManager.renameFile(readBufferFileNum,
162: FileManager.BAD_SUFFIX);
163:
164: } else {
165: /* There's data in this file, throw up to the app. */
166: throw origException;
167: }
168: return lastNum;
169: }
170:
171: public void setEndOfFile() throws IOException, DatabaseException {
172:
173: fileManager.truncateLog(readBufferFileNum, nextUnprovenOffset);
174: }
175:
176: /**
177: * @return The LSN to be used for the next log entry.
178: */
179: public long getEndOfLog() {
180: return DbLsn.makeLsn(readBufferFileNum, nextUnprovenOffset);
181: }
182:
183: public long getLastValidLsn() {
184: return DbLsn.makeLsn(readBufferFileNum, lastValidOffset);
185: }
186:
187: public long getPrevOffset() {
188: return lastValidOffset;
189: }
190:
191: public LogEntryType getEntryType() {
192: return entryType;
193: }
194:
195: /**
196: * Tell the reader that we are interested in these kind of entries.
197: */
198: public void setTargetType(LogEntryType type) {
199: trackableEntries.add(type);
200: }
201:
202: /**
203: * @return The last LSN seen in the log for this kind of entry, or null.
204: */
205: public long getLastSeen(LogEntryType type) {
206: Long typeNumber = (Long) lastOffsetSeen.get(type);
207: if (typeNumber != null) {
208: return DbLsn.makeLsn(readBufferFileNum, typeNumber
209: .longValue());
210: } else {
211: return DbLsn.NULL_LSN;
212: }
213: }
214:
215: /**
216: * Validate the checksum on each entry, see if we should remember the LSN
217: * of this entry.
218: */
219: protected boolean processEntry(ByteBuffer entryBuffer) {
220:
221: /* Skip over the data, we're not doing anything with it. */
222: entryBuffer.position(entryBuffer.position()
223: + currentEntryHeader.getItemSize());
224:
225: /* If we're supposed to remember this lsn, record it. */
226: entryType = new LogEntryType(currentEntryHeader.getType(),
227: currentEntryHeader.getVersion());
228: if (trackableEntries.contains(entryType)) {
229: lastOffsetSeen.put(entryType, new Long(currentEntryOffset));
230: }
231:
232: return true;
233: }
234:
235: /**
236: * readNextEntry will stop at a bad entry.
237: * @return true if an element has been read.
238: */
239: public boolean readNextEntry() throws DatabaseException,
240: IOException {
241:
242: boolean foundEntry = false;
243:
244: try {
245:
246: /*
247: * At this point,
248: * currentEntryOffset is the entry we just read.
249: * nextEntryOffset is the entry we're about to read.
250: * currentEntryPrevOffset is 2 entries ago.
251: * Note that readNextEntry() moves all the offset pointers up.
252: */
253:
254: foundEntry = super .readNextEntry();
255:
256: /*
257: * Note that initStartingPosition() makes sure that the file header
258: * entry is valid. So by the time we get to this method, we know
259: * we're at a file with a valid file header entry.
260: */
261: lastValidOffset = currentEntryOffset;
262: nextUnprovenOffset = nextEntryOffset;
263: } catch (DbChecksumException e) {
264: Tracer
265: .trace(
266: Level.INFO,
267: envImpl,
268: "Found checksum exception while searching "
269: + " for end of log. Last valid entry is at "
270: + DbLsn.toString(DbLsn.makeLsn(
271: readBufferFileNum,
272: lastValidOffset))
273: + " Bad entry is at "
274: + DbLsn.makeLsn(readBufferFileNum,
275: nextUnprovenOffset));
276: }
277: return foundEntry;
278: }
279: }
|