// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. 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; import java.util.HashSet; import java.util.Set; import de.ugoe.cs.util.StringTools; import de.ugoe.cs.util.console.Console; /** *
* 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 *
* 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 webAppId the ID of the web application used by the client * @param clientId the ID of the client, for which this writer logs */ public HtmlMonitorOutputWriter(String logFileBaseDir, String webAppId, String clientId) { if (logFileBaseDir == null) { this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR); } else { this.logFileBaseDir = new File(logFileBaseDir); } this.webAppId = webAppId; 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, webAppId); clientLogDir = new File(clientLogDir, clientId); if (!clientLogDir.exists()) { if (!clientLogDir.mkdirs()) { throw new HtmlMonitorException("client log file directory " + clientLogDir + " can not be created"); } } else if (!clientLogDir.isDirectory()) { throw new HtmlMonitorException("client log file directory " + clientLogDir + " already exists as a file"); } handleOldLogFiles(new File(logFileBaseDir, clientId), clientLogDir); 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, HtmlGUIElement guiStructure, HtmlEvent[] events) { if (outputWriter == null) { throw new IllegalStateException("not initialized. Call init() first"); } if (guiStructure != null) { dumpGuiStructure(guiStructure); } 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(); } /** ** dumps the GUI structure provided by the parameter into the log file. Calls itself * recursively to traverse the GUI structure. *
* * @param guiStructure the GUI structure to be logged */ private void dumpGuiStructure(HtmlGUIElement guiStructure) { if (!loggedGUIElements.contains(guiStructure)) { outputWriter.print("* 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) { outputWriter.print("* dumps a parameter with the given name and value to the log file. The result is a * tag named param with a name attribute and a value attribute. The value is transformed * to a String if it is no String already. Furthermore, an XML entity replacement is performed * if required. *
* * @param name the name of the parameter to be dumped * @param value the value of the parameter to be dumped */ private void dumpParam(String name, Object value) { if (value == null) { return; } String val; if (value instanceof String) { val = (String) value; } else { val = String.valueOf(value); } 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()); if (!logFile.renameTo(checkFile)) { Console.printerrln("could not rename log file " + logFile + " to " + checkFile + ". Will not perform log rotation."); } else { 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")); outputWriter.println(""); outputWriter.println("* closed the current writer if it is open. *
*/ private void closeLogWriter() { if (outputWriter != null) { outputWriter.println("* this method moves old logfiles of the same client resisting in the wrong old directory * structure to the new one. *
* * @param file * @param clientLogDir */ private void handleOldLogFiles(File oldLogDir, File newLogDir) { if (oldLogDir.exists() && oldLogDir.isDirectory()) { for (File oldLogFile : oldLogDir.listFiles()) { oldLogFile.renameTo(new File(newLogDir, oldLogFile.getName())); } oldLogDir.delete(); } } /* (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; } }