package de.ugoe.cs.autoquest.htmlmonitor; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.text.DecimalFormat; /** *

* dumps messages to a log file belonging to a specific client id. In the provided base log * directory, it creates a subdirectory with the client id. In this directory it creates * appropriate log files. The name of each finished log file starts with the "htmlmonitor_" * followed by the client id and an index of the log file. An unfinished log file has no index yet. * A log file is finished if *

*

* * @author Patrick Harms * @version 1.0 * */ public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener { /** * the maximum size of an individual log file */ private static final int MAXIMUM_LOG_FILE_SIZE = 10000000; /** * the default log base directory if none is provided through the constructor */ private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs"; /** * the currently used log file base directory */ private File logFileBaseDir; /** * the is of the client of which all messages are logged through this writer */ private String clientId; /** * the log file into which all messages are currently written */ private File logFile; /** * an output writer to be used for writing into the log file */ private PrintWriter outputWriter; /** * the time stamp of the last action taken on this writer (such as logging a message) */ private long lastUpdate; /** *

* initializes the writer with the log file base directory and the id of the client for which * this writer logs the messages. *

* * @param logFileBaseDir the log file base directory, or null if the default directory shall * be taken * @param clientId the ID of the client, for which this writer logs */ public HtmlMonitorOutputWriter(String logFileBaseDir, String clientId) { if (logFileBaseDir == null) { this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR); } else { this.logFileBaseDir = new File(logFileBaseDir); } this.clientId = clientId; lastUpdate = System.currentTimeMillis(); } /* (non-Javadoc) * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init() */ @Override public synchronized void init() throws HtmlMonitorException { if (outputWriter != null) { throw new IllegalStateException("already initialized. Call close() first"); } synchronized (HtmlMonitorOutputWriter.class) { try { File clientLogDir = new File(logFileBaseDir, clientId); if (!clientLogDir.exists()) { clientLogDir.mkdirs(); } else if (!clientLogDir.isDirectory()) { throw new HtmlMonitorException("client log file directory " + clientLogDir + " already exists as a file"); } logFile = new File(clientLogDir, getLogFileName(-1)); if (logFile.exists()) { rotateLogFile(); } createLogWriter(); } catch (IOException e) { throw new HtmlMonitorException("could not open logfile " + logFile, e); } } lastUpdate = System.currentTimeMillis(); } /** *

* used to calculate a log file name. If the provided index is smaller 0, then no index * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log". *

* * @param index the index of the log file or -1 one, if no index shall be added * * @return the file name as described */ private String getLogFileName(int index) { String result = "htmlmonitor_" + clientId; if (index >= 0) { result += "_" + new DecimalFormat("000" ).format(index); } result += ".log"; return result; } /* (non-Javadoc) * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start() */ @Override public synchronized void start() throws IllegalStateException, HtmlMonitorException { lastUpdate = System.currentTimeMillis(); } /* (non-Javadoc) * @see HtmlMonitorMessageListener#handleMessage(HtmlClientInfos, HtmlEvent[]) */ @Override public void handleMessage(HtmlClientInfos clientInfos, HtmlEvent[] events) { if (outputWriter == null) { throw new IllegalStateException("not initialized. Call init() first"); } for (HtmlEvent event : events) { dumpEvent(event); } outputWriter.flush(); try { considerLogRotate(); } catch (IOException e) { throw new IllegalStateException("could not perform log rotation: " + e, e); } lastUpdate = System.currentTimeMillis(); } /** *

* formats a received event and writes it to the log file. One event results in one line * in the log file containing all infos of the event. *

* * @param event to be written to the log file */ private void dumpEvent(HtmlEvent event) { dumpString(event.getClientInfos().getClientId()); outputWriter.print(' '); dumpString(event.getTime().toString()); outputWriter.print(' '); dumpString(event.getClientInfos().getTitle()); outputWriter.print(' '); dumpString(event.getClientInfos().getUrl().toString()); outputWriter.print(' '); dumpString(event.getClientInfos().getUserAgent()); outputWriter.print(' '); dumpString(event.getEventType()); outputWriter.print(' '); dumpString(event.getPath()); if (event.getCoordinates() != null) { outputWriter.print(' '); StringBuffer value = new StringBuffer(); for (int i = 0; i < event.getCoordinates().length; i++) { if (i > 0) { value.append(','); } value.append(event.getCoordinates()[i]); } dumpString(value.toString()); } if (event.getKey() != null) { outputWriter.print(' '); dumpString(event.getKey().toString()); } if (event.getScrollPosition() != null) { outputWriter.print(' '); dumpString(event.getScrollPosition().toString()); } outputWriter.println(); } /** *

* convenience method to dump a string with trailing and leading " as well as replaced * backslashes, ", and newlines *

* * @param clientId2 */ private void dumpString(String str) { outputWriter.print('"'); outputWriter.print (str.replaceAll("\\\\", "\\\\").replaceAll("\\\"", "\\\"").replaceAll("\n", " ")); outputWriter.print('"'); } /** *

* checks, if the log file exeeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current * log file is closed, the next log file name is determined and this new file is opend for * writing. *

*/ private synchronized void considerLogRotate() throws IOException { if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) { closeLogWriter(); rotateLogFile(); createLogWriter(); } } /** *

* renames the current log file to a new log file with the next available index. It further * sets the current log file to the default name, i.e. without index. *

*/ private void rotateLogFile() { File clientLogDir = logFile.getParentFile(); File checkFile; int logFileIndex = -1; do { logFileIndex++; checkFile = new File(clientLogDir, getLogFileName(logFileIndex)); } while (checkFile.exists()); logFile.renameTo(checkFile); logFileIndex++; logFile = new File(clientLogDir, getLogFileName(-1)); } /** *

* instantiates a writer to be used for writing messages into the log file. *

*/ private void createLogWriter() throws IOException { FileOutputStream fis = new FileOutputStream(logFile); outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8")); } /** *

* closed the current writer if it is open. *

*/ private void closeLogWriter() { if (outputWriter != null) { outputWriter.flush(); outputWriter.close(); outputWriter = null; } } /* (non-Javadoc) * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop() */ @Override public synchronized void stop() { closeLogWriter(); rotateLogFile(); lastUpdate = System.currentTimeMillis(); } /** *

* return the time stamp of the last activity that happened on this writer. *

* * @return as described */ public long getLastUpdate() { return lastUpdate; } }