/*
* JournalWriter.java
*
* Created on Mar 17, 2009, 9:56:20 AM
*
* Description: Provides a thread-safe journal writer.
*
* Copyright (C) Mar 17, 2009 Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.kb.journal;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.jcip.annotations.ThreadSafe;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.texai.util.TexaiException;
/** Provides a thread-safe journal writer.
*
* @author Stephen L. Reed
*/
@ThreadSafe
public final class JournalWriter {
/** the log4j logger */
private static final Logger LOGGER = Logger.getLogger(JournalWriter.class);
/** the indicator whether debug logging is enabled */
private static final boolean IS_DEBUG_LOGGING_ENABLED = LOGGER.isDebugEnabled();
/** the journal file writer dictionary, repository name --> print writer */
private static final Map<String, PrintWriter> JOURNAL_FILE_WRITER_DICTIONARY = new HashMap<>();
/** the indicator that the print writers are closed */
private static boolean arePrintWritersClosed = false;
/** the indicator whether to inefficiently, but safely, flush the buffer when each operation is written */
private boolean isWrittenImmediately = true;
/** the indicator whether this object is being unit tested */
private boolean isUnitTest = false;
/** the journal file path */
private String journalFilePath;
/** the journal requests of the current transaction */
private final List<JournalRequest> transactionJournalRequests = new ArrayList<>();
/** Constructs a new JournalWriter instance. */
public JournalWriter() {
}
/** Deletes journal files. */
public static void deleteJournalFiles() {
final File directory = new File("./journals");
if (!directory.exists()) {
LOGGER.info("creating journals directory");
final boolean wasDirectoryCreated = directory.mkdir();
if (!wasDirectoryCreated) {
throw new TexaiException("journals directory file was not created: " + directory);
}
}
assert directory.exists() : "./journals does not exist, current working directory is " + System.getProperty("user.dir");
final File[] files = directory.listFiles();
assert files != null;
for (final File file : files) {
if (!file.isHidden()) {
LOGGER.info("deleting file: " + file);
final boolean wasFileDeleted = file.delete();
if (!wasFileDeleted) {
LOGGER.warn("journal file not deleted: " + file);
}
}
}
}
/** Writes the given list of journal requests to each request's corresponding journal file in a critical section.
*
* @param journalRequests the given list of journal requests
*/
public synchronized void write(final List<JournalRequest> journalRequests) {
//Preconditions
assert journalRequests != null : "journalRequests must not be null";
transactionJournalRequests.addAll(journalRequests);
}
/** Commits the given list of journal requests to each request's corresponding journal file in a critical section. */
public synchronized void commit() {
final File directory = new File("./journals");
if (!directory.exists()) {
LOGGER.info("creating journals directory");
final boolean wasDirectoryCreated = directory.mkdir();
if (!wasDirectoryCreated) {
throw new TexaiException("journals directory was not created: " + directory);
}
}
assert directory.exists() : "./journals does not exist, current working directory is " + System.getProperty("user.dir");
final DateTime dateTime = new DateTime();
int suffixNbr = 0;
for (final JournalRequest transactionJournalRequest : transactionJournalRequests) {
PrintWriter journalFileWriter = JOURNAL_FILE_WRITER_DICTIONARY.get(transactionJournalRequest.getRepositoryName());
if (journalFileWriter == null) {
journalFilePath = "./journals/" + transactionJournalRequest.getRepositoryName() + "-" + (new DateTime()).toString().replace(':', '_') + ".jrnl";
try {
final File journalFile = new File(journalFilePath);
if (!journalFile.exists()) {
final boolean wasFileCreated = journalFile.createNewFile();
if (!wasFileCreated) {
throw new TexaiException("file was not created: " + journalFilePath);
}
}
journalFileWriter = new PrintWriter(journalFilePath);
} catch (final IOException ex) {
LOGGER.error("problem with file " + journalFilePath);
throw new TexaiException(ex);
}
JOURNAL_FILE_WRITER_DICTIONARY.put(transactionJournalRequest.getRepositoryName(), journalFileWriter);
}
final JournalEntry journalEntry = new JournalEntry(
dateTime,
++suffixNbr,
transactionJournalRequest.getOperation(),
transactionJournalRequest.getStatement());
final String journalEntryString = journalEntry.toString();
if (IS_DEBUG_LOGGING_ENABLED) {
LOGGER.debug("writing: " + journalEntryString);
}
journalFileWriter.println(journalEntryString);
if (isWrittenImmediately) {
journalFileWriter.flush();
}
}
transactionJournalRequests.clear();
}
/** Rolls back the journal requests that belong to the current transaction. */
public synchronized void rollback() {
transactionJournalRequests.clear();
}
/** Closes the journal file writers. */
public static synchronized void close() {
if (!arePrintWritersClosed) {
for (final PrintWriter journalFileWriter : JOURNAL_FILE_WRITER_DICTIONARY.values()) {
journalFileWriter.flush();
journalFileWriter.close();
}
arePrintWritersClosed = true;
JOURNAL_FILE_WRITER_DICTIONARY.clear();
}
}
/** Gets the indicator whether to inefficiently, but safely, flush the buffer when each operation is written.
*
* @return the indicator whether to inefficiently, but safely, flush the buffer when each operation is written
*/
public boolean isWrittenImmediately() {
return isWrittenImmediately;
}
/** Sets the indicator whether to inefficiently, but safely, flush the buffer when each operation is written.
*
* @param isWrittenImmediately the indicator whether to inefficiently, but safely, flush the buffer when each operation is written
*/
public void setIsWrittenImmediately(final boolean isWrittenImmediately) {
this.isWrittenImmediately = isWrittenImmediately;
}
/** Gets the indicator whether this object is being unit tested.
*
* @return the indicator whether this object is being unit tested
*/
public boolean isUnitTest() {
return isUnitTest;
}
/** Sets the indicator whether this object is being unit tested.
*
* @param isUnitTest the indicator whether this object is being unit tested
*/
public void setIsUnitTest(final boolean isUnitTest) {
this.isUnitTest = isUnitTest;
}
/** Gets the journal file path.
*
* @return the journal file path
*/
public synchronized String getJournalFilePath() {
return journalFilePath;
}
}