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

Last change on this file since 927 was 927, checked in by sherbold, 12 years ago
  • added copyright under the Apache License, Version 2.0
File size: 10.8 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.console.Console;
25
26/**
27 * <p>
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>
39 * </p>
40 *
41 * @author Patrick Harms
42 * @version 1.0
43 *
44 */
45public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener {
46   
47    /**
48     * the maximum size of an individual log file
49     */
50    private static final int MAXIMUM_LOG_FILE_SIZE = 10000000;
51
52    /**
53     * the default log base directory if none is provided through the constructor
54     */
55    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";
56
57    /**
58     * the currently used log file base directory
59     */
60    private File logFileBaseDir;
61
62    /**
63     * the is of the client of which all messages are logged through this writer
64     */
65    private String clientId;
66
67    /**
68     * the log file into which all messages are currently written
69     */
70    private File logFile;
71
72    /**
73     * an output writer to be used for writing into the log file
74     */
75    private PrintWriter outputWriter;
76
77    /**
78     * the time stamp of the last action taken on this writer (such as logging a message)
79     */
80    private long lastUpdate;
81
82    /**
83     * <p>
84     * initializes the writer with the log file base directory and the id of the client for which
85     * this writer logs the messages.
86     * </p>
87     *
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
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()) {
119                    if (!clientLogDir.mkdirs()) {
120                        throw new HtmlMonitorException("client log file directory " + clientLogDir +
121                                                       " can not be created");
122                    }
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>
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".
149     * </p>
150     *
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
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)
176     * @see HtmlMonitorMessageListener#handleMessage(HtmlClientInfos, HtmlEvent[])
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>
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.
204     * </p>
205     *
206     * @param event to be written to the log file
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(' ');
244            dumpString(event.getScrollPosition().toString());
245        }
246           
247        outputWriter.println();
248    }
249
250    /**
251     * <p>
252     * convenience method to dump a string with trailing and leading " as well as replaced
253     * backslashes, ", and newlines
254     * </p>
255     *
256     * @param clientId2
257     */
258    private void dumpString(String str) {
259        outputWriter.print('"');
260        outputWriter.print
261            (str.replaceAll("\\\\", "\\\\").replaceAll("\\\"", "\\\"").replaceAll("\n", " "));
262        outputWriter.print('"');
263    }
264
265    /**
266     * <p>
267     * checks, if the log file exeeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current
268     * log file is closed, the next log file name is determined and this new file is opend for
269     * writing.
270     * </p>
271     */
272    private synchronized void considerLogRotate() throws IOException {
273        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
274            closeLogWriter();
275            rotateLogFile();
276            createLogWriter();
277        }
278    }
279
280    /**
281     * <p>
282     * renames the current log file to a new log file with the next available index. It further
283     * sets the current log file to the default name, i.e. without index.
284     * </p>
285     */
286    private void rotateLogFile() {
287        File clientLogDir = logFile.getParentFile();
288        File checkFile;
289
290        int logFileIndex = -1;
291        do {
292            logFileIndex++;
293           
294            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
295        }
296        while (checkFile.exists());
297   
298        if (!logFile.renameTo(checkFile)) {
299            Console.printerrln("could not rename log file " + logFile + " to " + checkFile +
300                               ". Will not perform log rotation.");
301        }
302        else {
303            logFileIndex++;
304            logFile = new File(clientLogDir, getLogFileName(-1));
305        }
306    }
307
308    /**
309     * <p>
310     * instantiates a writer to be used for writing messages into the log file.
311     * </p>
312     */
313    private void createLogWriter() throws IOException {
314        FileOutputStream fis = new FileOutputStream(logFile);
315        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
316    }
317
318    /**
319     * <p>
320     * closed the current writer if it is open.
321     * </p>
322     */
323    private void closeLogWriter() {
324        if (outputWriter != null) {
325            outputWriter.flush();
326            outputWriter.close();
327            outputWriter = null;
328        }
329    }
330
331    /* (non-Javadoc)
332     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
333     */
334    @Override
335    public synchronized void stop() {
336        closeLogWriter();
337        rotateLogFile();
338
339        lastUpdate = System.currentTimeMillis();
340    }
341
342    /**
343     * <p>
344     * return the time stamp of the last activity that happened on this writer.
345     * </p>
346     *
347     * @return as described
348     */
349    public long getLastUpdate() {
350        return lastUpdate;
351    }
352}
Note: See TracBrowser for help on using the repository browser.