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

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