| 1 | // Copyright 2012 Georg-August-Universität Göttingen, Germany |
|---|
| 2 | // |
|---|
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
|---|
| 4 | // you may not use this file except in compliance with the License. |
|---|
| 5 | // You may obtain a copy of the License at |
|---|
| 6 | // |
|---|
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
|---|
| 8 | // |
|---|
| 9 | // Unless required by applicable law or agreed to in writing, software |
|---|
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
|---|
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|---|
| 12 | // See the License for the specific language governing permissions and |
|---|
| 13 | // limitations under the License. |
|---|
| 14 | |
|---|
| 15 | package de.ugoe.cs.autoquest.htmlmonitor; |
|---|
| 16 | |
|---|
| 17 | import java.io.File; |
|---|
| 18 | import java.io.FileOutputStream; |
|---|
| 19 | import java.io.IOException; |
|---|
| 20 | import java.io.OutputStreamWriter; |
|---|
| 21 | import java.io.PrintWriter; |
|---|
| 22 | import java.text.DecimalFormat; |
|---|
| 23 | import java.util.HashSet; |
|---|
| 24 | import java.util.Set; |
|---|
| 25 | |
|---|
| 26 | import de.ugoe.cs.util.StringTools; |
|---|
| 27 | import de.ugoe.cs.util.console.Console; |
|---|
| 28 | |
|---|
| 29 | /** |
|---|
| 30 | * <p> |
|---|
| 31 | * dumps messages to a log file belonging to a specific client id. In the provided base log |
|---|
| 32 | * directory, it creates a subdirectory with the client id. In this directory it creates |
|---|
| 33 | * appropriate log files. The name of each finished log file starts with the "htmlmonitor_" |
|---|
| 34 | * followed by the client id and an index of the log file. An unfinished log file has no index yet. |
|---|
| 35 | * A log file is finished if |
|---|
| 36 | * <ul> |
|---|
| 37 | * <li>the client session is closed by a timeout</li> |
|---|
| 38 | * <li>the HTML monitor is normally shut down</li> |
|---|
| 39 | * <li>on startup an unfinished log file is detected.</li> |
|---|
| 40 | * <li>the {@link #MAXIMUM_LOG_FILE_SIZE} is reached</li> |
|---|
| 41 | * </ul> |
|---|
| 42 | * </p> |
|---|
| 43 | * |
|---|
| 44 | * @author Patrick Harms |
|---|
| 45 | * @version 1.0 |
|---|
| 46 | * |
|---|
| 47 | */ |
|---|
| 48 | public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener { |
|---|
| 49 | |
|---|
| 50 | /** |
|---|
| 51 | * the maximum size of an individual log file |
|---|
| 52 | */ |
|---|
| 53 | private static final int MAXIMUM_LOG_FILE_SIZE = 100000000; |
|---|
| 54 | |
|---|
| 55 | /** |
|---|
| 56 | * the default log base directory if none is provided through the constructor |
|---|
| 57 | */ |
|---|
| 58 | private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs"; |
|---|
| 59 | |
|---|
| 60 | /** |
|---|
| 61 | * the currently used log file base directory |
|---|
| 62 | */ |
|---|
| 63 | private File logFileBaseDir; |
|---|
| 64 | |
|---|
| 65 | /** |
|---|
| 66 | * the id of the web application used by the client |
|---|
| 67 | */ |
|---|
| 68 | private String webAppId; |
|---|
| 69 | |
|---|
| 70 | /** |
|---|
| 71 | * the id of the client of which all messages are logged through this writer |
|---|
| 72 | */ |
|---|
| 73 | private String clientId; |
|---|
| 74 | |
|---|
| 75 | /** |
|---|
| 76 | * the log file into which all messages are currently written |
|---|
| 77 | */ |
|---|
| 78 | private File logFile; |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * an output writer to be used for writing into the log file |
|---|
| 82 | */ |
|---|
| 83 | private PrintWriter outputWriter; |
|---|
| 84 | |
|---|
| 85 | /** |
|---|
| 86 | * the time stamp of the last action taken on this writer (such as logging a message) |
|---|
| 87 | */ |
|---|
| 88 | private long lastUpdate; |
|---|
| 89 | |
|---|
| 90 | /** |
|---|
| 91 | * the GUI elements, that were already logged and need therefore not be logged again into |
|---|
| 92 | * the same file |
|---|
| 93 | */ |
|---|
| 94 | private Set<HtmlGUIElement> loggedGUIElements = new HashSet<HtmlGUIElement>(); |
|---|
| 95 | |
|---|
| 96 | /** |
|---|
| 97 | * <p> |
|---|
| 98 | * initializes the writer with the log file base directory and the id of the client for which |
|---|
| 99 | * this writer logs the messages. |
|---|
| 100 | * </p> |
|---|
| 101 | * |
|---|
| 102 | * @param logFileBaseDir the log file base directory, or null if the default directory shall |
|---|
| 103 | * be taken |
|---|
| 104 | * @param webAppId the ID of the web application used by the client |
|---|
| 105 | * @param clientId the ID of the client, for which this writer logs |
|---|
| 106 | */ |
|---|
| 107 | public HtmlMonitorOutputWriter(String logFileBaseDir, String webAppId, String clientId) { |
|---|
| 108 | if (logFileBaseDir == null) { |
|---|
| 109 | this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR); |
|---|
| 110 | } |
|---|
| 111 | else { |
|---|
| 112 | this.logFileBaseDir = new File(logFileBaseDir); |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | this.webAppId = webAppId; |
|---|
| 116 | this.clientId = clientId; |
|---|
| 117 | |
|---|
| 118 | lastUpdate = System.currentTimeMillis(); |
|---|
| 119 | } |
|---|
| 120 | |
|---|
| 121 | /* (non-Javadoc) |
|---|
| 122 | * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init() |
|---|
| 123 | */ |
|---|
| 124 | @Override |
|---|
| 125 | public synchronized void init() throws HtmlMonitorException { |
|---|
| 126 | if (outputWriter != null) { |
|---|
| 127 | throw new IllegalStateException("already initialized. Call close() first"); |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | synchronized (HtmlMonitorOutputWriter.class) { |
|---|
| 131 | try { |
|---|
| 132 | File clientLogDir = new File(logFileBaseDir, webAppId); |
|---|
| 133 | clientLogDir = new File(clientLogDir, clientId); |
|---|
| 134 | |
|---|
| 135 | if (!clientLogDir.exists()) { |
|---|
| 136 | if (!clientLogDir.mkdirs()) { |
|---|
| 137 | throw new HtmlMonitorException("client log file directory " + clientLogDir + |
|---|
| 138 | " can not be created"); |
|---|
| 139 | } |
|---|
| 140 | } |
|---|
| 141 | else if (!clientLogDir.isDirectory()) { |
|---|
| 142 | throw new HtmlMonitorException("client log file directory " + clientLogDir + |
|---|
| 143 | " already exists as a file"); |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | handleOldLogFiles(new File(logFileBaseDir, clientId), clientLogDir); |
|---|
| 147 | |
|---|
| 148 | logFile = new File(clientLogDir, getLogFileName(-1)); |
|---|
| 149 | |
|---|
| 150 | if (logFile.exists()) { |
|---|
| 151 | rotateLogFile(); |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | createLogWriter(); |
|---|
| 155 | } |
|---|
| 156 | catch (IOException e) { |
|---|
| 157 | throw new HtmlMonitorException("could not open logfile " + logFile, e); |
|---|
| 158 | } |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | lastUpdate = System.currentTimeMillis(); |
|---|
| 162 | } |
|---|
| 163 | |
|---|
| 164 | /** |
|---|
| 165 | * <p> |
|---|
| 166 | * used to calculate a log file name. If the provided index is smaller 0, then no index |
|---|
| 167 | * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log". |
|---|
| 168 | * </p> |
|---|
| 169 | * |
|---|
| 170 | * @param index the index of the log file or -1 one, if no index shall be added |
|---|
| 171 | * |
|---|
| 172 | * @return the file name as described |
|---|
| 173 | */ |
|---|
| 174 | private String getLogFileName(int index) { |
|---|
| 175 | String result = "htmlmonitor_" + clientId; |
|---|
| 176 | |
|---|
| 177 | if (index >= 0) { |
|---|
| 178 | result += "_" + new DecimalFormat("000" ).format(index); |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | result += ".log"; |
|---|
| 182 | |
|---|
| 183 | return result; |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | /* (non-Javadoc) |
|---|
| 187 | * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start() |
|---|
| 188 | */ |
|---|
| 189 | @Override |
|---|
| 190 | public synchronized void start() throws IllegalStateException, HtmlMonitorException { |
|---|
| 191 | lastUpdate = System.currentTimeMillis(); |
|---|
| 192 | } |
|---|
| 193 | |
|---|
| 194 | /* (non-Javadoc) |
|---|
| 195 | * @see HtmlMonitorMessageListener#handleMessage(HtmlClientInfos, HtmlEvent[]) |
|---|
| 196 | */ |
|---|
| 197 | @Override |
|---|
| 198 | public synchronized void handleMessage(HtmlClientInfos clientInfos, |
|---|
| 199 | HtmlGUIElement guiStructure, |
|---|
| 200 | HtmlEvent[] events) |
|---|
| 201 | { |
|---|
| 202 | if (outputWriter == null) { |
|---|
| 203 | throw new IllegalStateException("not initialized. Call init() first"); |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | for (HtmlEvent event : events) { |
|---|
| 207 | dumpEvent(event); |
|---|
| 208 | } |
|---|
| 209 | |
|---|
| 210 | outputWriter.flush(); |
|---|
| 211 | |
|---|
| 212 | try { |
|---|
| 213 | considerLogRotate(); |
|---|
| 214 | } |
|---|
| 215 | catch (IOException e) { |
|---|
| 216 | throw new IllegalStateException("could not perform log rotation: " + e, e); |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | lastUpdate = System.currentTimeMillis(); |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | /** |
|---|
| 223 | * <p> |
|---|
| 224 | * formats a received event and writes it to the log file. One event results in one line |
|---|
| 225 | * in the log file containing all infos of the event. |
|---|
| 226 | * </p> |
|---|
| 227 | * |
|---|
| 228 | * @param event to be written to the log file |
|---|
| 229 | */ |
|---|
| 230 | private void dumpEvent(HtmlEvent event) { |
|---|
| 231 | if (event.getTarget() != null) { |
|---|
| 232 | ensureGuiElementDumped(event.getTarget()); |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | outputWriter.print("<event type=\""); |
|---|
| 236 | outputWriter.print(event.getEventType()); |
|---|
| 237 | outputWriter.println("\">"); |
|---|
| 238 | |
|---|
| 239 | if (event.getCoordinates() != null) { |
|---|
| 240 | dumpParam("X", event.getCoordinates()[0]); |
|---|
| 241 | dumpParam("Y", event.getCoordinates()[1]); |
|---|
| 242 | } |
|---|
| 243 | |
|---|
| 244 | dumpParam("key", event.getKey()); |
|---|
| 245 | |
|---|
| 246 | if (event.getScrollPosition() != null) { |
|---|
| 247 | dumpParam("scrollX", event.getScrollPosition()[0]); |
|---|
| 248 | dumpParam("scrollY", event.getScrollPosition()[1]); |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | dumpParam("selectedValue", event.getSelectedValue()); |
|---|
| 252 | |
|---|
| 253 | if (event.getTarget() != null) { |
|---|
| 254 | dumpParam("target", event.getTarget().getId()); |
|---|
| 255 | } |
|---|
| 256 | else { |
|---|
| 257 | dumpParam("targetDocument", event.getTargetDocument().getId()); |
|---|
| 258 | dumpParam("targetDOMPath", event.getTargetDOMPath()); |
|---|
| 259 | } |
|---|
| 260 | dumpParam("timestamp", event.getTime()); |
|---|
| 261 | |
|---|
| 262 | outputWriter.println("</event>"); |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | /** |
|---|
| 266 | * <p> |
|---|
| 267 | * ensures that a GUI element, its parents as well as all other GUI elements on the same page |
|---|
| 268 | * are dumped |
|---|
| 269 | * </p> |
|---|
| 270 | * |
|---|
| 271 | * @param guiStructure the GUI structure to be logged |
|---|
| 272 | */ |
|---|
| 273 | private void ensureGuiElementDumped(HtmlGUIElement guiElement) { |
|---|
| 274 | if (!loggedGUIElements.contains(guiElement)) { |
|---|
| 275 | |
|---|
| 276 | // determine the document as the whole document needs to be dumped. Ensure also that |
|---|
| 277 | // the server is dumped |
|---|
| 278 | |
|---|
| 279 | HtmlGUIElement parent = guiElement; |
|---|
| 280 | HtmlDocument document = null; |
|---|
| 281 | HtmlServer server = null; |
|---|
| 282 | |
|---|
| 283 | while (parent != null) { |
|---|
| 284 | if (parent instanceof HtmlDocument) { |
|---|
| 285 | document = (HtmlDocument) parent; |
|---|
| 286 | } |
|---|
| 287 | else if (parent instanceof HtmlServer) { |
|---|
| 288 | server = (HtmlServer) parent; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | parent = parent.getParent(); |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | if (server != null) { |
|---|
| 295 | dumpGuiElement(server); |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | if (document != null) { |
|---|
| 299 | dumpGuiStructure(document); |
|---|
| 300 | } |
|---|
| 301 | } |
|---|
| 302 | } |
|---|
| 303 | |
|---|
| 304 | /** |
|---|
| 305 | * <p> |
|---|
| 306 | * dumps the GUI structure provided by the parameter into the log file. Calls itself |
|---|
| 307 | * recursively to traverse the GUI structure. |
|---|
| 308 | * </p> |
|---|
| 309 | * |
|---|
| 310 | * @param guiStructure the GUI structure to be logged |
|---|
| 311 | */ |
|---|
| 312 | private void dumpGuiStructure(HtmlGUIElement guiStructure) { |
|---|
| 313 | dumpGuiElement(guiStructure); |
|---|
| 314 | |
|---|
| 315 | if (guiStructure.getChildren() != null) { |
|---|
| 316 | for (HtmlGUIElement child : guiStructure.getChildren()) { |
|---|
| 317 | dumpGuiStructure(child); |
|---|
| 318 | } |
|---|
| 319 | } |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | /** |
|---|
| 323 | * <p> |
|---|
| 324 | * dumps the GUI element provided by the parameter into the log file. |
|---|
| 325 | * </p> |
|---|
| 326 | * |
|---|
| 327 | * @param guiElement the GUI element to be logged |
|---|
| 328 | */ |
|---|
| 329 | private void dumpGuiElement(HtmlGUIElement guiElement) { |
|---|
| 330 | if (!loggedGUIElements.contains(guiElement)) { |
|---|
| 331 | outputWriter.print("<component id=\""); |
|---|
| 332 | outputWriter.print(guiElement.getId()); |
|---|
| 333 | outputWriter.println("\">"); |
|---|
| 334 | |
|---|
| 335 | if (guiElement instanceof HtmlServer) { |
|---|
| 336 | dumpParam("host", ((HtmlServer) guiElement).getName()); |
|---|
| 337 | dumpParam("port", ((HtmlServer) guiElement).getPort()); |
|---|
| 338 | } |
|---|
| 339 | else if (guiElement instanceof HtmlDocument) { |
|---|
| 340 | dumpParam("path", ((HtmlDocument) guiElement).getPath()); |
|---|
| 341 | dumpParam("query", ((HtmlDocument) guiElement).getQuery()); |
|---|
| 342 | dumpParam("title", ((HtmlDocument) guiElement).getTitle()); |
|---|
| 343 | } |
|---|
| 344 | else if (guiElement instanceof HtmlPageElement) { |
|---|
| 345 | dumpParam("tagname", ((HtmlPageElement) guiElement).getTagName()); |
|---|
| 346 | dumpParam("htmlid", ((HtmlPageElement) guiElement).getHtmlId()); |
|---|
| 347 | dumpParam("index", ((HtmlPageElement) guiElement).getIndex()); |
|---|
| 348 | } |
|---|
| 349 | |
|---|
| 350 | if (guiElement.getParent() != null) { |
|---|
| 351 | dumpParam("parent", guiElement.getParent().getId()); |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | outputWriter.println("</component>"); |
|---|
| 355 | |
|---|
| 356 | loggedGUIElements.add(guiElement); |
|---|
| 357 | } |
|---|
| 358 | } |
|---|
| 359 | |
|---|
| 360 | /** |
|---|
| 361 | * <p> |
|---|
| 362 | * dumps a parameter with the given name and value to the log file. The result is a |
|---|
| 363 | * tag named param with a name attribute and a value attribute. The value is transformed |
|---|
| 364 | * to a String if it is no String already. Furthermore, an XML entity replacement is performed |
|---|
| 365 | * if required. |
|---|
| 366 | * </p> |
|---|
| 367 | * |
|---|
| 368 | * @param name the name of the parameter to be dumped |
|---|
| 369 | * @param value the value of the parameter to be dumped |
|---|
| 370 | */ |
|---|
| 371 | private void dumpParam(String name, Object value) { |
|---|
| 372 | if (value == null) { |
|---|
| 373 | return; |
|---|
| 374 | } |
|---|
| 375 | |
|---|
| 376 | String val; |
|---|
| 377 | |
|---|
| 378 | if (value instanceof String) { |
|---|
| 379 | val = (String) value; |
|---|
| 380 | } |
|---|
| 381 | else { |
|---|
| 382 | val = String.valueOf(value); |
|---|
| 383 | } |
|---|
| 384 | |
|---|
| 385 | outputWriter.print(" <param name=\""); |
|---|
| 386 | outputWriter.print(name); |
|---|
| 387 | outputWriter.print("\" value=\""); |
|---|
| 388 | outputWriter.print(StringTools.xmlEntityReplacement(val)); |
|---|
| 389 | outputWriter.println("\"/>"); |
|---|
| 390 | } |
|---|
| 391 | |
|---|
| 392 | /** |
|---|
| 393 | * <p> |
|---|
| 394 | * checks, if the log file exeeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current |
|---|
| 395 | * log file is closed, the next log file name is determined and this new file is opend for |
|---|
| 396 | * writing. |
|---|
| 397 | * </p> |
|---|
| 398 | */ |
|---|
| 399 | private synchronized void considerLogRotate() throws IOException { |
|---|
| 400 | if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) { |
|---|
| 401 | closeLogWriter(); |
|---|
| 402 | rotateLogFile(); |
|---|
| 403 | createLogWriter(); |
|---|
| 404 | } |
|---|
| 405 | } |
|---|
| 406 | |
|---|
| 407 | /** |
|---|
| 408 | * <p> |
|---|
| 409 | * renames the current log file to a new log file with the next available index. It further |
|---|
| 410 | * sets the current log file to the default name, i.e. without index. |
|---|
| 411 | * </p> |
|---|
| 412 | */ |
|---|
| 413 | private void rotateLogFile() { |
|---|
| 414 | File clientLogDir = logFile.getParentFile(); |
|---|
| 415 | File checkFile; |
|---|
| 416 | |
|---|
| 417 | int logFileIndex = -1; |
|---|
| 418 | do { |
|---|
| 419 | logFileIndex++; |
|---|
| 420 | |
|---|
| 421 | checkFile = new File(clientLogDir, getLogFileName(logFileIndex)); |
|---|
| 422 | } |
|---|
| 423 | while (checkFile.exists()); |
|---|
| 424 | |
|---|
| 425 | if (!logFile.renameTo(checkFile)) { |
|---|
| 426 | Console.printerrln("could not rename log file " + logFile + " to " + checkFile + |
|---|
| 427 | ". Will not perform log rotation."); |
|---|
| 428 | } |
|---|
| 429 | else { |
|---|
| 430 | logFileIndex++; |
|---|
| 431 | logFile = new File(clientLogDir, getLogFileName(-1)); |
|---|
| 432 | } |
|---|
| 433 | } |
|---|
| 434 | |
|---|
| 435 | /** |
|---|
| 436 | * <p> |
|---|
| 437 | * instantiates a writer to be used for writing messages into the log file. |
|---|
| 438 | * </p> |
|---|
| 439 | */ |
|---|
| 440 | private void createLogWriter() throws IOException { |
|---|
| 441 | FileOutputStream fis = new FileOutputStream(logFile); |
|---|
| 442 | outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8")); |
|---|
| 443 | outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
|---|
| 444 | outputWriter.println("<session>"); |
|---|
| 445 | |
|---|
| 446 | loggedGUIElements.clear(); |
|---|
| 447 | } |
|---|
| 448 | |
|---|
| 449 | /** |
|---|
| 450 | * <p> |
|---|
| 451 | * closed the current writer if it is open. |
|---|
| 452 | * </p> |
|---|
| 453 | */ |
|---|
| 454 | private void closeLogWriter() { |
|---|
| 455 | if (outputWriter != null) { |
|---|
| 456 | outputWriter.println("</session>"); |
|---|
| 457 | outputWriter.flush(); |
|---|
| 458 | outputWriter.close(); |
|---|
| 459 | outputWriter = null; |
|---|
| 460 | } |
|---|
| 461 | } |
|---|
| 462 | |
|---|
| 463 | /** |
|---|
| 464 | * <p> |
|---|
| 465 | * this method moves old logfiles of the same client resisting in the wrong old directory |
|---|
| 466 | * structure to the new one. |
|---|
| 467 | * </p> |
|---|
| 468 | * |
|---|
| 469 | * @param oldLogDir the old log directory |
|---|
| 470 | * @param newLogDir the new log directory |
|---|
| 471 | */ |
|---|
| 472 | private void handleOldLogFiles(File oldLogDir, File newLogDir) { |
|---|
| 473 | if (oldLogDir.exists() && oldLogDir.isDirectory()) { |
|---|
| 474 | boolean allFilesRenamed = true; |
|---|
| 475 | for (File oldLogFile : oldLogDir.listFiles()) { |
|---|
| 476 | allFilesRenamed &= oldLogFile.renameTo(new File(newLogDir, oldLogFile.getName())); |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | if (allFilesRenamed) { |
|---|
| 480 | if (!oldLogDir.delete()) { |
|---|
| 481 | Console.printerrln("could not move old file directory structure to new one"); |
|---|
| 482 | } |
|---|
| 483 | } |
|---|
| 484 | } |
|---|
| 485 | } |
|---|
| 486 | |
|---|
| 487 | /* (non-Javadoc) |
|---|
| 488 | * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop() |
|---|
| 489 | */ |
|---|
| 490 | @Override |
|---|
| 491 | public synchronized void stop() { |
|---|
| 492 | closeLogWriter(); |
|---|
| 493 | rotateLogFile(); |
|---|
| 494 | |
|---|
| 495 | lastUpdate = System.currentTimeMillis(); |
|---|
| 496 | } |
|---|
| 497 | |
|---|
| 498 | /** |
|---|
| 499 | * <p> |
|---|
| 500 | * return the time stamp of the last activity that happened on this writer. |
|---|
| 501 | * </p> |
|---|
| 502 | * |
|---|
| 503 | * @return as described |
|---|
| 504 | */ |
|---|
| 505 | public long getLastUpdate() { |
|---|
| 506 | return lastUpdate; |
|---|
| 507 | } |
|---|
| 508 | } |
|---|