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

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