Index: /trunk/autoquest-htmlmonitor/.classpath
===================================================================
--- /trunk/autoquest-htmlmonitor/.classpath	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.classpath	(revision 857)
@@ -0,0 +1,27 @@
+<?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="src" path="src/main/js"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6">
+		<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-htmlmonitor/.project
===================================================================
--- /trunk/autoquest-htmlmonitor/.project	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.project	(revision 857)
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>autoautoquest-htmlmonitor</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<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>
+		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+	</natures>
+</projectDescription>
Index: /trunk/autoquest-htmlmonitor/.settings/.jsdtscope
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/.jsdtscope	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/.jsdtscope	(revision 857)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry excluding="target/" kind="src" path=""/>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
+	<classpathentry kind="output" path=""/>
+</classpath>
Index: /trunk/autoquest-htmlmonitor/.settings/org.eclipse.core.resources.prefs
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/org.eclipse.core.resources.prefs	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/org.eclipse.core.resources.prefs	(revision 857)
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+encoding//src/main/resources=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/<project>=UTF-8
Index: /trunk/autoquest-htmlmonitor/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/org.eclipse.jdt.core.prefs	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/org.eclipse.jdt.core.prefs	(revision 857)
@@ -0,0 +1,5 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.source=1.6
Index: /trunk/autoquest-htmlmonitor/.settings/org.eclipse.m2e.core.prefs
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/org.eclipse.m2e.core.prefs	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/org.eclipse.m2e.core.prefs	(revision 857)
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
Index: /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.container
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.container	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.container	(revision 857)
@@ -0,0 +1,1 @@
+org.eclipse.wst.jsdt.launching.baseBrowserLibrary
Index: /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.name
===================================================================
--- /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.name	(revision 857)
+++ /trunk/autoquest-htmlmonitor/.settings/org.eclipse.wst.jsdt.ui.superType.name	(revision 857)
@@ -0,0 +1,1 @@
+Window
Index: /trunk/autoquest-htmlmonitor/pom.xml
===================================================================
--- /trunk/autoquest-htmlmonitor/pom.xml	(revision 857)
+++ /trunk/autoquest-htmlmonitor/pom.xml	(revision 857)
@@ -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-htmlmonitor</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>autoquest-htmlmonitor</name>
+    <packaging>js</packaging>
+    <scm>
+        <url>https://autoquest.informatik.uni-goettingen.de/svn/autoquest/trunk/autoquest-htmlmonitor</url>
+    </scm>
+    <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>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>6.1.22</version>
+        </dependency>
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty-sslengine</artifactId>
+            <version>6.1.22</version>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.json-simple</groupId>
+            <artifactId>json-simple</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <extensions>
+            <extension>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>javascript-maven-plugin</artifactId>
+                <version>2.0.0-alpha-1</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>jslint-maven-plugin</artifactId>
+                <version>1.0.1</version>
+                <configuration>
+                    <allowOneVarStatementPerFunction>false</allowOneVarStatementPerFunction>
+                    <failOnIssues>false</failOnIssues>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>jslint</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>2.3.2</version>
+                <configuration>
+                    <source>1.6</source>
+                    <target>1.6</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.htmlmonitor.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-htmlmonitor/src/main/assembly/bin.xml
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/assembly/bin.xml	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/assembly/bin.xml	(revision 857)
@@ -0,0 +1,17 @@
+<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>
+      <outputDirectory></outputDirectory>
+    </dependencySet>
+  </dependencySets>
+</assembly>
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlClientInfos.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlClientInfos.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlClientInfos.java	(revision 857)
@@ -0,0 +1,79 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import java.net.URL;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class HtmlClientInfos {
+
+    /**
+     * 
+     */
+    private String clientId;
+
+    /**
+     * 
+     */
+    private String userAgent;
+
+    /**
+     * 
+     */
+    private URL url;
+
+    /**
+     * 
+     */
+    private String title;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param clientId
+     * @param userAgent
+     * @param url
+     * @param title
+     */
+    public HtmlClientInfos(String clientId, String userAgent, URL url, String title) {
+        this.clientId = clientId;
+        this.userAgent = userAgent;
+        this.url = url;
+        this.title = title;
+    }
+
+    /**
+     * @return the clientId
+     */
+    String getClientId() {
+        return clientId;
+    }
+
+    /**
+     * @return the userAgent
+     */
+    String getUserAgent() {
+        return userAgent;
+    }
+
+    /**
+     * @return the url
+     */
+    URL getUrl() {
+        return url;
+    }
+
+    /**
+     * @return the title
+     */
+    String getTitle() {
+        return title;
+    }
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlEvent.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlEvent.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlEvent.java	(revision 857)
@@ -0,0 +1,126 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+class HtmlEvent {
+
+    /**
+     * 
+     */
+    private HtmlClientInfos clientInfos;
+
+    /**
+     * 
+     */
+    private Long time;
+
+    /**
+     * 
+     */
+    private String path;
+
+    /**
+     * 
+     */
+    private String eventType;
+
+    /**
+     * 
+     */
+    private Integer[] coordinates;
+
+    /**
+     * 
+     */
+    private Integer key;
+
+    /**
+     * 
+     */
+    private Integer scrollPosition;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param clientInfos
+     * @param time
+     * @param path
+     * @param eventType
+     * @param coordinates
+     * @param key
+     * @param scrollPosition
+     */
+    HtmlEvent(HtmlClientInfos clientInfos,
+              Long            time,
+              String          path,
+              String          eventType,
+              Integer[]       coordinates,
+              Integer         key,
+              Integer         scrollPosition)
+    {
+        this.clientInfos = clientInfos;
+        this.time = time;
+        this.path = path;
+        this.eventType = eventType;
+        this.coordinates = coordinates;
+        this.key = key;
+        this.scrollPosition = scrollPosition;
+    }
+
+    /**
+     * @return the clientInfos
+     */
+    HtmlClientInfos getClientInfos() {
+        return clientInfos;
+    }
+
+    /**
+     * @return the time
+     */
+    Long getTime() {
+        return time;
+    }
+
+    /**
+     * @return the path
+     */
+    String getPath() {
+        return path;
+    }
+
+    /**
+     * @return the eventType
+     */
+    String getEventType() {
+        return eventType;
+    }
+
+    /**
+     * @return the coordinates
+     */
+    Integer[] getCoordinates() {
+        return coordinates;
+    }
+
+    /**
+     * @return the key
+     */
+    Integer getKey() {
+        return key;
+    }
+
+    /**
+     * @return the scrollPosition
+     */
+    Integer getScrollPosition() {
+        return scrollPosition;
+    }
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitor.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitor.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitor.java	(revision 857)
@@ -0,0 +1,114 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class HtmlMonitor implements HtmlMonitorComponent {
+
+    /** */
+    private int port = 8090;
+    
+    /** */
+    private HtmlMonitorServer server;
+    
+    /** */
+    private String logFileBaseDir;
+
+    /** */
+    private HtmlMonitorLogManager logManager;
+
+    /** */
+    private Thread shutdownHook;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param logFileBaseDir
+     */
+    HtmlMonitor(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.htmlmonitor.HtmlMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws HtmlMonitorException {
+        if (server != null) {
+            throw new IllegalStateException("already initialized.");
+        }
+        
+        try {
+            logManager = new HtmlMonitorLogManager(logFileBaseDir);
+            logManager.init();
+        
+            server = new HtmlMonitorServer(port, logManager);
+            server.init();
+        
+            shutdownHook = new Thread(new ShutdownHook(server, logManager));
+        }
+        catch (HtmlMonitorException e) {
+            Console.printerrln("could not initialize HTML monitor: " + e);
+            Console.logException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() {
+        if (server == null) {
+            throw new IllegalStateException("not initialized.");
+        }
+        
+        try {
+            Runtime.getRuntime().addShutdownHook(shutdownHook);
+            logManager.start();
+            server.start();
+        }
+        catch (HtmlMonitorException e) {
+            Console.printerrln("could not start HTML monitor: " + e);
+            Console.logException(e);
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#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-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorComponent.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorComponent.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorComponent.java	(revision 857)
@@ -0,0 +1,26 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public interface HtmlMonitorComponent {
+
+    /**
+     * 
+     */
+    void init() throws IllegalStateException, HtmlMonitorException;
+
+    /**
+     * 
+     */
+    void start() throws IllegalStateException, HtmlMonitorException;
+
+    /**
+     * 
+     */
+    void stop();
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorException.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorException.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorException.java	(revision 857)
@@ -0,0 +1,36 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class HtmlMonitorException extends Exception {
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * <p>
+     * </p>
+     *
+     * @param message
+     */
+    public HtmlMonitorException(String message) {
+        super(message);
+    }
+
+    /**
+     * <p>
+     * </p>
+     *
+     * @param message
+     * @param cause
+     */
+    public HtmlMonitorException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorLogManager.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorLogManager.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorLogManager.java	(revision 857)
@@ -0,0 +1,172 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+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>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class HtmlMonitorLogManager implements HtmlMonitorComponent, HtmlMonitorMessageListener {
+    
+    /**
+     * 
+     */
+    private static final int SESSION_TIMEOUT = 100000;
+    
+    /**
+     * 
+     */
+    private String logFileBaseDir;
+
+    /**
+     * 
+     */
+    private Map<String, HtmlMonitorOutputWriter> writers;
+
+    /**
+     * 
+     */
+    private Timer logFileMonitorTimer;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param logFileBaseDir
+     */
+    HtmlMonitorLogManager(String logFileBaseDir) {
+        this.logFileBaseDir = logFileBaseDir;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws IllegalStateException, HtmlMonitorException {
+        if (writers != null) {
+            throw new IllegalStateException("already initialized");
+        }
+        
+        writers = new HashMap<String, HtmlMonitorOutputWriter>();
+        logFileMonitorTimer = new Timer();
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws IllegalStateException, HtmlMonitorException {
+        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.htmlmonitor.HtmlMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        if (writers != null) {
+            logFileMonitorTimer.cancel();
+            
+            for (HtmlMonitorOutputWriter writer : writers.values()) {
+                writer.stop();
+            }
+        }
+        
+        writers = null;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitoringListener#handleEvents(de.ugoe.cs.quest.htmlmonitor.HtmlClientInfos, de.ugoe.cs.quest.htmlmonitor.HtmlEvent[])
+     */
+    @Override
+    public void handleMessage(HtmlClientInfos clientInfos, HtmlEvent[] events) {
+        HtmlMonitorOutputWriter writer = writers.get(clientInfos.getClientId());
+        
+        try {
+            if (writer == null) {
+                synchronized (this) {
+                    writer = writers.get(clientInfos.getClientId());
+                    if (writer == null) {
+                        writer =
+                            new HtmlMonitorOutputWriter(logFileBaseDir, clientInfos.getClientId());
+                        writer.init();
+                        writer.start();
+                        writers.put(clientInfos.getClientId(), writer);
+                    }
+                }
+            }
+
+            writer.handleMessage(clientInfos, events);
+        }
+        catch (Exception e) {
+            Console.printerrln("could not handle message of client " + clientInfos.getClientId() +
+                               ": " + e);
+            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.handleMessage(clientInfos, events);
+                }
+                catch (Exception e1) {
+                    synchronized (this) {
+                        writers.remove(clientInfos.getClientId());
+                        writer.stop();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * TODO comment
+     * </p>
+     * 
+     * @author Patrick Harms
+     */
+    public class LogFileMonitorTimerTask extends TimerTask {
+
+        /* (non-Javadoc)
+         * @see java.lang.Runnable#run()
+         */
+        @Override
+        public void run() {
+            synchronized (HtmlMonitorLogManager.this) {
+                List<String> timeoutSessions = new ArrayList<String>();
+                for (Map.Entry<String, HtmlMonitorOutputWriter> entry : writers.entrySet()) {
+                    HtmlMonitorOutputWriter writer = entry.getValue();
+                    
+                    if (System.currentTimeMillis() - writer.getLastUpdate() > SESSION_TIMEOUT) {
+                        timeoutSessions.add(entry.getKey());
+                    }
+                }
+                
+                for (String clientId : timeoutSessions) {
+                    HtmlMonitorOutputWriter writer = writers.remove(clientId);
+                    writer.stop();
+                }
+            }
+        }
+
+    }
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorMessageListener.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorMessageListener.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorMessageListener.java	(revision 857)
@@ -0,0 +1,22 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public interface HtmlMonitorMessageListener {
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param clientInfos
+     * @param events
+     */
+    void handleMessage(HtmlClientInfos clientInfos, HtmlEvent[] events);
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorOutputWriter.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorOutputWriter.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorOutputWriter.java	(revision 857)
@@ -0,0 +1,315 @@
+
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.text.DecimalFormat;
+
+/**
+ * <p>
+ * comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ * 
+ */
+public class HtmlMonitorOutputWriter implements HtmlMonitorComponent, HtmlMonitorMessageListener {
+    
+    /**
+     * 
+     */
+    private static final int MAXIMUM_LOG_FILE_SIZE = 10000000;
+
+    /**
+     * 
+     */
+    private static final String DEFAULT_LOG_FILE_BASE_DIR = "logs";
+
+    /**
+     * 
+     */
+    private File logFileBaseDir;
+
+    /**
+     *
+     */
+    private String clientId;
+
+    /**
+     *
+     */
+    private File logFile;
+
+    /**
+     * <p>
+     * Writer for logging events.
+     * </p>
+     */
+    private PrintWriter outputWriter;
+
+    /**
+     * 
+     */
+    private long lastUpdate;
+
+    /**
+     * <p>
+     * 
+     * </p>
+     * 
+     * @param outputWriter
+     *            writer for the logged information
+     */
+    public HtmlMonitorOutputWriter(String logFileBaseDir, String clientId) {
+        if (logFileBaseDir == null) {
+            this.logFileBaseDir = new File(DEFAULT_LOG_FILE_BASE_DIR);
+        }
+        else {
+            this.logFileBaseDir = new File(logFileBaseDir);
+        }
+        
+        this.clientId = clientId;
+        
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() throws HtmlMonitorException {
+        if (outputWriter != null) {
+            throw new IllegalStateException("already initialized. Call close() first");
+        }
+        
+        synchronized (HtmlMonitorOutputWriter.class) {
+            try {
+                File clientLogDir = new File(logFileBaseDir, clientId);
+            
+                if (!clientLogDir.exists()) {
+                    clientLogDir.mkdirs();
+                }
+                else if (!clientLogDir.isDirectory()) {
+                    throw new HtmlMonitorException("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 HtmlMonitorException("could not open logfile " + logFile, e);
+            }
+        }
+        
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param logFileIndex2
+     * @return
+     */
+    private String getLogFileName(int index) {
+        String result = "htmlmonitor_" + clientId;
+        
+        if (index >= 0) {
+            result += "_" + new DecimalFormat("000" ).format(index);
+        }
+        
+        result += ".log";
+        
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws IllegalStateException, HtmlMonitorException {
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorMessageListener#handleMessage(de.ugoe.cs.quest.htmlmonitor.HtmlClientInfos, de.ugoe.cs.quest.htmlmonitor.HtmlEvent[])
+     */
+    @Override
+    public void handleMessage(HtmlClientInfos clientInfos, HtmlEvent[] events) {
+        if (outputWriter == null) {
+            throw new IllegalStateException("not initialized. Call init() first");
+        }
+        
+        for (HtmlEvent 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>
+     * TODO: comment
+     * </p>
+     *
+     * @param event
+     */
+    private void dumpEvent(HtmlEvent event) {
+        dumpString(event.getClientInfos().getClientId());
+        outputWriter.print(' ');
+        dumpString(event.getTime().toString());
+        outputWriter.print(' ');
+        dumpString(event.getClientInfos().getTitle());
+        outputWriter.print(' ');
+        dumpString(event.getClientInfos().getUrl().toString());
+        outputWriter.print(' ');
+        dumpString(event.getClientInfos().getUserAgent());
+        outputWriter.print(' ');
+        dumpString(event.getEventType());
+        outputWriter.print(' ');
+        dumpString(event.getPath());
+
+        if (event.getCoordinates() != null) {
+            outputWriter.print(' ');
+            
+            StringBuffer value = new StringBuffer();
+            for (int i = 0; i < event.getCoordinates().length; i++) {
+                if (i > 0) {
+                    value.append(',');
+                }
+                value.append(event.getCoordinates()[i]);
+            }
+            
+            dumpString(value.toString());
+        }
+
+        if (event.getKey() != null) {
+            outputWriter.print(' ');
+            dumpString(event.getKey().toString());
+        }
+            
+        if (event.getScrollPosition() != null) {
+            outputWriter.print(' ');
+            dumpString(event.getScrollPosition().toString());
+        }
+            
+        outputWriter.println();
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param clientId2
+     */
+    private void dumpString(String str) {
+        outputWriter.print('"');
+        outputWriter.print(str.replaceAll("\\\\", "\\\\").replaceAll("\\\"", "\\\""));
+        outputWriter.print('"');
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    private synchronized void considerLogRotate() throws IOException {
+        if (logFile.length() > MAXIMUM_LOG_FILE_SIZE) {
+            closeLogWriter();
+            rotateLogFile();
+            createLogWriter();
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    private void rotateLogFile() {
+        File clientLogDir = logFile.getParentFile();
+        File checkFile;
+
+        int logFileIndex = -1;
+        do {
+            logFileIndex++;
+            
+            checkFile = new File(clientLogDir, getLogFileName(logFileIndex));
+        }
+        while (checkFile.exists());
+    
+        logFile.renameTo(checkFile);
+        logFileIndex++;
+        logFile = new File(clientLogDir, getLogFileName(-1));
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    private void createLogWriter() throws IOException {
+        FileOutputStream fis = new FileOutputStream(logFile);
+        outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    private void closeLogWriter() {
+        if (outputWriter != null) {
+            outputWriter.flush();
+            outputWriter.close();
+            outputWriter = null;
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        closeLogWriter();
+        rotateLogFile();
+
+        lastUpdate = System.currentTimeMillis();
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @return
+     */
+    public long getLastUpdate() {
+        return lastUpdate;
+    }
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServer.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServer.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServer.java	(revision 857)
@@ -0,0 +1,93 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * TODO: comment
+ * 
+ * @author Patrick Harms
+ */
+class HtmlMonitorServer implements HtmlMonitorComponent {
+    
+    /**
+     * 
+     */
+    private int port;
+
+    /**
+     * 
+     */
+    private Server server;
+
+    /**
+     * 
+     */
+    private HtmlMonitorMessageListener messageListener;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    HtmlMonitorServer(int port, HtmlMonitorMessageListener messageListener) {
+        this.port = port;
+        this.messageListener = messageListener;
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#init()
+     */
+    @Override
+    public synchronized void init() {
+        if (server != null) {
+            throw new IllegalStateException("already initialized. First call stop()");
+        }
+
+        server = new Server(port);
+        Context root = new Context(server, "/", Context.SESSIONS);
+
+        HtmlMonitorServlet servlet = new HtmlMonitorServlet(messageListener);
+        ServletHolder servletHolder = new ServletHolder(servlet);
+        root.addServlet(servletHolder, "/*");
+    }
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#start()
+     */
+    @Override
+    public synchronized void start() throws HtmlMonitorException {
+        if (server == null) {
+            throw new IllegalStateException("server not initialized yet. First call init()");
+        }
+        
+        try {
+            server.start();
+        }
+        catch (Exception e) {
+            throw new HtmlMonitorException("could not start server", e);
+        }
+    }
+
+
+    /* (non-Javadoc)
+     * @see de.ugoe.cs.autoquest.htmlmonitor.HtmlMonitorComponent#stop()
+     */
+    @Override
+    public synchronized void stop() {
+        try {
+            if (server != null) {
+                server.stop();
+            }
+        }
+        catch (Exception e) {
+            Console.printerrln("could not stop HTML monitor server: " + e.getMessage());
+            Console.logException(e);
+        }
+    }
+
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServlet.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServlet.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/HtmlMonitorServlet.java	(revision 857)
@@ -0,0 +1,461 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+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.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import org.json.simple.parser.ParseException;
+import org.mortbay.jetty.servlet.DefaultServlet;
+
+import de.ugoe.cs.util.console.Console;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class HtmlMonitorServlet extends DefaultServlet {
+
+    /**  */
+    private static final long serialVersionUID = 1L;
+    
+    /**
+     * 
+     */
+    private HtmlMonitorMessageListener messageListener;
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param htmlMonitoringListener
+     */
+    HtmlMonitorServlet(HtmlMonitorMessageListener messageListener) {
+        this.messageListener = messageListener;
+    }
+
+    /* (non-Javadoc)
+     * @see org.mortbay.jetty.servlet.DefaultServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    @Override
+    protected void doPost(HttpServletRequest request, HttpServletResponse response)
+        throws ServletException, IOException
+    {
+        Object value = null;
+        try {
+            value = JSONValue.parseWithException(new InputStreamReader(request.getInputStream()));
+            
+            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() + ")");
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param object
+     */
+    private void handleJSONObject(JSONObject object) {
+        dumpJSONObject(object, "");
+        
+        JSONObject message = assertValue(object, "message", JSONObject.class);
+        
+        if (message == null) {
+            Console.printerrln("incoming data is no valid message --> discarding it");
+        }
+        else {
+            HtmlClientInfos clientInfos = extractClientInfos(message);
+
+            if (clientInfos == null) {
+                Console.printerrln
+                    ("incoming message does not contain valid client infos --> discarding it");
+            }
+            else {
+                HtmlEvent[] events = extractHtmlEvents(message, clientInfos);
+                if (events == null) {
+                    Console.printerrln
+                    ("incoming message does not contain valid events --> discarding it");
+                }
+                else {
+                    messageListener.handleMessage(clientInfos, events);
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param object
+     * @return
+     */
+    private HtmlClientInfos extractClientInfos(JSONObject message) {
+        HtmlClientInfos clientInfos = null;
+        
+        JSONObject infos = assertValue(message, "clientInfos", JSONObject.class);
+        
+        if (infos != null) {
+            String clientId = assertValue((JSONObject) infos, "clientId", String.class);
+            String userAgent = assertValue((JSONObject) infos, "userAgent", String.class);
+            URL url = assertValue((JSONObject) infos, "url", URL.class);
+            String title = assertValue((JSONObject) infos, "title", String.class);
+            
+            if (clientId == null) {
+                Console.printerrln("client infos do not contain a valid client id");
+            }
+            else if (userAgent == null) {
+                Console.printerrln("client infos do not contain a valid user agent");
+            }
+            else if (url == null) {
+                Console.printerrln("client infos do not contain a valid URL");
+            }
+            else if (title == null) {
+                Console.printerrln("client infos do not contain a valid title");
+            }
+            else {
+                clientInfos = new HtmlClientInfos(clientId, userAgent, url, title);
+            }
+        }
+        
+        return clientInfos;
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param object
+     * @param clientInfos
+     * @return
+     */
+    private HtmlEvent[] extractHtmlEvents(JSONObject object, HtmlClientInfos clientInfos) {
+        List<HtmlEvent> events = null;
+        
+        JSONArray eventArray = assertValue(object, "events", JSONArray.class);
+        
+        if (eventArray != null) {
+            events = new ArrayList<HtmlEvent>();
+            
+            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 path = assertValue(((JSONObject) eventObj), "path", String.class);
+                    String eventType =
+                        assertValue(((JSONObject) eventObj), "eventType", String.class);
+                    Integer[] coordinates =
+                        assertValue(((JSONObject) eventObj), "coordinates", Integer[].class);
+                    Integer key = assertValue(((JSONObject) eventObj), "key", Integer.class);
+                    Integer scrollPosition =
+                        assertValue(((JSONObject) eventObj), "scrollPosition", Integer.class);
+                    
+                    if (time == null) {
+                        Console.printerrln("event number " + (i + 1) + " has no valid timestamp");
+                    }
+                    else if (path == null) {
+                        Console.printerrln("event number " + (i + 1) + " has no valid path");
+                    }
+                    else if (eventType == null) {
+                        Console.printerrln("event number " + (i + 1) + " has no valid event type");
+                    }
+                    else if ((coordinates != null) && (coordinates.length != 2)) {
+                        Console.printerrln("event number " + (i + 1) + " has no valid coordinates");
+                    }
+                    else if (checkEventParameterCombinations
+                                (eventType, coordinates, key, scrollPosition))
+                    {
+                        events.add(new HtmlEvent(clientInfos, time, path, eventType, coordinates,
+                                                 key, scrollPosition));
+                    }
+                    else {
+                        Console.printerrln
+                            ("event number " + (i + 1) + " has no valid parameter combination");
+                    }
+                }
+            }
+            
+        }
+        
+        if ((events != null) && (events.size() > 0)) {
+            return events.toArray(new HtmlEvent[events.size()]);
+        }
+        else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param eventType
+     * @param coordinates
+     * @param key
+     * @param scrollPosition
+     * @return
+     */
+    private boolean checkEventParameterCombinations(String    eventType,
+                                                    Integer[] coordinates,
+                                                    Integer   key,
+                                                    Integer   scrollPosition)
+    {
+        boolean result = false;
+        
+        if ("onscroll".equals(eventType)) {
+            if ((coordinates == null) && (key == null) && (scrollPosition != null)) {
+                result = true;
+            }
+            else {
+                Console.printerrln(eventType + " event has invalid parameters");
+            }
+        }
+        else if ("onclick".equals(eventType) || "ondblclick".equals(eventType)) {
+            if ((coordinates != null) && (key == null) && (scrollPosition == null)) {
+                result = true;
+            }
+            else {
+                Console.printerrln(eventType + " event has invalid parameters");
+            }
+        }
+        else if ("onkeypress".equals(eventType) || "onkeydown".equals(eventType) ||
+                 "onkeyup".equals(eventType))
+        {
+            if ((coordinates == null) && (key != null) && (scrollPosition == null)) {
+                result = true;
+            }
+            else {
+                Console.printerrln(eventType + " event has invalid parameters");
+            }
+        }
+        else if ("onfocus".equals(eventType) || "onmouseout".equals(eventType) ||
+                 "onmousemove".equals(eventType) || "onunload".equals(eventType))
+        {
+            if ((coordinates == null) && (key == null) && (scrollPosition == null)) {
+                result = true;
+            }
+            else {
+                Console.printerrln(eventType + " event has invalid parameters");
+            }
+        }
+        else {
+            Console.printerrln("'" + eventType + "' is not a valid event type");
+        }
+        
+        return result;
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param object
+     * @param string
+     * @param class1
+     * @return
+     */
+    @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) new Integer(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) new Long(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;
+                }
+            }
+        }
+        
+        return result;
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param object
+     */
+    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-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/Runner.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/Runner.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/Runner.java	(revision 857)
@@ -0,0 +1,38 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.TextConsole;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ */
+public class Runner {
+
+    /**
+     * <p>
+     * Main method of the application.
+     * </p>
+     * 
+     * @param args
+     *            TODO comment
+     */
+    public static void main(String[] args) {
+        new TextConsole();
+        
+        HtmlMonitor monitor = new HtmlMonitor(args);
+        try {
+            monitor.init();
+        }
+        catch (HtmlMonitorException e) {
+            Console.printerrln("could not initialize HTML monitor server: " + e.getMessage());
+            Console.logException(e);
+        }
+        
+        monitor.start();
+    }
+}
Index: /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/ShutdownHook.java
===================================================================
--- /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/ShutdownHook.java	(revision 857)
+++ /trunk/autoquest-htmlmonitor/src/main/java/de/ugoe/cs/autoquest/htmlmonitor/ShutdownHook.java	(revision 857)
@@ -0,0 +1,37 @@
+package de.ugoe.cs.autoquest.htmlmonitor;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class ShutdownHook implements Runnable {
+
+    /** */
+    private HtmlMonitorComponent[] components;
+    
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    public ShutdownHook(HtmlMonitorComponent... components) {
+        this.components = components;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Thread#run()
+     */
+    @Override
+    public void run() {
+        for (HtmlMonitorComponent component : components) {
+            if (component != null) {
+                component.stop();
+            }
+        }
+    }
+
+}
