<commit_before>package bitronix.tm.journal;

import bitronix.tm.BitronixXid;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.utils.Decoder;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.transaction.Status;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;

/**
 * Simple implementation of a journal that writes on a two-files disk log.
 * <p>Files are pre-allocated in size, never grow and when the first one is full, dangling records are copied to the
 * second file and logging starts again on the latter.</p>
 * <p>This implementation is not highly efficient but quite robust and simple. It is based on one of the implementations
 * proposed by <NAME>.</p>
 * <p>Configurable properties are all starting with <code>bitronix.tm.journal.disk</code>.</p>
 * <p>&copy; <a href="http://www.bitronix.be">Bitronix Software</a></p>
 *
 * @see bitronix.tm.Configuration
 * @see <a href="http://jroller.com/page/pyrasun?entry=xa_exposed_part_iii_the">XA Exposed, Part III: The Implementor's Notebook</a>
 * @author lorban
 */
public class DiskJournal implements Journal {

    private final static Logger log = LoggerFactory.getLogger(DiskJournal.class);

    /**
     * The active log appender. This is exactly the same reference as tla1 or tla2 depending on which one is
     * currently active
     */
    private TransactionLogAppender activeTla;

    /**
     * The transaction log appender writing on the 1st file
     */
    private TransactionLogAppender tla1;

    /**
     * The transaction log appender writing on the 2nd file
     */
    private TransactionLogAppender tla2;


    /**
     * Create an uninitialized disk journal. You must call open() prior you can use it.
     */
    public DiskJournal() {
    }

    /**
     * Log a new transaction status to journal. Note that the DiskJournal will not check the flow of the transaction.
     * If you call this method with erroneous data, it will be added to the journal anyway.
     * @param status transaction status to log. See {@link javax.transaction.Status} constants
     * @param gtrid raw GTRID of the transaction
     * @param uniqueNames unique names of the {@link javax.sql.XADataSource}s participating in this transaction
     * @throws IOException
     */
    public void log(int status, Uid gtrid, Set uniqueNames) throws IOException {
        if (activeTla == null)
            throw new IOException("cannot write log, disk logger is not open");

        if (TransactionManagerServices.getConfiguration().isFilterLogStatus()) {
            if (status != Status.STATUS_COMMITTING && status != Status.STATUS_COMMITTED && status != Status.STATUS_UNKNOWN) {
                if (log.isDebugEnabled()) log.debug("filtered out write to log for status " + Decoder.decodeStatus(status));
                return;
            }
        }

        synchronized (this) {
            TransactionLogRecord tlog = new TransactionLogRecord(status, gtrid, uniqueNames);
            boolean written = activeTla.writeLog(tlog);
            if (!written) {
                // time to swap log files
                swapJournalFiles();

                written = activeTla.writeLog(tlog);
                if (!written)
                    throw new IOException("no room to write log to journal even after swap, circular collision avoided");
            }
        } //synchronized
    }

    /**
     * Force active log file to synchronize with the underlying disk device.
     * @throws IOException
     */
    public void force() throws IOException {
        if (activeTla == null)
            throw new IOException("cannot force log writing, disk logger is not open");

        activeTla.force();
    }

    /**
     * Open the disk journal. Files are checked for integrity and DiskJournal will refuse to open corrupted log files.
     * If files are not present on disk, this method will create and pre-allocate them.
     * @throws IOException
     */
    public synchronized void open() throws IOException {
        if (activeTla != null) {
            log.warn("disk journal already open");
            return;
        }

        File file1 = new File(TransactionManagerServices.getConfiguration().getLogPart1Filename());
        File file2 = new File(TransactionManagerServices.getConfiguration().getLogPart2Filename());

        if (!file1.exists() && !file2.exists()) {
            log.debug("creation of log files");
            createLogfile(file2, TransactionManagerServices.getConfiguration().getMaxLogSizeInMb());
            // let the clock run a little before creating the 2nd log file to make the timestamp headers not the same
            try { Thread.sleep(10); } catch (InterruptedException ex) { /* ignore */ }
            createLogfile(file1, TransactionManagerServices.getConfiguration().getMaxLogSizeInMb());
        }

        if (file1.length() != file2.length()) {
            if (!TransactionManagerServices.getConfiguration().isSkipCorruptedLogs())
                throw new IOException("transaction log files are not of the same length, assuming they're corrupt");
            log.error("transaction log files are not of the same length: corrupted files?");
        }

        long maxFileLength = Math.max(file1.length(), file2.length());
        if (log.isDebugEnabled()) log.debug("disk journal files max length: " + maxFileLength);

        tla1 = new TransactionLogAppender(file1, maxFileLength);
        tla2 = new TransactionLogAppender(file2, maxFileLength);

        byte cleanStatus = pickActiveJournalFile(tla1, tla2);
        if (cleanStatus != TransactionLogHeader.CLEAN_LOG_STATE) {
            log.warn("active log file is unclean, did you call BitronixTransactionManager.shutdown() at the end of the last run?");
        }

        if (log.isDebugEnabled()) log.debug("disk journal opened");
    }

