net.timewalker.ffmq4.storage.data.impl.journal.JournalRecovery.java Source code

Java tutorial

Introduction

Here is the source code for net.timewalker.ffmq4.storage.data.impl.journal.JournalRecovery.java

Source

/*
 * This file is part of FFMQ.
 *
 * FFMQ is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * FFMQ is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with FFMQ; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package net.timewalker.ffmq4.storage.data.impl.journal;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.LinkedList;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * JournalRecovery
 */
public final class JournalRecovery {
    private static final Log log = LogFactory.getLog(JournalRecovery.class);

    // Attributes
    private String baseName;
    private File[] journalFiles;
    private RandomAccessFile allocationTableRandomAccessFile;
    private RandomAccessFile dataRandomAccessFile;

    /**
     * Constructor
     */
    public JournalRecovery(String baseName, File[] journalFiles, RandomAccessFile allocationTableRandomAccessFile,
            RandomAccessFile dataRandomAccessFile) {
        this.baseName = baseName;
        this.journalFiles = journalFiles;
        this.allocationTableRandomAccessFile = allocationTableRandomAccessFile;
        this.dataRandomAccessFile = dataRandomAccessFile;
    }

    /**
     * Start the recovery process
     * @return the new block count of the store, or -1 if unchanged
     * @throws JournalException
     */
    public int recover() throws JournalException {
        int newBlockCount = -1;

        log.warn("[" + baseName + "] Recovery required for data store : found " + journalFiles.length
                + " journal file(s)");
        for (int i = 0; i < journalFiles.length; i++)
            newBlockCount = recoverFromJournalFile(journalFiles[i]);

        return newBlockCount;
    }

    private int recoverFromJournalFile(File journalFile) throws JournalException {
        log.debug("[" + baseName + "] Processing " + journalFile.getAbsolutePath());

        DataInputStream in;
        try {
            // Create a buffered data input stream from file
            in = new DataInputStream(new BufferedInputStream(new FileInputStream(journalFile)));
        } catch (IOException e) {
            throw new JournalException("Cannot open journal file : " + journalFile.getAbsolutePath(), e);
        }

        int replayedOperations = 0;
        int replayedTransactions = 0;
        long currentTransactionId = -1;
        int newBlockCount = -1;
        LinkedList<AbstractJournalOperation> transactionQueue = new LinkedList<>();
        try {
            AbstractJournalOperation op;
            while ((op = readJournalOperation(in)) != null) {
                // Check transaction id
                if (currentTransactionId == -1)
                    currentTransactionId = op.getTransactionId();
                else if (currentTransactionId != op.getTransactionId())
                    throw new IllegalStateException("Transaction id inconsistency : " + currentTransactionId
                            + " -> " + op.getTransactionId());

                if (op instanceof CommitOperation) {
                    // Check transaction size
                    int opCount = ((CommitOperation) op).getOperationsCount();
                    if (transactionQueue.size() != opCount) {
                        throw new IllegalStateException("Transaction size mismatch (expected " + opCount + ", got "
                                + transactionQueue.size() + ")");
                    } else {
                        // Everything looks fine, proceed ...
                        log.trace("[" + baseName + "] Replaying transaction #" + currentTransactionId + " ("
                                + transactionQueue.size() + " operation(s))");
                        replayedOperations += transactionQueue.size();
                        replayedTransactions++;
                        newBlockCount = applyOperations(transactionQueue);
                        currentTransactionId = -1;
                    }
                } else
                    transactionQueue.addLast(op);
            }

            if (transactionQueue.size() > 0) {
                op = transactionQueue.removeFirst();
                log.warn("[" + baseName + "] Dropping incomplete transaction : #" + op.getTransactionId());
            }

            syncStore();

            log.warn("[" + baseName + "] Recovery complete. (Replayed " + replayedTransactions
                    + " transaction(s) and " + replayedOperations + " operation(s))");
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                throw new JournalException("Cannot close journal file : " + journalFile.getAbsolutePath(), e);
            }
        }

