Index: /trunk/autoquest-generic-event-monitor-test/.classpath
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/.classpath	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/.classpath	(revision 2155)
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Index: /trunk/autoquest-generic-event-monitor-test/.project
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/.project	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/.project	(revision 2155)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>autoquest-generic-event-monitor-test</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.jdt.core.prefs	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.jdt.core.prefs	(revision 2155)
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.7
Index: /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.m2e.core.prefs
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.m2e.core.prefs	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/.settings/org.eclipse.m2e.core.prefs	(revision 2155)
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
Index: /trunk/autoquest-generic-event-monitor-test/pom.xml
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/pom.xml	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/pom.xml	(revision 2155)
@@ -0,0 +1,37 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>de.ugoe.cs.autoquest</groupId>
+        <artifactId>autoquest-test</artifactId>
+        <version>0.2.2-SNAPSHOT</version>
+        <relativePath>../autoquest-test/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>autoquest-generic-event-monitor-test</artifactId>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+    <scm>
+        <url>${autoquest-scm-trunk-dir}/${project.artifactId}</url>
+        <connection>scm:svn:${autoquest-scm-trunk-dir}/${project.artifactId}</connection>
+    </scm>
+    <properties>
+        <tested-artifactId>autoquest-generic-event-monitor</tested-artifactId>
+    </properties>
+    <dependencies>
+        <!--dependency>
+            <groupId>de.ugoe.cs.autoquest</groupId>
+            <artifactId>autoquest-plugin-generic-events</artifactId>
+            <version>${project.parent.version}</version>
+            <scope>test</scope>
+        </dependency-->
+        <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.2.1</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
Index: /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServerTest.java
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServerTest.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServerTest.java	(revision 2155)
@@ -0,0 +1,649 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import de.ugoe.cs.autoquest.genericeventmonitor.ClientInfos;
+import de.ugoe.cs.autoquest.genericeventmonitor.GenericEvent;
+import de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorMessageListener;
+import de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorServer;
+import de.ugoe.cs.autoquest.genericeventmonitor.GenericEventTarget;
+import de.ugoe.cs.util.console.TextConsole;
+
+/**
+ *
+ * @author Patrick Harms
+ */
+public class GenericEventMonitorServerTest implements GenericEventMonitorMessageListener {
+    
+    /**
+     * 
+     */
+    public static final TextConsole CONSOLE = new TextConsole();
+    
+    /**
+     * 
+     */
+    private static final int PORT = 19098;
+
+    /**
+     * 
+     */
+    private GenericEvent[] events;
+
+    /**
+     * 
+     */
+    private GenericEventTarget[] eventTargetStructures;
+
+    /**
+     * 
+     */
+    private String eventHandlingError;
+
+    /**
+     * 
+     */
+    private GenericEventMonitorServer server;
+
+    /**
+     * 
+     */
+    @Before
+    public void setUp() throws Exception {
+        server = new GenericEventMonitorServer(PORT, this);
+        server.init();
+        server.start();
+    }
+    
+    /**
+     * 
+     */
+    @After
+    public void tearDown() throws Exception {
+        events = null;
+        eventHandlingError = null;
+
+        if (server != null) {
+            try {
+                server.stop();
+            }
+            finally {
+                server = null;
+            }
+        }
+    }
+    
+
+    /**
+     * 
+     */
+    @Test
+    public void testSimpleMessage() throws Exception {
+        String clientId = "123";
+        String appId = "456";
+        
+        String message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"" + clientId + "\"," +
+            "      \"appId\":\"" + appId + "\"," +
+            "    }," +
+            "    \"targetStructure\": [{" +
+            "      \"targetId\":\"target1\"," +
+            "      \"param1\":\"value1\"," +
+            "      \"param3\":\"value3\"," +
+            "      \"param2\":\"value2\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"targetId\":\"target3\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"targetId\":\"target2\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }]," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"12345\"," +
+            "        \"eventTargetId\":\"target2\"," +
+            "        \"eventType\":\"gaze\"," +
+            "        \"xcoordinate\": \"194\"," +
+            "        \"ycoordinate\": \"12\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+        
+        sendMessageAndAssertResponse(message);
+        
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+        
+        // check the target structure
+        GenericEventTarget parent = null;
+        
+        assertEquals(1, eventTargetStructures.length);
+        GenericEventTarget target = eventTargetStructures[0];
+        
+        assertTarget(target, parent, 2, new String[] {"param1", "value1"},
+                     new String[] {"param2", "value2"},
+                     new String[] {"param3", "value3"});
+        
+        parent = target;
+        target = parent.getChildren().get(0);
+        assertTarget(target, parent, 0, new String[] {"index", "0"});
+        target = parent.getChildren().get(1);
+        assertTarget(target, parent, 0, new String[] {"htmlId", "gsr"});
+
+        // check the event
+        assertNotNull(events);
+        assertEquals(1, events.length);
+        
+        assertEvent(0, 12345, target, "gaze", new String[] {"xcoordinate", "194"},
+                    new String[] {"ycoordinate", "12"});
+        
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void testComplexMessage() throws Exception {
+        String clientId = "123";
+        String appId = "456";
+        
+        String message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"" + clientId + "\"," +
+            "      \"appId\":\"" + appId + "\"," +
+            "    }," +
+            "    \"targetStructure\": [{" +
+            "      \"targetId\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"targetId\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"targetId\":\"gsr\"," +
+            "          \"children\":" +
+            "          [ {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input1\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input2\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input3\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input4\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input5\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input6\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input7\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input8\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input9\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input10\"," +
+            "            }," +
+            "          ]" +
+            "        }" +
+            "      ]" +
+            "    }]," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"1\"," +
+            "        \"eventTargetId\":\"input1\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"2\"," +
+            "        \"eventTargetId\":\"input2\"," +
+            "        \"eventType\":\"ondblclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"3\"," +
+            "        \"eventTargetId\":\"input3\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"4\"," +
+            "        \"eventTargetId\":\"input4\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"255\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"5\"," +
+            "        \"eventTargetId\":\"input5\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"6\"," +
+            "        \"eventTargetId\":\"input6\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"7\"," +
+            "        \"eventTargetId\":\"input7\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"8\"," +
+            "        \"eventTargetId\":\"input8\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"255\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"9\"," +
+            "        \"eventTargetId\":\"input9\"," +
+            "        \"eventType\":\"onscroll\"," +
+            "        \"scrollPosition\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"10\"," +
+            "        \"eventTargetId\":\"input10\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+ 
+        sendMessageAndAssertResponse(message);
+        
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+        
+        // check the target structure
+        GenericEventTarget parent = null;
+        
+        assertEquals(1, eventTargetStructures.length);
+        GenericEventTarget target = eventTargetStructures[0];
+        
+        assertTarget(target, parent, 2, new String[] {"index", "0"});
+
+        parent = target;
+        target = parent.getChildren().get(0);
+        assertTarget(target, parent, 0, new String[] {"index", "0"});
+        target = parent.getChildren().get(1);
+        assertTarget(target, parent, 10, new String[] {"tagName", "body"});
+        
+        parent = target;
+        for (GenericEventTarget child : parent.getChildren()) {
+            assertTarget(child, parent, 0, new String[] {"tagName", "input_button"});
+        }
+
+        // check the events
+        assertNotNull(events);
+        assertEquals(10, events.length);
+
+        for (int i = 0; i < events.length; i++) {
+            String eventType;
+            if ((i == 1)) {
+                eventType = "ondblclick";
+            }
+            else if ((i == 2) || ((4 <= i) && (i <= 6))) {
+                eventType = "onfocus";
+            }
+            else if ((i == 8)) {
+                eventType = "onscroll";
+            }
+            else {
+                eventType = "onclick";
+            }
+            
+            String[] parameter = null;
+            if (i <= 1) {
+                parameter = new String[] { "coordinates", "194" };
+            }
+            else if (i == 3) {
+                parameter = new String[] { "coordinates", "255" };
+            }
+            else if (i == 7) {
+                parameter = new String[] { "coordinates", "255" };
+            }
+            else if (i == 8) {
+                parameter = new String[] { "scrollPosition", "194" };
+            }
+            else if (i == 9) {
+                parameter = new String[] { "coordinates", "194" };
+            }
+           
+            if (parameter != null) {
+                 assertEvent(i, i + 1, target.getChildren().get(i), eventType, parameter);
+            }
+            else {
+                assertEvent(i, i + 1, target.getChildren().get(i), eventType);
+            }
+            
+        }
+    }
+    
+    /**
+     * 
+     */
+    @Test
+    public void testInvalidMessages() throws Exception {
+        String message = "}";
+        
+        sendMessageAndAssertResponse(message);
+            
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+
+        assertNull(events);
+
+        message = "blaublbidalslkdjflqkerowercalksdjfdlkkjdjfk";
+        
+        sendMessageAndAssertResponse(message);
+            
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+
+        assertNull(events);
+        
+        // the following message doesn't work because of the missing scroll position in the event
+        message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"123\"," +
+            "      \"userAgent\":\"Agent\"," +
+            "      \"title\":\"Title\"," +
+            "      \"url\":\"http://host/path\"" +
+            "    }," +
+            "    \"guiModel\": {" +
+            "      \"tagName\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"tagName\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"9\"," +
+            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
+            "        \"eventType\":\"onscroll\"," +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+        
+        sendMessageAndAssertResponse(message);
+        
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+
+        assertNull(events);
+        
+        // the following message doesn't work because of the missing client id
+        message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"userAgent\":\"Agent\"," +
+            "      \"title\":\"Title\"," +
+            "      \"url\":\"http://host/path\"" +
+            "    }," +
+            "    \"guiModel\": {" +
+            "      \"tagName\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"tagName\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"12345\"," +
+            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
+            "        \"eventType\":\"onunload\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+        
+        sendMessageAndAssertResponse(message);
+        
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+
+        assertNull(events);
+        
+        // the following message doesn't work because of the invalid time stamp
+        message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"123\"," +
+            "      \"userAgent\":\"Agent\"," +
+            "      \"title\":\"Title\"," +
+            "      \"url\":\"http://host/path\"" +
+            "    }," +
+            "    \"guiModel\": {" +
+            "      \"tagName\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"tagName\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"blub\"," +
+            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
+            "        \"eventType\":\"onunload\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+        
+        sendMessageAndAssertResponse(message);
+        
+        if (eventHandlingError != null) {
+            fail(eventHandlingError);
+        }
+
+        assertNull(events);
+        
+    }
+    
+    /* (non-Javadoc)
+     * @see GenericEventMonitorMessageListener#handleEvents(ClientInfos, GenericEvent[])
+     */
+    @Override
+    public void handleEvents(ClientInfos clientInfos, GenericEvent[] events) {
+        if (clientInfos == null) {
+            eventHandlingError = "client infos were null";
+        }
+        else if (events == null) {
+            eventHandlingError = "events were null";
+        }
+        else {
+            Map<String, GenericEventTarget> roots = new HashMap<>();
+            
+            for (GenericEvent event : events) {
+                if (!clientInfos.equals(event.getClientInfos())) {
+                    eventHandlingError = "one of the events did not have a correct client info";
+                }
+                
+                GenericEventTarget parent = event.getTarget();
+                
+                while (parent.getParent() != null) {
+                    parent = parent.getParent();
+                }
+                
+                roots.put(parent.getId(), parent);
+            }
+            
+            this.events = events;
+            this.eventTargetStructures =
+                roots.values().toArray(new GenericEventTarget[roots.size()]);
+        }
+    }
+
+    /**
+     *
+     */
+    private void sendMessageAndAssertResponse(String message) throws Exception {
+        DefaultHttpClient httpclient = new DefaultHttpClient();
+        HttpPost httpPost = new HttpPost("http://localhost:" + PORT + "/");
+        //HttpPost httpPost = new HttpPost("http://localhost:8090/");
+        HttpEntity entity = new StringEntity(message, ContentType.APPLICATION_JSON);
+        httpPost.setEntity(entity);
+        
+        try {
+            HttpResponse response = httpclient.execute(httpPost);
+            
+            // the monitor always returns 200 without any additional information. The client must
+            // never get more or less information. This is especially important for preventing
+            // hackers from finding out more
+            assertEquals(200, response.getStatusLine().getStatusCode());
+            assertTrue
+                ((response.getEntity() == null) || (response.getEntity().getContentLength() == 0) ||
+                 ((response.getEntity().getContentLength() == 1) &&
+                  (response.getEntity().getContent().read() == ' ')));
+        }
+        finally {
+            httpPost.releaseConnection();
+        }
+    }
+
+    /**
+     *
+     */
+    private void assertTarget(GenericEventTarget target,
+                              GenericEventTarget parent,
+                              int                noOfChildren,
+                              String[] ...       parameters)
+    {
+        if (parent != null) {
+            assertNotNull(target.getParent());
+            assertEquals(parent.getId(), target.getParent().getId());
+        }
+        else {
+            assertNull(target.getParent());
+        }
+        
+        assertNotNull(target.getId());
+        
+        if (noOfChildren > 0) {
+            assertNotNull(target.getChildren());
+            assertEquals(noOfChildren, target.getChildren().size());
+        }
+        else {
+            assertNull(target.getChildren());
+        }
+        
+        assertEquals(parameters.length, target.getParameters().size());
+        
+        for (String[] parameter : parameters) {
+            assertEquals(parameter[1], target.getParameters().get(parameter[0]));
+        }
+    }
+
+    /**
+     *
+     */
+    private void assertEvent(int                index,
+                             long               timestamp,
+                             GenericEventTarget target,
+                             String             eventType,
+                             String[] ...       parameters)
+    {
+        assertEquals("event " + index, new Long(timestamp), events[index].getTime());
+        assertEquals("event " + index, target, events[index].getTarget());
+        assertEquals("event " + index, eventType, events[index].getEventType());
+        
+        
+        assertEquals("event " + index, parameters.length, events[index].getParameters().size());
+        
+        for (String[] parameter : parameters) {
+            assertEquals
+                ("event " + index, parameter[1], events[index].getParameters().get(parameter[0]));
+        }
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorTest.java
===================================================================
--- /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorTest.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor-test/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorTest.java	(revision 2155)
@@ -0,0 +1,668 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitor;
+import de.ugoe.cs.util.console.TextConsole;
+
+/**
+ *  
+ * @author Patrick Harms
+ */
+public class GenericEventMonitorTest {
+
+    /**
+     * 
+     */
+    public static final TextConsole CONSOLE = new TextConsole();
+    
+    /**
+     * 
+     */
+    private final static String LOG_FILE_DIR = "target/tmp/logfiles/";
+    
+    /**
+     * 
+     */
+    private static final int PORT = 19098;
+
+    /**
+     * 
+     */
+    private GenericEventMonitor genericEventMonitor;
+
+    /**
+     *
+     */
+    @Before
+    public void setUp() throws Exception {
+        genericEventMonitor = new GenericEventMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) });
+        genericEventMonitor.init();
+        genericEventMonitor.start();
+    }
+
+    /**
+     *
+     */
+    @After
+    public void tearDown() throws Exception {
+        if (genericEventMonitor != null) {
+            try {
+                genericEventMonitor.stop();
+            }
+            finally {
+                genericEventMonitor = null;
+            }
+        }
+        
+        deleteFiles(new File(LOG_FILE_DIR));
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testOneSimpleMessage() throws Exception {
+        String clientId = "123";
+        String appId = "456";
+        
+        String message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"" + clientId + "\"," +
+            "      \"appId\":\"" + appId + "\"," +
+            "    }," +
+            "    \"targetStructure\": [{" +
+            "      \"targetId\":\"target1\"," +
+            "      \"param1\":\"value1\"," +
+            "      \"param3\":\"value3\"," +
+            "      \"param2\":\"value2\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"targetId\":\"target3\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"targetId\":\"target2\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }]," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"12345\"," +
+            "        \"eventTargetId\":\"target2\"," +
+            "        \"eventType\":\"gaze\"," +
+            "        \"xcoordinate\": \"194\"," +
+            "        \"ycoordinate\": \"12\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+
+        sendMessageAndAssertResponse(message);
+        
+        genericEventMonitor.stop();
+        genericEventMonitor = null;
+
+        File logFile = new File(LOG_FILE_DIR + File.separator + appId + File.separator +
+                                clientId + File.separator + "genericevents_" + clientId + "_000.log");
+        
+        assertTrue(logFile.exists());
+        
+        /*HTMLLogParser parser = new HTMLLogParser(null);
+        
+        parser.parseFile(logFile);
+        
+        // check the GUI model
+        GUIModel guiModel = parser.getGuiModel();
+        assertNotNull(guiModel);
+        
+        List<IGUIElement> nodes = guiModel.getRootElements();
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get server node
+        IGUIElement node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLServer);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get document node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLDocument);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get html node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLPageElement);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
+        
+        // get body node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLPageElement);
+        assertEquals("HTML", node.getPlatform());
+        assertTrue(node.isUsed());
+
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(0, nodes.size());
+        
+        // check the sequences
+        Collection<List<Event>> sequences = parser.getSequences();
+        
+        assertNotNull(sequences);
+        
+        Iterator<List<Event>> iterator = sequences.iterator();
+        assertTrue(iterator.hasNext());
+        
+        List<Event> sequence = iterator.next();
+        assertFalse(iterator.hasNext());
+        
+        assertNotNull(sequence);
+        assertEquals(1, sequence.size());
+        
+        assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7);*/
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testSeveralMessagesInOneSession() throws Exception {
+        String clientId = "123";
+        String appId = "456";
+        
+        String message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"" + clientId + "\"," +
+            "      \"appId\":\"" + appId + "\"," +
+            "    }," +
+            "    \"targetStructure\": [{" +
+            "      \"targetId\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"targetId\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"targetId\":\"gsr\"," +
+            "          \"children\":" +
+            "          [ {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input1\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input2\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input3\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input4\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input5\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input6\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input7\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input8\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input9\"," +
+            "            }," +
+            "            {" +
+            "              \"tagName\":\"input_button\"," +
+            "              \"targetId\":\"input10\"," +
+            "            }," +
+            "          ]" +
+            "        }" +
+            "      ]" +
+            "    }]," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"1\"," +
+            "        \"eventTargetId\":\"input1\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"2\"," +
+            "        \"eventTargetId\":\"input2\"," +
+            "        \"eventType\":\"ondblclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"3\"," +
+            "        \"eventTargetId\":\"input3\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"4\"," +
+            "        \"eventTargetId\":\"input4\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"255\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"5\"," +
+            "        \"eventTargetId\":\"input5\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"6\"," +
+            "        \"eventTargetId\":\"input6\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"7\"," +
+            "        \"eventTargetId\":\"input7\"," +
+            "        \"eventType\":\"onfocus\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"8\"," +
+            "        \"eventTargetId\":\"input8\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"255\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"9\"," +
+            "        \"eventTargetId\":\"input9\"," +
+            "        \"eventType\":\"onscroll\"," +
+            "        \"scrollPosition\": \"194\"" +
+            "      }," +
+            "      {" +
+            "        \"time\":\"10\"," +
+            "        \"eventTargetId\":\"input10\"," +
+            "        \"eventType\":\"onclick\"," +
+            "        \"coordinates\": \"194\"" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+ 
+        sendMessageAndAssertResponse(message);
+        
+        genericEventMonitor.stop();
+        genericEventMonitor = null;
+
+        File logFile = new File(LOG_FILE_DIR + File.separator + appId + File.separator +
+                                clientId + File.separator + "genericevents_" + clientId + "_000.log");
+        
+        assertTrue(logFile.exists());
+        
+        /*HTMLLogParser parser = new HTMLLogParser(null);
+        
+        parser.parseFile(logFile);
+        
+        // check the GUI model
+        GUIModel guiModel = parser.getGuiModel();
+        assertNotNull(guiModel);
+        
+        List<IGUIElement> nodes = guiModel.getRootElements();
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get server node
+        IGUIElement node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLServer);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get document node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLDocument);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get html node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLPageElement);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
+        
+        // get body node
+        IGUIElement body = nodes.get(0);
+        assertNotNull(body);
+        assertTrue(body instanceof HTMLPageElement);
+        assertEquals("HTML", body.getPlatform());
+        assertTrue(body.isUsed());
+
+        nodes = guiModel.getChildren(body);
+        assertNotNull(nodes);
+        
+        // wait for all 10 GUI elements on the same page to be logged although only 9 are used
+        assertEquals(10, nodes.size());
+        
+        Map<String, IGUIElement> inputs = new HashMap<>();
+        
+        // get input nodes
+        for (int i = 0; i < nodes.size(); i++) {
+            node = nodes.get(i);
+            assertNotNull(node);
+            assertTrue(node instanceof HTMLPageElement);
+            assertEquals("HTML", node.getPlatform());
+            
+            if (!"input9".equals(((HTMLPageElement) node).getHtmlId())) {
+                assertTrue(node.isUsed());
+            }
+            else {
+                assertFalse(node.isUsed());
+            }
+            
+            inputs.put(((HTMLPageElement) node).getHtmlId(), node);
+
+            assertNotNull(guiModel.getChildren(node));
+            assertEquals(0, guiModel.getChildren(node).size());
+        }
+        
+        // check the sequences
+        Collection<List<Event>> sequences = parser.getSequences();
+        
+        assertNotNull(sequences);
+        
+        Iterator<List<Event>> iterator = sequences.iterator();
+        assertTrue(iterator.hasNext());
+        
+        List<Event> sequence = iterator.next();
+        assertFalse(iterator.hasNext());
+        
+        assertNotNull(sequence);
+        assertEquals(10, sequence.size());
+        
+        assertEvent(sequence.get(0), 1, MouseClick.class, inputs.get("input1"), 194, 7);
+        assertEvent(sequence.get(1), 2, MouseDoubleClick.class, inputs.get("input2"), 194, 7);
+        assertEvent(sequence.get(2), 3, KeyboardFocusChange.class, inputs.get("input3"), 0, 0);
+        assertEvent(sequence.get(3), 4, MouseClick.class, inputs.get("input4"), 125, 14);
+        assertEvent(sequence.get(4), 5, KeyboardFocusChange.class, inputs.get("input5"), 0, 0);
+        assertEvent(sequence.get(5), 6, KeyboardFocusChange.class, inputs.get("input6"), 0, 0);
+        assertEvent(sequence.get(6), 7, KeyboardFocusChange.class, inputs.get("input7"), 0, 0);
+        assertEvent(sequence.get(7), 8, MouseClick.class, inputs.get("input8"), 255, 4);
+        assertEvent(sequence.get(8), 9, Scroll.class, body, 0, 0);
+        assertEvent(sequence.get(9), 10, MouseClick.class, inputs.get("input10"), 516, 154);*/
+
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testSeveralSessions() throws Exception {
+        String clientId = "123";
+       
+        String message =
+            "{" +
+            "  \"message\": {" +
+            "    \"clientInfos\": {" +
+            "      \"clientId\":\"" + clientId + "\"," +
+            "      \"userAgent\":\"Agent\"," +
+            "      \"title\":\"Title\"," +
+            "      \"url\":\"http://host/path\"" +
+            "    }," +
+            "    \"guiModel\": {" +
+            "      \"tagName\":\"html\"," +
+            "      \"index\":\"0\"," +
+            "      \"children\":" +
+            "      [ {" +
+            "          \"tagName\":\"head\"," +
+            "          \"index\":\"0\"," +
+            "        }," +
+            "        {" +
+            "          \"tagName\":\"body\"," +
+            "          \"htmlId\":\"gsr\"," +
+            "        }" +
+            "      ]" +
+            "    }," +
+            "    \"events\":" +
+            "    [ {" +
+            "        \"time\":\"12345\"," +
+            "        \"path\":\"/html[0]/body(htmlId=gsr)\"," +
+            "        \"eventType\":\"onclick\"" +
+            "        \"coordinates\": [\"194\", \"7\"]" +
+            "      }" +
+            "    ]" +
+            "  }" +
+            "}";
+
+        sendMessageAndAssertResponse(message);
+        
+        int numberOfSessions = 10;
+        for (int i = 0; i < numberOfSessions; i++) {
+            genericEventMonitor.stop();
+            genericEventMonitor =
+                new GenericEventMonitor(new String[] { LOG_FILE_DIR, Integer.toString(PORT) });
+            
+            genericEventMonitor.init();
+            genericEventMonitor.start();
+            sendMessageAndAssertResponse(message);
+        }
+        
+        genericEventMonitor.stop();
+        genericEventMonitor = null;
+        
+        /*HTMLLogParser parser = new HTMLLogParser(null);
+        
+        // assert 9 already rotated log files
+        for (int i = 0; i < numberOfSessions; i++) {
+            File logFile = new File(LOG_FILE_DIR + File.separator + "host" + File.separator +
+                                    clientId + File.separator + "htmlmonitor_" + clientId + "_00" +
+                                    i + ".log");
+       
+            assertTrue(logFile.exists());
+       
+            parser.parseFile(logFile);
+        }
+
+        // check the GUI model
+        GUIModel guiModel = parser.getGuiModel();
+        assertNotNull(guiModel);
+        
+        List<IGUIElement> nodes = guiModel.getRootElements();
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get server node
+        IGUIElement node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLServer);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get document node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLDocument);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size());
+        
+        // get html node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLPageElement);
+        assertEquals("HTML", node.getPlatform());
+        assertFalse(node.isUsed());
+        
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(1, nodes.size()); // only one child as the head tag should have been ignored
+        
+        // get body node
+        node = nodes.get(0);
+        assertNotNull(node);
+        assertTrue(node instanceof HTMLPageElement);
+        assertEquals("HTML", node.getPlatform());
+        assertTrue(node.isUsed());
+
+        nodes = guiModel.getChildren(node);
+        assertNotNull(nodes);
+        assertEquals(0, nodes.size());
+        
+        // check the sequences
+        Collection<List<Event>> sequences = parser.getSequences();
+        
+        assertNotNull(sequences);
+        assertEquals(numberOfSessions, sequences.size());
+        
+        Iterator<List<Event>> iterator = sequences.iterator();
+        
+        while (iterator.hasNext()) {
+            List<Event> sequence = iterator.next();
+        
+            assertNotNull(sequence);
+            assertEquals(1, sequence.size());
+       
+            assertEvent(sequence.get(0), 12345, MouseClick.class, node, 194, 7);
+        }*/
+    }
+
+    /**
+     *
+     */
+    private void sendMessageAndAssertResponse(String message) throws Exception {
+        DefaultHttpClient httpclient = new DefaultHttpClient();
+        HttpPost httpPost = new HttpPost("http://localhost:" + PORT + "/");
+        HttpEntity entity = new StringEntity(message, ContentType.APPLICATION_JSON);
+        httpPost.setEntity(entity);
+        
+        try {
+            HttpResponse response = httpclient.execute(httpPost);
+            
+            // the monitor always returns 200 without any additional information. The client must
+            // never get more or less information. This is especially important for preventing
+            // hackers from finding out more
+            assertEquals(200, response.getStatusLine().getStatusCode());
+            assertTrue
+                ((response.getEntity() == null) || (response.getEntity().getContentLength() == 0) ||
+                 ((response.getEntity().getContentLength() == 1) &&
+                  (response.getEntity().getContent().read() == ' ')));
+        }
+        finally {
+            httpPost.releaseConnection();
+        }
+    }
+
+    /**
+     *
+     */
+    /*private void assertEvent(Event                       event,
+                             int                         timestamp,
+                             Class<? extends IEventType> eventType,
+                             IGUIElement                 eventTarget,
+                             int                         xCoordinate,
+                             int                         yCoordinate)
+    {
+        assertEquals(timestamp, event.getTimestamp());
+        assertTrue(eventType.isInstance(event.getType()));
+        assertEquals(eventTarget, event.getTarget());
+        
+        if (event.getType() instanceof MouseButtonInteraction) {
+            assertEquals(xCoordinate, ((MouseButtonInteraction) event.getType()).getX());
+            assertEquals(yCoordinate, ((MouseButtonInteraction) event.getType()).getY());
+        }
+    }
+
+    /**
+     *
+     */
+    private void deleteFiles(File file) {
+        if (file.exists()) {
+            if (file.isDirectory()) {
+                for (File child : file.listFiles()) {
+                    deleteFiles(child);
+                }
+            }
+            
+            try {
+                file.delete();
+            }
+            catch (Exception e) {
+                // ignore and delete as much as possible
+            }
+        }
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/.classpath
===================================================================
--- /trunk/autoquest-generic-event-monitor/.classpath	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/.classpath	(revision 2155)
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>
Index: /trunk/autoquest-generic-event-monitor/.project
===================================================================
--- /trunk/autoquest-generic-event-monitor/.project	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/.project	(revision 2155)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>autoquest-generic-event-monitor</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
Index: /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.core.resources.prefs
===================================================================
--- /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.core.resources.prefs	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.core.resources.prefs	(revision 2155)
@@ -0,0 +1,3 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding/<project>=UTF-8
Index: /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.jdt.core.prefs	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.jdt.core.prefs	(revision 2155)
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.7
Index: /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.m2e.core.prefs
===================================================================
--- /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.m2e.core.prefs	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/.settings/org.eclipse.m2e.core.prefs	(revision 2155)
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
Index: /trunk/autoquest-generic-event-monitor/bin/isRunning.sh
===================================================================
--- /trunk/autoquest-generic-event-monitor/bin/isRunning.sh	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/bin/isRunning.sh	(revision 2155)
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+#   Copyright 2012 Georg-August-Universit�t G�ttingen, Germany
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+
+# This script checks if the autoquest-generic-event-monitor, which is in the same folder as
+# this script, is running
+
+
+# Absolute path to this script, e.g. /home/user/bin/stop.sh
+SCRIPT=$(readlink -f $0)
+
+# Find the autoquest-htmlmonitor JAR-File and start in the Folder of this Script
+JAR_FILE=`find $(dirname $SCRIPT) -type f -name 'autoquest-generic-event-monitor-*.jar'`
+
+# Get the ProcessID of the autoquest-generic-event-monitor which is in the same folder as this script
+PID=$(ps ax | grep "$JAR_FILE" | grep -v ' grep ' | awk '{print $1}')
+
+if [ -z "$PID" ]; then
+  echo "autoquest-generic-event-monitor is not running"
+else
+  echo "autoquest-generic-event-monitor is running with PID: $PID"
+fi
+
Index: /trunk/autoquest-generic-event-monitor/bin/run.sh
===================================================================
--- /trunk/autoquest-generic-event-monitor/bin/run.sh	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/bin/run.sh	(revision 2155)
@@ -0,0 +1,177 @@
+#!/bin/sh
+#   Copyright 2012 Georg-August-Universit�t G�ttingen, Germany
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+
+# This script starts the autoquest-generic-event-monitor which is in the same folder as
+# this script
+
+# Absolute path to this script, e.g. /home/user/bin/foo.sh
+SCRIPT=$(readlink -f $0)
+
+# Find the autoquest-generic-event-monitor JAR-File and start in the Folder of this Script
+JAR_FILE=`find $(dirname $SCRIPT) -type f -name 'autoquest-generic-event-monitor-*.jar'`
+
+# Get the ProcessID of the autoquest-generic-event-monitor which is in the same folder as this script
+PID=$(ps ax | grep "$JAR_FILE" | grep -v ' grep ' | awk '{print $1 " " $7}')
+
+
+# Check if this autoquest-generic-event-monitor is already running
+if [ ! -z "$PID" ]; then
+  echo "autoquest-generic-event-monitor is running with PID:$PID"
+  exit 1
+fi
+
+
+
+HOME_DIR=`dirname $0`
+
+# Given the "java" executable as an argument, find JAVA_HOME
+find_java() {
+  # First check if it is a JDK in the /usr/lib/jvm directory, or a symlink there.
+  # The test is somewhat complicated due to the different ways the Java implementations
+  # are set up with the alternatives system
+  # e.g.
+  #  /usr/bin/java -> /etc/alternatives/java -> /usr/lib/jvm/java-1.5.0-sun/jre/bin/java
+  # or
+  #  /usr/bin/java -> /etc/alternatives/java -> /usr/lib/jvm/java-gcj/bin/java -> /usr/bin/gij-4.2
+
+  JAVA_HOME=$1
+  while true ; do
+    case $JAVA_HOME in
+      /usr/lib/jvm/*)
+        # Found it! Return the correct paremt directory.
+
+        JAVA_HOME=`echo $JAVA_HOME | sed 's:\(/usr/lib/jvm/[^/]*\).*:\1:'`
+	return
+	;;
+      *) ;;
+    esac
+
+    if [ -h $JAVA_HOME ] ; then
+      JAVA_HOME=`readlink $JAVA_HOME`
+    else
+      break
+    fi
+  done
+        
+  # Not found in the Debian alternatives system, so presumably
+  # it is a user-installed JDK/JRE. Might as well be helpful
+  # and try to find JAVA_HOME.
+
+  # First try for a JDK:
+  JAVA_HOME=`readlink -e $1`
+  while [ `dirname $JAVA_HOME` != /  ]; do
+    if [ -e $JAVA_HOME/lib/tools.jar ]; then
+      return
+    fi
+
+    JAVA_HOME=`dirname $JAVA_HOME`
+  done
+
+  # If we get here we did not find a JDK. Search again for a JRE:
+  JAVA_HOME=`readlink -e $1`
+  while [ `dirname $JAVA_HOME` != /  ]; do
+    if [ -e $JAVA_HOME/bin/java ]; then
+      return
+    fi
+
+    JAVA_HOME=`dirname $JAVA_HOME`
+  done
+
+  # Nothing found; leave blank
+  JAVA_HOME=
+}
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=`java-config --jre-home`
+  else
+    # Debian patch - search for preferred JRE
+    if [ -n "$JAVACMD" ] ; then
+      find_java "$JAVACMD"
+    else
+      find_java `which java`
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="`which java`"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly."
+  echo "  We cannot execute $JAVACMD"
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+cd ${HOME_DIR}
+
+
+if [ ! -f "$JAR_FILE" ] ; then
+  echo "Error: Could not find executable jar file in distribution."
+  echo "  Execution aborted."
+  exit 1
+fi
+
+# If the logdir was given to the script: Use it but test if it exist
+# if not, create it
+if [ ! -z "$1" ] ; then
+  LOGDIR="$1"
+  if [ -d "$LOGDIR" ] ; then
+    echo "Using LogDir $LOGDIR"
+  else
+    echo "Creating Folder $LOGDIR"
+    mkdir "$LOGDIR"
+  fi
+else
+  # ./log is the standard log file. If this folder does not exist
+  # create it
+  if [ ! -d "./log" ] ; then
+     mkdir "./log"
+  fi
+  LOGDIR="./log"
+  echo "Using LogDir ./log"
+fi
+
+
+# Every output from the JAR File will be saved in the Console.log
+# &1 is stdout, &2 is stderr
+echo "Starting autoquest-generic-event-monitor"
+exec 3>&1 4>&2 >>"$LOGDIR"/console.log 2>&1
+
+echo ""
+echo "starting generic event monitor"
+date
+echo "using java $JAVACMD"
+
+$JAVACMD -jar ${JAR_FILE} $LOGDIR $2 2>&1 >> "$LOGDIR"/console.log &
+
+# restore stdout and stderr
+exec 1>&3 2>&4
Index: /trunk/autoquest-generic-event-monitor/bin/stop.sh
===================================================================
--- /trunk/autoquest-generic-event-monitor/bin/stop.sh	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/bin/stop.sh	(revision 2155)
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+#   Copyright 2012 Georg-August-Universit�t G�ttingen, Germany
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+
+
+# This script stops the autoquest-generic-event-monitor which is in the same folder as
+# this script
+
+
+# Absolute path to this script, e.g. /home/user/bin/stop.sh
+SCRIPT=$(readlink -f $0)
+
+# Find the autoquest-generic-event-monitor JAR-File and start in the Folder of this Script
+JAR_FILE=`find $(dirname $SCRIPT) -type f -name 'autoquest-generic-event-monitor-*.jar'`
+
+# Get the ProcessID of the autoquest-generic-event-monitor which is in the same folder as this script
+PID=$(ps ax | grep "$JAR_FILE" | grep -v ' grep ' | awk '{print $1}')
+
+if [ -z "$PID" ]; then
+  echo autoquest-generic-event-monitor is not running
+else
+  #Kill this autoquest-generic-event-monitor-process
+  kill "$PID"
+  sleep 2
+  # Check if Process is no more
+  CHECK=$(ps ax | grep "$JAR_FILE" | grep -v ' grep ' | awk '{print $1}')
+  if [ ! -z "$CHECK" ]; then
+	kill -9 "$CHECK"
+  fi
+  echo autoquest-generic-event-monitor stopped
+fi
+
Index: /trunk/autoquest-generic-event-monitor/pom.xml
===================================================================
--- /trunk/autoquest-generic-event-monitor/pom.xml	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/pom.xml	(revision 2155)
@@ -0,0 +1,120 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+    <groupId>de.ugoe.cs.autoquest</groupId>
+    <artifactId>autoquest-generic-event-monitor</artifactId>
+    <version>0.2.2-SNAPSHOT</version>
+    <name>autoquest-generic-event-monitor</name>
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+        </license>
+    </licenses>
+    <packaging>jar</packaging>
+    <scm>
+        <url>https://autoquest.informatik.uni-goettingen.de/svn/autoquest/trunk/${project.artifactId}</url>
+        <connection>scm:svn:https://autoquest.informatik.uni-goettingen.de/svn/autoquest/trunk/${project.artifactId}</connection>
+    </scm>
+    <distributionManagement>
+        <snapshotRepository>
+            <id>autoquest-snapshots</id>
+            <url>https://trex.informatik.uni-goettingen.de/nexus/content/repositories/autoquest-snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>autoquest</id>
+            <url>https://trex.informatik.uni-goettingen.de/nexus/content/repositories/autoquest-releases</url>
+        </repository>
+    </distributionManagement>
+    <pluginRepositories>
+        <pluginRepository>
+            <id>javascript</id>
+            <name>Codehaus Snapshot Repository</name>
+            <url>https://nexus.codehaus.org/content/groups/snapshots-group/</url>
+            <layout>default</layout>
+            <snapshots>
+                <enabled>true</enabled>
+            </snapshots>
+            <releases>
+                <updatePolicy>never</updatePolicy>
+            </releases>
+        </pluginRepository>
+    </pluginRepositories>
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>de.ugoe.cs</groupId>
+            <artifactId>java-utils</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-server</artifactId>
+            <version>9.1.0.M0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlet</artifactId>
+            <version>9.1.0.M0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.eclipse.jetty</groupId>
+            <artifactId>jetty-servlets</artifactId>
+            <version>9.1.0.M0</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.json-simple</groupId>
+            <artifactId>json-simple</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.7</version>
+        </dependency>
+    </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <addClasspath>true</addClasspath>
+                            <classpathPrefix>lib/</classpathPrefix>
+                            <mainClass>de.ugoe.cs.autoquest.genericeventmonitor.Runner</mainClass>
+                        </manifest>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.2-beta-2</version>
+                <configuration>
+                    <descriptors>
+                        <descriptor>src/main/assembly/bin.xml</descriptor>
+                    </descriptors>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+       </plugins>
+  </build>
+</project>
Index: /trunk/autoquest-generic-event-monitor/src/main/assembly/bin.xml
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/assembly/bin.xml	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/assembly/bin.xml	(revision 2155)
@@ -0,0 +1,37 @@
+<assembly
+    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
+  <id>bin</id>
+  <formats>
+    <format>tar.gz</format>
+    <format>dir</format>
+    <!-- format>tar.bz2</format>
+    <format>zip</format-->
+  </formats>
+  <dependencySets>
+    <dependencySet>
+      <includes></includes>
+      <excludes>
+        <exclude>de.ugoe.cs.autoquest:autoquest-generic-event-monitor</exclude>
+      </excludes>
+      <outputDirectory>lib</outputDirectory>
+    </dependencySet>
+    <dependencySet>
+      <includes>
+          <include>de.ugoe.cs.autoquest:autoquest-generic-event-monitor</include>
+      </includes>
+      <outputDirectory></outputDirectory>
+    </dependencySet>
+  </dependencySets>
+  <fileSets>
+    <fileSet>
+      <directory>bin</directory>
+      <outputDirectory></outputDirectory>
+      <fileMode>775</fileMode>
+      <includes>
+        <include>*</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ClientInfos.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ClientInfos.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ClientInfos.java	(revision 2155)
@@ -0,0 +1,63 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+/**
+ * <p>
+ * represents infos of a client, e.g., its id
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class ClientInfos {
+
+    /**
+     * id of a client
+     */
+    private String clientId;
+
+    /**
+     * id of the app the client uses
+     */
+    private String appId;
+
+    /**
+     * <p>
+     * instantiates an infos object with client id and an app id
+     * </p>
+     *
+     * @param clientId  id of a client
+     * @param appId  id of the app that the client uses
+     */
+    ClientInfos(String clientId, String appId) {
+        this.clientId = clientId;
+        this.appId = appId;
+    }
+
+    /**
+     * @return the clientId
+     */
+    String getClientId() {
+        return clientId;
+    }
+
+    /**
+     * @return the appId
+     */
+    String getAppId() {
+        return appId;
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEvent.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEvent.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEvent.java	(revision 2155)
@@ -0,0 +1,122 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * represents an event caused by a user on a client. An event contains the infos
+ * about the client ({@link ClientInfos}, when and where the event took place, the type of
+ * event and some additional infos such as the event coordinates or the number of the pressed
+ * key. 
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEvent {
+
+    /**
+     * infos about the client that caused the event
+     */
+    private ClientInfos clientInfos;
+
+    /**
+     * the time stamp of the event
+     */
+    private Long time;
+
+    /**
+     * the target on which the event was executed
+     */
+    private GenericEventTarget target;
+
+    /**
+     * the type of the event, e.g. onclick
+     */
+    private String eventType;
+
+    /**
+     * additional parameters of the event
+     */
+    private Map<String, String> parameters;
+
+    /**
+     * <p>
+     * initializes the event with all relevant infos
+     * </p>
+     *
+     * @param clientInfos    infos about the client that caused the event
+     * @param time           the time stamp of the event
+     * @param target         the HTML element on which the event was executed
+     * @param eventType      the type of the event
+     * @param parameters     the parameters of the event
+     */
+    GenericEvent(ClientInfos         clientInfos,
+                 Long                time,
+                 GenericEventTarget  target,
+                 String              eventType,
+                 Map<String, String> parameters)
+    {
+        this.clientInfos = clientInfos;
+        this.time = time;
+        this.target = target;
+        this.eventType = eventType;
+        this.parameters = parameters;
+    }
+
+    /**
+     * @return the clientInfos
+     */
+    ClientInfos getClientInfos() {
+        return clientInfos;
+    }
+
+    /**
+     * @return the time
+     */
+    Long getTime() {
+        return time;
+    }
+
+    /**
+     * @return the target
+     */
+    GenericEventTarget getTarget() {
+        return target;
+    }
+
+    /**
+     * @return the eventType
+     */
+    String getEventType() {
+        return eventType;
+    }
+
+    /**
+     * @return the parameters
+     */
+    Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return eventType + " on " + target;
+    }
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitor.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitor.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitor.java	(revision 2155)
@@ -0,0 +1,143 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * The generic event monitor starts a web server ({@link GenericEventMonitorServer}) that receives
+ * events in JSON format. These events are logged using the {@link GenericEventMonitorLogManager}.
+ * The class assures that on shutdown e.g. caused by CTRL-C the server and the log manager are
+ * stopped correctly.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class GenericEventMonitor implements GenericEventMonitorComponent {
+
+    /**
+     * the port on which the webserver shall listen. Defaults to 8090.
+     */
+    private int port = 8090;
+    
+    /**
+     * the web server receiving the log messages
+     */
+    private GenericEventMonitorServer server;
+    
+    /**
+     * the directory into which the log files shall be written
+     */
+    private String logFileBaseDir;
+
+    /**
+     * the log manager being responsible for performing the logging
+     */
+    private GenericEventMonitorLogManager logManager;
+
+    /**
+     * the thread needed to handle CTRL-C events
+     */
+    private Thread shutdownHook;
+
+    /**
+     * <p>
+     * initializes the monitor with the command line arguments. Those may be the log directory
+     * as first argument and the port to listen on as second
+     * </p>
+     *
+     * @param commandLineArguments the command line arguments when starting the monitor using
+     *                             the {@link Runner}
+     */
+    public GenericEventMonitor(String[] commandLineArguments) {
+        if (commandLineArguments.length > 0) {
+            this.logFileBaseDir = commandLineArguments[0];
+            Console.println("putting logs into directory " + this.logFileBaseDir);
+        }
+        
+        if (commandLineArguments.length > 1) {
+            try {
+                this.port = Integer.parseInt(commandLineArguments[1]);
+            }
+            catch (NumberFormatException e) {
+                Console.println("ignoring invalid port specification " + commandLineArguments[1]);
+            }
+            Console.println("listening on port " + this.port);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws GenericEventMonitorException {
+        if (server != null) {
+            throw new IllegalStateException("already initialized.");
+        }
+        
+        try {
+            logManager = new GenericEventMonitorLogManager(logFileBaseDir);
+            logManager.init();
+        
+            server = new GenericEventMonitorServer(port, logManager);
+            server.init();
+        
+            shutdownHook = new Thread(new ShutdownHook(server, logManager));
+        }
+        catch (GenericEventMonitorException e) {
+            Console.printerrln("could not initialize generic event monitor: " + e);
+            Console.logException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() {
+        if (server == null) {
+            throw new IllegalStateException("not initialized.");
+        }
+        
+        try {
+            Runtime.getRuntime().addShutdownHook(shutdownHook);
+            logManager.start();
+            server.start();
+        }
+        catch (GenericEventMonitorException e) {
+            Console.printerrln("could not start generic event monitor: " + e);
+            Console.logException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        if (server == null) {
+            throw new IllegalStateException("not initialized.");
+        }
+        
+        Runtime.getRuntime().removeShutdownHook(shutdownHook);
+        server.stop();
+        logManager.stop();
+        
+        server = null;
+        logManager = null;
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorComponent.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorComponent.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorComponent.java	(revision 2155)
@@ -0,0 +1,56 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+/**
+ * <p>
+ * convenience interface for all components making up the generic event monitor. All components
+ * implement this interface to ensure homogeneity throughout them. A component must first be
+ * initialized ({@link #init()}), then started ({@link #start()}) and finally stopped
+ * ({@link #stop()}).
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+interface GenericEventMonitorComponent {
+
+    /**
+     * initializes a component, i.e. it does everything needed to prepare starting the component
+     * 
+     * @throws IllegalStateException        thrown if the method was already called before but
+     *                                      {@link #stop()} wasn't called yet
+     * @throws GenericEventMonitorException  thrown if the initialization fails, e.g. because needed
+     *                                      infos are missing
+     */
+    void init() throws IllegalStateException, GenericEventMonitorException;
+
+    /**
+     * starts a component
+     * 
+     * @throws IllegalStateException        thrown if {@link #init()} wasn't called yet of if the
+     *                                      component is already started through a preceding call
+     *                                      to this method
+     * @throws GenericEventMonitorException  thrown if the startup fails, e.g. because needed infos
+     *                                      are missing
+     */
+    void start() throws IllegalStateException, GenericEventMonitorException;
+
+    /**
+     * stops a component and cleans up any derivate. In the following, {@link #init()} must be
+     * callable again. If the component is not initialized or started, nothing must happen. If the
+     * component is initialized but not started, the initialization is revoked.
+     */
+    void stop();
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorException.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorException.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorException.java	(revision 2155)
@@ -0,0 +1,53 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+/**
+ * <p>
+ * Exception to notify all irregularities, that are specific to the generic event monitor and its
+ * components.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEventMonitorException extends Exception {
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * <p>
+     * initializes an exception with a simple message
+     * </p>
+     *
+     * @param message the message of the exception
+     */
+    GenericEventMonitorException(String message) {
+        super(message);
+    }
+
+    /**
+     * <p>
+     * initializes an exception with a simple message and a causing exception
+     * </p>
+     *
+     * @param message the message of the exception
+     * @param cause   the root cause of the exception
+     */
+    GenericEventMonitorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorLogManager.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorLogManager.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorLogManager.java	(revision 2155)
@@ -0,0 +1,198 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * The log manager handles different {@link OutputWriter}s to perform the logging
+ * of recorded events. It initializes a new writer if the first event set for a specific
+ * client is received and it closes a writer if for a specific period of time no further message
+ * of the same client was received. The writers themselves consider log rotation. For handling
+ * messages, the {@link GenericEventMonitorMessageListener} mechanism provided by the
+ * {@link GenericEventMonitorServer} is used.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEventMonitorLogManager
+    implements GenericEventMonitorComponent, GenericEventMonitorMessageListener
+{
+    
+    /**
+     * the timeout after which a writer of an inactive client is closed
+     */
+    private static final int SESSION_TIMEOUT = 10 * 60 * 1000;
+    
+    /**
+     * the directory into which the log files shall be written
+     */
+    private String logFileBaseDir;
+
+    /**
+     * the mapping of client ids to the respective writers.
+     */
+    private Map<String, OutputWriter> writers;
+
+    /**
+     * a timer used to detect client timeouts
+     */
+    private Timer logFileMonitorTimer;
+
+    /**
+     * <p>
+     * initializes the log manager with the directory into which the log files shall be stored
+     * </p>
+     *
+     * @param logFileBaseDir the directory into which the log files shall be stored
+     */
+    GenericEventMonitorLogManager(String logFileBaseDir) {
+        this.logFileBaseDir = logFileBaseDir;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws IllegalStateException, GenericEventMonitorException {
+        if (writers != null) {
+            throw new IllegalStateException("already initialized");
+        }
+        
+        writers = new HashMap<String, OutputWriter>();
+        logFileMonitorTimer = new Timer();
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws IllegalStateException, GenericEventMonitorException {
+        if (writers == null) {
+            throw new IllegalStateException("not initialized");
+        }
+        
+        logFileMonitorTimer.schedule
+            (new LogFileMonitorTimerTask(), SESSION_TIMEOUT / 2, SESSION_TIMEOUT / 2);
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        if (writers != null) {
+            logFileMonitorTimer.cancel();
+            
+            for (OutputWriter writer : writers.values()) {
+                writer.stop();
+            }
+        }
+        
+        writers = null;
+    }
+
+    /* (non-Javadoc)
+     * @see GenericEventMonitorMessageListener#handleEvents(ClientInfos, GenericEvent[])
+     */
+    @Override
+    public void handleEvents(ClientInfos          clientInfos,
+                             GenericEvent[]       events)
+    {
+        OutputWriter writer = writers.get(clientInfos.getAppId() + clientInfos.getClientId());
+        
+        try {
+            if (writer == null) {
+                synchronized (this) {
+                    writer = writers.get(clientInfos.getAppId() + clientInfos.getClientId());
+                    if (writer == null) {
+                        writer = new OutputWriter(logFileBaseDir,
+                                                  clientInfos.getAppId(),
+                                                  clientInfos.getClientId());
+
+                        writer.init();
+                        writer.start();
+                        writers.put(clientInfos.getAppId() + clientInfos.getClientId(), writer);
+                    }
+                }
+            }
+
+            writer.handleEvents(clientInfos, events);
+        }
+        catch (Exception e) {
+            Console.printerrln("could not handle message of client " + clientInfos.getClientId() +
+                               " for app " + clientInfos.getAppId() + ": " + e);
+            e.printStackTrace();
+            Console.logException(e);
+            
+            // determine, if the writer exists but is not able to log something. In this case,
+            // destroy the writer (part of the message may be logged twice through this).
+            writer = writers.get(clientInfos.getClientId());
+            if (writer != null) {
+                try {
+                    writer.handleEvents(clientInfos, events);
+                }
+                catch (Exception e1) {
+                    synchronized (this) {
+                        writers.remove(clientInfos.getClientId());
+                        writer.stop();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * internal timer task used for detecting session timeouts of clients
+     * </p>
+     * 
+     * @author Patrick Harms
+     */
+    public class LogFileMonitorTimerTask extends TimerTask {
+
+        /* (non-Javadoc)
+         * @see java.lang.Runnable#run()
+         */
+        @Override
+        public void run() {
+            synchronized (GenericEventMonitorLogManager.this) {
+                List<String> timeoutSessions = new ArrayList<String>();
+                for (Map.Entry<String, OutputWriter> entry : writers.entrySet()) {
+                    OutputWriter writer = entry.getValue();
+                    
+                    if (System.currentTimeMillis() - writer.getLastUpdate() > SESSION_TIMEOUT) {
+                        timeoutSessions.add(entry.getKey());
+                    }
+                }
+                
+                for (String sessionId : timeoutSessions) {
+                    OutputWriter writer = writers.remove(sessionId);
+                    writer.stop();
+                }
+            }
+        }
+
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorMessageListener.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorMessageListener.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorMessageListener.java	(revision 2155)
@@ -0,0 +1,39 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+/**
+ * <p>
+ * a message listener to be registered with the {@link GenericEventMonitorServer} for being called
+ * for new messages containing events.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public interface GenericEventMonitorMessageListener {
+
+    /**
+     * <p>
+     * called for new received messages containing events. The client is described through the
+     * <code>clientInfos</code>. The events are several events that were recorded at client side.
+     * </p>
+     *
+     * @param clientInfos  infos about the client that send the events
+     * @param events       the received events
+     */
+    void handleEvents(ClientInfos          clientInfos,
+                      GenericEvent[]       events);
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServer.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServer.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServer.java	(revision 2155)
@@ -0,0 +1,142 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.util.EnumSet;
+
+import javax.servlet.DispatcherType;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.FilterHolder;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.servlets.CrossOriginFilter;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * this is the web server, that receives the events. It is initialized with a port on which it
+ * shall listen, as well as a message listener to forward the received messages to. Internally it
+ * starts a jetty web server with a single {@link GenericEventMonitorServlet} to receive the
+ * events.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEventMonitorServer implements GenericEventMonitorComponent {
+    
+    /**
+     * the port to listen on
+     */
+    private int port;
+
+    /**
+     * the jetty web server used for receiving messages
+     */
+    private Server server;
+
+    /**
+     * the message listener to forward the messages to
+     */
+    private GenericEventMonitorMessageListener messageListener;
+
+    /**
+     * <p>
+     * initializes the server with the port to listen on and the message listener to forward
+     * the messages to.
+     * </p>
+     *
+     * @param port            the port to listen on
+     * @param messageListener the message listener to forward the messages to
+     */
+    GenericEventMonitorServer(int port, GenericEventMonitorMessageListener messageListener) {
+        this.port = port;
+        this.messageListener = messageListener;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() {
+        if (server != null) {
+            throw new IllegalStateException("already initialized. First call stop()");
+        }
+
+        server = new Server(port);
+        
+        /*
+        // the following code can be used to support SSL directly
+        server = new Server();
+        
+        SslSocketConnector connector = new SslSocketConnector();
+        connector.setPort(port);
+        connector.setKeystore("data/keystore");
+        connector.setPassword("123456");
+        connector.setKeyPassword("123456");
+        connector.setTruststore("data/keystore");
+        connector.setTrustPassword("123456");
+        server.addConnector(connector);*/
+
+        ServletContextHandler root =
+            new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
+
+        GenericEventMonitorServlet servlet = new GenericEventMonitorServlet(messageListener);
+        ServletHolder servletHolder = new ServletHolder(servlet);
+        root.addServlet(servletHolder, "/*");
+        
+        CrossOriginFilter filter = new CrossOriginFilter();
+        FilterHolder filterHolder = new FilterHolder(filter);
+        filterHolder.setInitParameter("allowedOrigins", "*");
+        filterHolder.setInitParameter("allowedMethods", "GET,POST");
+        root.addFilter(filterHolder, "/*", EnumSet.allOf(DispatcherType.class));
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws GenericEventMonitorException {
+        if (server == null) {
+            throw new IllegalStateException("server not initialized yet. First call init()");
+        }
+        
+        try {
+            server.start();
+        }
+        catch (Exception e) {
+            throw new GenericEventMonitorException("could not start server", e);
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        try {
+            if (server != null) {
+                server.stop();
+            }
+        }
+        catch (Exception e) {
+            Console.printerrln("could not stop generic event monitor server: " + e.getMessage());
+            Console.logException(e);
+        }
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServlet.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServlet.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventMonitorServlet.java	(revision 2155)
@@ -0,0 +1,613 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import org.json.simple.parser.ParseException;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * the servlet deployed in the web server that receives all generic events. The messages are parsed,
+ * validated, and forwarded to the provided message listener. If a message is not valid, it is
+ * discarded. If an event in a message is not valid, it is discarded. Messages are only received via
+ * the POST HTTP method.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEventMonitorServlet extends DefaultServlet {
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+    
+    /** must be true to do some detailed logging of what the server does */
+    private static final boolean DO_TRACE = false;
+
+    /**
+     * the map of event target ids to the concrete target objects.
+     */
+    private transient Map<String, GenericEventTarget> eventTargets = new HashMap<>();
+
+    /**
+     * the message listener to forward received messages to.
+     */
+    private transient GenericEventMonitorMessageListener messageListener;
+
+    /**
+     * <p>
+     * initializes the servlet with the message listener to which all events shall be forwarded
+     * </p>
+     *
+     * @param messageListener the message listener that shall receive all events
+     */
+    GenericEventMonitorServlet(GenericEventMonitorMessageListener messageListener) {
+        this.messageListener = messageListener;
+    }
+
+    /**
+     * this implements handling of doPost. For this servlet this means that
+     * the data from the post request will be parsed and validated.
+     * 
+     * (non-Javadoc)
+     * @see org.mortbay.jetty.servlet.DefaultServlet#doPost(HttpServletRequest, HttpServletResponse)
+     */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        Object value = null;
+        try {
+            //InputStream requestInputStream = dumpStreamContent(request.getInputStream());
+            InputStream requestInputStream = request.getInputStream();
+
+            value = JSONValue.parseWithException
+                (new InputStreamReader(requestInputStream, "UTF-8"));
+            
+            if (!(value instanceof JSONObject)) {
+                Console.printerrln("incoming data is not of the expected type --> discarding it");
+            }
+            else {
+                handleJSONObject((JSONObject) value);
+            }
+        }
+        catch (ParseException e) {
+            Console.printerrln
+                ("could not parse incoming data --> discarding it (" + e.toString() + ")");
+        }
+        
+        // this must be done to prevent firefox from notifying the "element not found" error
+        response.setContentType("text/plain");
+        response.getOutputStream().write(' ');
+    }
+
+    /**
+     * <p>
+     * processes a received JSON object and validates it. If the message is ok, it is forwarded
+     * to the message listener
+     * </p>
+     *
+     * @param object the JSON object that contains a client message
+     */
+    private void handleJSONObject(JSONObject object) {
+        if (DO_TRACE) {
+            dumpJSONObject(object, "");
+        }
+        
+        JSONObject message = assertValue(object, "message", JSONObject.class);
+        
+        if (message == null) {
+            Console.printerrln("incoming data is no valid message --> discarding it");
+        }
+        else {
+            ClientInfos clientInfos = extractClientInfos(message);
+
+            if (clientInfos == null) {
+                Console.printerrln
+                    ("incoming message does not contain valid client infos --> discarding it");
+            }
+            else {
+                extractGenericEventTargets(message, clientInfos);
+                GenericEvent[] events = extractGenericEvents(message, clientInfos);
+                
+                if (events == null) {
+                    Console.printerrln
+                        ("incoming message does not contain valid events --> discarding it");
+                }
+                else {
+                    messageListener.handleEvents(clientInfos, events);
+                    Console.println
+                        ("handled message of " + clientInfos.getClientId() + " for app " + 
+                         clientInfos.getAppId() + " (" + events.length + " events)");
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * tries to extract the client infos out of the received JSON object. If this is not fully
+     * possible, an appropriate message is dumped and the whole message is discarded (the method
+     * return null).
+     * </p>
+     *
+     * @param message the message to parse the client infos from
+     * 
+     * @return the client infos, if the message is valid in this respect, or null if not
+     */
+    private ClientInfos extractClientInfos(JSONObject message) {
+        ClientInfos clientInfos = null;
+        
+        JSONObject infos = assertValue(message, "clientInfos", JSONObject.class);
+        
+        if (infos != null) {
+            String clientId = assertValue((JSONObject) infos, "clientId", String.class);
+            String appId = assertValue((JSONObject) infos, "appId", String.class);
+           
+            if (clientId == null) {
+                Console.printerrln("client infos do not contain a valid client id");
+            }
+            else if (appId == null) {
+                Console.printerrln("client infos do not contain a valid application id");
+            }
+            else {
+                clientInfos = new ClientInfos(clientId, appId);
+            }
+        }
+        
+        return clientInfos;
+    }
+
+    /**
+     * <p>
+     * tries to extract the events out of the received JSON object. If this is not fully
+     * possible, an appropriate message is dumped and the errorprone event is discarded. If no
+     * valid event is found, the whole message is discarded.
+     * </p>
+     *
+     * @param object      the message to parse the events from
+     * @param clientInfos the infos about the client that send the events
+     *  
+     * @return the valid events stored in the message, or null if there are none
+     */
+    private GenericEvent[] extractGenericEvents(JSONObject object, ClientInfos clientInfos) {
+        List<GenericEvent> events = null;
+        
+        JSONArray eventArray = assertValue(object, "events", JSONArray.class);
+        
+        if (eventArray != null) {
+            events = new ArrayList<GenericEvent>();
+            
+            for (int i = 0; i < eventArray.size(); i++) {
+                Object eventObj = eventArray.get(i);
+                if (!(eventObj instanceof JSONObject)) {
+                    Console.printerrln("event number " + (i + 1) + " is not a valid event object");
+                }
+                else {
+                    Long time = assertValue(((JSONObject) eventObj), "time", Long.class);
+                    String eventType =
+                        assertValue(((JSONObject) eventObj), "eventType", String.class);
+                    String eventTargetId =
+                        assertValue(((JSONObject) eventObj), "eventTargetId", String.class);
+                    
+                    if (eventType == null) {
+                        Console.printerrln("event number " + (i + 1) + " has no valid event type");
+                    }
+                    else if (eventTargetId == null) {
+                        Console.printerrln(eventType + " event has no valid target id");
+                    }
+                    else if (time == null) {
+                        Console.printerrln(eventType + " event has no valid timestamp");
+                    }
+                    else {
+                        GenericEventTarget target = eventTargets.get(eventTargetId);
+                        Map<String, String> parameters = getParameters
+                            (((JSONObject) eventObj), "time", "eventType", "eventTargetId");
+                        
+                        if (target != null) {
+                            events.add(new GenericEvent(clientInfos, time, target, eventType,
+                                                        parameters));
+                        }
+                        else {
+                            Console.printerrln(eventType + " event has no known target");
+                        }
+                    }
+                }
+            }
+            
+        }
+        
+        if ((events != null) && (events.size() > 0)) {
+            return events.toArray(new GenericEvent[events.size()]);
+        }
+        else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * extracts the event target structure from the provided JSON object.
+     * </p>
+     *
+     * @param object      the JSON object to extract the event target structure from
+     * @param clientInfos infos about the client who send the data
+     * 
+     * @return the event target structure extracted from the JSON object of which the root nodes
+     *         are provided
+     */
+    private GenericEventTarget[] extractGenericEventTargets(JSONObject  object,
+                                                            ClientInfos clientInfos)
+    {
+        JSONObject[] jsonTargets = assertValue(object, "targetStructure", JSONObject[].class);
+        
+        if (jsonTargets != null) {
+            GenericEventTarget[] result = new GenericEventTarget[jsonTargets.length];
+
+            for (int i = 0; i < jsonTargets.length; i++) {
+                result[i] = convert(jsonTargets[i], null);
+            }
+
+            return result;
+        }
+        else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * converts a JSON object representing a target to the generic target. Calls
+     * itself recursively to also convert the children of the element, if any.
+     * </p>
+     *
+     * @param jsonTarget      the JSON object to be converted
+     * @param parent          the parent target of the converted element, or null, if none
+     *                        is present.
+     *                        
+     * @return as described.
+     */
+    private GenericEventTarget convert(JSONObject         jsonTarget,
+                                       GenericEventTarget parent)
+    {
+        GenericEventTarget result = null;
+
+        if (jsonTarget != null) {
+            String targetId = assertValue(jsonTarget, "targetId", String.class);
+            Map<String, String> parameters = getParameters(jsonTarget, "targetId", "children");
+
+            result = new GenericEventTarget(targetId, parameters, parent);
+
+            JSONArray childElements = assertValue(jsonTarget, "children", JSONArray.class);
+            
+            if (childElements != null) {
+                Object jsonChild;
+
+                for (int i = 0; i < childElements.size(); i++) {
+                    jsonChild = childElements.get(i);
+                    if (!(jsonChild instanceof JSONObject)) {
+                        Console.printerrln("child " + (i + 1) + " of target " + targetId +
+                                           " is no valid event target");
+                    }
+                    else {
+                        result.addChild(convert((JSONObject) jsonChild, result));
+                    }
+                }
+            }
+            
+        }
+        
+        if (result != null) {
+            eventTargets.put(result.getId(), result);
+        }
+        
+        return result;    
+    }
+
+    /**
+     * <p>
+     * converts a value in the provided object matching the provided key to the provided type. If
+     * there is no value with the key or if the value can not be transformed to the provided type,
+     * the method returns null. 
+     * </p>
+     *
+     * @param object the object to read the value from
+     * @param key    the key of the value
+     * @param clazz  the type to which the value should be transformed
+     * 
+     * @return the value or null if either the value does not exist or if it can not be transformed
+     *         to the expected type
+     */
+    @SuppressWarnings("unchecked")
+    private <T> T assertValue(JSONObject object, String key, Class<T> clazz) {
+        Object value = object.get(key);
+        T result = null;
+        
+        if (clazz.isInstance(value)) {
+            result = (T) value;
+        }
+        else if (value instanceof String) {
+            if (URL.class.equals(clazz)) {
+                try {
+                    result = (T) new URL((String) value);
+                }
+                catch (MalformedURLException e) {
+                    e.printStackTrace();
+                    Console.printerrln("retrieved malformed URL for key '" + key + "': " + value +
+                                       " (" + e.toString() + ")");
+                }
+            }
+            else if ((int.class.equals(clazz)) || (Integer.class.equals(clazz))) {
+                try {
+                    result = (T) Integer.valueOf(Integer.parseInt((String) value));
+                }
+                catch (NumberFormatException e) {
+                    Console.printerrln
+                        ("retrieved malformed integer for key '" + key + "': " + value);
+                }
+            }
+            else if ((long.class.equals(clazz)) || (Long.class.equals(clazz))) {
+                try {
+                    result = (T) Long.valueOf(Long.parseLong((String) value));
+                }
+                catch (NumberFormatException e) {
+                    Console.printerrln
+                        ("retrieved malformed long for key '" + key + "': " + value);
+                }
+            }
+        }
+        else if (value instanceof Long) {
+            if ((int.class.equals(clazz)) || (Integer.class.equals(clazz))) {
+                result = (T) (Integer) ((Long) value).intValue();
+            }
+        }
+        else if (value instanceof JSONArray) {
+            if ((int[].class.equals(clazz)) || (Integer[].class.equals(clazz))) {
+                Integer[] resultArray = new Integer[((JSONArray) value).size()];
+                boolean allCouldBeParsed = true;
+                
+                for (int i = 0; i < ((JSONArray) value).size(); i++) {
+                    try {
+                        if (((JSONArray) value).get(i) instanceof Long) {
+                            resultArray[i] = (int) (long) (Long) ((JSONArray) value).get(i);
+                        }
+                        else if (((JSONArray) value).get(i) instanceof String) {
+                            try {
+                                resultArray[i] =
+                                    (int) Long.parseLong((String) ((JSONArray) value).get(i));
+                            }
+                            catch (NumberFormatException e) {
+                                Console.printerrln
+                                    ("retrieved malformed integer array for key '" + key + "': " +
+                                     value);
+                        
+                                allCouldBeParsed = false;
+                                break;
+                            }
+                        }
+                        else {
+                            Console.printerrln
+                                ("can not handle type of value in expected integer array '" + key +
+                                 "': " + value);
+                        }
+                    }
+                    catch (ClassCastException e) {
+                        e.printStackTrace();
+                        Console.printerrln("expected integer array for key '" + key +
+                                           "' but it was something else: " + value);
+                        
+                        allCouldBeParsed = false;
+                        break;
+                    }
+                }
+                
+                if (allCouldBeParsed) {
+                    result = (T) resultArray;
+                }
+            }
+            else if (JSONObject[].class.equals(clazz)) {
+                JSONObject[] resultArray = new JSONObject[((JSONArray) value).size()];
+                boolean allCouldBeParsed = true;
+                
+                for (int i = 0; i < ((JSONArray) value).size(); i++) {
+                    try {
+                        if (((JSONArray) value).get(i) instanceof JSONObject) {
+                            resultArray[i] = (JSONObject) ((JSONArray) value).get(i);
+                        }
+                    }
+                    catch (ClassCastException e) {
+                        e.printStackTrace();
+                        Console.printerrln("expected JSON Object array for key '" + key +
+                                           "' but it was something else: " + value);
+                        
+                        allCouldBeParsed = false;
+                        break;
+                    }
+                }
+                
+                if (allCouldBeParsed) {
+                    result = (T) resultArray;
+                }
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * <p>
+     * determines other parameters provided in the JSON object. The parameters with the provided
+     * keys to ignore are not put into the resulting map.
+     * </p>
+     *
+     * @param object      the object, from which parameters shall be read
+     * @param ignoredKeys the parameters not to be put into the resulting map
+     * 
+     * @return a map with the parameters contained in the provided JSON object
+     */
+    private Map<String, String> getParameters(JSONObject object, String ... ignoredKeys) {
+        Map<String, String> result = new HashMap<>();
+        
+        NEXT_KEY:
+        for (Object key : object.keySet()) {
+            if (!(key instanceof String)) {
+                continue NEXT_KEY;
+            }
+            
+            for (String ignoredKey : ignoredKeys) {
+                if (ignoredKey.equals(key)) {
+                    continue NEXT_KEY;
+                }
+            }
+            
+            String value = assertValue(object, (String) key, String.class);
+            
+            result.put((String) key, value);
+        }
+        
+        return result;
+    }
+
+    /**
+     * <p>
+     * convenience method for dumping the content of a stream and returning a new stream
+     * containing the same data.
+     * </p>
+     *
+     * @param inputStream the stream to be dumped and copied
+     * @return the copy of the stream
+     * 
+     * @throws IOException if the stream can not be read 
+     */
+/*    private InputStream dumpStreamContent(ServletInputStream inputStream) throws IOException {
+        List<Byte> bytes = new ArrayList<Byte>();
+        int buf;
+        
+        while ((buf = inputStream.read()) >= 0) {
+            bytes.add((byte) buf);
+        }
+        
+        byte[] byteArray = new byte[bytes.size()];
+        for (int i = 0; i < bytes.size(); i++) {
+            byteArray[i] = bytes.get(i);
+        }
+        
+        System.out.println(new String(byteArray, "UTF-8"));
+        
+        return new ByteArrayInputStream(byteArray);
+    }*/
+
+    /**
+     * <p>
+     * convenience method for dumping an object to std out. If the object is a JSON object, it is
+     * deeply analyzed and its internal structure is dumped as well.
+     * </p>
+     *
+     * @param object the object to dump
+     * @param indent the indentation to be used.
+     */
+    private void dumpJSONObject(Object object, String indent) {
+        if (object instanceof JSONArray) {
+            boolean arrayContainsJSONObjects = false;
+            for (Object arrayElem : (JSONArray) object) {
+                if (arrayElem instanceof JSONObject) {
+                    arrayContainsJSONObjects = true;
+                    break;
+                }                
+            }
+            
+            if (arrayContainsJSONObjects) {
+                System.out.println();
+                System.out.print(indent);
+                System.out.println('[');
+                System.out.print(indent);
+                System.out.print(' ');
+            }
+            else {
+                System.out.print(' ');
+                System.out.print('[');
+            }
+            
+            int index = 0;
+            for (Object arrayElem : (JSONArray) object) {
+                if (index++ > 0) {
+                    System.out.print(",");
+                    if (arrayContainsJSONObjects) {
+                        System.out.println();
+                        System.out.print(indent);
+                    }
+
+                    System.out.print(' ');
+                }
+
+                dumpJSONObject(arrayElem, indent + "  ");
+            }
+            
+            if (arrayContainsJSONObjects) {
+                System.out.println();
+                System.out.print(indent);
+            }
+            
+            System.out.print(']');
+        }
+        else if (object instanceof JSONObject) {
+            System.out.println(" {");
+            
+            @SuppressWarnings("unchecked")
+            Set<Map.Entry<?,?>> entrySet = ((JSONObject) object).entrySet();
+            
+            int index = 0;
+            for (Map.Entry<?,?> entry : entrySet) {
+                if (index++ > 0) {
+                    System.out.println(",");
+                }
+                System.out.print(indent);
+                System.out.print("  \"");
+                System.out.print(entry.getKey());
+                System.out.print("\":");
+                dumpJSONObject(entry.getValue(), indent + "  ");
+            }
+            
+            System.out.println();
+            System.out.print(indent);
+            System.out.print('}');
+        }
+        else {
+            System.out.print('"');
+            System.out.print(object);
+            System.out.print('"');
+        }
+    }
+
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventTarget.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventTarget.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/GenericEventTarget.java	(revision 2155)
@@ -0,0 +1,112 @@
+//   Copyright 2015 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * a generic representation of event target. An event target has an id, optionally a parent or a
+ * list of children, and parameters.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class GenericEventTarget {
+
+    /** the id of the target */
+    private String targetId;
+
+    /** the parameters of the target providing further important target details */
+    private Map<String, String> parameters;
+
+    /** if available the parent of the given target (used for creating target hierarchies) */
+    private GenericEventTarget parent;
+
+    /** if available the children of the given target (used for creating target hierarchies) */
+    private List<GenericEventTarget> children = null;
+
+    /**
+     * <p>
+     * initialized the generic event target with an id, the parameters and a parent if any. All
+     * parameters may be null.
+     * </p>
+     *
+     * @param targetId   the id of the target
+     * @param parameters the parameters to provide further target details
+     * @param parent     if available the parent of the given target
+     */
+    GenericEventTarget(String              targetId,
+                       Map<String, String> parameters,
+                       GenericEventTarget  parent)
+    {
+        this.targetId = targetId;
+        this.parameters = parameters;
+        this.parent = parent;
+    }
+
+    /**
+     * <p>
+     * used to add a child to this target
+     * </p>
+     *
+     * @param child the new child of this event target
+     */
+    synchronized void addChild(GenericEventTarget child) {
+        if (children == null) {
+            children = new ArrayList<>();
+        }
+        
+        this.children.add(child);
+    }
+
+    /**
+     * @return the targetId
+     */
+    String getId() {
+        return targetId;
+    }
+
+    /**
+     * @return the parameters
+     */
+    Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    /**
+     * @return the parent
+     */
+    GenericEventTarget getParent() {
+        return parent;
+    }
+
+    /**
+     * @return the children
+     */
+    List<GenericEventTarget> getChildren() {
+        return children;
+    }
+
+    /**
+     * 
+     */
+    @Override
+    public String toString() {
+        return targetId;
+    }
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/OutputWriter.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/OutputWriter.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/OutputWriter.java	(revision 2155)
@@ -0,0 +1,488 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import de.ugoe.cs.util.StringTools;
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * dumps messages to a log file belonging to a specific app and client id. In the provided base log
+ * directory, it creates a subdirectory for the app and a subsubdirectory for the client id. In this
+ * subsubdirectory it creates appropriate log files. The name of each finished log file starts with
+ * "genericevents_" followed by the client id and an index of the log file. An unfinished log file
+ * has no index yet. A log file is finished if
+ * <ul>
+ *   <li>the client session is closed by a timeout</li>
+ *   <li>the generic event monitor is normally shut down</li>
+ *   <li>on startup an unfinished log file is detected.</li>
+ *   <li>the {@link #MAXIMUM_LOG_FILE_SIZE} is reached</li>
+ * </ul>
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ * 
+ */
+public class OutputWriter implements GenericEventMonitorComponent, GenericEventMonitorMessageListener
+{
+    
+    /**
+     * the maximum size of an individual log file
+     */
+    private static final int MAXIMUM_LOG_FILE_SIZE = 100000000;
+
+    /**
+     * the default log base directory if none is provided through the constructor 
+     */
+    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";
+
+    /**
+     * the currently used log file base directory
+     */
+    private File logFileBaseDir;
+
+    /**
+     * the id of the client of which all messages are logged through this writer
+     */
+    private String clientId;
+
+    /**
+     * the id of the application that the client uses
+     */
+    private String appId;
+
+    /**
+     * the log file into which all messages are currently written
+     */
+    private File logFile;
+
+    /**
+     * an output writer to be used for writing into the log file
+     */
+    private PrintWriter outputWriter;
+
+    /**
+     * the time stamp of the last action taken on this writer (such as logging a message)
+     */
+    private long lastUpdate;
+    
+    /**
+     * the id of the targets, that were already logged and need therefore not be logged again into
+     * the same file
+     */
+    private Set<String> loggedTargets = new HashSet<>();
+
+    /**
+     * <p>
+     * initializes the writer with the log file base directory, the id of the client for which
+     * this writer logs the messages and the id of the app that is used by the client.
+     * </p>
+     * 
+     * @param logFileBaseDir the log file base directory, or null if the default directory shall
+     *                       be taken
+     * @param appId          the ID of the app used by the client
+     * @param clientId       the ID of the client, for which this writer logs
+     */
+    public OutputWriter(String logFileBaseDir, String appId, String clientId) {
+        if (logFileBaseDir == null) {
+            this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR);
+        }
+        else {
+            this.logFileBaseDir = new File(logFileBaseDir);
+        }
+        
+        this.appId = appId;
+        this.clientId = clientId;
+        
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws GenericEventMonitorException {
+        if (outputWriter != null) {
+            throw new IllegalStateException("already initialized. Call close() first");
+        }
+        
+        synchronized (OutputWriter.class) {
+            try {
+                File clientLogDir = new File(new File(logFileBaseDir, appId), clientId);
+                
+                if (!clientLogDir.exists()) {
+                    if (!clientLogDir.mkdirs()) {
+                        throw new GenericEventMonitorException("client log file directory " +
+                                                              clientLogDir + " cannot be created");
+                    }
+                }
+                else if (!clientLogDir.isDirectory()) {
+                    throw new GenericEventMonitorException("client log file directory " +
+                                                          clientLogDir + " already exists as a file");
+                }
+                
+                logFile = new File(clientLogDir, getLogFileName(-1));
+                
+                if (logFile.exists()) {
+                    rotateLogFile();
+                }
+            
+                createLogWriter();
+            }
+            catch (IOException e) {
+                throw new GenericEventMonitorException("could not open logfile " + logFile, e);
+            }
+        }
+        
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>
+     * used to calculate a log file name. If the provided index is smaller 0, then no index
+     * is added to the file name. A filename is e.g. "genericevents_12345_001.log". 
+     * </p>
+     *
+     * @param index the index of the log file or -1 one, if no index shall be added
+     * 
+     * @return the file name as described
+     */
+    private String getLogFileName(int index) {
+        String result = "genericevents_" + clientId;
+        
+        if (index >= 0) {
+            result += "_" + new DecimalFormat("000" ).format(index);
+        }
+        
+        result += ".log";
+        
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws IllegalStateException, GenericEventMonitorException {
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /* (non-Javadoc)
+     * @see GenericEventMonitorMessageListener#handleEvents(ClientInfos, GenericEvent[])
+     */
+    @Override
+    public void handleEvents(ClientInfos          clientInfos,
+                             GenericEvent[]       events)
+    {
+        if (outputWriter == null) {
+            throw new IllegalStateException("not initialized. Call init() first");
+        }
+        
+        for (GenericEvent event : events) {
+            dumpEvent(event);
+        }
+        
+        outputWriter.flush();
+        
+        try {
+            considerLogRotate();
+        }
+        catch (IOException e) {
+            throw new IllegalStateException("could not perform log rotation: " + e, e);
+        }
+        
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>
+     * formats a received event and writes it to the log file. One event results in one line
+     * in the log file containing all infos of the event.
+     * </p>
+     *
+     * @param event to be written to the log file
+     */
+    private void dumpEvent(GenericEvent event) {
+        if (event.getTarget() != null) {
+            ensureGuiElementDumped(event.getTarget());
+        }
+        
+        outputWriter.print("<event type=\"");
+        outputWriter.print(event.getEventType());
+        outputWriter.println("\">");
+        
+        dumpParam("timestamp", event.getTime());
+        dumpParam("targetId", event.getTarget().getId());
+        
+        for (Map.Entry<String, String> parameter : event.getParameters().entrySet()) {
+            dumpParam(parameter.getKey(), parameter.getValue());
+        }
+        
+        outputWriter.println("</event>");
+    }
+
+    /**
+     * <p>
+     * ensures that a target element and its corresponding target element tree are logged
+     * </p>
+     *
+     * @param target the GUI structure to be logged
+     */
+    private void ensureGuiElementDumped(GenericEventTarget target) {
+        if (!loggedTargets.contains(target.getId())) {
+            GenericEventTarget parent = target;
+            
+            // get the root
+            while (parent.getParent() != null) {
+                parent = parent.getParent();
+            }
+            
+            dumpTargetStructure(parent);
+        }
+    }
+
+    /**
+     * <p>
+     * dumps the target provided by the parameter into the log file. Calls itself
+     * recursively to traverse the target structure.
+     * </p>
+     *
+     * @param target the target structure to be logged
+     */
+    private void dumpTargetStructure(GenericEventTarget target) {
+        dumpTarget(target);
+        
+        if (target.getChildren() != null) {
+            for (GenericEventTarget child : target.getChildren()) {
+                dumpTargetStructure(child);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * dumps the target provided by the parameter into the log file.
+     * </p>
+     *
+     * @param guiElement the GUI element to be logged
+     */
+    private void dumpTarget(GenericEventTarget target) {
+        if (!loggedTargets.contains(target.getId())) {
+            outputWriter.print("<target id=\"");
+            outputWriter.print(target.getId());
+            outputWriter.println("\">");
+        
+            if (target.getParent() != null) {
+                dumpParam("parent", target.getParent().getId());
+            }
+            
+            for (Map.Entry<String, String> parameter : target.getParameters().entrySet()) {
+                dumpParam(parameter.getKey(), parameter.getValue());
+            }
+        
+            outputWriter.println("</target>");
+        
+            loggedTargets.add(target.getId());
+        }
+    }
+
+    /**
+     * <p>
+     * dumps a parameter with the given name and value to the log file. The result is a
+     * tag named param with a name attribute and a value attribute. The value is transformed
+     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
+     * if required.
+     * </p>
+     *
+     * @param name  the name of the parameter to be dumped
+     * @param value the value of the parameter to be dumped
+     */
+    private void dumpParam(String name, Object value) {
+        if (value == null) {
+            return;
+        }
+        
+        String val;
+        
+        if (value instanceof String) {
+            val = (String) value;
+        }
+        else {
+            val = String.valueOf(value);
+        }
+        
+        outputWriter.print(" <param name=\"");
+        outputWriter.print(name);
+        outputWriter.print("\" value=\"");
+        outputWriter.print(StringTools.xmlEntityReplacement(val));
+        outputWriter.println("\"/>");
+    }
+
+    /**
+     * <p>
+     * checks, if the log file exceeded the {@link #MAXIMUM_LOG_FILE_SIZE}. If so, the current
+     * log file is closed, the next log file name is determined and this new file is opened for
+     * writing. 
+     * </p>
+     */
+    private synchronized void considerLogRotate() throws IOException {
+        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
+            closeLogWriter();
+            rotateLogFile();
+            createLogWriter();
+        }
+    }
+
+    /**
+     * <p>
+     * renames the current log file to a new log file with the next available index. It further
+     * sets the current log file to the default name, i.e. without index.
+     * </p>
+     */
+    private void rotateLogFile() {
+        File clientLogDir = logFile.getParentFile();
+        File checkFile;
+
+        int logFileIndex = -1;
+        do {
+            logFileIndex++;
+            
+            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
+        }
+        while (checkFile.exists());
+    
+        if (!logFile.renameTo(checkFile)) {
+            Console.printerrln("could not rename log file " + logFile + " to " + checkFile +
+                               ". Will not perform log rotation.");
+        }
+        else {
+            logFileIndex++;
+            logFile = new File(clientLogDir, getLogFileName(-1));
+        }
+    }
+
+    /**
+     * <p>
+     * instantiates a writer to be used for writing messages into the log file.
+     * </p>
+     */
+    private void createLogWriter() throws IOException {
+        FileOutputStream fis = new FileOutputStream(logFile);
+        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
+        outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
+        outputWriter.print("<session appId=\"");
+        outputWriter.print(StringTools.xmlEntityReplacement(this.appId));
+        outputWriter.print("\" clientId=\"");
+        outputWriter.print(StringTools.xmlEntityReplacement(this.clientId));
+        outputWriter.print("\" monitorInfos=\"");
+        outputWriter.print(StringTools.xmlEntityReplacement(getMonitorVersion()));
+        
+        outputWriter.println("\">");
+        
+        loggedTargets.clear();
+    }
+
+    /**
+     * <p>
+     * parses the POM properties if accessible and returns them. If they are not available,
+     * unknown is returned.
+     * </p>
+     */
+    private String getMonitorVersion() {
+        InputStream properties = this.getClass().getClassLoader().getResourceAsStream
+            ("META-INF/maven/de.ugoe.cs.autoquest/autoquest-generic-event-monitor/pom.properties");
+        
+        if (properties != null) {
+            try {
+                StringBuffer result = new StringBuffer();
+                BufferedReader reader = new BufferedReader(new InputStreamReader(properties));
+                
+                String line = null;
+                
+                do {
+                    line = reader.readLine();
+                    
+                    if (line != null) {
+                        if (result.length() > 0) {
+                            result.append("  ");
+                        }
+                        
+                        result.append(line);
+                    }
+                }
+                while (line != null);
+                
+                reader.close();
+                return result.toString();
+            }
+            catch (IOException e) {
+                return "unknown";
+            }
+        }
+        else {
+            return "unknown";
+        }
+    }
+
+    /**
+     * <p>
+     * closed the current writer if it is open.
+     * </p>
+     */
+    private void closeLogWriter() {
+        if (outputWriter != null) {
+            outputWriter.println("</session>");
+            outputWriter.flush();
+            outputWriter.close();
+            outputWriter = null;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.genericeventmonitor.GenericEventMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        closeLogWriter();
+        rotateLogFile();
+
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>
+     * return the time stamp of the last activity that happened on this writer.
+     * </p>
+     *
+     * @return as described
+     */
+    public long getLastUpdate() {
+        return lastUpdate;
+    }
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/Runner.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/Runner.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/Runner.java	(revision 2155)
@@ -0,0 +1,51 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.TextConsole;
+
+/**
+ * <p>
+ * implements the main method to start the {@link GenericEventMonitor}.
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ */
+public class Runner {
+
+    /**
+     * <p>
+     * Main method of the application.
+     * </p>
+     * 
+     * @param args command line arguments
+     */
+    public static void main(String[] args) {
+        new TextConsole();
+        
+        GenericEventMonitor monitor = new GenericEventMonitor(args);
+        try {
+            monitor.init();
+        }
+        catch (GenericEventMonitorException e) {
+            Console.printerrln("could not initialize generic event monitor: " + e.getMessage());
+            Console.logException(e);
+        }
+        
+        monitor.start();
+    }
+}
Index: /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ShutdownHook.java
===================================================================
--- /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ShutdownHook.java	(revision 2155)
+++ /trunk/autoquest-generic-event-monitor/src/main/java/de/ugoe/cs/autoquest/genericeventmonitor/ShutdownHook.java	(revision 2155)
@@ -0,0 +1,60 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.genericeventmonitor;
+
+/**
+ * <p>
+ * used to be able to shut down any {@link GenericEventMonitorComponent} that must be stopped if the
+ * application stops.
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class ShutdownHook implements Runnable {
+
+    /**
+     * the components to be stopped on shutdown
+     */
+    private GenericEventMonitorComponent[] components;
+    
+    /**
+     * <p>
+     * initializes the shutdown hook with the components to be shut down on shutdown of the whole
+     * application
+     * </p>
+     *
+     */
+    public ShutdownHook(GenericEventMonitorComponent... components) {
+        this.components = components;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Thread#run()
+     */
+    @Override
+    public void run() {
+        for (GenericEventMonitorComponent component : components) {
+            if (component != null) {
+                try {
+                    component.stop();
+                }
+                catch (Exception e) {
+                    // ignore and go on
+                }
+            }
+        }
+    }
+
+}
