org.mule.util.journal.TransactionJournalFile.java Source code

Java tutorial

Introduction

Here is the source code for org.mule.util.journal.TransactionJournalFile.java

Source

/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */
package org.mule.util.journal;

import org.mule.api.MuleRuntimeException;

import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

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

/**
 * Manages a transaction journal file.
 *
 * @param <T> type of the transaction identifier
 * @param <K> type of the journal entry
 */
class TransactionJournalFile<T, K extends JournalEntry<T>> {

    /**
     * Defines the minimum number of entries in the log that are required
     * to clear the log file once there are no more transactions pending.
     */
    private static final int MINIMUM_ENTRIES_TO_CLEAR_FILE = 10000;

    protected transient Log logger = LogFactory.getLog(getClass());

    private final File journalFile;
    private final JournalEntrySerializer<T, K> journalEntrySerializer;

    private Multimap<T, K> entries = LinkedHashMultimap.create();

    private DataOutputStream logFileOutputStream;
    private int journalOperations = 0;

    /**
     * @param journalFile journal file to use. Will be created if it doesn't exists. If exists then transaction entries will get loaded from it.
     * @param journalEntrySerializer serializer for {@link org.mule.util.journal.JournalEntry}
     */
    public TransactionJournalFile(File journalFile, JournalEntrySerializer journalEntrySerializer) {
        this.journalFile = journalFile;
        this.journalEntrySerializer = journalEntrySerializer;
        if (journalFile.exists()) {
            loadAllEntries();
        }
        createLogOutputStream();
    }

    /**
     * Adds a journal entry for an operation done over a transactional resource
     * @param journalEntry operation details
     */
    public synchronized void logOperation(K journalEntry) {
        entries.put(journalEntry.getTxId(), journalEntry);
        journalEntrySerializer.serialize(journalEntry, logFileOutputStream);
        journalOperations++;
    }

    /**
     * Removes all journal entries for a particular transactions.
     *
     * If there are no transaction entries left then the journal file is emptied.
     *
     * @param txId transaction identifier
     */
    public synchronized void clearEntriesForTransaction(T txId) {
        Collection<K> entries = this.entries.removeAll(txId);
        if (logger.isDebugEnabled()) {
            logger.debug("Evicted from tx log file " + entries.size() + " entries from txid " + txId);
        }
        if (this.entries.isEmpty()) {
            if (journalOperations > MINIMUM_ENTRIES_TO_CLEAR_FILE) {
                clear();
                journalOperations = 0;
            }
        }
    }

    /**
     * Free all resources held.
     *
     * Will not remove the transaction context.
     */
    public synchronized void close() {
        try {
            logFileOutputStream.close();
        } catch (IOException e) {
            logger.warn(e.getMessage());
            if (logger.isDebugEnabled()) {
                logger.debug(e);
            }
        }
    }

    /**
     * @param txId transaction identifier
     * @return a copy collection of the journal entries for txId
     */
    public Collection<K> getLogEntries(T txId) {
        final Collection<K> entries = this.entries.asMap().get(txId);
        if (entries == null) {
            return Collections.emptyList();
        }
        synchronized (entries) {
            return Collections.unmodifiableCollection(new ArrayList<K>(entries));
        }
    }

    /**
     * @return all journal entries exactly as stored in the journal.
     * No modifications should be done to such collections and the journal file should not
     * be access concurrently for other purposes while working with the collection.
     */
    public synchronized Multimap<T, K> getAllLogEntries() {
        return entries;
    }

    /**
     * Remove all the entries from the transaction journal and cleans the transaction journal fle.
     */
    public synchronized void clear() {
        close();
        entries.clear();
        FileUtils.deleteQuietly(journalFile);
        createLogOutputStream();
    }

    private void createLogOutputStream() {
        if (!journalFile.exists()) {
            try {
                journalFile.createNewFile();
            } catch (IOException e) {
                throw new MuleRuntimeException(e);
            }
        }
        try {
            this.logFileOutputStream = new DataOutputStream(new FileOutputStream(journalFile, true));
        } catch (FileNotFoundException e) {
            throw new MuleRuntimeException(e);
        }
    }

    private void loadAllEntries() {
        if (!journalFile.exists()) {
            return;
        }
        DataInputStream dataInputStream = null;
        try {
            dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(journalFile)));
            boolean logEntryCreationFailed = false;
            while (!logEntryCreationFailed) {
                try {
                    K journalEntry = journalEntrySerializer.deserialize(dataInputStream);
                    if (journalEntry != null) {
                        this.entries.put(journalEntry.getTxId(), journalEntry);
                    } else {
                        logEntryCreationFailed = true;
                    }
                } catch (EOFException e) {
                    logger.debug("Expected exception since there are no more log entries", e);
                    logEntryCreationFailed = true;
                } catch (Exception e) {
                    logger.warn(
                            "Exception reading transaction content. This is normal if the mule server was shutdown due to a failure"
                                    + e.getMessage());
                    if (logger.isDebugEnabled()) {
                        logger.debug(e);
                    }
                    logEntryCreationFailed = true;
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (dataInputStream != null) {
                    dataInputStream.close();
                }
            } catch (IOException e) {
                logger.error(e);
            }
        }
    }

    /**
     * @return the number different transaction identifier held by the journal.
     */
    public int size() {
        return entries.size();
    }

    /**
     * @param txId transaction identifier
     * @return true if contains entries for that txId, false otherwise
     */
    public boolean containsTx(T txId) {
        return this.entries.containsKey(txId);
    }

}