        return newBlockCount;
    }

    private int applyOperations(LinkedList<AbstractJournalOperation> transactionQueue) throws JournalException {
        int newBlockCount = -1;
        while (transactionQueue.size() > 0) {
            AbstractJournalOperation op = transactionQueue.removeFirst();

            if (op instanceof MetaDataWriteOperation) {
                ((MetaDataWriteOperation) op).writeTo(allocationTableRandomAccessFile);
            } else if (op instanceof MetaDataBlockWriteOperation) {
                ((MetaDataBlockWriteOperation) op).writeTo(allocationTableRandomAccessFile);
            } else if (op instanceof DataBlockWriteOperation) {
                ((DataBlockWriteOperation) op).writeTo(dataRandomAccessFile);
            } else if (op instanceof StoreExtendOperation) {
                newBlockCount = ((StoreExtendOperation) op).extend(allocationTableRandomAccessFile,
                        dataRandomAccessFile);
            } else
                throw new IllegalArgumentException("Unexpected journal operation : " + op);
        }

        return newBlockCount;
    }

    private void syncStore() throws JournalException {
        try {
            allocationTableRandomAccessFile.getFD().sync();
        } catch (IOException e) {
            log.error("[" + baseName + "] Could not sync store allocation table file", e);
            throw new JournalException("Could not sync store allocation table file");
        }
        try {
            dataRandomAccessFile.getFD().sync();
        } catch (IOException e) {
            log.error("[" + baseName + "] Could not sync store data file", e);
            throw new JournalException("Could not sync store data file");
        }
    }

    //-------------------------------------------------------------------------

    public static AbstractJournalOperation readJournalOperation(DataInputStream in) {
        try {
            int operationType = in.read();
            if (operationType == -1)
                return null; // EOF

            switch (operationType) {
            case 0: // Padding
                return null;

            case AbstractJournalOperation.TYPE_META_DATA_WRITE:
                return readMetaDataWriteOperation(in);

            case AbstractJournalOperation.TYPE_META_DATA_BLOCK_WRITE:
                return readMetaDataBlockWriteOperation(in);

            case AbstractJournalOperation.TYPE_DATA_BLOCK_WRITE:
                return readDataBlockWriteOperation(in);

            case AbstractJournalOperation.TYPE_STORE_EXTEND:
                return readStoreExtendOperation(in);

            case AbstractJournalOperation.TYPE_COMMIT:
                return readCommitOperation(in);

            default:
                throw new IllegalArgumentException("Invalid operation type : " + operationType);
            }
        } catch (Exception e) {
            log.error("Corrupted or truncated journal operation, skipping.", e);
            return null;
        }
    }

    private static MetaDataWriteOperation readMetaDataWriteOperation(DataInputStream in) throws IOException {
        long transactionId = in.readLong();
        long metaDataOffset = in.readLong();
        int metaData = in.readInt();

        return new MetaDataWriteOperation(transactionId, metaDataOffset, metaData);
    }

    private static MetaDataBlockWriteOperation readMetaDataBlockWriteOperation(DataInputStream in)
            throws IOException {
        long transactionId = in.readLong();
        long metaDataOffset = in.readLong();
        int len = in.readInt();
        byte[] metaData = new byte[len];
        in.readFully(metaData);

        return new MetaDataBlockWriteOperation(transactionId, metaDataOffset, metaData);
    }

    private static DataBlockWriteOperation readDataBlockWriteOperation(DataInputStream in) throws IOException {
        long transactionId = in.readLong();
        long blockOffset = in.readLong();
        int len = in.readInt();
        byte[] dataBlock = new byte[len];
        in.readFully(dataBlock);

        return new DataBlockWriteOperation(transactionId, -1, blockOffset, dataBlock);
    }

    private static StoreExtendOperation readStoreExtendOperation(DataInputStream in) throws IOException {
        long transactionId = in.readLong();
        int blockSize = in.readInt();
        int oldBlockCount = in.readInt();
        int newBlockCount = in.readInt();

        return new StoreExtendOperation(transactionId, blockSize, oldBlockCount, newBlockCount);
    }

    private static CommitOperation readCommitOperation(DataInputStream in) throws IOException {
        long transactionId = in.readLong();
        int operationsCount = in.readInt();

        return new CommitOperation(transactionId, operationsCount, null);
    }
}