/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sun.jini.outrigger.snaplogstore;
import com.sun.jini.outrigger.OutriggerServerImpl;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.space.InternalSpaceException;
import net.jini.id.Uuid;
/**
* A class to help you read log files created by <code>LogOutputFile</code>.
*
* @author Sun Microsystems, Inc.
*
* @see LogOutputFile
*/
class LogInputFile extends LogFile {
private File file; // the current log file
private static final long intBytes = 4;
/** Logger for logging persistent store related information */
private static final Logger logger =
Logger.getLogger(OutriggerServerImpl.storeLoggerName);
/**
* Return an <code>Iterator</code> that will loop through all
* the logs that match the given <code>basePath</code> pattern,
* interpreted as described in the matching <code>LogStream</code>
* constructor. If <code>returnAll</code> is <code>false</code>,
* the most recent file will be left off the list. This would be
* the proper value for an ongoing poll looking for completed log
* files. You would specify <code>true</code> during recovery,
* when all existing logs should be committed because no new ones
* are currently being created
*
* @see java.util.Iterator
* @see LogStream#LogStream(String)
*/
static Iterator logs(String basePath, boolean returnAll)
throws IOException
{
LogFile lf = new LogFile(basePath);// an object to represent the path
ArrayList inDir = new ArrayList();
lf.existingLogs(inDir);
// strip off most recent if we're not trying to read them all
if (!returnAll && inDir.size() > 0)
inDir.remove(inDir.size() - 1);
return new LogInputFileIterator(inDir, lf);
}
/**
* The implementation of <code>Iterator</code> returned by
* <code>LogInputStream.logs</code>. The <code>next</code> method
* occasionally returns <code>null</code>.
*
* @see LogInputFileIterator#next
*/
private static class LogInputFileIterator implements Iterator {
private LogFile baseLogFile;
private Iterator fileList;
/**
* Create a new <code>LogInputFileIterator</code> object
* for the given list.
*/
LogInputFileIterator(Collection files, LogFile baseLogFile) {
this.baseLogFile = baseLogFile;
fileList = files.iterator();
}
public boolean hasNext() {
return fileList.hasNext();
}
/**
* Return the next <code>File</code> object, or
* <code>null</code>. You will get <code>null</code> when the
* file existed at the time of listing, but no longer exists
* when the iterator gets to it. For example, if a process is
* consuming all completed logs, the listing might find a log,
* but that process may have consumed and removed it by the
* time you invoke <code>next</code>, so you will get a
* <code>null</code>.
*/
public Object next() {
File file = (File) fileList.next();
try {
return new LogInputFile(baseLogFile, file);
} catch (IOException e) {
file.delete(); // file is malformed -- remove it
return null; // can't throw any reasonable exception,
// so signal the problem with a null
}
}
/**
* Remove the <code>File</code> object returned by the iterator
* from the list. This does <em>not</em> remove the file
* itself.
*/
public void remove() {
fileList.remove();
}
}
/**
* Create a new <code>LogInputFile</code>.
* <p>
* <b>Note:</b> Don't invoke this. This is needed by the
* enumeration returned by <code>logs</code>, which is how you
* should be getting <code>LogInputFile</code> objects. When
* nested classes arrive, this constructor can be properly
* protected.
* @see logs
*/
private LogInputFile(LogFile desc, File path) throws IOException {
super(desc.baseDir, desc.baseFile);
file = path;
}
/**
* Consume the input file, invoking the appropriate operations on
* the given object.
*/
synchronized void consume(BackEnd opOn) {
try {
DataInputStream din =
new DataInputStream(new BufferedInputStream(
new FileInputStream(file)));
ObjectInputStream in = new ObjectInputStream(din);
long length = file.length();
int fileVer = din.readInt();
if (fileVer != LOG_VERSION)
failure("unsupported log version: " + fileVer);
long logBytes = intBytes;
int updateLen = din.readInt();
Long txnId;
int count;
Resource rep;
byte[] cookie;
while (updateLen != 0) { /* 0 is expected termination case */
if (updateLen < 0) /* serious corruption */
failure("file corrupted, negative record length at " +
logBytes);
if (length - logBytes - intBytes < updateLen)
/* partial record at end of log; this should not happen
* if forceToDisk is always true, but might happen if
* buffered updates are used.
*/
failure("file corrupted, partial record at " + logBytes);
int op = in.readByte();
switch (op) {
case BOOT_OP:
long time = in.readLong();
long sessionId = in.readLong();
opOn.bootOp(time, sessionId);
break;
case JOINSTATE_OP:
BaseObject state = (BaseObject)in.readObject();
opOn.joinStateOp(state);
break;
case WRITE_OP:
rep = (Resource)in.readObject();
txnId = (Long)in.readObject();
opOn.writeOp(rep, txnId);
break;
case BATCH_WRITE_OP:
txnId = (Long)in.readObject();
count = in.readInt();
for (int i=0; i<count; i++) {
rep = (Resource)in.readObject();
opOn.writeOp(rep, txnId);
}
break;
case TAKE_OP:
cookie = new byte[16];
in.readFully(cookie);
txnId = (Long)in.readObject();
opOn.takeOp(cookie, txnId);
break;
case BATCH_TAKE_OP:
txnId = (Long)in.readObject();
count = in.readInt();
for (int i=0; i<count; i++) {
cookie = new byte[16];
in.readFully(cookie);
opOn.takeOp(cookie, txnId);
}
break;
case REGISTER_OP:
Registration registration =
(Registration)in.readObject();
opOn.registerOp(registration);
break;
case RENEW_OP:
cookie = new byte[16];
in.readFully(cookie);
long expires = in.readLong();
opOn.renewOp(cookie, expires);
break;
case CANCEL_OP:
cookie = new byte[16];
in.readFully(cookie);
opOn.cancelOp(cookie);
break;
case PREPARE_OP:
txnId = (Long)in.readObject();
BaseObject transaction = (BaseObject)in.readObject();
opOn.prepareOp(txnId, transaction);
break;
case COMMIT_OP:
txnId = (Long)in.readObject();
opOn.commitOp(txnId);
break;
case ABORT_OP:
txnId = (Long)in.readObject();
opOn.abortOp(txnId);
break;
case UUID_OP:
final byte uuid[] = new byte[16];
in.readFully(uuid);
opOn.uuidOp(uuid);
break;
default:
failure("log record corrupted, unknown opcode");
} // case
logBytes += (intBytes + updateLen);
// deal with padding
int offset = (int)logBytes & 3;
if (offset > 0) {
offset = 4 - offset;
logBytes += offset;
din.skipBytes(offset);
}
updateLen = din.readInt();
} // while
} catch (EOFException e) {
failure("unexpected end-of-file", e);
} catch (IOException e) {
failure("I/O error while consuming logs", e);
} catch (ClassNotFoundException e) {
failure("unexpected class?", e);
}
}
/**
* Report a failure consuming the log file and throw an
* <code>InternalSpaceException</code> containing <code>message</code>.
*/
private void failure(String message) {
failure(message, null);
}
/**
* Report a exception while consuming the log file and throw an
* <code>InternalSpaceException</code> containing <code>message</code>.
*/
private void failure(String message, Exception e) {
String errorMsg = "Error consuming log file: " + file + ", " +
message + "Log file consumption stopped";
final InternalSpaceException ise =
new InternalSpaceException(errorMsg, e);
logger.log(Level.SEVERE, errorMsg, ise);
throw ise;
}
/**
* This log has been successfully drained, and committed -- it can be
* removed.
*/
void finished() {
file.delete();
}
public String toString() {
return file.toString();
}
}