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

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