source: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogCompressor.java @ 1234

Last change on this file since 1234 was 1234, checked in by pharms, 11 years ago
  • corrected log compression and log file restructuring
File size: 11.6 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.plugin.html;
16
17import java.io.File;
18import java.io.FileNotFoundException;
19import java.io.FileOutputStream;
20import java.io.OutputStreamWriter;
21import java.io.PrintWriter;
22import java.io.UnsupportedEncodingException;
23import java.text.DecimalFormat;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29
30import org.xml.sax.SAXException;
31
32import de.ugoe.cs.util.StringTools;
33import de.ugoe.cs.util.console.Console;
34
35/**
36 * <p>
37 * This class can be used to compress a bunch of HTML log files to one file, that contains only
38 * one full GUI model.
39 * </p>
40 *
41 * @author Patrick Harms
42 * @version 1.0
43 *
44 */
45public class HTMLLogCompressor extends AbstractDefaultLogParser {
46   
47    /**
48     * <p>
49     * The output writer into which the compressed variant of the log files is written
50     * </p>
51     */
52    private PrintWriter outputWriter;
53   
54    /**
55     * <p>
56     * the GUI elements, that were already logged and need therefore not be logged again into
57     * the same file
58     * </p>
59     */
60    private Set<String> loggedGUIElementIds = new HashSet<String>();
61
62    /**
63     * <p>
64     * the events that were read
65     * </p>
66     */
67    private List<EventEntry> sortedEvents = new LinkedList<EventEntry>();
68
69    /**
70     * <p>
71     * called to compress all log files in the provided directory. The method reuses
72     * {@link #compressFilesInDirectory(File)}.
73     * </p>
74     *
75     * @param directory the directory containing the log files to be compressed
76     */
77    public void compressFilesInDirectory(String directory) {
78        if (directory == null) {
79            throw new IllegalArgumentException("directory must not be null");
80        }
81
82        compressFilesInDirectory(new File(directory));
83    }
84
85    /**
86     * <p>
87     * called to compress all log files in the provided directory. The directory if processed
88     * recursively. For each directory containing at least one log file the log files are
89     * replaced by one large log file containing the GUI model as well as all events contained in
90     * the compressed log file. If one log file could not be parsed, it is ignored and not
91     * compressed with the other ones.
92     * </p>
93     *
94     * @param directory the directory containing the log files to be compressed
95     */
96    public void compressFilesInDirectory(File directory) {
97        if (directory == null) {
98            throw new IllegalArgumentException("directory must not be null");
99        }
100       
101        if (!directory.exists()) {
102            throw new IllegalArgumentException("directory must denote an existing directory");
103        }
104       
105        if (!directory.isDirectory()) {
106            throw new IllegalArgumentException("directory must denote a directory");
107        }
108       
109        File[] files = directory.listFiles();
110       
111        if ((files == null) || (files.length == 0)) {
112            Console.println("directory is empty");
113            return;
114        }
115       
116        File outFile = null;
117       
118        int logFileIndex = -1;
119        do {
120            outFile = new File(directory, getLogFileName(directory.getName(), logFileIndex));
121            logFileIndex++;
122        }
123        while (outFile.exists());
124       
125        List<File> compressedFiles = new LinkedList<File>();
126       
127        try {
128            FileOutputStream fis = new FileOutputStream(outFile);
129            outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
130            outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
131            outputWriter.println("<session>");
132
133            loggedGUIElementIds.clear();
134            sortedEvents.clear();
135
136            for (File file : files) {
137                if (file.isFile()) {
138                    try {
139                        super.parseFile(file);
140                        Console.println("compressed " + file);
141                        compressedFiles.add(file);
142                    }
143                    catch (Exception e) {
144                        Console.printerrln("could not parse and compress file " + file);
145                        Console.printerrln(e.getMessage());
146                    }
147                }
148            }
149           
150            for (EventEntry event : sortedEvents) {
151                event.dump();
152            }
153
154            outputWriter.println("</session>");
155            outputWriter.flush();
156        }
157        catch (FileNotFoundException e) {
158            Console.printerrln("could not create compressed file " + outFile);
159            compressedFiles.clear();
160        }
161        catch (UnsupportedEncodingException e) {
162            // this should never happen
163            e.printStackTrace();
164            compressedFiles.clear();
165        }
166        finally {
167            if (outputWriter != null) {
168                outputWriter.close();
169                outputWriter = null;
170            }
171        }
172       
173        if (compressedFiles.size() > 0) {
174            for (File file : compressedFiles) {
175                if (!file.delete()) {
176                    Console.printerrln("could not delete compressed file " + file);
177                }
178            }
179       
180            if (outFile.exists()) {
181                File finalOutFile = null;
182           
183                logFileIndex = -1;
184                do {
185                    logFileIndex++;
186                    finalOutFile =
187                        new File(directory, getLogFileName(directory.getName(), logFileIndex));
188                }
189                while (finalOutFile.exists());
190
191                outFile.renameTo(finalOutFile);
192                Console.println("created " + finalOutFile);
193            }
194        }
195        else {
196            outFile.delete();
197        }
198    }
199   
200    /* (non-Javadoc)
201     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.lang.String)
202     */
203    @Override
204    public void parseFile(String filename) {
205        throw new IllegalStateException("this method must not be called externally");
206    }
207
208    /* (non-Javadoc)
209     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.io.File)
210     */
211    @Override
212    public void parseFile(File file) {
213        throw new IllegalStateException("this method must not be called externally");
214    }
215
216    /* (non-Javadoc)
217     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
218     */
219    @Override
220    protected boolean handleGUIElement(String id, Map<String, String> parameters)
221        throws SAXException
222    {
223        if (!loggedGUIElementIds.contains(id)) {
224            outputWriter.print("<component id=\"");
225            outputWriter.print(id);
226            outputWriter.println("\">");
227       
228            for (Map.Entry<String, String> param : parameters.entrySet()) {
229                dumpParam(param.getKey(), param.getValue());
230            }
231           
232            outputWriter.println("</component>");
233       
234            loggedGUIElementIds.add(id);
235        }
236       
237        return true;
238    }
239
240    /* (non-Javadoc)
241     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String,Map)
242     */
243    @Override
244    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
245        String timestampStr = parameters.get("timestamp");
246       
247        long timestamp = Long.MAX_VALUE;
248       
249        if (timestampStr != null) {
250            timestamp = Long.parseLong(timestampStr);
251        }
252       
253        EventEntry newEvent = new EventEntry(type, parameters, timestamp);
254       
255        boolean added = false;
256        for (int i = 0; i < sortedEvents.size(); i++) {
257            if (sortedEvents.get(i).timestamp > newEvent.timestamp) {
258                sortedEvents.add(i, newEvent);
259                added = true;
260                break;
261            }
262        }
263       
264        if (!added) {
265            sortedEvents.add(newEvent);
266        }
267
268        return true;
269    }
270
271    /**
272     * <p>
273     * dumps a parameter with the given name and value to the log file. The result is a
274     * tag named param with a name attribute and a value attribute. The value is transformed
275     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
276     * if required.
277     * </p>
278     *
279     * @param name  the name of the parameter to be dumped
280     * @param value the value of the parameter to be dumped
281     */
282    private void dumpParam(String name, Object value) {
283        if (value == null) {
284            return;
285        }
286       
287        String val;
288       
289        if (value instanceof String) {
290            val = (String) value;
291        }
292        else {
293            val = String.valueOf(value);
294        }
295       
296        outputWriter.print(" <param name=\"");
297        outputWriter.print(name);
298        outputWriter.print("\" value=\"");
299        outputWriter.print(StringTools.xmlEntityReplacement(val));
300        outputWriter.println("\"/>");
301    }
302
303
304    /**
305     * <p>
306     * used to calculate a log file name. If the provided index is smaller 0, then no index
307     * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log".
308     * </p>
309     *
310     * @param index the index of the log file or -1 one, if no index shall be added
311     *
312     * @return the file name as described
313     */
314    private String getLogFileName(String clientId, int index) {
315        String result = "htmlmonitor_" + clientId;
316       
317        if (index >= 0) {
318            result += "_" + new DecimalFormat("000" ).format(index);
319        }
320       
321        result += ".log";
322       
323        return result;
324    }
325   
326    /**
327     * <p>
328     * this class is used internally for storing events in a sorted list together with the
329     * timestamps, being the sort criteria.
330     * </p>
331     */
332    private class EventEntry {
333       
334        /**
335         * <p>
336         * the type of the event
337         * </p>
338         */
339        private String type;
340       
341        /**
342         * <p>
343         * the parameters of the event
344         * </p>
345         */
346        private Map<String, String> parameters;
347       
348        /**
349         * <p>
350         * the timestamp of the event
351         * </p>
352         */
353        private long timestamp;
354
355        /**
356         * <p>
357         * creates a new event entry with event type, parameters and the timestamp
358         * </p>
359         */
360        private EventEntry(String type, Map<String, String> parameters, long timestamp) {
361            this.type = type;
362            this.parameters = parameters;
363            this.timestamp = timestamp;
364        }
365       
366        /**
367         * <p>
368         * convenience method for dumping the event into the compressed log file
369         * </p>
370         */
371        private void dump() {
372            outputWriter.print("<event type=\"");
373            outputWriter.print(type);
374            outputWriter.println("\">");
375           
376            for (Map.Entry<String, String> param : parameters.entrySet()) {
377                dumpParam(param.getKey(), param.getValue());
378            }
379           
380            outputWriter.println("</event>");
381        }
382    }
383}
Note: See TracBrowser for help on using the repository browser.