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

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