/* The contents of this file are subject to the license and copyright terms
* detailed in the license directory at the root of the source tree (also
* available online at http://fedora-commons.org/license/).
*/
package org.fcrepo.server.utilities.status;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* The file consists of one or more serialized <code>ServerStatusMessage</code>s.
* The format of the file is simple:
*
* <pre>
* [STATUS]
* State
* Date
* [/STATUS]
* [STATUS]
* State
* Date
* DetailLine1
* DetailLine2
* [/STATUS]
* </pre>
*/
public class ServerStatusFile {
public static final String FILENAME = "status";
public static final String BEGIN_LINE = "[STATUS]";
public static final String END_LINE = "[/STATUS]";
private final File _file;
public ServerStatusFile(File serverHome)
throws Exception {
if (!serverHome.isDirectory()) {
throw new Exception("Server home directory not found: "
+ serverHome.getPath());
}
_file = new File(serverHome, FILENAME);
}
public String getPath() {
return _file.getPath();
}
public synchronized void clear() throws Exception {
if (_file.exists()) {
boolean deleted = _file.delete();
if (!deleted) {
throw new Exception("Failed to delete server status file: "
+ _file.getPath());
}
}
}
public synchronized boolean exists() {
return _file.exists();
}
public synchronized void appendError(ServerState state, Throwable detail)
throws Exception {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter, true);
detail.printStackTrace(writer);
writer.close();
append(state, stringWriter.toString());
}
public synchronized void append(ServerState state, String detail)
throws Exception {
ServerStatusMessage message =
new ServerStatusMessage(state, null, detail);
FileOutputStream out = null;
FileChannel channel = null;
FileLock lock = null;
try {
out = new FileOutputStream(_file, true); // append mode
channel = out.getChannel();
lock = channel.lock(); // block till we get an OS-level exclusive lock
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out));
serialize(message, writer);
writer.close();
} catch (IOException ioe) {
throw new Exception("Error opening server status file for writing: "
+ _file.getPath(),
ioe);
} finally {
if (lock != null) {
try {
lock.release();
} catch (Exception e) {
}
}
if (channel != null) {
try {
channel.close();
} catch (Exception e) {
}
}
if (out != null) {
try {
out.close();
} catch (Exception e) {
}
}
}
}
/**
* Get all messages in the status file, or only those after the given
* message if it is non-null. If the status file doesn't exist or can't be
* parsed, throw an exception.
*/
public synchronized ServerStatusMessage[] getMessages(ServerStatusMessage afterMessage)
throws Exception {
boolean sawAfterMessage;
String afterMessageString = null;
if (afterMessage == null) {
sawAfterMessage = true;
} else {
sawAfterMessage = false;
afterMessageString = afterMessage.toString();
}
FileInputStream in = null;
try {
in = new FileInputStream(_file);
BufferedReader reader =
new BufferedReader(new InputStreamReader(in));
List<ServerStatusMessage> messages =
new ArrayList<ServerStatusMessage>();
ServerStatusMessage message = getNextMessage(reader);
while (message != null) {
if (!sawAfterMessage) {
if (message.toString().equals(afterMessageString)) {
sawAfterMessage = true;
}
} else {
messages.add(message);
}
message = getNextMessage(reader);
}
return messages.toArray(STATUS_MSG_ARRAY_TYPE);
} catch (IOException ioe) {
throw new Exception("Error opening server status file for reading: "
+ _file.getPath(),
ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
}
}
}
}
private static final ServerStatusMessage[] STATUS_MSG_ARRAY_TYPE =
new ServerStatusMessage[0];
private void serialize(ServerStatusMessage message, PrintWriter writer)
throws Exception {
writer.println(BEGIN_LINE);
writer.println(message.getState().toString());
writer.println(ServerStatusMessage.dateToString(message.getDate()));
if (message.getDetail() != null) {
writer.println(message.getDetail());
}
writer.println(END_LINE);
writer.println();
}
// return the next message, or null if there are no more messages in the file
private ServerStatusMessage getNextMessage(BufferedReader reader)
throws Exception {
boolean messageStarted = false;
String line = reader.readLine();
while (line != null && !messageStarted) {
if (line.equals(BEGIN_LINE)) {
messageStarted = true;
} else {
line = reader.readLine();
}
}
if (messageStarted) {
// get and parse first two required lines
ServerState state = ServerState.fromString(getNextLine(reader));
Date time = ServerStatusMessage.stringToDate(getNextLine(reader));
String detail = null;
// read optional detail lines till END_LINE
line = getNextLine(reader);
if (!line.equals(END_LINE)) {
StringBuffer buf = new StringBuffer();
while (!line.equals(END_LINE)) {
buf.append(line + "\n");
line = getNextLine(reader);
}
detail = buf.toString();
}
return new ServerStatusMessage(state, time, detail);
} else {
return null;
}
}
// get the next line or throw an exception if eof was reached
private String getNextLine(BufferedReader reader) throws Exception {
String line = reader.readLine();
if (line != null) {
return line;
} else {
throw new Exception("Error parsing server status file (unexpectedly ended): "
+ _file.getPath());
}
}
}