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

Last change on this file since 1265 was 1265, checked in by pharms, 11 years ago
  • performance improvements to be able to parse larger amounts of log files at once
File size: 11.7 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.security.MessageDigest;
24import java.security.NoSuchAlgorithmException;
25import java.util.HashSet;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.Map;
29import java.util.Set;
30
31import org.apache.commons.codec.binary.Base64;
32import org.xml.sax.SAXException;
33
34import de.ugoe.cs.util.StringTools;
35import de.ugoe.cs.util.console.Console;
36
37/**
38 * <p>
39 * TODO comment
40 * </p>
41 *
42 * @author Patrick Harms
43 * @version 1.0
44 *
45 */
46public class HTMLLogTextInputPseudomizer extends AbstractDefaultLogParser {
47   
48    /**
49     * <p>
50     * The output writer into which the pseudomized variant of the log file is written
51     * </p>
52     */
53    private PrintWriter outputWriter;
54   
55    /**
56     * <p>
57     * The set of text input fields found in the GUI model
58     * </p>
59     */
60    private Set<String> textInputFieldIds = 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 pseudomize all text inputs in the given log file. The method reuses
72     * {@link #pseudomizeFile(File)}.
73     * </p>
74     *
75     * @param file the log file in which the text inputs must be pseudomized
76     */
77    public void pseudomizeFile(String file) {
78        if (file == null) {
79            throw new IllegalArgumentException("file must not be null");
80        }
81
82        pseudomizeFile(new File(file));
83    }
84
85    /**
86     * <p>
87     * called to pseudomize all text inputs in the given log file. The given file is read
88     * completely. All GUI elements are written to an output file as they are. All events are
89     * written to an output file as they are, as well, as long as they do not represent test inputs.
90     * If they are text input events, the entered text is replaced by its hash value for
91     * pseudomizing the text input. Finally, the original log file is deleted and replaced by
92     * the pseudomized variant. Log files, which are already pseudomized, stay untouched.
93     * </p>
94     *
95     * @param file the log file in which the text inputs must be pseudomized
96     */
97    public void pseudomizeFile(File file) {
98        if (file == null) {
99            throw new IllegalArgumentException("file must not be null");
100        }
101       
102        if (!file.exists()) {
103            throw new IllegalArgumentException("file must denote an existing file");
104        }
105       
106        if (!file.isFile()) {
107            throw new IllegalArgumentException("file must denote a file");
108        }
109       
110        File outFile = new File(file.getParentFile(), file.getName() + "_tmp");
111        boolean parsingFailed = false;
112       
113        try {
114            FileOutputStream fis = new FileOutputStream(outFile);
115            outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
116            outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
117            outputWriter.println("<session>");
118
119            textInputFieldIds.clear();
120            sortedEvents.clear();
121           
122            try {
123                super.parseFile(file);
124            }
125            catch (SAXException e) {
126                parsingFailed = true;
127            }
128           
129            for (EventEntry event : sortedEvents) {
130                event.dump();
131            }
132           
133            outputWriter.println("</session>");
134            outputWriter.flush();
135        }
136        catch (FileNotFoundException e) {
137            Console.printerrln("could not create pseudomized file " + outFile);
138        }
139        catch (UnsupportedEncodingException e) {
140            // this should never happen
141            e.printStackTrace();
142        }
143        finally {
144            if (outputWriter != null) {
145                outputWriter.close();
146                outputWriter = null;
147            }
148        }
149       
150        if (!parsingFailed && outFile.exists()) {
151            if (!file.delete()) {
152                Console.printerrln("could not delete pseudomized file " + file);
153            }
154            else if (!outFile.renameTo(file)) {
155                Console.printerrln
156                    ("could not rename pseudomized file to original file name " + file);
157            }           
158            else {
159                Console.println("pseudomized file " + file);
160            }
161        }
162        else {
163            if (!outFile.delete()) {
164                Console.printerrln("could not delete temporary file " + outFile);
165            }
166        }
167    }
168   
169    /* (non-Javadoc)
170     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.lang.String)
171     */
172    @Override
173    public void parseFile(String filename) {
174        throw new IllegalStateException("this method must not be called externally");
175    }
176
177    /* (non-Javadoc)
178     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.io.File)
179     */
180    @Override
181    public void parseFile(File file) {
182        throw new IllegalStateException("this method must not be called externally");
183    }
184
185    /* (non-Javadoc)
186     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
187     */
188    @Override
189    protected boolean handleGUIElement(String id, Map<String, String> parameters)
190        throws SAXException
191    {
192        outputWriter.print("<component id=\"");
193        outputWriter.print(id);
194        outputWriter.println("\">");
195       
196        for (Map.Entry<String, String> param : parameters.entrySet()) {
197            dumpParam(param.getKey(), param.getValue());
198           
199            if ("tagname".equals(param.getKey()) && "input_text".equals(param.getValue())) {
200                textInputFieldIds.add(id);
201            }
202        }
203           
204        outputWriter.println("</component>");
205       
206        return true;
207    }
208
209    /* (non-Javadoc)
210     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String,Map)
211     */
212    @Override
213    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
214        String timestampStr = parameters.get("timestamp");
215       
216        long timestamp = Long.MAX_VALUE;
217       
218        if (timestampStr != null) {
219            timestamp = Long.parseLong(timestampStr);
220        }
221       
222        EventEntry newEvent = new EventEntry(type, parameters, timestamp);
223       
224        int start = 0;
225        int end = sortedEvents.size();
226        int center = 0;
227        long centerTimestamp;
228       
229        while (start != end) {
230            center = start + ((end - start) / 2);
231           
232            if ((center != start) || (center != end)) {
233                centerTimestamp = sortedEvents.get(center).timestamp;
234           
235                if (centerTimestamp < newEvent.timestamp) {
236                    start = Math.max(center, start + 1);
237                }
238                else if (centerTimestamp > newEvent.timestamp) {
239                    end = Math.min(center, end - 1);
240                }
241                else {
242                    // add the event directly where the center is, as the timestamps of the center
243                    // and the new event are equal
244                    start = end = center;
245                    break;
246                }
247            }
248            else {
249                // add the event to the position denoted by the add index
250                break;
251            }
252        }
253       
254        sortedEvents.add(start, newEvent);
255       
256        if ("onchange".equals(type)) {
257            String targetId = parameters.get("target");
258       
259            if ((targetId != null) && textInputFieldIds.contains(targetId)) {
260                String value = parameters.get("selectedValue");
261               
262                if ((value != null) && !value.endsWith("==")) {
263                    try {
264                        MessageDigest md = MessageDigest.getInstance("SHA-512");
265                        md.update(value.getBytes("UTF-8"));
266                        value =  Base64.encodeBase64String(md.digest());
267                    }
268                    catch (UnsupportedEncodingException e) {
269                        throw new IllegalStateException("Java VM does not support this code");
270                    }
271                    catch (NoSuchAlgorithmException e) {
272                        throw new IllegalStateException("Java VM does not support this code");
273                    }
274                   
275                    parameters.put("selectedValue", value);
276                }
277            }
278        }
279
280        return true;
281    }
282
283    /**
284     * <p>
285     * dumps a parameter with the given name and value to the log file. The result is a
286     * tag named param with a name attribute and a value attribute. The value is transformed
287     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
288     * if required.
289     * </p>
290     *
291     * @param name  the name of the parameter to be dumped
292     * @param value the value of the parameter to be dumped
293     */
294    private void dumpParam(String name, Object value) {
295        if (value == null) {
296            return;
297        }
298       
299        String val;
300       
301        if (value instanceof String) {
302            val = (String) value;
303        }
304        else {
305            val = String.valueOf(value);
306        }
307       
308        outputWriter.print(" <param name=\"");
309        outputWriter.print(name);
310        outputWriter.print("\" value=\"");
311        outputWriter.print(StringTools.xmlEntityReplacement(val));
312        outputWriter.println("\"/>");
313    }
314
315
316    /**
317     * <p>
318     * this class is used internally for storing events in a sorted list together with the
319     * timestamps, being the sort criteria.
320     * </p>
321     */
322    private class EventEntry {
323       
324        /**
325         * <p>
326         * the type of the event
327         * </p>
328         */
329        private String type;
330       
331        /**
332         * <p>
333         * the parameters of the event
334         * </p>
335         */
336        private Map<String, String> parameters;
337       
338        /**
339         * <p>
340         * the timestamp of the event
341         * </p>
342         */
343        private long timestamp;
344
345        /**
346         * <p>
347         * creates a new event entry with event type, parameters and the timestamp
348         * </p>
349         */
350        private EventEntry(String type, Map<String, String> parameters, long timestamp) {
351            this.type = type;
352            this.parameters = parameters;
353            this.timestamp = timestamp;
354        }
355       
356        /**
357         * <p>
358         * convenience method for dumping the event into the compressed log file
359         * </p>
360         */
361        private void dump() {
362            outputWriter.print("<event type=\"");
363            outputWriter.print(type);
364            outputWriter.println("\">");
365           
366            for (Map.Entry<String, String> param : parameters.entrySet()) {
367                dumpParam(param.getKey(), param.getValue());
368            }
369           
370            outputWriter.println("</event>");
371        }
372    }
373}
Note: See TracBrowser for help on using the repository browser.