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

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