    /**
     * Close the disk journal and the underlying files.
     * @throws IOException
     */
    public synchronized void close() throws IOException {
        if (activeTla == null) {
            return;
        }

        try {
            tla1.close();
        } catch (IOException ex) {
            log.error("cannot close " + tla1, ex);
        }
        tla1 = null;
        try {
            tla2.close();
        } catch (IOException ex) {
            log.error("cannot close " + tla2, ex);
        }
        tla2 = null;
        activeTla = null;

        log.debug("disk journal closed");
    }

    public void shutdown() {
        try {
            close();
        } catch (IOException ex) {
            log.error("error shutting down disk journal. Transaction log integrity could be compromised!", ex);
        }
    }

    /**
     * Collect all dangling records of the active log file.
     * @return a Map using Uid objects GTRID as key and {@link TransactionLogRecord} as value
     * @throws IOException
     */
    public Map collectDanglingRecords() throws IOException {
        if (activeTla == null)
            throw new IOException("cannot collect dangling records, disk logger is not open");
        return collectDanglingRecords(activeTla);
    }

    /*
     * Internal impl.
     */

    /**
     * Create a fresh log file on disk. If the specified file already exists it will be deleted then recreated.
     * @param logfile the file to create
     * @param maxLogSizeInMb the file size in megabytes to preallocate
     * @throws java.io.IOException in case of disk IO failure.
     */
    private static void createLogfile(File logfile, int maxLogSizeInMb) throws IOException {
        if (logfile.isDirectory())
            throw new IOException("log file is referring to a directory: " + logfile.getAbsolutePath());
        if (logfile.exists()) {
            boolean deleted = logfile.delete();
            if (!deleted)
                throw new IOException("log file exists but cannot be overwritten: " + logfile.getAbsolutePath());
        }
        if (logfile.getParentFile() != null) {
            logfile.getParentFile().mkdirs();
        }

        RandomAccessFile raf = null;
        try {<commit_msg>javadoc cleanup<commit_after>package bitronix.tm.journal;

import bitronix.tm.BitronixXid;
import bitronix.tm.TransactionManagerServices;
import bitronix.tm.utils.Decoder;
import bitronix.tm.utils.Uid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.transaction.Status;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.*;

/**
 * Simple implementation of a journal that writes on a two-files disk log.
 * <p>Files are pre-allocated in size, never grow and when the first one is full, dangling records are copied to the
 * second file and logging starts again on the latter.</p>
 * <p>This implementation is not highly efficient but quite robust and simple. It is based on one of the implementations
 * proposed by <NAME>.</p>
 * <p>Configurable properties are all starting with <code>bitronix.tm.journal.disk</code>.</p>
 * <p>&copy; <a href="http://www.bitronix.be">Bitronix Software</a></p>
 *
 * @see bitronix.tm.Configuration
 * @see <a href="http://jroller.com/page/pyrasun?entry=xa_exposed_part_iii_the">XA Exposed, Part III: The Implementor's Notebook</a>
 * @author lorban
 */
public class DiskJournal implements Journal {

    private final static Logger log = LoggerFactory.getLogger(DiskJournal.class);

    /**
     * The active log appender. This is exactly the same reference as tla1 or tla2 depending on which one is
     * currently active
     */
    private TransactionLogAppender activeTla;

    /**
     * The transaction log appender writing on the 1st file
     */
    private TransactionLogAppender tla1;

    /**
     * The transaction log appender writing on the 2nd file
     */
    private TransactionLogAppender tla2;


    /**
     * Create an uninitialized disk journal. You must call open() prior you can use it.
     */
    public DiskJournal() {
    }

    /**
     * Log a new transaction status to journal. Note that the DiskJournal will not check the flow of the transaction.
     * If you call this method with erroneous data, it will be added to the journal anyway.
     * @param status transaction status to log. See {@link javax.transaction.Status} constants.
     * @param gtrid raw GTRID of the transaction.
     * @param uniqueNames unique names of the {@link bitronix.tm.resource.common.ResourceBean}s participating in
     * this transaction.
     * @throws java.io.IOException in case of disk IO failure or if the disk journal is not open.
     */
    public void log(int status, Uid gtrid, Set uniqueNames) throws IOException {
        if (activeTla == null)
            throw new IOException("cannot write log, disk logger is not open");

        if (TransactionManagerServices.getConfiguration().isFilterLogStatus()) {
            if (status != Status.STATUS_COMMITTING && status != Status.STATUS_COMMITTED && status != Status.STATUS_UNKNOWN) {
                if (log.isDebugEnabled()) log.debug("filtered out write to log for status " + Decoder.decodeStatus(status));
                return;
            }
        }

        synchronized (this) {
            TransactionLogRecord tlog = new TransactionLogRecord(status, gtrid, uniqueNames);
            boolean written = activeTla.writeLog(tlog);
            if (!written) {
                // time to swap log files
                swapJournalFiles();

                written = activeTla.writeLog(tlog);
                if (!written)
                    throw new IOException("no room to write log to journal even after swap, circular collision avoided");
            }
        } //synchronized
    }

    /**
     * Force active log file to synchronize with the underlying disk device.
     * @throws java.io.IOException in case of disk IO failure or if the disk journal is not open.
     */
    public void force() throws IOException {
        if (activeTla == null)
            throw new IOException("cannot force log writing, disk logger is not open");

        activeTla.force();
    }

    /**
     * Open the disk journal. Files are checked for integrity and DiskJournal will refuse to open corrupted log files.
     * If files are not present on disk, this method will create and pre-allocate them.
     * @throws java.io.IOException in case of disk IO failure.
     */
    public synchronized void open() throws IOException {
        if (activeTla != null) {
            log.warn("disk journal already open");
            return;
        }

        File file1 = new File(TransactionManagerServices.getConfiguration().getLogPart1Filename());
        File file2 = new File(TransactionManagerServices.getConfiguration().getLogPart2Filename());

        if (!file1.exists() && !file2.exists()) {
            log.debug("creation of log files");
            createLogfile(file2, TransactionManagerServices.getConfiguration().getMaxLogSizeInMb());
            // let the clock run a little before creating the 2nd log file to make the timestamp headers not the same
            try { Thread.sleep(10); } catch (InterruptedException ex) { /* ignore */ }
            createLogfile(file1, TransactionManagerServices.getConfiguration().getMaxLogSizeInMb());
        }

        if (file1.length() != file2.length()) {
            if (!TransactionManagerServices.getConfiguration().isSkipCorruptedLogs())
                throw new IOException("transaction log files are not of the same length, assuming they're corrupt");
            log.error("transaction log files are not of the same length: corrupted files?");
        }

        long maxFileLength = Math.max(file1.length(), file2.length());
        if (log.isDebugEnabled()) log.debug("disk journal files max length: " + maxFileLength);

        tla1 = new TransactionLogAppender(file1, maxFileLength);
        tla2 = new TransactionLogAppender(file2, maxFileLength);

        byte cleanStatus = pickActiveJournalFile(tla1, tla2);
        if (cleanStatus != TransactionLogHeader.CLEAN_LOG_STATE) {
            log.warn("active log file is unclean, did you call BitronixTransactionManager.shutdown() at the end of the last run?");
        }

        if (log.isDebugEnabled()) log.debug("disk journal opened");
    }

    /**
     * Close the disk journal and the underlying files.
     * @throws java.io.IOException in case of disk IO failure.
     */
    public synchronized void close() throws IOException {
        if (activeTla == null) {
            return;
        }

        try {
            tla1.close();
        } catch (IOException ex) {
            log.error("cannot close " + tla1, ex);
        }
        tla1 = null;
        try {
            tla2.close();
        } catch (IOException ex) {
            log.error("cannot close " + tla2, ex);
        }
        tla2 = null;
        activeTla = null;

        log.debug("disk journal closed");
    }

    public void shutdown() {
        try {
            close();
        } catch (IOException ex) {
            log.error("error shutting down disk journal. Transaction log integrity could be compromised!", ex);
        }
    }

    /**
     * Collect all dangling records of the active log file.
     * @return a Map using Uid objects GTRID as key and {@link TransactionLogRecord} as value
     * @throws java.io.IOException in case of disk IO failure or if the disk journal is not open.
     */
    public Map collectDanglingRecords() throws IOException {
        if (activeTla == null)
            throw new IOException("cannot collect dangling records, disk logger is not open");
        return collectDanglingRecords(activeTla);
    }

    /*
     * Internal impl.
     */

    /**
     * Create a fresh log file on disk. If the specified file already exists it will be deleted then recreated.
     * @param logfile the file to create
     * @param maxLogSizeInMb the file size in megabytes to preallocate
     * @throws java.io.IOException in case of disk IO failure.
     */
    private static void createLogfile(File logfile, int maxLogSizeInMb) throws IOException {
        if (logfile.isDirectory())
            throw new IOException("log file is referring to a directory: " + logfile.getAbsolutePath());
        if (logfile.exists()) {
            boolean deleted = logfile.delete();
            if (!deleted)
                throw new IOException("log file exists but cannot be overwritten: " + logfile.getAbsolutePath());
        }
        if (logfile.getParentFile() != null) {
            logfile.getParentFile().mkdirs();
        }

        RandomAccessFile raf = null;
        try {
{'fraction_non_alphanumeric': 0.09200024005281161, 'fraction_numerical': 0.004200924203324731, 'mean_word_length': 2.5606837606837605, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2353383', 'n_tokens_mistral': 4422, 'n_tokens_neox': 4283, 'n_words': 1539}