source: trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorOutputWriter.java @ 1075

Last change on this file since 1075 was 1069, checked in by pharms, 11 years ago
  • support of new HTML logging format
File size: 13.0 KB
Line 
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
15package de.ugoe.cs.autoquest.htmlmonitor;
16
17import java.io.File;
18import java.io.FileOutputStream;
19import java.io.IOException;
20import java.io.OutputStreamWriter;
21import java.io.PrintWriter;
22import java.text.DecimalFormat;
23
24import de.ugoe.cs.util.StringTools;
25import de.ugoe.cs.util.console.Console;
26
27/**
28 * <p>
29 * dumps messages to a log file belonging to a specific client id. In the provided base log
30 * directory, it creates a subdirectory with the client id. In this directory it creates
31 * appropriate log files. The name of each finished log file starts with the "htmlmonitor_"
32 * followed by the client id and an index of the log file. An unfinished log file has no index yet.
33 * A log file is finished if
34 * <ul>
35 *   <li>the client session is closed by a timeout</li>
36 *   <li>the HTML monitor is normally shut down</li>
37 *   <li>on startup an unfinished log file is detected.</li>
38 *   <li>the {@link #MAXIMUM_LOG_FILE_SIZE} is reached</li>
39 * </ul>
40 * </p>
41 *
42 * @author Patrick Harms
43 * @version 1.0
44 *
45 */
46public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener {
47   
48    /**
49     * the maximum size of an individual log file
50     */
51    private static final int MAXIMUM_LOG_FILE_SIZE = 10000000;
52
53    /**
54     * the default log base directory if none is provided through the constructor
55     */
56    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";
57
58    /**
59     * the currently used log file base directory
60     */
61    private File logFileBaseDir;
62
63    /**
64     * the is of the client of which all messages are logged through this writer
65     */
66    private String clientId;
67
68    /**
69     * the log file into which all messages are currently written
70     */
71    private File logFile;
72
73    /**
74     * an output writer to be used for writing into the log file
75     */
76    private PrintWriter outputWriter;
77
78    /**
79     * the time stamp of the last action taken on this writer (such as logging a message)
80     */
81    private long lastUpdate;
82
83    /**
84     * <p>
85     * initializes the writer with the log file base directory and the id of the client for which
86     * this writer logs the messages.
87     * </p>
88     *
89     * @param logFileBaseDir the log file base directory, or null if the default directory shall
90     *                       be taken
91     * @param clientId       the ID of the client, for which this writer logs
92     */
93    public HtmlMonitorOutputWriter(String logFileBaseDir, String clientId) {
94        if (logFileBaseDir == null) {
95            this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR);
96        }
97        else {
98            this.logFileBaseDir = new File(logFileBaseDir);
99        }
100       
101        this.clientId = clientId;
102       
103        lastUpdate = System.currentTimeMillis();
104    }
105
106    /* (non-Javadoc)
107     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
108     */
109    @Override
110    public synchronized void init() throws HtmlMonitorException {
111        if (outputWriter != null) {
112            throw new IllegalStateException("already initialized. Call close() first");
113        }
114       
115        synchronized (HtmlMonitorOutputWriter.class) {
116            try {
117                File clientLogDir = new File(logFileBaseDir, clientId);
118           
119                if (!clientLogDir.exists()) {
120                    if (!clientLogDir.mkdirs()) {
121                        throw new HtmlMonitorException("client log file directory " + clientLogDir +
122                                                       " can not be created");
123                    }
124                }
125                else if (!clientLogDir.isDirectory()) {
126                    throw new HtmlMonitorException("client log file directory " + clientLogDir +
127                                                   " already exists as a file");
128                }
129               
130                logFile = new File(clientLogDir, getLogFileName(-1));
131               
132                if (logFile.exists()) {
133                    rotateLogFile();
134                }
135           
136                createLogWriter();
137            }
138            catch (IOException e) {
139                throw new HtmlMonitorException("could not open logfile " + logFile, e);
140            }
141        }
142       
143        lastUpdate = System.currentTimeMillis();
144    }
145
146    /**
147     * <p>
148     * used to calculate a log file name. If the provided index is smaller 0, then no index
149     * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log".
150     * </p>
151     *
152     * @param index the index of the log file or -1 one, if no index shall be added
153     *
154     * @return the file name as described
155     */
156    private String getLogFileName(int index) {
157        String result = "htmlmonitor_" + clientId;
158       
159        if (index >= 0) {
160            result += "_" + new DecimalFormat("000" ).format(index);
161        }
162       
163        result += ".log";
164       
165        return result;
166    }
167
168    /* (non-Javadoc)
169     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
170     */
171    @Override
172    public synchronized void start() throws IllegalStateException, HtmlMonitorException {
173        lastUpdate = System.currentTimeMillis();
174    }
175
176    /* (non-Javadoc)
177     * @see HtmlMonitorMessageListener#handleMessage(HtmlClientInfos, HtmlEvent[])
178     */
179    @Override
180    public void handleMessage(HtmlClientInfos clientInfos,
181                              HtmlGUIElement  guiStructure,
182                              HtmlEvent[]     events)
183    {
184        if (outputWriter == null) {
185            throw new IllegalStateException("not initialized. Call init() first");
186        }
187       
188        if (guiStructure != null) {
189            dumpGuiStructure(guiStructure);
190        }
191       
192        for (HtmlEvent event : events) {
193            dumpEvent(event);
194        }
195       
196        outputWriter.flush();
197       
198        try {
199            considerLogRotate();
200        }
201        catch (IOException e) {
202            throw new IllegalStateException("could not perform log rotation: " + e, e);
203        }
204       
205        lastUpdate = System.currentTimeMillis();
206    }
207
208    /**
209     * <p>
210     * dumps the GUI structure provided by the parameter into the log file. Calls itself
211     * recursively to traverse the GUI structure.
212     * </p>
213     *
214     * @param guiStructure the GUI structure to be logged
215     */
216    private void dumpGuiStructure(HtmlGUIElement guiStructure) {
217        outputWriter.print("<component id=\"");
218        outputWriter.print(guiStructure.getId());
219        outputWriter.println("\">");
220       
221        if (guiStructure instanceof HtmlServer) {
222            dumpParam("host", ((HtmlServer) guiStructure).getName());
223            dumpParam("port", ((HtmlServer) guiStructure).getPort());
224        }
225        else if (guiStructure instanceof HtmlDocument) {
226            dumpParam("path", ((HtmlDocument) guiStructure).getPath());
227            dumpParam("query", ((HtmlDocument) guiStructure).getQuery());
228            dumpParam("title", ((HtmlDocument) guiStructure).getTitle());
229        }
230        else if (guiStructure instanceof HtmlPageElement) {
231            dumpParam("tagname", ((HtmlPageElement) guiStructure).getTagName());
232            dumpParam("htmlid", ((HtmlPageElement) guiStructure).getHtmlId());
233            dumpParam("index", ((HtmlPageElement) guiStructure).getIndex());
234        }
235       
236        dumpParam("parent", guiStructure.getParentId());
237       
238        outputWriter.println("</component>");
239       
240        if (guiStructure.getChildren() != null) {
241            for (HtmlGUIElement child : guiStructure.getChildren()) {
242                dumpGuiStructure(child);
243            }
244        }
245    }
246
247    /**
248     * <p>
249     * formats a received event and writes it to the log file. One event results in one line
250     * in the log file containing all infos of the event.
251     * </p>
252     *
253     * @param event to be written to the log file
254     */
255    private void dumpEvent(HtmlEvent event) {
256        outputWriter.print("<event type=\"");
257        outputWriter.print(event.getEventType());
258        outputWriter.println("\">");
259       
260        if (event.getCoordinates() != null) {
261            dumpParam("X", event.getCoordinates()[0]);
262            dumpParam("Y", event.getCoordinates()[1]);
263        }
264
265        dumpParam("key", event.getKey());
266           
267        if (event.getScrollPosition() != null) {
268            dumpParam("scrollX", event.getScrollPosition()[0]);
269            dumpParam("scrollY", event.getScrollPosition()[1]);
270        }
271
272        dumpParam("selectedValue", event.getSelectedValue());
273       
274        if (event.getTarget() != null) {
275            dumpParam("target", event.getTarget().getId());
276        }
277        else {
278            dumpParam("targetDocument", event.getTargetDocument().getId());
279            dumpParam("targetDOMPath", event.getTargetDOMPath());
280        }
281        dumpParam("timestamp", event.getTime());
282       
283        outputWriter.println("</event>");
284    }
285
286    /**
287     * <p>
288     * dumps a parameter with the given name and value to the log file. The result is a
289     * tag named param with a name attribute and a value attribute. The value is transformed
290     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
291     * if required.
292     * </p>
293     *
294     * @param name  the name of the parameter to be dumped
295     * @param value the value of the parameter to be dumped
296     */
297    private void dumpParam(String name, Object value) {
298        if (value == null) {
299            return;
300        }
301       
302        String val;
303       
304        if (value instanceof String) {
305            val = (String) value;
306        }
307        else {
308            val = value.toString();
309        }
310       
311        outputWriter.print(" <param name=\"");
312        outputWriter.print(name);
313        outputWriter.print("\" value=\"");
314        outputWriter.print(StringTools.xmlEntityReplacement(val));
315        outputWriter.println("\"/>");
316    }
317
318    /**
319     * <p>
320     * checks, if the log file exeeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current
321     * log file is closed, the next log file name is determined and this new file is opend for
322     * writing.
323     * </p>
324     */
325    private synchronized void considerLogRotate() throws IOException {
326        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
327            closeLogWriter();
328            rotateLogFile();
329            createLogWriter();
330        }
331    }
332
333    /**
334     * <p>
335     * renames the current log file to a new log file with the next available index. It further
336     * sets the current log file to the default name, i.e. without index.
337     * </p>
338     */
339    private void rotateLogFile() {
340        File clientLogDir = logFile.getParentFile();
341        File checkFile;
342
343        int logFileIndex = -1;
344        do {
345            logFileIndex++;
346           
347            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
348        }
349        while (checkFile.exists());
350   
351        if (!logFile.renameTo(checkFile)) {
352            Console.printerrln("could not rename log file " + logFile + " to " + checkFile +
353                               ". Will not perform log rotation.");
354        }
355        else {
356            logFileIndex++;
357            logFile = new File(clientLogDir, getLogFileName(-1));
358        }
359    }
360
361    /**
362     * <p>
363     * instantiates a writer to be used for writing messages into the log file.
364     * </p>
365     */
366    private void createLogWriter() throws IOException {
367        FileOutputStream fis = new FileOutputStream(logFile);
368        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
369        outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
370        outputWriter.println("<session>");
371    }
372
373    /**
374     * <p>
375     * closed the current writer if it is open.
376     * </p>
377     */
378    private void closeLogWriter() {
379        if (outputWriter != null) {
380            outputWriter.println("</session>");
381            outputWriter.flush();
382            outputWriter.close();
383            outputWriter = null;
384        }
385    }
386
387    /* (non-Javadoc)
388     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
389     */
390    @Override
391    public synchronized void stop() {
392        closeLogWriter();
393        rotateLogFile();
394
395        lastUpdate = System.currentTimeMillis();
396    }
397
398    /**
399     * <p>
400     * return the time stamp of the last activity that happened on this writer.
401     * </p>
402     *
403     * @return as described
404     */
405    public long getLastUpdate() {
406        return lastUpdate;
407    }
408}
Note: See TracBrowser for help on using the repository browser.