Projekt von de.halbekunst.juplo nach de.juplo verschoben und aufgeräumt
authorKai Moritz <kai@coolibri.de>
Thu, 23 Aug 2012 20:20:53 +0000 (22:20 +0200)
committerKai Moritz <kai@coolibri.de>
Fri, 31 Aug 2012 09:54:40 +0000 (11:54 +0200)
162 files changed:
accelerator-examples/jsp/jetty.sh [new file with mode: 0755]
accelerator-examples/jsp/pom.xml [new file with mode: 0644]
accelerator-examples/jsp/src/main/resources/config.xml [new file with mode: 0644]
accelerator-examples/jsp/src/main/resources/log4j.xml [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/WEB-INF/c.tld [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/WEB-INF/included.jsp [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/faulty-page.jsp [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/index.html [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/page-with-forward.jsp [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/page-with-include.jsp [new file with mode: 0644]
accelerator-examples/jsp/src/main/webapp/simple-page.jsp [new file with mode: 0644]
accelerator-examples/jsp/src/test/java/de/juplo/accelerator/examples/JspTest.java [new file with mode: 0644]
accelerator-examples/jsp/tomcat.sh [new file with mode: 0755]
accelerator-examples/pom.xml [new file with mode: 0644]
accelerator-examples/servlet/jetty.sh [new file with mode: 0755]
accelerator-examples/servlet/pom.xml [new file with mode: 0644]
accelerator-examples/servlet/src/main/java/de/juplo/accelerator/examples/FaultyServlet.java [new file with mode: 0644]
accelerator-examples/servlet/src/main/resources/config.xml [new file with mode: 0644]
accelerator-examples/servlet/src/main/resources/log4j.xml [new file with mode: 0644]
accelerator-examples/servlet/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
accelerator-examples/servlet/src/main/webapp/index.html [new file with mode: 0644]
accelerator-examples/servlet/src/test/java/de/juplo/accelerator/examples/ServletTest.java [new file with mode: 0644]
accelerator-examples/servlet/tomcat.sh [new file with mode: 0755]
accelerator-examples/spring/jetty.sh [new file with mode: 0755]
accelerator-examples/spring/pom.xml [new file with mode: 0644]
accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/FaultyController.java [new file with mode: 0644]
accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/SpringController.java [new file with mode: 0644]
accelerator-examples/spring/src/main/resources/config.xml [new file with mode: 0644]
accelerator-examples/spring/src/main/resources/log4j.xml [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/WEB-INF/views/index.jsp [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
accelerator-examples/spring/src/main/webapp/index.jsp [new file with mode: 0644]
accelerator-examples/spring/src/test/java/de/juplo/accelerator/examples/SpringMVCTest.java [new file with mode: 0644]
accelerator-examples/spring/tomcat.sh [new file with mode: 0755]
accelerator-examples/static/jetty.sh [new file with mode: 0755]
accelerator-examples/static/pom.xml [new file with mode: 0644]
accelerator-examples/static/src/main/resources/config.xml [new file with mode: 0644]
accelerator-examples/static/src/main/resources/log4j.xml [new file with mode: 0644]
accelerator-examples/static/src/main/webapp/WEB-INF/web.xml [new file with mode: 0644]
accelerator-examples/static/src/main/webapp/index.html [new file with mode: 0644]
accelerator-examples/static/src/main/webapp/static/page.html [new file with mode: 0644]
accelerator-examples/static/src/main/webapp/static/stylesheets.css [new file with mode: 0644]
accelerator-examples/static/src/test/java/de/juplo/accelerator/examples/StaticTest.java [new file with mode: 0644]
accelerator-examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java [new file with mode: 0644]
accelerator/pom.xml [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/AcceleratorFilter.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/CacheControl.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/CacheControlInterceptor.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/CacheMethodHandle.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/Headers.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/Accepts.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/AdditionalHeaders.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/CacheControl.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/CacheSeconds.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/Cacheable.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/ETag.java [new file with mode: 0644]
accelerator/src/main/java/de/juplo/accelerator/annotations/LastModified.java [new file with mode: 0644]
accelerator/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java [new file with mode: 0644]
accelerator/src/test/java/de/juplo/accelerator/ParameterGuessingTest.java [new file with mode: 0644]
accelerator/src/test/java/de/juplo/accelerator/RequestSizeTest.java [new file with mode: 0644]
accelerator/src/test/java/de/juplo/accelerator/TestServlet.java [new file with mode: 0644]
accelerator/src/test/resources/config.xml [new file with mode: 0644]
accelerator/src/test/resources/log4j.xml [new file with mode: 0644]
accelerator/src/test/resources/web.xml [new file with mode: 0644]
cachecontrol/pom.xml [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/AcceleratorFilter.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControl.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControlInterceptor.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheMethodHandle.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/Headers.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Accepts.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/AdditionalHeaders.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheControl.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheSeconds.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Cacheable.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/ETag.java [deleted file]
cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/LastModified.java [deleted file]
cachecontrol/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java [deleted file]
cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/ParameterGuessingTest.java [deleted file]
cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/RequestSizeTest.java [deleted file]
cachecontrol/src/test/resources/config.xml [deleted file]
cachecontrol/src/test/resources/log4j.xml [deleted file]
cachecontrol/src/test/resources/web.xml [deleted file]
examples/jsp/catalog.xml [deleted file]
examples/jsp/jetty.sh [deleted file]
examples/jsp/pom.xml [deleted file]
examples/jsp/src/main/resources/config.xml [deleted file]
examples/jsp/src/main/resources/log4j.xml [deleted file]
examples/jsp/src/main/webapp/WEB-INF/c.tld [deleted file]
examples/jsp/src/main/webapp/WEB-INF/included.jsp [deleted file]
examples/jsp/src/main/webapp/WEB-INF/web.xml [deleted file]
examples/jsp/src/main/webapp/faulty-page.jsp [deleted file]
examples/jsp/src/main/webapp/index.html [deleted file]
examples/jsp/src/main/webapp/page-with-forward.jsp [deleted file]
examples/jsp/src/main/webapp/page-with-include.jsp [deleted file]
examples/jsp/src/main/webapp/simple-page.jsp [deleted file]
examples/jsp/src/test/java/de/halbekunst/cachecontrol/examples/JspTest.java [deleted file]
examples/jsp/tomcat.sh [deleted file]
examples/pom.xml [deleted file]
examples/servlet/jetty.sh [deleted file]
examples/servlet/pom.xml [deleted file]
examples/servlet/src/main/java/de/halbekunst/cachecontrol/examples/FaultyServlet.java [deleted file]
examples/servlet/src/main/resources/config.xml [deleted file]
examples/servlet/src/main/resources/log4j.xml [deleted file]
examples/servlet/src/main/webapp/WEB-INF/web.xml [deleted file]
examples/servlet/src/main/webapp/index.html [deleted file]
examples/servlet/src/test/java/de/halbekunst/cachecontrol/examples/ServletTest.java [deleted file]
examples/servlet/tomcat.sh [deleted file]
examples/spring/jetty.sh [deleted file]
examples/spring/pom.xml [deleted file]
examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/FaultyController.java [deleted file]
examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/SpringController.java [deleted file]
examples/spring/src/main/resources/config.xml [deleted file]
examples/spring/src/main/resources/log4j.xml [deleted file]
examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp [deleted file]
examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp [deleted file]
examples/spring/src/main/webapp/WEB-INF/views/index.jsp [deleted file]
examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp [deleted file]
examples/spring/src/main/webapp/WEB-INF/web.xml [deleted file]
examples/spring/src/main/webapp/index.jsp [deleted file]
examples/spring/src/test/java/de/halbekunst/cachecontrol/examples/SpringMVCTest.java [deleted file]
examples/spring/tomcat.sh [deleted file]
examples/static/jetty.sh [deleted file]
examples/static/pom.xml [deleted file]
examples/static/src/main/resources/config.xml [deleted file]
examples/static/src/main/resources/log4j.xml [deleted file]
examples/static/src/main/webapp/WEB-INF/web.xml [deleted file]
examples/static/src/main/webapp/index.html [deleted file]
examples/static/src/main/webapp/static/page.html [deleted file]
examples/static/src/main/webapp/static/stylesheets.css [deleted file]
examples/static/src/test/java/de/halbekunst/cachecontrol/examples/StaticTest.java [deleted file]
examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java [deleted file]
maven-plugins/hibernate4/pom.xml [new file with mode: 0644]
maven-plugins/hibernate4/src/main/java/de/juplo/plugins/hibernate4/Hbm2DdlMojo.java [new file with mode: 0644]
maven-plugins/pom.xml [new file with mode: 0644]
maven/hibernate4-maven-plugin/pom.xml [deleted file]
maven/hibernate4-maven-plugin/src/main/java/de/halbekunst/Hbm2DdlMojo.java [deleted file]
maven/pom.xml [deleted file]
percentcodec/pom.xml [new file with mode: 0644]
percentcodec/src/main/java/de/juplo/percentcodec/PercentCodec.java [new file with mode: 0644]
percentcodec/src/test/java/de/juplo/percentcodec/PercentCodecTest.java [new file with mode: 0644]
percentcodec/src/test/resources/log4j.xml [new file with mode: 0644]
pom.xml
test/pom.xml [deleted file]
test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java [deleted file]
test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java [deleted file]
test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java [deleted file]
test/src/main/java/de/halbekunst/juplo/test/TestServlet.java [deleted file]
test/src/main/java/de/halbekunst/juplo/test/WebContextTestExecutionListener.java [deleted file]
testingtools/pom.xml [new file with mode: 0644]
testingtools/src/main/java/de/juplo/testingtools/HttpTestCase.java [new file with mode: 0644]
testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseFilter.java [new file with mode: 0644]
testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseWrapper.java [new file with mode: 0644]
testingtools/src/main/java/de/juplo/testingtools/WebContextTestExecutionListener.java [new file with mode: 0644]
utils/pom.xml [deleted file]
utils/src/main/java/de/halbekunst/juplo/utils/PercentCodec.java [deleted file]
utils/src/test/java/de/halbekunst/juplo/utils/PercentCodecTest.java [deleted file]
utils/src/test/resources/log4j.xml [deleted file]

diff --git a/accelerator-examples/jsp/jetty.sh b/accelerator-examples/jsp/jetty.sh
new file mode 100755 (executable)
index 0000000..4950796
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn jetty:run
diff --git a/accelerator-examples/jsp/pom.xml b/accelerator-examples/jsp/pom.xml
new file mode 100644 (file)
index 0000000..dc99eb6
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>de.juplo</groupId>
+    <artifactId>juplo-accelerator-examples</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>juplo-accelerator-examples-jsp</artifactId>
+  <packaging>war</packaging>
+  <name>Juplo - Accelerator-Examples: JSP-Pages</name>
+
+</project>
diff --git a/accelerator-examples/jsp/src/main/resources/config.xml b/accelerator-examples/jsp/src/main/resources/config.xml
new file mode 100644 (file)
index 0000000..a04b130
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <!-- Activates the AspectJ-Weaver -->
+  <context:component-scan base-package="de.juplo"/>
+  <context:spring-configured/>
+
+  <bean id="eTag" class="java.lang.String">
+    <constructor-arg value="Hallo Welt!"/>
+  </bean>
+
+  <bean id="weak" class="java.lang.Boolean">
+    <constructor-arg value="true"/>
+  </bean>
+
+  <bean id="lastModified" class="java.lang.Long">
+    <constructor-arg value="1338593731"/>
+  </bean>
+
+  <bean id="cacheSeconds" class="java.lang.Integer">
+    <constructor-arg value="3600"/>
+  </bean>
+
+</beans>
diff --git a/accelerator-examples/jsp/src/main/resources/log4j.xml b/accelerator-examples/jsp/src/main/resources/log4j.xml
new file mode 100644 (file)
index 0000000..b1e35f8
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%p - %C{1}.%M(%L) | %m%n"/>
+    </layout>
+  </appender>
+
+  <logger name="de.juplo">
+   <level value="trace"/>
+  </logger>
+
+  <root>
+    <level value="info"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</log4j:configuration>
diff --git a/accelerator-examples/jsp/src/main/webapp/WEB-INF/c.tld b/accelerator-examples/jsp/src/main/webapp/WEB-INF/c.tld
new file mode 100644 (file)
index 0000000..4b85b44
--- /dev/null
@@ -0,0 +1,578 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!--
+Workaround for a bug in ServletUnit:
+The TLD in jstl.jar is not found when the webapp is running in this
+environment. Because of that, the TLD must be copied here and this
+copy must be referenced explicitly in all JSP's
+-->
+<taglib xmlns="http://java.sun.com/xml/ns/javaee"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
+    version="2.1">
+    
+  <description>JSTL 1.1 core library</description>
+  <display-name>JSTL core</display-name>
+  <tlib-version>1.1</tlib-version>
+  <short-name>c</short-name>
+  <uri>http://java.sun.com/jsp/jstl/core</uri>
+
+  <validator>
+    <description>
+        Provides core validation features for JSTL tags.
+    </description>
+    <validator-class>
+        org.apache.taglibs.standard.tlv.JstlCoreTLV
+    </validator-class>
+  </validator>
+
+  <tag>
+    <description>
+        Catches any Throwable that occurs in its body and optionally
+        exposes it.
+    </description>
+    <name>catch</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.CatchTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+exception thrown from a nested action. The type of the
+scoped variable is the type of the exception thrown.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+       Simple conditional tag that establishes a context for
+       mutually exclusive conditional operations, marked by
+       &lt;when&gt; and &lt;otherwise&gt;
+    </description>
+    <name>choose</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.ChooseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+       Simple conditional tag, which evalutes its body if the
+       supplied condition is true and optionally exposes a Boolean
+       scripting variable representing the evaluation of this condition
+    </description>
+    <name>if</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.IfTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or
+not the body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+       <type>boolean</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resulting value of the test condition. The type
+of the scoped variable is Boolean.        
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Retrieves an absolute or relative URL and exposes its contents
+        to either the page, a String in 'var', or a Reader in 'varReader'.
+    </description>
+    <name>import</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ImportTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ImportTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to import.
+        </description>
+        <name>url</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+resource's content. The type of the scoped
+variable is Reader.
+        </description>
+        <name>varReader</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when accessing a relative
+URL resource that belongs to a foreign
+context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Character encoding of the content at the input
+resource.
+        </description>
+        <name>charEncoding</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+       The basic iteration tag, accepting many different
+        collection types and supporting subsetting and other
+        functionality
+    </description>
+    <name>forEach</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForEachTag</tag-class>
+    <tei-class>org.apache.taglibs.standard.tei.ForEachTEI</tei-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Collection of items to iterate over.
+        </description>
+       <name>items</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>java.lang.Object</type>
+        <deferred-value>
+           <type>java.lang.Object</type>
+        </deferred-value>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration begins at the item located at the
+specified index. First item of the collection has
+index 0.
+If items not specified:
+Iteration begins with index set at the value
+specified.
+        </description>
+       <name>begin</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+If items specified:
+Iteration ends at the item located at the
+specified index (inclusive).
+If items not specified:
+Iteration ends when index reaches the value
+specified.
+        </description>
+       <name>end</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step items of
+the collection, starting with the first one.
+        </description>
+       <name>step</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility. Its type depends
+on the object of the underlying collection.
+        </description>
+       <name>var</name>
+       <required>false</required>
+       <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of type
+javax.servlet.jsp.jstl.core.LoopTagStatus. This scoped variable has nested
+visibility.
+        </description>
+       <name>varStatus</name>
+       <required>false</required>
+       <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+       Iterates over tokens, separated by the supplied delimeters
+    </description>
+    <name>forTokens</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ForTokensTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+String of tokens to iterate over.
+        </description>
+       <name>items</name>
+       <required>true</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>java.lang.String</type>
+        <deferred-value>
+           <type>java.lang.String</type>
+        </deferred-value>
+    </attribute>
+    <attribute>
+        <description>
+The set of delimiters (the characters that
+separate the tokens in the string).
+        </description>
+       <name>delims</name>
+       <required>true</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>java.lang.String</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration begins at the token located at the
+specified index. First token has index 0.
+        </description>
+       <name>begin</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration ends at the token located at the
+specified index (inclusive).
+        </description>
+       <name>end</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Iteration will only process every step tokens
+of the string, starting with the first one.
+        </description>
+       <name>step</name>
+       <required>false</required>
+       <rtexprvalue>true</rtexprvalue>
+       <type>int</type>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+current item of the iteration. This scoped
+variable has nested visibility.
+        </description>
+       <name>var</name>
+       <required>false</required>
+       <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+status of the iteration. Object exported is of
+type
+javax.servlet.jsp.jstl.core.LoopTag
+Status. This scoped variable has nested
+visibility.
+        </description>
+       <name>varStatus</name>
+       <required>false</required>
+       <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Like &lt;%= ... &gt;, but for expressions.
+    </description> 
+    <name>out</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.OutTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Default value if the resulting value is null.
+        </description>
+        <name>default</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Determines whether characters &lt;,&gt;,&amp;,'," in the
+resulting string should be converted to their
+corresponding character entity codes. Default value is
+true.
+        </description>
+        <name>escapeXml</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+
+  <tag>
+    <description>
+        Subtag of &lt;choose&gt; that follows &lt;when&gt; tags
+        and runs only if all of the prior conditions evaluated to
+        'false'
+    </description>
+    <name>otherwise</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.OtherwiseTag</tag-class>
+    <body-content>JSP</body-content>
+  </tag>
+
+  <tag>
+    <description>
+        Adds a parameter to a containing 'import' tag's URL.
+    </description>
+    <name>param</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.ParamTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the query string parameter.
+        </description>
+        <name>name</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Value of the parameter.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Redirects to a new URL.
+    </description>
+    <name>redirect</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.RedirectTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The URL of the resource to redirect to.
+        </description>
+        <name>url</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when redirecting to a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Removes a scoped variable (from a particular scope, if specified).
+    </description>
+    <name>remove</name>
+    <tag-class>org.apache.taglibs.standard.tag.common.core.RemoveTag</tag-class>
+    <body-content>empty</body-content>
+    <attribute>
+        <description>
+Name of the scoped variable to be removed.
+        </description>
+        <name>var</name>
+        <required>true</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+ <tag>
+    <description>
+        Sets the result of an expression evaluation in a 'scope'
+    </description>
+    <name>set</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.SetTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable to hold the value
+specified in the action. The type of the scoped variable is
+whatever type the value expression evaluates to.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Expression to be evaluated.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+        <deferred-value>
+           <type>java.lang.Object</type>
+        </deferred-value>
+    </attribute>
+    <attribute>
+        <description>
+Target object whose property will be set. Must evaluate to
+a JavaBeans object with setter property property, or to a
+java.util.Map object.
+        </description>
+        <name>target</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the property to be set in the target object.
+        </description>
+        <name>property</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+        Creates a URL with optional query parameters.
+    </description>
+    <name>url</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.UrlTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+Name of the exported scoped variable for the
+processed url. The type of the scoped variable is
+String.
+        </description>
+        <name>var</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Scope for var.
+        </description>
+        <name>scope</name>
+        <required>false</required>
+        <rtexprvalue>false</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+URL to be processed.
+        </description>
+        <name>value</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+        <description>
+Name of the context when specifying a relative URL
+resource that belongs to a foreign context.
+        </description>
+        <name>context</name>
+        <required>false</required>
+        <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+
+  <tag>
+    <description>
+       Subtag of &lt;choose&gt; that includes its body if its
+       condition evalutes to 'true'
+    </description>
+    <name>when</name>
+    <tag-class>org.apache.taglibs.standard.tag.rt.core.WhenTag</tag-class>
+    <body-content>JSP</body-content>
+    <attribute>
+        <description>
+The test condition that determines whether or not the
+body content should be processed.
+        </description>
+        <name>test</name>
+        <required>true</required>
+        <rtexprvalue>true</rtexprvalue>
+       <type>boolean</type>
+    </attribute>
+  </tag>
+
+</taglib>
diff --git a/accelerator-examples/jsp/src/main/webapp/WEB-INF/included.jsp b/accelerator-examples/jsp/src/main/webapp/WEB-INF/included.jsp
new file mode 100644 (file)
index 0000000..1931d89
--- /dev/null
@@ -0,0 +1,2 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%>
+<p>Hello World, again...</p>
diff --git a/accelerator-examples/jsp/src/main/webapp/WEB-INF/web.xml b/accelerator-examples/jsp/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..938240c
--- /dev/null
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+  <!-- Context Configuration locations for Spring XML files -->
+
+  <context-param>
+    <param-name>contextConfigLocation</param-name>
+    <param-value>classpath:/config.xml</param-value>
+  </context-param>
+
+
+  <!-- Listener-Definitions -->
+
+  <listener>
+    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+  </listener>
+
+
+  <!-- Filter-Definitions -->
+
+  <filter>
+    <filter-name>accelerator</filter-name>
+    <filter-class>de.juplo.accelerator.AcceleratorFilter</filter-class>
+  </filter>
+
+  <filter>
+    <filter-name>logger</filter-name>
+    <filter-class>de.juplo.testingtools.LoggingHttpServletResponseFilter</filter-class>
+  </filter>
+
+
+  <!-- Filter-Mappings -->
+
+  <filter-mapping>
+    <filter-name>logger</filter-name>
+    <url-pattern>*.jsp</url-pattern>
+  </filter-mapping>
+
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>*.jsp</url-pattern>
+  </filter-mapping>
+
+</web-app>
diff --git a/accelerator-examples/jsp/src/main/webapp/faulty-page.jsp b/accelerator-examples/jsp/src/main/webapp/faulty-page.jsp
new file mode 100644 (file)
index 0000000..e40c213
--- /dev/null
@@ -0,0 +1,21 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<%@taglib uri="/WEB-INF/c.tld" prefix="c"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Faulty Page</title>
+  </head>
+  <body>
+    <h1>Faulty Page</h1>
+    <p>
+      This page will raise an error<c:forEach begin="1" end="${param['n']}" step="1">.</c:forEach>
+      after a while!
+    </p>
+    <p>
+      <strong>Ecactly, NOW:</strong>
+      <% if (true) throw new RuntimeException("Oh no!"); %>
+    </p>
+  </body>
+</html>
diff --git a/accelerator-examples/jsp/src/main/webapp/index.html b/accelerator-examples/jsp/src/main/webapp/index.html
new file mode 100644 (file)
index 0000000..2246263
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>JSP Examples</title>
+  </head>
+  <body>
+    <h1>JSP Examples</h1>
+    <ul>
+      <li><a href="/simple-page.jsp">A really simple JSP-page</a></li>
+      <li><a href="/page-with-include.jsp">A JSP-page with several includes</a></li>
+      <li><a href="/page-with-forward.jsp">A JSP-page with a forward to /simple-page.jsp</a></li>
+      <li><a href="/faulty-page.jsp?n=8822">A JSP-page with raises an error</a></li>
+    </ul>
+  </body>
+</html>
diff --git a/accelerator-examples/jsp/src/main/webapp/page-with-forward.jsp b/accelerator-examples/jsp/src/main/webapp/page-with-forward.jsp
new file mode 100644 (file)
index 0000000..35e4905
--- /dev/null
@@ -0,0 +1,14 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Page with forward</title>
+  </head>
+  <body>
+    <h1>Hello World!</h1>
+    <p>This should not be seen, because the page is forwardes to /simple-page.jsp</p>
+    <jsp:forward page="/simple-page.jsp" />
+  </body>
+</html>
diff --git a/accelerator-examples/jsp/src/main/webapp/page-with-include.jsp b/accelerator-examples/jsp/src/main/webapp/page-with-include.jsp
new file mode 100644 (file)
index 0000000..3347af7
--- /dev/null
@@ -0,0 +1,14 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<%@taglib uri="/WEB-INF/c.tld" prefix="c"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Page with include</title>
+  </head>
+  <body>
+    <h1>Hello World!</h1>
+    <c:forEach var="i" begin="1" end="100" step="1">${i}:<jsp:include page="/WEB-INF/included.jsp" /></c:forEach>
+  </body>
+</html>
diff --git a/accelerator-examples/jsp/src/main/webapp/simple-page.jsp b/accelerator-examples/jsp/src/main/webapp/simple-page.jsp
new file mode 100644 (file)
index 0000000..99d92d7
--- /dev/null
@@ -0,0 +1,13 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Simple Page</title>
+  </head>
+  <body>
+    <h1>Simple Page</h1>
+    <p>This page is a simple jsp-page</p>
+  </body>
+</html>
diff --git a/accelerator-examples/jsp/src/test/java/de/juplo/accelerator/examples/JspTest.java b/accelerator-examples/jsp/src/test/java/de/juplo/accelerator/examples/JspTest.java
new file mode 100644 (file)
index 0000000..5000a9c
--- /dev/null
@@ -0,0 +1,43 @@
+package de.juplo.accelerator.examples;
+
+import com.meterware.httpunit.WebResponse;
+import de.juplo.testingtools.HttpTestCase;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class JspTest extends HttpTestCase {
+
+  private static final Logger log = LoggerFactory.getLogger(JspTest.class);
+
+
+  public JspTest() {
+    super("src/main/webapp/WEB-INF/web.xml");
+  }
+
+
+  @Test
+  public void testSimplePage() throws Exception {
+    WebResponse response = executeRequest("http://localhost:8080/simple-page.jsp");
+    log.info("Title:\t\t{}", response.getTitle());
+    log.debug("Text:\t\t{}", response.getText());
+  }
+
+  @Test
+  public void testPageWithInclude() throws Exception {
+    WebResponse response = executeRequest("http://localhost:8080/page-with-include.jsp");
+    log.info("Title:\t\t{}", response.getTitle());
+    log.debug("Text:\t\t{}", response.getText());
+  }
+
+  @Test
+  public void testPageWithForward() throws Exception {
+    WebResponse response = executeRequest("http://localhost:8080/page-with-forward.jsp");
+    log.info("Title:\t\t{}", response.getTitle());
+    log.debug("Text:\t\t{}", response.getText());
+  }
+}
diff --git a/accelerator-examples/jsp/tomcat.sh b/accelerator-examples/jsp/tomcat.sh
new file mode 100755 (executable)
index 0000000..4378a12
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn tomcat:run-war
diff --git a/accelerator-examples/pom.xml b/accelerator-examples/pom.xml
new file mode 100644 (file)
index 0000000..fcd9e1d
--- /dev/null
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>de.juplo</groupId>
+  <artifactId>juplo-accelerator-examples</artifactId>
+  <name>Juplo - Accelerator-Examples</name>
+  <version>0.1-SNAPSHOT</version>
+  <packaging>pom</packaging>
+  <url>http://www.juplo.de/accelerator-examples</url>
+
+  <prerequisites>
+    <maven>2.0.6</maven>
+  </prerequisites>
+
+  <developers>
+    <developer>
+      <id>kai</id>
+      <name>Kai Moritz</name>
+      <email>kai@juplo.de</email>
+    </developer>
+  </developers>
+
+  <modules>
+    <module>jsp</module>
+    <module>static</module>
+    <module>servlet</module>
+    <module>spring</module>
+  </modules>
+
+  <properties>
+    <!-- Zeichensatz -->
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <!-- Verwendete Versionen -->
+    <aspectj.version>1.6.11</aspectj.version>
+    <commons-io.version>1.3.2</commons-io.version>
+    <jasper.version>6.0.29</jasper.version>
+    <jpa.version>1.0</jpa.version>
+    <jstl.version>1.2</jstl.version>
+    <junit.version>4.8.1</junit.version>
+    <juplo-testingtools.version>1.0</juplo-testingtools.version>
+    <juplo-accelerator.version>0.1-SNAPSHOT</juplo-accelerator.version>
+    <log4j.version>1.2.16</log4j.version>
+    <servlet-api.version>2.5</servlet-api.version>
+    <slf4j.binding>slf4j-log4j12</slf4j.binding>
+    <slf4j.version>1.6.1</slf4j.version>
+    <springframework.version>3.0.6.RELEASE</springframework.version>
+  </properties>
+
+  <dependencies>
+    <!-- Juplo -->
+    <dependency>
+      <groupId>de.juplo</groupId>
+      <artifactId>juplo-accelerator</artifactId>
+      <version>${juplo-accelerator.version}</version>
+    </dependency>
+    <!-- JSP-Stuff... -->
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>jstl</artifactId>
+      <version>${jstl.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <!--  Spring -->
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-webmvc</artifactId>
+      <version>${springframework.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+      <version>${springframework.version}</version>
+      <scope>runtime</scope>
+      <exclusions>
+        <!-- Exclude Commons Logging in favor of SLF4j -->
+        <exclusion>
+          <groupId>commons-logging</groupId>
+          <artifactId>commons-logging</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-aspects</artifactId>
+      <version>${springframework.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.aspectj</groupId>
+      <artifactId>aspectjrt</artifactId>
+      <version>${aspectj.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <!-- Logging -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>${slf4j.binding}</artifactId>
+      <version>${slf4j.version}</version>
+      <scope>runtime</scope>
+    </dependency>
+    <!-- Test -->
+    <dependency>
+      <groupId>de.juplo</groupId>
+      <artifactId>juplo-testingtools</artifactId>
+      <version>${juplo-testingtools.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>jasper</artifactId>
+      <version>${jasper.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <encoding>utf8</encoding>
+          <showWarnings>true</showWarnings>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.mortbay.jetty</groupId>
+        <artifactId>jetty-maven-plugin</artifactId>
+        <configuration>
+          <connectors>
+            <connector implementation="org.eclipse.jetty.server.nio.BlockingChannelConnector">
+              <host>0.0.0.0</host>
+              <port>8080</port>
+              <acceptors>2</acceptors>
+            </connector>
+          </connectors>
+          <scanIntervalSeconds>0</scanIntervalSeconds>
+          <scanTargetPatterns>
+            <scanTargetPattern>
+              <directory>${project.basedir}/src/main/webapp/WEB-INF</directory>
+              <excludes>
+                <exclude>**/*.jsp</exclude>
+              </excludes>
+              <includes>
+                <include>**/*.properties</include>
+                <include>**/*.xml</include>
+              </includes>
+            </scanTargetPattern>
+          </scanTargetPatterns>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>tomcat-maven-plugin</artifactId>
+        <version>1.1</version>
+        <configuration>
+          <path>/</path>
+          <uriEncoding>UTF-8</uriEncoding>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <artifactId>maven-changes-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>cobertura-maven-plugin</artifactId>
+        <version>2.0</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jxr-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <configuration>
+          <linkXref>true</linkXref>
+          <targetJdk>1.5</targetJdk>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.7.2</version>
+      </plugin>
+    </plugins>
+  </reporting>
+
+</project>
diff --git a/accelerator-examples/servlet/jetty.sh b/accelerator-examples/servlet/jetty.sh
new file mode 100755 (executable)
index 0000000..4950796
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn jetty:run
diff --git a/accelerator-examples/servlet/pom.xml b/accelerator-examples/servlet/pom.xml
new file mode 100644 (file)
index 0000000..92a5e6f
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>de.juplo</groupId>
+    <artifactId>juplo-accelerator-examples</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>juplo-accelerator-examples-servlet</artifactId>
+  <packaging>war</packaging>
+  <name>Juplo - Accelerator-Examples: Servlet</name>
+
+</project>
diff --git a/accelerator-examples/servlet/src/main/java/de/juplo/accelerator/examples/FaultyServlet.java b/accelerator-examples/servlet/src/main/java/de/juplo/accelerator/examples/FaultyServlet.java
new file mode 100644 (file)
index 0000000..d9eee5f
--- /dev/null
@@ -0,0 +1,43 @@
+package de.juplo.accelerator.examples;
+
+import java.io.IOException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class FaultyServlet extends HttpServlet {
+  private final static Logger log = LoggerFactory.getLogger(FaultyServlet.class);
+  private final static long lastModified = System.currentTimeMillis();
+
+
+  @Override
+  protected long getLastModified(HttpServletRequest req) {
+    return lastModified;
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    int n = 0;
+    try {
+      /**
+       * Wenn der Parameter n gesetzt ist, wird ein Antwort-Body erzeugt, der
+       * exakt die Anzahl der geforderten Bytes enthält.
+       */
+      n = Integer.parseInt(request.getParameter("n"));
+    }
+    catch(Exception e) {}
+    log.debug("Error will be risen after {} bytes: {}", n, request.getRequestURI());
+    ServletOutputStream out = response.getOutputStream();
+    for (int i=0; i<n; i++)
+      out.write(i%2 + 48); /** ASCII-Codes für "0" und "1" */
+    log.debug("Failing.... NOW:");
+    throw new RuntimeException("Oh, no!");
+  }
+}
diff --git a/accelerator-examples/servlet/src/main/resources/config.xml b/accelerator-examples/servlet/src/main/resources/config.xml
new file mode 100644 (file)
index 0000000..cab8042
--- /dev/null
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <!-- Activates the AspectJ-Weaver -->
+  <context:component-scan base-package="de.juplo"/>
+  <context:spring-configured/>
+
+  <bean id="eTag" class="java.lang.String">
+    <constructor-arg value="Hallo Welt!"/>
+  </bean>
+
+  <bean id="weak" class="java.lang.Boolean">
+    <constructor-arg value="true"/>
+  </bean>
+
+  <bean id="lastModified" class="java.lang.Long">
+    <constructor-arg value="1338593731"/>
+  </bean>
+
+  <bean id="cacheSeconds" class="java.lang.Integer">
+    <constructor-arg value="3600"/>
+  </bean>
+
+  <bean id="simpleUrlHandlerMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
+    <property name="defaultHandler" value="urlFilenameViewController"/>
+  </bean>
+
+  <bean id="urlFilenameViewController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
+
+  <!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
+  <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+    <property name="prefix" value="/WEB-INF/views/"/>
+    <property name="suffix" value=".jsp"/>
+  </bean>
+
+</beans>
diff --git a/accelerator-examples/servlet/src/main/resources/log4j.xml b/accelerator-examples/servlet/src/main/resources/log4j.xml
new file mode 100644 (file)
index 0000000..b1e35f8
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%p - %C{1}.%M(%L) | %m%n"/>
+    </layout>
+  </appender>
+
+  <logger name="de.juplo">
+   <level value="trace"/>
+  </logger>
+
+  <root>
+    <level value="info"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</log4j:configuration>
diff --git a/accelerator-examples/servlet/src/main/webapp/WEB-INF/web.xml b/accelerator-examples/servlet/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..994a904
--- /dev/null
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+  <!-- Context Configuration locations for Spring XML files -->
+
+  <context-param>
+    <param-name>contextConfigLocation</param-name>
+    <param-value>classpath:/config.xml</param-value>
+  </context-param>
+
+
+  <!-- Listener-Definitions -->
+
+  <listener>
+    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+  </listener>
+
+
+  <!-- Filter-Definitions -->
+
+  <filter>
+    <filter-name>accelerator</filter-name>
+    <filter-class>de.juplo.accelerator.AcceleratorFilter</filter-class>
+  </filter>
+
+  <filter>
+    <filter-name>logger</filter-name>
+    <filter-class>de.juplo.testingtools.LoggingHttpServletResponseFilter</filter-class>
+  </filter>
+
+
+  <!-- Filter-Mappings -->
+
+  <filter-mapping>
+    <filter-name>logger</filter-name>
+    <url-pattern>/test-servlet</url-pattern>
+  </filter-mapping>
+  <filter-mapping>
+    <filter-name>logger</filter-name>
+    <url-pattern>/faulty-servlet</url-pattern>
+  </filter-mapping>
+
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>/test-servlet</url-pattern>
+  </filter-mapping>
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>/faulty-servlet</url-pattern>
+  </filter-mapping>
+
+
+  <!-- Servlet-Definitions -->
+
+  <servlet>
+    <servlet-name>test-servlet</servlet-name>
+    <servlet-class>de.juplo.testintools.TestServlet</servlet-class>
+  </servlet>
+  <servlet>
+    <servlet-name>faulty-servlet</servlet-name>
+    <servlet-class>de.juplo.accelerator.examples.FaultyServlet</servlet-class>
+  </servlet>
+
+
+  <!-- Servlet-Mappings -->
+
+  <servlet-mapping>
+    <servlet-name>test-servlet</servlet-name>
+    <url-pattern>/test-servlet</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>faulty-servlet</servlet-name>
+    <url-pattern>/faulty-servlet</url-pattern>
+  </servlet-mapping>
+
+</web-app>
diff --git a/accelerator-examples/servlet/src/main/webapp/index.html b/accelerator-examples/servlet/src/main/webapp/index.html
new file mode 100644 (file)
index 0000000..c08ba7c
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Servlet Examples</title>
+  </head>
+  <body>
+    <h1>Servlet Examples</h1>
+    <p>This page is a static HTML-page</p>
+    <ul>
+      <li><a href="/test-servlet">Empty Answer</a></li>
+      <li><a href="/test-servlet?n=16">16-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=32">32-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=64">64-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=128">128-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=256">256-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=512">512-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=1024">1024-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=2048">2048-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=4096">4096-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=8192">8192-Bytes-Answer</a></li>
+      <li><a href="/test-servlet?n=16384">16384-Bytes-Answer</a></li>
+    </ul>
+    <ul>
+      <li><a href="/faulty-servlet">Empty Faulty Answer</a></li>
+      <li><a href="/faulty-servlet?n=16">Error after 16 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=32">Error after 32 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=64">Error after 64 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=128">Error after 128 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=256">Error after 256 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=512">Error after 512 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=1024">Error after 1024 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=2048">Error after 2048 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=4096">Error after 4096 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=8192">Error after 8192 Bytes</a></li>
+      <li><a href="/faulty-servlet?n=16384">Error after 16384 Bytes</a></li>
+    </ul>
+  </body>
+</html>
diff --git a/accelerator-examples/servlet/src/test/java/de/juplo/accelerator/examples/ServletTest.java b/accelerator-examples/servlet/src/test/java/de/juplo/accelerator/examples/ServletTest.java
new file mode 100644 (file)
index 0000000..77592ab
--- /dev/null
@@ -0,0 +1,28 @@
+package de.juplo.accelerator.examples;
+
+import de.juplo.testingtools.HttpTestCase;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class ServletTest extends HttpTestCase {
+
+  private static final Logger log = LoggerFactory.getLogger(ServletTest.class);
+
+
+  public ServletTest() {
+    super("src/main/webapp/WEB-INF/web.xml");
+  }
+
+
+  @Test
+  public void test() throws Exception {
+//    WebResponse response = executeRequest("http://localhost:8080/simple-page.jsp");
+//    log.info("Title:\t\t{}", response.getTitle());
+//    log.debug("Text:\t\t{}", response.getText());
+  }
+}
diff --git a/accelerator-examples/servlet/tomcat.sh b/accelerator-examples/servlet/tomcat.sh
new file mode 100755 (executable)
index 0000000..4378a12
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn tomcat:run-war
diff --git a/accelerator-examples/spring/jetty.sh b/accelerator-examples/spring/jetty.sh
new file mode 100755 (executable)
index 0000000..4950796
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn jetty:run
diff --git a/accelerator-examples/spring/pom.xml b/accelerator-examples/spring/pom.xml
new file mode 100644 (file)
index 0000000..4eddbc9
--- /dev/null
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>de.juplo</groupId>
+    <artifactId>juplo-accelerator-examples</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>juplo-accelerator-examples-spring</artifactId>
+  <packaging>war</packaging>
+  <name>Juplo - Accelerator-Examples: Spring-MVC</name>
+
+  <dependencies>
+
+    <!--  Spring -->
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+      <version>${springframework.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-web</artifactId>
+      <version>${springframework.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-webmvc</artifactId>
+      <version>${springframework.version}</version>
+    </dependency>
+
+  </dependencies>
+
+</project>
diff --git a/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/FaultyController.java b/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/FaultyController.java
new file mode 100644 (file)
index 0000000..58c45c6
--- /dev/null
@@ -0,0 +1,40 @@
+package de.juplo.accelerator.examples.spring;
+
+import de.juplo.accelerator.annotations.CacheSeconds;
+import de.juplo.accelerator.annotations.Cacheable;
+import de.juplo.accelerator.annotations.LastModified;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+
+/**
+ * Simple Spring-MVC Controller
+ * @author kai
+ */
+@Controller
+@Cacheable(eager=true)
+public class FaultyController
+{
+  public static final String ACCESS_TIME = FaultyController.class.getCanonicalName() + ".ACCESS_TIME";
+  public static final Integer DEFAULT_MAX_AGE = 60;
+
+  private final static long lastModified = System.currentTimeMillis();
+
+  @CacheSeconds
+  public int cacheSeconds(HttpServletRequest request) {
+    return DEFAULT_MAX_AGE;
+  }
+
+  @LastModified
+  public long lastModified(HttpServletRequest request) {
+    return lastModified;
+  }
+
+  @RequestMapping("/faulty-controller.html")
+  public ModelAndView process(HttpServletRequest request)
+  {
+    throw new RuntimeException("Oh, no!");
+  }
+}
\ No newline at end of file
diff --git a/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/SpringController.java b/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/SpringController.java
new file mode 100644 (file)
index 0000000..6671658
--- /dev/null
@@ -0,0 +1,65 @@
+package de.juplo.accelerator.examples.spring;
+
+import de.juplo.accelerator.annotations.CacheSeconds;
+import de.juplo.accelerator.annotations.Cacheable;
+import de.juplo.accelerator.annotations.LastModified;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.servlet.ModelAndView;
+
+
+/**
+ * Simple Spring-MVC Controller
+ * @author kai
+ */
+@Controller
+@Cacheable(eager=true)
+public class SpringController
+{
+  public static final String ACCESS_TIME = SpringController.class.getCanonicalName() + ".ACCESS_TIME";
+  public static final Integer DEFAULT_MAX_AGE = 60;
+
+  private Date lastModified = new Date();
+  private Map<Date,String> requests = new TreeMap<Date,String>();
+  private Map<String,Date> accessTimes = new HashMap<String,Date>();
+
+
+  @CacheSeconds
+  public int cacheSeconds(HttpServletRequest request) {
+    String maxAge = request.getParameter("max-age");
+    if (maxAge != null && maxAge.matches("^\\s*\\d+\\s*$"))
+      return Integer.parseInt(maxAge);
+    else
+      return DEFAULT_MAX_AGE;
+  }
+
+  @LastModified
+  public long lastModified(HttpServletRequest request) {
+    String query = request.getQueryString();
+    query = query == null ? "" : query;
+    Date accessTime = accessTimes.get(query);
+    if (accessTime == null || !accessTime.equals(lastModified))
+      /** Neuer Zugriff! */
+      accessTime = new Date();
+    request.setAttribute(ACCESS_TIME, accessTime);
+    return accessTime.getTime();
+  }
+
+  @RequestMapping("/spring-controller.html")
+  public ModelAndView process(HttpServletRequest request)
+  {
+    lastModified = (Date)request.getAttribute(ACCESS_TIME);
+    String query = request.getQueryString();
+    query = query == null ? "" : query;
+    requests.put(lastModified, query);
+    accessTimes.put(query, lastModified);
+    ModelAndView mav = new ModelAndView("controller-view");
+    mav.addObject("requests", requests);
+    return mav;
+  }
+}
\ No newline at end of file
diff --git a/accelerator-examples/spring/src/main/resources/config.xml b/accelerator-examples/spring/src/main/resources/config.xml
new file mode 100644 (file)
index 0000000..fc1de60
--- /dev/null
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:mvc="http://www.springframework.org/schema/mvc"
+       xsi:schemaLocation="
+           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
+           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
+
+  <!-- Activates the AspectJ-Weaver -->
+  <context:component-scan base-package="de.juplo"/>
+  <context:spring-configured/>
+
+  <bean id="eTag" class="java.lang.String">
+    <constructor-arg value="Hallo Welt!"/>
+  </bean>
+
+  <bean id="weak" class="java.lang.Boolean">
+    <constructor-arg value="true"/>
+  </bean>
+
+  <bean id="lastModified" class="java.lang.Long">
+    <constructor-arg value="1338593731"/>
+  </bean>
+
+  <bean id="cacheSeconds" class="java.lang.Integer">
+    <constructor-arg value="3600"/>
+  </bean>
+
+  <!-- Configures the CacheControlInterceptor -->
+  <mvc:interceptors>
+    <bean class="de.juplo.accelerator.CacheControlInterceptor"/>
+  </mvc:interceptors>
+
+  <bean id="defaultAnnotationlHandlerMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
+    <property name="defaultHandler" value="urlFilenameViewController"/>
+  </bean>
+
+  <bean id="urlFilenameViewController" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>
+
+  <!-- Resolves view names to protected .jsp resources within the /WEB-INF/views directory -->
+  <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+    <property name="prefix" value="/WEB-INF/views/"/>
+    <property name="suffix" value=".jsp"/>
+  </bean>
+
+</beans>
diff --git a/accelerator-examples/spring/src/main/resources/log4j.xml b/accelerator-examples/spring/src/main/resources/log4j.xml
new file mode 100644 (file)
index 0000000..b6030f9
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%p - %C{1}.%M(%L) | %m%n"/>
+    </layout>
+  </appender>
+
+  <logger name="de.juplo">
+   <level value="trace"/>
+  </logger>
+
+  <logger name="org.springframework">
+    <level value="debug" />
+  </logger>
+
+  <root>
+    <level value="info"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</log4j:configuration>
diff --git a/accelerator-examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp
new file mode 100644 (file)
index 0000000..26abdbd
--- /dev/null
@@ -0,0 +1,43 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<%@page import="java.util.Map" %>
+<%@page import="java.util.Date"%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Page, that is handled by a controller</title>
+  </head>
+  <body>
+    <% Map<Date,String> requests = (Map<Date,String>)request.getAttribute("requests");
+       if (requests == null) { %>
+    <h1 style="color: red">This page associated with a controller and should be viewed as such!</h1>
+    <p style="color: red">Go to: <a href="/spring-controller.html">/spring-controller.html</a></p>
+    <% } else { %>
+    <h1>Controlled Page</h1>
+    <form action="/spring-controller.html">
+      <label for="max-age">Max Age:</label>
+      <input type="text" name="max-age" value="<% if (request.getParameter("max-age") != null) { %><%= request.getParameter("max-age") %><% } %>" />
+      <br />
+    </form>
+    <p>(Uncached) requests so far:</p>
+    <ol>
+      <% for(Map.Entry<Date,String> entry : requests.entrySet()) { %>
+      <li>
+        <%= entry.getKey() %>:
+        <% if (entry.getValue().equals("")) { %>
+          <a href="/spring-controller.html">No parameters...</a>
+        <% } else { %>
+          <a href="/spring-controller.html?<%= entry.getValue() %>">
+            <% for (String parameter : entry.getValue().split("&")) { %>
+            <strong><%= parameter %></strong>
+            <% } %>
+          </a>
+        <% } %>
+      </li>
+      <% } %>
+    </ol>
+    <% } %>
+    <p>This page was delivered via SPRING!</p>
+  </body>
+</html>
diff --git a/accelerator-examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp
new file mode 100644 (file)
index 0000000..8718f54
--- /dev/null
@@ -0,0 +1,17 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Simple Page</title>
+  </head>
+  <body>
+    <h1>Faulty Page</h1>
+    <p>This page will raise an error!</p>
+    <p>
+      <strong>Ecactly, NOW:</strong>
+      <% if (true) throw new RuntimeException("Oh no!"); %>
+    </p>
+  </body>
+</html>
diff --git a/accelerator-examples/spring/src/main/webapp/WEB-INF/views/index.jsp b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/index.jsp
new file mode 100644 (file)
index 0000000..a329acd
--- /dev/null
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Simple Spring-MVC Examples</title>
+  </head>
+  <body>
+    <h1>Simple Spring-MVC Examples</h1>
+    <ul>
+      <li><a href="/spring-page.html">Simple Spring-View</a></li>
+      <li><a href="/spring-controller.html">Simple Spring-Controller</a></li>
+      <li><a href="/faulty-page.html">Spring-View, which will raise an error</a></li>
+      <li><a href="/faulty-controller.html">Spring-Controller, which will raise an error</a></li>
+    </ul>
+    <p>This page was delivered via SPRING!</p>
+    <h2>Note:</h2>
+    <p>
+      Since the <code>org.springframework.web.servlet.DispatcherServlet</code>
+      ist configured to handle all <code>*.html</code>-requests, the path
+      <code>/index.html</code> points to a view.
+    </p>
+    <p>
+      Therefore, this page must be stored under
+      <code>/WEB-INF/views/index.jsp</code> in order to be served as
+      <code>/index.html</code>
+    </p>
+    <p>
+      Additionatly, a file <code>/index.jsp</code> in the root-directory of
+      the webappliction is needed, to forward unqualified requests
+      (like <code>http://HOSTNAME/</code>) to the welcome-page served by
+      the Spring-Dispatcher-Servlet.
+    </p>
+  </body>
+</html>
diff --git a/accelerator-examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp
new file mode 100644 (file)
index 0000000..892bf1a
--- /dev/null
@@ -0,0 +1,14 @@
+<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Simple Page</title>
+  </head>
+  <body>
+    <h1>Hello World!</h1>
+    <p>This is a really simple page...</p>
+    <p>This page was delivered via SPRING!</p>
+  </body>
+</html>
diff --git a/accelerator-examples/spring/src/main/webapp/WEB-INF/web.xml b/accelerator-examples/spring/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..683aa66
--- /dev/null
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+  <!-- Context Configuration locations for Spring XML files -->
+
+  <context-param>
+    <param-name>contextConfigLocation</param-name>
+    <param-value>classpath:/config.xml</param-value>
+  </context-param>
+
+
+  <!-- Listener-Definitions -->
+
+  <listener>
+    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+  </listener>
+
+
+  <!-- Filter-Definitions -->
+
+  <filter>
+    <filter-name>accelerator</filter-name>
+    <filter-class>de.juplo.accelerator.AcceleratorFilter</filter-class>
+  </filter>
+
+  <filter>
+    <filter-name>logger</filter-name>
+    <filter-class>de.juplo.testingtools.LoggingHttpServletResponseFilter</filter-class>
+  </filter>
+
+
+  <!-- Filter-Mappings -->
+
+  <filter-mapping>
+    <filter-name>logger</filter-name>
+    <url-pattern>*.html</url-pattern>
+  </filter-mapping>
+
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>*.html</url-pattern>
+  </filter-mapping>
+
+
+  <!-- Servlet-Definitions -->
+
+  <servlet>
+    <servlet-name>dispatcher-servlet</servlet-name>
+    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+    <init-param>
+      <param-name>contextConfigLocation</param-name>
+      <param-value>
+      </param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+
+  <!-- Servlet-Mappings -->
+
+  <servlet-mapping>
+    <servlet-name>dispatcher-servlet</servlet-name>
+    <url-pattern>*.html</url-pattern>
+  </servlet-mapping>
+
+</web-app>
diff --git a/accelerator-examples/spring/src/main/webapp/index.jsp b/accelerator-examples/spring/src/main/webapp/index.jsp
new file mode 100644 (file)
index 0000000..9082000
--- /dev/null
@@ -0,0 +1,8 @@
+<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" session="false" %>
+<jsp:forward page="/index.html"/>
+<%--
+
+Unfortionatly, the welcome-mechanism in web.xml does not work with a page
+served by a servlet...
+
+--%>
diff --git a/accelerator-examples/spring/src/test/java/de/juplo/accelerator/examples/SpringMVCTest.java b/accelerator-examples/spring/src/test/java/de/juplo/accelerator/examples/SpringMVCTest.java
new file mode 100644 (file)
index 0000000..f2ab9f1
--- /dev/null
@@ -0,0 +1,29 @@
+package de.juplo.accelerator.examples;
+
+import com.meterware.httpunit.WebResponse;
+import de.juplo.testingtools.HttpTestCase;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class SpringMVCTest extends HttpTestCase {
+
+  private static final Logger log = LoggerFactory.getLogger(SpringMVCTest.class);
+
+
+  public SpringMVCTest() {
+    super("src/main/webapp/WEB-INF/web.xml");
+  }
+
+
+  @Test
+  public void testSpringPage() throws Exception {
+    WebResponse response = executeRequest("http://localhost:8080/spring-page.html");
+    log.info("Title:\t\t{}", response.getTitle());
+    log.debug("Text:\t\t{}", response.getText());
+  }
+}
diff --git a/accelerator-examples/spring/tomcat.sh b/accelerator-examples/spring/tomcat.sh
new file mode 100755 (executable)
index 0000000..4378a12
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn tomcat:run-war
diff --git a/accelerator-examples/static/jetty.sh b/accelerator-examples/static/jetty.sh
new file mode 100755 (executable)
index 0000000..4950796
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+
+# OutOfMemoryException beim "mvn jetty:run" umgehen und
+# Parameter zum nachträglichen anhängen eines Debuggers
+# setzen
+export MAVEN_OPTS="-Xmx512m -XX:MaxPermSize=256m -XX:-UseGCOverheadLimit -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=4000,server=y,suspend=n"
+
+rm -v src/main/webapp/WEB-INF/lib/juplo*
+
+mvn jetty:run
diff --git a/accelerator-examples/static/pom.xml b/accelerator-examples/static/pom.xml
new file mode 100644 (file)
index 0000000..a4af6ba
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>de.juplo</groupId>
+    <artifactId>juplo-accelerator-examples</artifactId>
+    <version>0.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>juplo-accelerator-examples-static</artifactId>
+  <packaging>war</packaging>
+  <name>Juplo - Accelerator-Examples: Static Content</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>${commons-io.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/accelerator-examples/static/src/main/resources/config.xml b/accelerator-examples/static/src/main/resources/config.xml
new file mode 100644 (file)
index 0000000..a04b130
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <!-- Activates the AspectJ-Weaver -->
+  <context:component-scan base-package="de.juplo"/>
+  <context:spring-configured/>
+
+  <bean id="eTag" class="java.lang.String">
+    <constructor-arg value="Hallo Welt!"/>
+  </bean>
+
+  <bean id="weak" class="java.lang.Boolean">
+    <constructor-arg value="true"/>
+  </bean>
+
+  <bean id="lastModified" class="java.lang.Long">
+    <constructor-arg value="1338593731"/>
+  </bean>
+
+  <bean id="cacheSeconds" class="java.lang.Integer">
+    <constructor-arg value="3600"/>
+  </bean>
+
+</beans>
diff --git a/accelerator-examples/static/src/main/resources/log4j.xml b/accelerator-examples/static/src/main/resources/log4j.xml
new file mode 100644 (file)
index 0000000..b1e35f8
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern" value="%p - %C{1}.%M(%L) | %m%n"/>
+    </layout>
+  </appender>
+
+  <logger name="de.juplo">
+   <level value="trace"/>
+  </logger>
+
+  <root>
+    <level value="info"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+
+</log4j:configuration>
diff --git a/accelerator-examples/static/src/main/webapp/WEB-INF/web.xml b/accelerator-examples/static/src/main/webapp/WEB-INF/web.xml
new file mode 100644 (file)
index 0000000..5edc75f
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+  <!-- Context Configuration locations for Spring XML files -->
+
+  <context-param>
+    <param-name>contextConfigLocation</param-name>
+    <param-value>classpath:/config.xml</param-value>
+  </context-param>
+
+
+  <!-- Listener-Definitions -->
+
+  <listener>
+    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+  </listener>
+
+
+  <!-- Filter-Definitions -->
+
+  <filter>
+    <filter-name>accelerator</filter-name>
+    <filter-class>de.juplo.accelerator.AcceleratorFilter</filter-class>
+  </filter>
+
+  <filter>
+    <filter-name>logger</filter-name>
+    <filter-class>de.juplo.testingtools.LoggingHttpServletResponseFilter</filter-class>
+  </filter>
+
+
+  <!-- Filter-Mappings -->
+
+  <filter-mapping>
+    <filter-name>logger</filter-name>
+    <url-pattern>/static/*</url-pattern>
+  </filter-mapping>
+
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>/static/*</url-pattern>
+  </filter-mapping>
+
+
+  <!-- Servlet-Definitions -->
+
+  <servlet>
+    <servlet-name>default</servlet-name>
+    <servlet-class>org.eclipse.jetty.servlet.DefaultServlet</servlet-class>
+  </servlet>
+
+
+  <!-- Servlet-Mappings -->
+
+  <servlet-mapping>
+    <servlet-name>default</servlet-name>
+    <url-pattern>/</url-pattern>
+  </servlet-mapping>
+
+
+</web-app>
diff --git a/accelerator-examples/static/src/main/webapp/index.html b/accelerator-examples/static/src/main/webapp/index.html
new file mode 100644 (file)
index 0000000..22c61d0
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>Examples for Static Content</title>
+  </head>
+  <body>
+    <h1>Examples for Static Content</h1>
+    <ul>
+      <li><a href="/static/page.html">A plain static HTML-file</a></li>
+      <li><a href="/static/stylesheets.css">A plain static CSS-file</a></li>
+    </ul>
+  </body>
+</html>
diff --git a/accelerator-examples/static/src/main/webapp/static/page.html b/accelerator-examples/static/src/main/webapp/static/page.html
new file mode 100644 (file)
index 0000000..f851795
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+    <title>A Static Page</title>
+    <link rel="stylesheet" type="text/css" media="all" href="/static/stylesheets.css">
+  </head>
+  <body>
+    <h1>A Static Page</h1>
+    <p>This page is a static HTML-page</p>
+  </body>
+</html>
diff --git a/accelerator-examples/static/src/main/webapp/static/stylesheets.css b/accelerator-examples/static/src/main/webapp/static/stylesheets.css
new file mode 100644 (file)
index 0000000..e04bba2
--- /dev/null
@@ -0,0 +1,7 @@
+body {
+  background-color: #ccc;
+  color: #444;
+}
+h1,h2,h3,h4 {
+  color: #000;
+}
\ No newline at end of file
diff --git a/accelerator-examples/static/src/test/java/de/juplo/accelerator/examples/StaticTest.java b/accelerator-examples/static/src/test/java/de/juplo/accelerator/examples/StaticTest.java
new file mode 100644 (file)
index 0000000..f27fec4
--- /dev/null
@@ -0,0 +1,28 @@
+package de.juplo.accelerator.examples;
+
+import com.meterware.httpunit.WebResponse;
+import de.juplo.testingtools.HttpTestCase;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class StaticTest extends HttpTestCase {
+
+  private static final Logger log = LoggerFactory.getLogger(StaticTest.class);
+
+
+  public StaticTest() {
+    super("src/main/webapp/WEB-INF/web.xml");
+  }
+
+  @Test
+  public void testStaticContent() throws Exception {
+    WebResponse response = executeRequest("http://localhost:8080/static/page.html");
+    log.info("Title:\t\t{}", response.getTitle());
+    log.debug("Text:\t\t{}", response.getText());
+  }
+}
diff --git a/accelerator-examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java b/accelerator-examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java
new file mode 100644 (file)
index 0000000..0173a1f
--- /dev/null
@@ -0,0 +1,28 @@
+package org.eclipse.jetty.servlet;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+
+/**
+ * Möglichst simple Fake-Implementierung für die Ausführung des Testfalls
+ *
+ * @author kai
+ */
+public class DefaultServlet extends HttpServlet {
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+    String path = getServletContext().getRealPath(request.getRequestURI());
+    if (path == null) {
+      response.sendError(HttpServletResponse.SC_NOT_FOUND);
+      return;
+    }
+    response.setContentType("text/html");
+    IOUtils.copy(new FileInputStream(path), response.getOutputStream());
+  }
+}
diff --git a/accelerator/pom.xml b/accelerator/pom.xml
new file mode 100644 (file)
index 0000000..fd36da9
--- /dev/null
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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/maven-v4_0_0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>de.juplo</groupId>
+  <artifactId>juplo-accelerator</artifactId>
+  <name>Juplo - Accelerator</name>
+  <version>0.1-SNAPSHOT</version>
+  <url>http://www.juplo.de/accelerator</url>
+
+  <prerequisites>
+    <maven>2.0.6</maven>
+  </prerequisites>
+
+  <developers>
+    <developer>
+      <id>kai</id>
+      <name>Kai Moritz</name>
+      <email>kai@juplo.de</email>
+    </developer>
+  </developers>
+
+  <properties>
+    <!-- Zeichensatz -->
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <!-- Verwendete Versionen -->
+    <aspectj.version>1.6.11</aspectj.version>
+    <jpa.version>1.0</jpa.version>
+    <junit.version>4.8.1</junit.version>
+    <juplo-testingtools.version>1.0</juplo-testingtools.version>
+    <log4j.version>1.2.16</log4j.version>
+    <servlet-api.version>2.5</servlet-api.version>
+    <slf4j.binding>slf4j-log4j12</slf4j.binding>
+    <slf4j.version>1.6.1</slf4j.version>
+    <springframework.version>3.0.6.RELEASE</springframework.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-webmvc</artifactId>
+      <version>${springframework.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-aspects</artifactId>
+      <version>${springframework.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-tx</artifactId>
+      <version>${springframework.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.aspectj</groupId>
+      <artifactId>aspectjrt</artifactId>
+      <version>${aspectj.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.servlet</groupId>
+      <artifactId>servlet-api</artifactId>
+      <version>${servlet-api.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>javax.persistence</groupId>
+      <artifactId>persistence-api</artifactId>
+      <version>${jpa.version}</version>
+      <scope>provided</scope>
+    </dependency>
+    <!-- Logging -->
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+      <version>${slf4j.version}</version>
+    </dependency>
+    <!-- Test -->
+    <dependency>
+      <groupId>de.juplo</groupId>
+      <artifactId>juplo-testingtools</artifactId>
+      <version>${juplo-testingtools.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>${slf4j.binding}</artifactId>
+      <version>${slf4j.version}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <distributionManagement>
+    <repository>
+      <id>juplo.internal</id>
+      <name>Internal Release Repository</name>
+      <url>http://juplo.de/archiva/repository/internal/</url>
+    </repository>
+    <snapshotRepository>
+      <id>juplo.snapshots</id>
+      <name>Internal Snapshot Repository</name>
+      <url>http://juplo.de/archiva/repository/snapshots/</url>
+    </snapshotRepository>
+  </distributionManagement>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <encoding>utf8</encoding>
+          <showWarnings>true</showWarnings>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>install</id>
+            <phase>install</phase>
+            <goals>
+              <goal>sources</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>verify</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>aspectj-maven-plugin</artifactId>
+        <configuration>
+          <complianceLevel>1.6</complianceLevel>
+          <aspectLibraries>
+            <aspectLibrary>
+              <groupId>org.springframework</groupId>
+              <artifactId>spring-aspects</artifactId>
+            </aspectLibrary>
+          </aspectLibraries>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <artifactId>maven-changes-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>cobertura-maven-plugin</artifactId>
+        <version>2.0</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-javadoc-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-jxr-plugin</artifactId>
+      </plugin>
+      <plugin>
+        <artifactId>maven-pmd-plugin</artifactId>
+        <configuration>
+          <linkXref>true</linkXref>
+          <targetJdk>1.5</targetJdk>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.7.2</version>
+      </plugin>
+    </plugins>
+  </reporting>
+
+</project>
diff --git a/accelerator/src/main/java/de/juplo/accelerator/AcceleratorFilter.java b/accelerator/src/main/java/de/juplo/accelerator/AcceleratorFilter.java
new file mode 100644 (file)
index 0000000..7b1e306
--- /dev/null
@@ -0,0 +1,724 @@
+package de.juplo.accelerator;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowire;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Configurable;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@Configurable(autowire=Autowire.BY_NAME)
+public class AcceleratorFilter implements Filter {
+  private final static Logger log = LoggerFactory.getLogger(AcceleratorFilter.class);
+
+  private final static Map<String,String> EMPTY = Collections.unmodifiableMap(new HashMap<String,String>());
+
+  public final static Integer DEFAULT_BUFFER_SIZE = 1024;
+  public final static String REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
+  public final static String RESPONSE_WRAPPER = AcceleratorFilter.class.getName() + ".RESPONSE_WRAPPER";
+
+
+  @Autowired CacheControl cacheControl;
+  @Autowired(required=false) @Qualifier("defaultBufferSize") Integer defaultBufferSize = DEFAULT_BUFFER_SIZE;
+  @Autowired String eTag;
+  @Autowired Boolean weak;
+  @Autowired Long lastModified;
+  @Autowired Integer cacheSeconds;
+
+
+  @Override
+  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+    if (!(request instanceof HttpServletRequest)) {
+      log.error("AcceleratorFilter can only handle HTTP-requests");
+      chain.doFilter(request, response);
+      return;
+    }
+
+    HttpServletRequest httpRequest = (HttpServletRequest)request;
+    HttpServletResponse httpResponse = (HttpServletResponse)response;
+
+    AccelerationWrapper wrapper;
+
+    wrapper = (AccelerationWrapper)request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER);
+    if (wrapper != null) {
+      if (wrapper.getFilter() == this) {
+        /** Ignore multiple mappings of the same filter-instance */
+        log.warn("Ignoring multiple mappings on same URL: {}", httpRequest.getRequestURI());
+        chain.doFilter(request, response);
+        return;
+      }
+      else {
+        log.error("Only one instance of AcceleratorFilter must be mapped to any URL: {}", httpRequest.getRequestURI());
+        httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Only one instance of AcceleratorFilter must be mapped to any URL!");
+        return;
+      }
+    }
+
+    wrapper = new AccelerationWrapper(httpRequest, httpResponse);
+    httpRequest.setAttribute(RESPONSE_WRAPPER, wrapper);
+    cacheControl.init(wrapper);
+    try {
+      chain.doFilter(request, wrapper);
+      wrapper.finish();
+    }
+    catch (NotModifiedException nm) {
+      log.trace("Not modified: {}", httpRequest.getRequestURI());
+    }
+  }
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+
+  @Override
+  public void destroy() {
+  }
+
+
+  class AccelerationWrapper implements HttpServletResponse, CacheMethodHandle {
+
+    private final HttpServletRequest request;
+    private final HttpServletResponse response;
+
+    private final AcceleratorServletOutputStream out;
+
+    private ServletOutputStream stream;
+    private PrintWriter writer;
+
+    private boolean guessing = true;
+    protected boolean zipped = false; // << CacheControll greift direkt auf diese Variable zu!
+
+    private long now;
+    private int status;
+    private int cacheSeconds;
+    private boolean cacheSecondsSet = false;
+    private long lastModified, expires = 0l;
+    private String eTag;
+    private boolean weak;
+    private Map<String,String> cacheParams;
+
+    /** Für den AcceleratorOutputStream */
+    private boolean committed = false;
+    private OutputStream os = null;
+    private int bufferSize;
+    private byte[] buffer;
+    private int pos = 0;
+    private int size = 0;
+
+
+
+    AccelerationWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
+
+      this.request = request;
+      this.response = response;
+
+      now = System.currentTimeMillis();
+      status = HttpServletResponse.SC_OK;
+      cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+      lastModified = AcceleratorFilter.this.lastModified;
+      eTag = AcceleratorFilter.this.eTag;
+      weak = AcceleratorFilter.this.weak;
+      cacheParams = new HashMap<String,String>();
+
+      Enumeration values = request.getHeaders(Headers.HEADER_ACCEPT_ENCODING);
+      while (values.hasMoreElements()) {
+        String value = (String) values.nextElement();
+        if (value.indexOf("gzip") != -1) {
+          zipped = true;
+          break;
+        }
+      }
+
+      out = new AcceleratorServletOutputStream();
+    }
+
+
+    private AcceleratorFilter getFilter() {
+      return AcceleratorFilter.this;
+    }
+
+    private void finish() throws IOException {
+      flushBuffer();
+      out.close();
+    }
+
+    @Override
+    public void setStatus(int sc) {
+      response.setStatus(sc);
+      status = sc;
+    }
+
+    @Override
+    public void setStatus(int sc, String sm) {
+      response.setStatus(sc,sm);
+      status = sc;
+    }
+
+    @Override
+    public void addDateHeader(String name, long value) {
+
+      if (!guessing) {
+        response.addDateHeader(name, value);
+        return;
+      }
+
+      if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
+        now = value;
+        calculateCacheSeconds();
+        return;
+      }
+
+      if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
+        expires = value;
+        calculateCacheSeconds();
+        return;
+      }
+
+      if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+        lastModified = value;
+        return;
+      }
+
+      /** Unknown header: pass throug! */
+      response.addDateHeader(name, value);
+    }
+
+    @Override
+    public void addHeader(String name, String value) {
+
+      if (!guessing) {
+        response.addHeader(name, value);
+        return;
+      }
+
+      if (value == null)
+        return;
+      analyzeHeader(name, value, false);
+    }
+
+    @Override
+    public void addIntHeader(String name, int value) {
+
+      if (!guessing) {
+        response.addIntHeader(name, value);
+        return;
+      }
+
+      analyzeHeader(name, Integer.toString(value), false);
+    }
+
+    @Override
+    public void setDateHeader(String name, long value) {
+
+      if (!guessing) {
+        response.setDateHeader(name, value);
+        return;
+      }
+
+      if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
+        now = value;
+        calculateCacheSeconds();
+        return;
+      }
+
+      if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
+        expires = value;
+        calculateCacheSeconds();
+        return;
+      }
+
+      if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+        lastModified = value;
+        return;
+      }
+
+      /** Unknown header: pass throug! */
+      response.setDateHeader(name, value);
+    }
+
+    @Override
+    public void setHeader(String name, String value) {
+
+      if (!guessing) {
+        response.setHeader(name, value);
+        return;
+      }
+
+      analyzeHeader(name, value, true);
+    }
+
+    @Override
+    public void setIntHeader(String name, int value) {
+
+      if (!guessing) {
+        response.setIntHeader(name, value);
+        return;
+      }
+
+      analyzeHeader(name, Integer.toString(value), true);
+    }
+
+    @Override
+    public ServletOutputStream getOutputStream() throws IOException {
+
+      if (writer != null)
+        throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+      if (stream == null) {
+        stream = out;
+      }
+
+      return out;
+    }
+
+    @Override
+    public PrintWriter getWriter() throws IOException {
+
+      if (stream != null)
+        throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+      if (writer == null) {
+        writer = new PrintWriter(new OutputStreamWriter(out, response.getCharacterEncoding()));
+      }
+
+      return writer;
+    }
+
+    @Override
+    public void setContentLength(int len) {
+      if (zipped)
+        log.info("Supressing explicit content-length {} for request {}, because content will be zipped!", len, request.getRequestURI());
+      else
+        response.setContentLength(len);
+    }
+
+    @Override
+    public int getBufferSize() {
+      return bufferSize;
+    }
+
+    @Override
+    public void setBufferSize(int size) {
+      if (this.size > 0)
+        throw new IllegalStateException("cannot change buffer size, because content was already written!");
+      bufferSize = size;
+      buffer = new byte[size];
+    }
+
+    @Override
+    public void resetBuffer() {
+      if (committed)
+        throw new IllegalStateException("cannot reset buffer, because response is already commited!");
+      pos = 0;
+      stream = null;
+      writer = null;
+    }
+
+    @Override
+    public void reset() {
+      if (committed)
+        throw new IllegalStateException("cannot reset response, because response is already commited!");
+      /**
+       * Da committed==false gilt, wurde die Dekoration noch nicht angestßen
+       * und muss entsprechend auch nicht rückgängig gemacht werden!
+       */
+      response.reset();
+      pos = 0;
+      size = 0;
+      stream = null;
+      writer = null;
+    }
+
+    @Override
+    public void flushBuffer() throws IOException {
+      if (writer != null)
+        writer.flush();
+      else if (stream != null)
+        stream.flush();
+    }
+
+    @Override
+    public void addCookie(Cookie cookie) {
+      // TODO: Je nach Vary-Einstellung ETag anpassen?
+      response.addCookie(cookie);
+    }
+
+    @Override
+    public boolean containsHeader(String name) {
+      return response.containsHeader(name);
+    }
+
+    @Override
+    public String encodeURL(String url) {
+      return response.encodeURL(url);
+    }
+
+    @Override
+    public String encodeRedirectURL(String url) {
+      return response.encodeRedirectURL(url);
+    }
+
+    @Override
+    public String encodeUrl(String url) {
+      return response.encodeUrl(url);
+    }
+
+    @Override
+    public String encodeRedirectUrl(String url) {
+      return response.encodeRedirectUrl(url);
+    }
+
+    @Override
+    public void sendError(int sc, String msg) throws IOException {
+      response.sendError(sc,msg);
+    }
+
+    @Override
+    public void sendError(int sc) throws IOException {
+      response.sendError(sc);
+    }
+
+    @Override
+    public void sendRedirect(String location) throws IOException {
+      response.sendRedirect(location);
+    }
+
+    @Override
+    public String getCharacterEncoding() {
+      return response.getCharacterEncoding();
+    }
+
+    @Override
+    public String getContentType() {
+      return response.getContentType();
+    }
+
+    @Override
+    public void setCharacterEncoding(String charset) {
+      // TODO: Je nach Vary-Einstellung ETag anpassen?
+      response.setCharacterEncoding(charset);
+    }
+
+    @Override
+    public void setContentType(String type) {
+      // TODO: Je nach Vary-Einstellung ETag anpassen?
+      response.setContentType(type);
+    }
+
+    @Override
+    public boolean isCommitted() {
+      return committed;
+    }
+
+    @Override
+    public void setLocale(Locale loc) {
+      // TODO: Je nach Vary-Einstellung ETag anpassen?
+      response.setLocale(loc);
+    }
+
+    @Override
+    public Locale getLocale() {
+      return getLocale();
+    }
+
+
+
+    @Override
+    public boolean isZipped() {
+      return zipped;
+    }
+
+    @Override
+    public long getTimestamp() {
+      return now;
+    }
+
+    @Override
+    public int accepts(HttpServletRequest request) {
+      return status;
+    }
+
+    @Override
+    public int getCacheSeconds(HttpServletRequest request) {
+      return cacheSeconds;
+    }
+
+    @Override
+    public long getLastModified(HttpServletRequest request) {
+      return lastModified;
+    }
+
+    @Override
+    public String getETag(HttpServletRequest request) {
+      return eTag;
+    }
+
+    @Override
+    public boolean isETagWeak() {
+      return weak;
+    }
+
+    @Override
+    public void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap) {
+      cacheControlMap.putAll(cacheParams);
+    }
+
+    @Override
+    public Map<String,String> getAdditionalHeaders(HttpServletRequest request) {
+      return EMPTY;
+    }
+
+    public void supressGuessing() {
+      guessing = false;
+    }
+
+
+    private void analyzeHeader(String name, String value, boolean overwrite) {
+      if (name == null)
+        return;
+      name = name.trim();
+
+      if (name.equalsIgnoreCase(Headers.HEADER_DATE)) {
+        if (value == null) {
+          if (overwrite) {
+            now = System.currentTimeMillis();
+            cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+          }
+          return;
+        }
+        try {
+          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+          now = parser.parse(value).getTime();
+          calculateCacheSeconds();
+        }
+        catch (ParseException e) {
+          log.warn("ignoring date for header \"Date\" in invalid format: {}", value);
+        }
+        return;
+      }
+
+      if (name.equalsIgnoreCase(Headers.HEADER_EXPIRES)) {
+        if (value == null) {
+          if (overwrite) {
+            expires = 0;
+            cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+          }
+          return;
+        }
+        try {
+          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+          expires = parser.parse(value).getTime();
+          calculateCacheSeconds();
+        }
+        catch (ParseException e) {
+          log.warn("ignoring date for header \"Expires\" in invalid format: {}", value);
+        }
+        return;
+      }
+
+      if (name.equalsIgnoreCase(Headers.HEADER_LAST_MODIFIED)) {
+        if (value == null) {
+          if (overwrite)
+            lastModified = AcceleratorFilter.this.lastModified;
+          return;
+        }
+        try {
+          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+          lastModified = parser.parse(value).getTime();
+        }
+        catch (ParseException e) {
+          log.warn("ignoring date for header \"Last-Modified\" in invalid format: {}", value);
+        }
+        return;
+      }
+
+      if (name.equalsIgnoreCase(Headers.HEADER_ETAG)) {
+        if (value == null) {
+          if (overwrite) {
+            eTag = AcceleratorFilter.this.eTag;
+            weak = AcceleratorFilter.this.weak;
+          }
+          return;
+        }
+        value = value.trim();
+        int start = 0;
+        int end = value.length();
+        if (value.startsWith("W/")) {
+          weak = true;
+          start = 2;
+        }
+        else {
+          weak = false;
+        }
+        if (value.charAt(start) == '"')
+          start++;
+        else
+          log.warn("Quote at the beginning ov ETag is missing: {}", value);
+        if (value.charAt(end -1) == '"')
+          end--;
+        else
+          log.warn("Quote at the end of ETag is missing: {}", value);
+        eTag = value.substring(start, end);
+        String filtered = eTag.replaceAll("[^\\x00-\\x21\\x23-\\x7F]+","");
+        if (filtered.length() < eTag.length()) {
+          log.warn("filtering out illegal characters in ETag: \"{}\" -> \"{}\"", eTag, filtered);
+          eTag = filtered;
+        }
+      }
+
+      if (name.equalsIgnoreCase(Headers.HEADER_CACHE_CONTROL)) {
+        if (overwrite)
+          cacheParams.clear();
+        if (value == null)
+          return;
+        for (String param : value.split(",")) {
+          param = param.trim();
+          int pos = param.indexOf("=");
+          if (pos < 0) {
+            cacheParams.put(param, null);
+          }
+          else {
+            String paramName = param.substring(0, pos).trim();
+            if (paramName.equalsIgnoreCase("max-age")) {
+              try {
+                cacheSeconds = Integer.parseInt(param.substring(pos + 1));
+                cacheSecondsSet = true;
+              }
+              catch (NumberFormatException e) {
+                log.warn("illegal value for Header \"Cache-Control\":", param);
+              }
+            }
+            else {
+              cacheParams.put(paramName, param.substring(pos + 1));
+            }
+          }
+        }
+        return;
+      }
+
+      if (name.equalsIgnoreCase(Headers.HEADER_PRAGMA)) {
+        if (value != null && value.trim().equalsIgnoreCase("no-cache"))
+          cacheSeconds = 0;
+        return;
+      }
+
+      /** Pass header through, if no value from intrest was found */
+      if (overwrite)
+        response.setHeader(name, value);
+      else
+        response.addHeader(name, value);
+    }
+
+    private void calculateCacheSeconds() {
+      if (!cacheSecondsSet && expires >= now) {
+        cacheSeconds = (int)(expires/1000 - now/1000);
+        log.debug("calculating cache-seconds from DATE and EXPIRES: {}", cacheSeconds);
+      }
+    }
+
+
+    private class AcceleratorServletOutputStream extends ServletOutputStream  {
+
+      private final ServletOutputStream sos;
+
+
+      private AcceleratorServletOutputStream() throws IOException {
+        bufferSize = defaultBufferSize;
+        buffer = new byte[bufferSize];
+        sos = AccelerationWrapper.this.response.getOutputStream();
+      }
+
+
+      private OutputStream out() throws IOException {
+        if (os == null)
+          os = zipped ? new GZIPOutputStream(sos) : sos;
+        return os;
+      }
+
+      @Override
+      public void write(int i) throws IOException {
+        if (pos == bufferSize) {
+          out().write(buffer);
+          committed = true;
+          /** Dekoration nur beim ersten Schreib-Schub anstoßen */
+          if (pos == size) {
+            if (!cacheControl.decorate(request, response)) {
+              zipped = false;
+              os = null;
+              pos = 0;
+              throw new NotModifiedException();
+            }
+          }
+          pos = 0;
+        }
+        buffer[pos++] = (byte) i;
+        size++;
+      }
+
+      @Override
+      public void flush() throws IOException {
+        if (pos == 0)
+          return;
+
+        committed = true;
+        /** Dekoration nur beim ersten Schreib-Schub anstoßen */
+        if (pos == size) {
+          if (!cacheControl.decorate(request, response)) {
+            zipped = false;
+            os = null;
+            pos = 0;
+            throw new NotModifiedException();
+          }
+        }
+        out().write(buffer, 0, pos);
+        out().flush();
+        pos = 0;
+      }
+
+      @Override
+      public void close() throws IOException {
+        if (size == 0) {
+          committed = true;
+          zipped = false;
+          if (!cacheControl.decorate(request, response))
+            throw new NotModifiedException();
+          sos.close();
+        }
+        else {
+          flush();
+          out().close();
+        }
+      }
+    }
+  }
+}
+
+class NotModifiedException extends IOException {}
\ No newline at end of file
diff --git a/accelerator/src/main/java/de/juplo/accelerator/CacheControl.java b/accelerator/src/main/java/de/juplo/accelerator/CacheControl.java
new file mode 100644 (file)
index 0000000..f119b60
--- /dev/null
@@ -0,0 +1,548 @@
+package de.juplo.accelerator;
+
+import de.juplo.accelerator.AcceleratorFilter.AccelerationWrapper;
+import de.juplo.accelerator.annotations.CacheSeconds;
+import de.juplo.accelerator.annotations.Accepts;
+import de.juplo.accelerator.annotations.AdditionalHeaders;
+import de.juplo.accelerator.annotations.LastModified;
+import de.juplo.accelerator.annotations.ETag;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
+
+/**
+ *
+ * @author kai
+ */
+@Component
+public class CacheControl {
+  private final static Logger log = LoggerFactory.getLogger(CacheControl.class);
+
+  private static final ThreadLocal<CacheMethodHandle> tl = new ThreadLocal<CacheMethodHandle>();
+
+  @Autowired @Qualifier("cacheSeconds") private Integer defaultCacheSeconds;
+  @Autowired @Qualifier("lastModified") private Long defaultLastModified;
+
+
+  public void init(CacheMethodHandle handle) {
+    CacheControl.tl.set(handle);
+  }
+
+  void init(Object handler, AccelerationWrapper wrapper) throws NoSuchMethodException {
+    CacheControl.tl.set(new ReflectionCacheMethodHandle(handler, wrapper == null ? false : wrapper.zipped));
+  }
+
+  public boolean decorate(
+      HttpServletRequest request,
+      HttpServletResponse response
+      )
+  {
+    try {
+    CacheMethodHandle handle = CacheControl.tl.get();
+
+    /** Doppelte Ausführung verhindern... */
+    if (handle == null) {
+      /** Dekoration wurde bereits durchgeführt! */
+      return true;
+    }
+
+    /**
+     * Alle Antworten (insbesondere auch 304) sollen nach dem {@plainlink
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 RFC 2616,
+     * Abschnitt 14.18} einen Date-Header enthalten
+     */
+    response.setDateHeader(Headers.HEADER_DATE, handle.getTimestamp());
+
+    /** Besondere Maßnahmen für besondere HTTP-Status-Codes ?!? */
+    int status = handle.accepts(request);
+    switch (status) {
+      case HttpServletResponse.SC_OK: // 200
+      case HttpServletResponse.SC_NO_CONTENT: // 204
+      case HttpServletResponse.SC_PARTIAL_CONTENT: // 206
+        /** Normale Antwort! Antwort dekorieren... */
+        break;
+      case HttpServletResponse.SC_MOVED_PERMANENTLY: // 301
+      case HttpServletResponse.SC_MOVED_TEMPORARILY: // 302
+      case HttpServletResponse.SC_SEE_OTHER: // 303
+      case HttpServletResponse.SC_NOT_MODIFIED: // 304
+      case HttpServletResponse.SC_USE_PROXY: // 305
+      case HttpServletResponse.SC_TEMPORARY_REDIRECT: // 307
+        /** Redirect-Antwort! Antwort dekodieren... */
+        // TODO: Kann das wirklich nicht zu Protokoll-Verletzungen führen?
+        break;
+      case HttpServletResponse.SC_BAD_REQUEST: // 400
+      case HttpServletResponse.SC_UNAUTHORIZED: // 401
+      case HttpServletResponse.SC_PAYMENT_REQUIRED: // 402
+      case HttpServletResponse.SC_FORBIDDEN: // 403
+      case HttpServletResponse.SC_NOT_FOUND: // 404
+      case HttpServletResponse.SC_METHOD_NOT_ALLOWED: // 405
+      case HttpServletResponse.SC_NOT_ACCEPTABLE: // 406
+      case HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED: // 407
+      case HttpServletResponse.SC_REQUEST_TIMEOUT: // 408
+      case HttpServletResponse.SC_CONFLICT: // 409
+      case HttpServletResponse.SC_GONE: // 410
+      case HttpServletResponse.SC_LENGTH_REQUIRED: // 411
+      case HttpServletResponse.SC_PRECONDITION_FAILED: // 412
+      case HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE: // 413
+      case HttpServletResponse.SC_REQUEST_URI_TOO_LONG: // 414
+      case HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE: // 415
+      case HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE: // 416
+      case HttpServletResponse.SC_INTERNAL_SERVER_ERROR: // 500
+      case HttpServletResponse.SC_NOT_IMPLEMENTED: // 501
+      case HttpServletResponse.SC_SERVICE_UNAVAILABLE: // 503
+      case HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED: // 505
+      default:
+        /**
+         * Es ist nicht klar, was der Handler noch machen wird/muss:
+         * Antwort nicht dekorieren und Kontroller an den Handler übergeben...
+         */
+        return true;
+    }
+
+    Map<String,String> headers = handle.getAdditionalHeaders(request);
+    for (String name : headers.keySet())
+      response.addHeader(name, headers.get(name));
+
+    String url = null;
+    if (log.isDebugEnabled()) {
+      if (request.getQueryString() == null) {
+        url = request.getRequestURI();
+      }
+      else {
+        StringBuilder builder = new StringBuilder();
+        builder.append(request.getRequestURI());
+        builder.append('?');
+        builder.append(request.getQueryString());
+        url = builder.toString();
+      }
+    }
+
+    int cacheSeconds = handle.getCacheSeconds(request);
+    if (cacheSeconds < 0) {
+      log.debug("{}: caching disabled!", url);
+      response.setDateHeader(Headers.HEADER_DATE, handle.getTimestamp());
+      response.setDateHeader(Headers.HEADER_EXPIRES, 0);
+      response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, "private");
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-cache");
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-store");
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, "max-age=0");
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, "s-max-age=0");
+      if (handle.isZipped())
+        response.addHeader(Headers.HEADER_CONTENT_ENCODING, "gzip");
+      return true;
+    }
+
+    long ifModifiedSince = -1;
+    try {
+      ifModifiedSince = request.getDateHeader(Headers.HEADER_IF_MODIFIED_SINCE);
+    }
+    catch (Exception e) {
+      log.error("Exception while fetching If-Modified-Since: {}", e);
+    }
+
+    long lastModified = handle.getLastModified(request);
+
+    /**
+     * Sicherstellen, dass der Wert keine Millisekunden enthält, da die
+     * Zeitangabe aus dem Modified-Since-Header keine Millisekunden enthalten
+     * kann und der Test unten dann stets fehlschlagen würde!
+     */
+    lastModified = lastModified - (lastModified % 1000);
+
+    String ifNoneMatch = request.getHeader(Headers.HEADER_IF_NONE_MATCH);
+    String eTag = handle.getETag(request);
+
+    /**
+     * 304-Antworten sollen nach dem {@plainlink
+     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 RFC
+     * 2616, Abschnitt 10.3.5} einen ETag-Header enthalten, wenn auch die
+     * 200-Antwort einen enthalten hätte.
+     */
+    if (eTag != null) {
+      StringBuilder builder = new StringBuilder();
+      if (handle.isETagWeak())
+        builder.append("W/");
+      builder.append('"');
+      builder.append(eTag);
+      builder.append('"');
+      response.setHeader(Headers.HEADER_ETAG, builder.toString());
+    }
+
+
+    if (ifModifiedSince >= lastModified && lastModified > 0) {
+      /**
+       * request.getDateHeader liefert die Zeit als long, oder -1, wenn der
+       * Header nicht existiert. D.h., wenn "If-Modified-Since" nicht gesetzt
+       * ist, wird die komplette Seite ausgeliefert.
+       * Der zusätzliche Test, ob lastModified größer 0 ist, ist nötig, um
+       * Fehler auszuschließen, wenn die Implementierung von Cachable
+       * negative Werte für Last-Modified zurückliefert.
+       */
+      if (log.isDebugEnabled())
+        log.debug("{}: Not modified since {}", url, new Date(ifModifiedSince));
+
+      if (ifNoneMatch == null) {
+        /** Neue Anfrage oder HTTP/1.0 Client! */
+        log.debug("{}: ETag nicht gesetzt -> 304", url);
+        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+        return false;
+      }
+    }
+
+    if (ifNoneMatch != null) {
+      boolean weak = false;
+      if (ifNoneMatch.startsWith("W/")) {
+        weak = true;
+        ifNoneMatch = ifNoneMatch.substring(3, ifNoneMatch.length() - 1);
+      }
+      else {
+        ifNoneMatch = ifNoneMatch.substring(1, ifNoneMatch.length() - 1);
+      }
+
+      if (!weak || (request.getMethod().equals("GET") && request.getHeader(Headers.HEADER_RANGE) == null)) {
+        /**
+         * Die Gleichheit gilt nur, wenn die ETag's der Anfrage _und_ der
+         * Antwort stark sind (starke Gleichheit!), oder wenn die Antwort nur
+         * schwache Gleichheit fordert...
+         */
+        if (ifNoneMatch.equals(eTag) && (handle.isETagWeak() || !weak)) {
+          log.debug("{}: ETag {} not changed -> 304 ", url, ifNoneMatch);
+          response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+          return false;
+        }
+      }
+      else {
+        log.warn("{}: ignoring weak ETag W/\"{}\", because the request was no GET-request or the Range-Header was present!", url, ifNoneMatch);
+      }
+    }
+
+
+    log.debug("{}: first up!", url);
+
+    if (handle.isZipped())
+      response.addHeader(Headers.HEADER_CONTENT_ENCODING, "gzip");
+
+    /** HTTP/1.1-Caching-Header richtig setzen!! */
+    response.setDateHeader(Headers.HEADER_LAST_MODIFIED, lastModified);
+
+    /** Cache-Control für HTTP/1.1-Clients generieren */
+    Map<String, String> cacheControl = new TreeMap<String, String>();
+
+    /**
+     * Wenn eins JSESSIONID in der URL enthalten ist, darf die Anfrage nur vom
+     * Browser gecached werden!
+     */
+    if (request.isRequestedSessionIdFromURL()) {
+      cacheControl.put("private", null);
+    }
+    else {
+      /**
+       * Hier muss nicht geprüft werden, ob cacheSeconds > 0 gilt, da in diesem
+       * Fall oben bereits No-Cache-Header generiert und <code>false</code>
+       * zurückgeliefert werden!
+       *
+       * Den Wert als <code>max-age</code> zu den Schlüssel-Wert-Paaren für den
+       * <code>Cache-Control</code>-Header hinzufügen und einen entsprechenden
+       * <code>Expires</code>-Header für HTTP/1.0-Clients setzen.
+       */
+      cacheControl.put("max-age", Integer.toString(cacheSeconds));
+      response.setDateHeader(Headers.HEADER_EXPIRES, (handle.getTimestamp() + (long) cacheSeconds * 1000));
+    }
+
+    /** Dem Handler die Gelegenheit geben, den Cache-Controll-Header anzupassen */
+    handle.cacheControl(request, cacheControl);
+
+
+    if (cacheControl.containsKey("private")) {
+      /**
+       * HTTP/1.0 Caches davon abhalten, die Ressource zu cachen (vgl.: RFC
+       * 2616, {@plainlink
+       * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
+       * Abschnitt 14.9.3} und {@plainlink
+       * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32
+       * Abschnitt 14.32})
+       */
+      response.setDateHeader(Headers.HEADER_EXPIRES, 0l);
+      response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
+    }
+
+    StringBuilder builder = new StringBuilder();
+    for (Entry<String, String> entry : cacheControl.entrySet()) {
+      builder.setLength(0);
+      builder.append(entry.getKey());
+      if (entry.getValue() != null) {
+        builder.append('=');
+        builder.append(entry.getValue());
+      }
+      response.addHeader(Headers.HEADER_CACHE_CONTROL, builder.toString());
+    }
+
+    return true;
+    }
+    finally {
+      /**
+       * Thread-Locale-Variable zurücksetzen, damit
+       * 1.) ein doppelter Aufruf dieser Methode pro Request erkannt werden kann
+       * 2.) der nächste Request nicht mit dem selben Handle weiterarbeitet
+       */
+      CacheControl.tl.set(null);
+    }
+  }
+
+  public void release() {
+    CacheControl.tl.set(null);
+  }
+
+
+  class ReflectionCacheMethodHandle implements CacheMethodHandle {
+
+    private Object handler;
+    private long now = System.currentTimeMillis();
+    private Integer cacheSeconds;
+    private Long lastModified;
+    private String eTag;
+    private Map<String,String> additionalHeaders;
+    private Method acceptsMethod;
+    private Method cacheSecondsMethod;
+    private Method lastModifiedMethod;
+    private Method eTagMethod;
+    private Method cacheControlMethod;
+    private Method additionalHeadersMethod;
+    private boolean isAcceptsMethodDefined;
+    private boolean isCacheSecondsMethodDefined;
+    private boolean isLastModifiedMethodDefined;
+    private boolean isETagMethodDefined;
+    private boolean isCacheControlMethodDefined;
+    private boolean isAdditionalHeadersMethodDefined;
+    private boolean weak;
+    private boolean zipped;
+
+
+    ReflectionCacheMethodHandle(Object handler, boolean zipped) throws NoSuchMethodException {
+
+      this.handler = handler;
+      this.zipped = zipped;
+
+      cacheSeconds = CacheControl.this.defaultCacheSeconds;
+      lastModified = CacheControl.this.defaultLastModified;
+
+      /** Class-Level-Annotations auslesen */
+      for (Annotation annotation : handler.getClass().getAnnotations()) {
+        if (annotation.annotationType().equals(CacheSeconds.class)) {
+          cacheSeconds = ((CacheSeconds)annotation).value();
+          isCacheSecondsMethodDefined = true;
+          continue;
+        }
+        if (annotation.annotationType().equals(LastModified.class)) {
+          lastModified = ((LastModified)annotation).value();
+          if (lastModified < 1) {
+            /**
+             * Ein Last-Modified-Header wurde angefordert, aber es wurde kein
+             * statischer Wert spezifiziert:
+             * globalen statischen Default-Wert benutzen!
+             */
+            lastModified = defaultLastModified;
+          }
+          isLastModifiedMethodDefined = true;
+          continue;
+        }
+        if (annotation.annotationType().equals(ETag.class)) {
+          ETag eTagAnnotation = (ETag)annotation;
+          eTag = eTagAnnotation.value();
+          weak = eTagAnnotation.weak();
+          isETagMethodDefined = true;
+          continue;
+        }
+        if (annotation.annotationType().equals(AdditionalHeaders.class)) {
+          AdditionalHeaders additionalHeadersAnnotation = (AdditionalHeaders)annotation;
+          additionalHeaders = new HashMap<String,String>();
+          for (String header : additionalHeadersAnnotation.value()) {
+            int i = header.indexOf(':');
+            if (i < 0) {
+              log.error("invalid header: [{}]", header);
+            }
+            else {
+              String name = header.substring(0,i).trim();
+              String value = header.substring(i+1,header.length()).trim();
+              additionalHeaders.put(name, value);
+            }
+          }
+          isAdditionalHeadersMethodDefined = true;
+          continue;
+        }
+      }
+
+      /** Method-Level-Annotations auslesen */
+      for (Method method : handler.getClass().getMethods()) {
+        for (Annotation annotation : method.getAnnotations()) {
+          if (annotation.annotationType().equals(Accepts.class)) {
+            if (isAcceptsMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @Accept wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            acceptsMethod = method;
+            isAcceptsMethodDefined = true;
+            continue;
+          }
+          if (annotation.annotationType().equals(CacheSeconds.class)) {
+            if (isCacheSecondsMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @CacheSeconds wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            cacheSecondsMethod = method;
+            isCacheSecondsMethodDefined = true;
+            continue;
+          }
+          if (annotation.annotationType().equals(LastModified.class)) {
+            if (isLastModifiedMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @LastModified wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            lastModifiedMethod = method;
+            isLastModifiedMethodDefined = true;
+            continue;
+          }
+          if (annotation.annotationType().equals(ETag.class)) {
+            if (isETagMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @ETag wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            eTagMethod = method;
+            weak = ((ETag)annotation).weak();
+            isETagMethodDefined = true;
+            continue;
+          }
+          if (annotation.annotationType().equals(de.juplo.accelerator.annotations.CacheControl.class)) {
+            if (isCacheControlMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @CacheControl wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            cacheControlMethod = method;
+            isCacheControlMethodDefined = true;
+            continue;
+          }
+          if (annotation.annotationType().equals(AdditionalHeaders.class)) {
+            if (isAdditionalHeadersMethodDefined)
+              throw new IllegalArgumentException("Die Annotation @AdditionalHeaders wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+            additionalHeadersMethod = method;
+            isAdditionalHeadersMethodDefined = true;
+            continue;
+          }
+        }
+      }
+
+      if (!isAdditionalHeadersMethodDefined)
+        additionalHeaders = new HashMap<String,String>();
+    }
+
+
+    @Override
+    public boolean isZipped() {
+      return zipped;
+    }
+
+    @Override
+    public long getTimestamp() {
+      return now;
+    }
+
+    @Override
+    public int accepts(HttpServletRequest request) throws IllegalArgumentException {
+      if (acceptsMethod == null) {
+        return HttpServletResponse.SC_OK;
+      }
+      else {
+        try {
+          return (Integer)acceptsMethod.invoke(handler, request);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+
+    @Override
+    public int getCacheSeconds(HttpServletRequest request) throws IllegalArgumentException {
+      if (cacheSecondsMethod == null) {
+        return cacheSeconds;
+      }
+      else {
+        try {
+          return (Integer)cacheSecondsMethod.invoke(handler, request);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+
+    @Override
+    public long getLastModified(HttpServletRequest request) throws IllegalArgumentException {
+      if (lastModifiedMethod == null) {
+        return lastModified;
+      }
+      else {
+        try {
+          return (Long)lastModifiedMethod.invoke(handler, request);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+
+    @Override
+    public String getETag(HttpServletRequest request) throws IllegalArgumentException {
+      if (eTagMethod == null) {
+        return eTag;
+      }
+      else {
+        try {
+          return (String)eTagMethod.invoke(handler, request);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+
+    @Override
+    public boolean isETagWeak() {
+      return weak;
+    }
+
+    @Override
+    public void cacheControl(
+        HttpServletRequest request,
+        Map<String, String> cacheControlMap
+        )
+        throws IllegalArgumentException
+    {
+      if (cacheControlMethod != null) {
+        try {
+          cacheControlMethod.invoke(handler, request, cacheControlMap);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+
+    @Override
+    public Map<String,String> getAdditionalHeaders(HttpServletRequest request) throws IllegalArgumentException {
+      if (additionalHeadersMethod == null) {
+        return additionalHeaders;
+      }
+      else {
+        try {
+          return (Map<String,String>)additionalHeadersMethod.invoke(handler, request);
+        }
+        catch (Exception e) {
+          throw new IllegalArgumentException(e);
+        }
+      }
+    }
+  }
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/CacheControlInterceptor.java b/accelerator/src/main/java/de/juplo/accelerator/CacheControlInterceptor.java
new file mode 100644 (file)
index 0000000..10aaee1
--- /dev/null
@@ -0,0 +1,83 @@
+package de.juplo.accelerator;
+
+import de.juplo.accelerator.AcceleratorFilter.AccelerationWrapper;
+import de.juplo.accelerator.annotations.Cacheable;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.ModelAndView;
+
+/**
+ *
+ * @author kai
+ */
+public class CacheControlInterceptor implements HandlerInterceptor {
+  private final static Logger log = LoggerFactory.getLogger(CacheControlInterceptor.class);
+
+
+  private CacheControl cacheControl;
+
+
+  @Override
+  public boolean preHandle(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      Object handler
+      ) throws Exception
+  {
+    Cacheable cacheable = handler.getClass().getAnnotation(Cacheable.class);
+    if (cacheable == null) {
+      /** Der Handler ist nicht mit @Cacheable annotiert: keine Dekorationen anbringen! */
+      return true;
+    }
+
+    AccelerationWrapper wrapper = (AccelerationWrapper)request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER);
+    if (wrapper != null)
+      wrapper.supressGuessing();
+
+    /** CacheControll initialisieren (Handler nach annotierte Methoden scannen etc.) */
+    cacheControl.init(handler, wrapper);
+
+    if (cacheable.eager()) {
+      return cacheControl.decorate(request, response);
+    }
+    else {
+      return true;
+    }
+  }
+
+  @Override
+  public void postHandle(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      Object handler,
+      ModelAndView modelAndView
+      ) throws Exception
+  {
+    /**
+     * Dekoration nur dann anstossen, wenn sie nicht bereits von dem
+     * AcceleratorFilter ausgelöst wird.
+     */
+    if (request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER) == null)
+      cacheControl.decorate(request, response);
+  }
+
+  @Override
+  public void afterCompletion(
+      HttpServletRequest request,
+      HttpServletResponse response,
+      Object handler, Exception ex
+      ) throws Exception
+  {
+    cacheControl.release();
+  }
+
+
+  @Autowired
+  public void setCacheControl(CacheControl cacheControl) {
+    this.cacheControl = cacheControl;
+  }
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/CacheMethodHandle.java b/accelerator/src/main/java/de/juplo/accelerator/CacheMethodHandle.java
new file mode 100644 (file)
index 0000000..96f529b
--- /dev/null
@@ -0,0 +1,20 @@
+package de.juplo.accelerator;
+
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ *
+ * @author kai
+ */
+public interface CacheMethodHandle {
+  boolean isZipped();
+  long getTimestamp();
+  int accepts(HttpServletRequest request);
+  int getCacheSeconds(HttpServletRequest request);
+  long getLastModified(HttpServletRequest request);
+  String getETag(HttpServletRequest request);
+  boolean isETagWeak();
+  void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap);
+  Map<String,String> getAdditionalHeaders(HttpServletRequest request);
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/Headers.java b/accelerator/src/main/java/de/juplo/accelerator/Headers.java
new file mode 100644 (file)
index 0000000..49f19fe
--- /dev/null
@@ -0,0 +1,24 @@
+package de.juplo.accelerator;
+
+/**
+ *
+ * @author kai
+ */
+public abstract class Headers {
+
+  public static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
+
+  public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
+  public static final String HEADER_CACHE_CONTROL = "Cache-Control";
+  public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
+  public static final String HEADER_CONTENT_TYPE = "Content-Type";
+  public static final String HEADER_DATE = "Date";
+  public static final String HEADER_ETAG = "ETag";
+  public static final String HEADER_EXPIRES = "Expires";
+  public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+  public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
+  public static final String HEADER_LAST_MODIFIED = "Last-Modified";
+  public static final String HEADER_PRAGMA = "Pragma";
+  public static final String HEADER_RANGE = "Range";
+
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/Accepts.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/Accepts.java
new file mode 100644 (file)
index 0000000..2f44afc
--- /dev/null
@@ -0,0 +1,30 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mit dieser Methode kann eine Methode annotiert werden, die Auskunft darüber
+ * erteilt, mit welchem HTTP-Status-Code der Handler die Anfrage beatnworten
+ * wird.
+ * <p>
+ * Die Methode muss eine Instanz von {@link HttpServletRequest} als (einziges!)
+ * Argument akzeptieren und einen Wert liefern, der sich nach
+ * <code>int</code> casten lässt.
+ * <p>
+ * Eine mit dieser Annotation markierte Methode wird nur benötigt, wenn die
+ * Caching-Dekoration im Modus <code>eager=true</code> ausgeführt wird. Sie
+ * wird in diesem Fall benötigt, weil die Entscheidungen zur Cache-Dekoration
+ * dann getroffen werden müssen, <em>bevor</em> die verarbeitende Klasse die
+ * Anfrage verarbeitet hat.
+ * Wenn die Cache-Dekoration im Modus <code>eager=true</code> betrieben wird
+ * und keine Methode mit dieser Annotation annotiert ist, geht {@link CacheControl}
+ * davon aus, dass die verarbeitende Klasse alle Anfragen annimmt.
+ *
+ * @author kai
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Accepts {}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/AdditionalHeaders.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/AdditionalHeaders.java
new file mode 100644 (file)
index 0000000..2f37920
--- /dev/null
@@ -0,0 +1,17 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author kai
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface AdditionalHeaders {
+
+  String[] value() default {};
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheControl.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheControl.java
new file mode 100644 (file)
index 0000000..6f7705d
--- /dev/null
@@ -0,0 +1,30 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mit dieser Annotation kann eine Methode markiert werden, die die von Juplo-
+ * CacheControl für den Header <code>Cache-Control</code> generierten
+ * Schlüssel/Wert-Kombinationen manipulieren oder ergänzen kann, bevor der
+ * Header an den Client ausgeliefert wird (s. {@plainlink
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3 RFC2616, Abschnitt 14.9.3}).
+ * <p>
+ * Die Methode muss zwei Parameter akzeptieren.
+ * Als ersten Parameter eine Instanz von {@link HttpServletRequest}.
+ * Als zweiten Parameter eine <code>Map<String,String></code>, die die von
+ * Juplo-CacheControl erzeugten Schlüssel/Wert-Paare enthält.
+ * <p>
+ * Diese Methode liefert eine Map mit Schlüssel-Wert-Paaren für den
+ * HTTP/1.1-Header <code>Cache-Control</code> (s. {@plainlink
+ * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3 RFC2616,
+ * Abschnitt 14.9.3}).
+ *
+ * @author kai
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface CacheControl {
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheSeconds.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheSeconds.java
new file mode 100644 (file)
index 0000000..0403646
--- /dev/null
@@ -0,0 +1,52 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
+ * <p>
+ * Wenn eine Methode markiert wird, muss diese eine Instanz von
+ * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
+ * Wert liefern, der sich nach <code>int</code> casten lässt.
+ * Die annotierte Methode ermöglicht eine einfache, zentrale aber Request-
+ * Abhängige Steuerung des Caching-Verhaltens.
+ * <p>
+ * Wenn eine Klasse annotiert wird, muss der Anotation die dann statisch für
+ * alle von der Klasse erzeugten Antworten gültige Cache-Zeit als Argument
+ * übergeben werden.
+ * Wird keine Cache-Zeit spezifiziert, wird der Wert <code>86400</code>
+ * (ein Tag) verwendet.
+ * <ul>
+ * <li>Wenn negativer Wert als Cache-Seconds festgelet wird, werden Cache-Header
+ * erzeugt, die das Cachen der Antwort für HTTP/1.0 und HTTP/1.1 vollständig
+ * untersagen.</li>
+ * <li>Wenn einen Wert größer oder gleich <code>0</code> festgelegt wird, wird
+ * für HTTP/1.0-Clients ein <code>Expires</code>-Header generiert und für
+ * HTTP/1.1-Clients ein <code>Cache-Control</code>-Header mit einem
+ * entsprechenden <code>max-age</code>-Eintrag. Dies reicht in Kombination mit
+ * der Annotation {@link LastModified} vollständig für ein einfaches aber
+ * effektives Caching aus.</li>
+ * </ul>
+ * <p>
+ * TODO
+ * <strong>Zu beachten:</strong> Wenn die Methode
+ * {@link #getCacheControl(javax.servlet.http.HttpServletRequest)} weitere
+ * Schlüssel-Wert-Paare für den <code>Cache-Control</code>-Header liefert,
+ * werden diese ergänzt. Wenn in der Rückgabe ein Wert für
+ * <code>max-age</code> enthalten ist, wir er allerdings von dem durch diese
+ * Methode vorgegebenen Wert überschrieben!
+ *
+ * @author kai
+ * @See Cacheable
+ * @See LastModified
+ * @See CacheControl
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface CacheSeconds {
+  int value() default 86400; /** Default: 1 Tag */
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/Cacheable.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/Cacheable.java
new file mode 100644 (file)
index 0000000..a5960fc
--- /dev/null
@@ -0,0 +1,45 @@
+package de.juplo.accelerator.annotations;
+
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+
+/**
+ * Marker-Annotation für Handler (i.A. eine Impelementierung von
+ * {@link Controller}), deren Antworten vom {@link CachingInterceptor} mit
+ *  HTTP/1.1-Caching-Header nach
+ * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html RFC 2616}
+ * dekoriert werden sollen.
+ * <p>
+ * Wenn der Parameter <code>eager</code> auf <code>true</code> gesetzt wird,
+ * ermittelt der {@link CachingInterceptor} die Cache-Parameter über die
+ * annotierten Methoden vorab.
+ * <strong>Achtung:</strong>
+ * Dies bedeutet, dass die annotierten Methoden aufgerufen werden <em>bevor</em>
+ * die eigentliche Verarbeitungs-Routine der markierten Klasse aufgerufen wird!
+ * Wenn sich dabei ergiebt, dass die Antwort nicht erneut ausgeliefert werden
+ * muss, wird die eigentliche Verarbeitungs-Routine <em>gar nicht aufgerufen</em>.
+ * <p>
+ * Wenn der Parameter <code>eager</code> nicht gesetzt ist (oder explizit auf
+ * <code>false</code> gesetzt wurde), kapselt der {@link CachingInterceptor}
+ * den Request und den Ausgabestrom für den Response-Body und trifft die
+ * Entscheidung über die zu ergänzenden Header, wenn der Status des
+ * {@link HttpServletResponse} gesetzt oder mit dem Schreiben des Response-Body
+ * begonnen wird.
+ *
+ * @see CacheControl
+ * @see Accepts
+ * @see CacheSeconds
+ * @see LastModified
+ * @see ETag
+ * @see CacheControl
+ * @author kai
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Cacheable {
+  boolean eager() default false;
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/ETag.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/ETag.java
new file mode 100644 (file)
index 0000000..81b6d16
--- /dev/null
@@ -0,0 +1,84 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Über diese Annotation kann der Inhalt des <code>ETag/code>-Headers
+ * gesteuert werden.
+ * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
+ * <p>
+ * Wenn eine Methode annotiert wird, muss diese eine Instanz von
+ * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
+ * <code>String</code> liefern.
+ * <p>
+ * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den
+ * <code>ETag</code>-Header übergeben werden.
+ * Da dieser Wert somit statisch ist, macht es nur Sinn, Klassen mit dieser
+ * Annotation zu markieren, die ausschließlich statische Ressourcen ausliefern,
+ * die sich nur mit der Neuinstallation der Webanwendung ändern.
+ * Wenn sich (z.B. nach einer Neuinstallation der Webanwendung) die statischen
+ * Ressourcen geändert haben, muss der übergebene statische ETag geändert
+ * werden, da Caches sonst weiterhin die alten Ressourcen ausliefern!
+ * </p>
+ * Frei wählbares ETag nach
+ * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 RFC 2616, Abschnitt 14.19 ETag}.
+ * Der gelieferte Wert darf die vom RFC geforderten Anführungszeichen noch nicht
+ * enthalten, da er, wenn <code>vary</code> gesetzt ist, noch um je nach
+ * erfolgter Content-Negotiation varriierende Teile ergänzt wird.
+ * <p>
+ * Die erzeugten <code>ETag</code>'s können über die Annotations-Parameter
+ * <code>weak</code> und <code>vary</code> weiter gesteuert werden.
+ * <ul>
+ * <li>
+ * Wenn der Parameter <strong>weak</strong> auf den wert <code>true</code>
+ * gesetzt wird, wird ein schwaches <code>ETag</code> erezeugt und der
+ * Vergleichs-Algorithmus verhält sich entsprechend anders (siehe:
+ * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3 RFC 2616, Abschnitt 13.3.3 Weak and Strong Validators}).
+ * </li>
+ * <li>
+ * Über den Parameter <strong>vary</strong> kann Juplo-CacheControl damit
+ * beauftragt werden, die Nötigen Maßnahmen für korrektes Content-Negotiating
+ * zu ergreifen (siehe:
+ * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6 RFC 2616, Abschnitt 13.6 Caching Negotiated Responses}).
+ * Als Eingabe werden die Header-Namen erwertet, die zu unterschiedlichen
+ * Ergebnissen der Content-Negotiation führen können (hier können folgende
+ * Header angegeben werden: <code>Accept</code>, <code>Accept-Charset</code>,
+ * <code>Accept-Encoding</code> und <code>Accept-Language</code>).
+ * Juplo-CacheControl modifizert den übergebenen <code>ETag</code> dann so,
+ * dass unterschiedliche Resultate der Content-Negotiation unterschieden
+ * werden können.
+ * Außerdem wird der <code>Vary</code>-Header entsprechend gesetzt.
+ *</li>
+ * </ul>
+ * <strong>Zu beachten:</strong>
+ * Wenn zugleich die Annotation {@link CacheSeconds} verwendet wird, wird
+ * die mit dieser Annotation markierte Methode nur aufgerufen, wenn die mit
+ * der Annotation {@link CacheSeconds} markierte Methode einen Wert größer
+ * oder gleich <code>0</code> liefert, bzw. für die mit Annotation
+ * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich
+ * <code>0</code> festgelegt wurde.
+ *
+ * @see #getCacheSeconds(javax.servlet.http.HttpServletRequest)
+ *
+ * @author kai
+ * @see Cacheable
+ * @see CacheSeconds
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface ETag {
+
+  public final static String ACCEPT = "Accept";
+  public final static String ACCEPT_CHARSET = "Accept-Charset";
+  public final static String ACCEPT_ENCODING = "Accept-Encoding";
+  public final static String ACCEPT_LANGUAGE = "Accept-Language";
+
+
+  String value() default "X";
+  boolean weak() default false;
+  String[] vary() default {};
+}
diff --git a/accelerator/src/main/java/de/juplo/accelerator/annotations/LastModified.java b/accelerator/src/main/java/de/juplo/accelerator/annotations/LastModified.java
new file mode 100644 (file)
index 0000000..c5e86a3
--- /dev/null
@@ -0,0 +1,53 @@
+package de.juplo.accelerator.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Über diese Annotation kann der Inhalt des <code>Last-Modified</code>-Headers
+ * gesteuert werden.
+ * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
+ * <p>
+ * Wenn eine Methode annotiert wird, muss diese eine Instanz von
+ * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
+ * Wert liefern, der sich nach <code>long</code> casten lässt.
+ * Die Signatur der Methode entspricht der Methode
+ * {@link HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)}
+ * aus dem <code>HttpServlet</code>-Interface.
+ * Um das Cache-Verhalten ein existierendes Servlet, das diese Methode bereits
+ * implementiert, mit Juplo-CacheControll zu verbessern, kann als erste
+ * Maßnahme daher einfach diese Methode mit dieser Annotation markiert werden.
+ * <p>
+ * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den
+ * <code>Last-Modified</code>-Header übergeben werden.
+ * Da dieser Wert somit statisch ist, macht es nur Sinn, Klassen mit dieser
+ * Annotation zu markieren, die ausschließlich statische Ressourcen ausliefern,
+ * die sich nur mit der Neuinstallation der Webanwendung ändern.
+ * </p>
+ * Über diese Annotation wird der Zeitpunkt gesteuert, zu dem die gelieferte
+ * Ressource zuletzt verändert wurde.
+ * Erwartet wird eine Zeitangabe in Millisekunden seit dem Unix-0-Zeitpunkt,
+ * die dann an {@link HttpServletResponse#setDateHeader(String, long)}
+ * weitergegeben wird.
+ * <p>
+ * <strong>Zu beachten:</strong>
+ * Wenn zugleich die Annotation {@link CacheSeconds} verwendet wird, wird
+ * die mit dieser Annotation markierte Methode nur aufgerufen, wenn die mit
+ * der Annotation {@link CacheSeconds} markierte Methode einen Wert größer
+ * oder gleich <code>0</code> liefert, bzw. für die mit Annotation
+ * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich
+ * <code>0</code> festgelegt wurde.
+ *
+ * @author kai
+ * @see Cacheable
+ * @see CacheSeconds
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface LastModified {
+  long value() default 0;
+}
diff --git a/accelerator/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java b/accelerator/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java
new file mode 100644 (file)
index 0000000..285d173
--- /dev/null
@@ -0,0 +1,621 @@
+package com.meterware.servletunit;
+/********************************************************************************************************************
+* $Id: ServletUnitHttpResponse.java 751 2006-03-24 19:59:12Z russgold $
+*
+* Copyright (c) 2000-2004,2006, Russell Gold
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions
+* of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+*******************************************************************************************************************/
+import com.meterware.httpunit.HttpUnitUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import java.util.*;
+import java.text.SimpleDateFormat;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+class ServletUnitHttpResponse implements HttpServletResponse {
+
+    // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
+    private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy HH:mm:ss z";
+    private boolean _committed;
+    private Locale _locale = Locale.getDefault();
+
+    private static final Hashtable ENCODING_MAP = new Hashtable();
+
+    /**
+     * @deprecated Use encodeURL(String url)
+     */
+    public String encodeUrl( String url ) {
+        return encodeURL( url );
+    }
+
+
+    /**
+     * Adds the specified cookie to the response.  It can be called
+     * multiple times to set more than one cookie.
+     */
+    public void addCookie( Cookie cookie ) {
+        _cookies.addElement( cookie );
+    }
+
+
+    /**
+     * Checks whether the response message header has a field with
+     * the specified name.
+     */
+    public boolean containsHeader( String name ) {
+        return _headers.containsKey( name.toUpperCase() );
+    }
+
+
+    /**
+     * @deprecated Use encodeRedirectURL(String url)
+     **/
+    public String encodeRedirectUrl( String url ) {
+        return encodeRedirectURL( url );
+    }
+
+
+    /**
+     * Encodes the specified URL by including the session ID in it,
+     * or, if encoding is not needed, returns the URL unchanged.
+     * The implementation of this method should include the logic to
+     * determine whether the session ID needs to be encoded in the URL.
+     * For example, if the browser supports cookies, or session
+     * tracking is turned off, URL encoding is unnecessary.
+     **/
+    public String encodeURL( String url ) {
+        return url;
+    }
+
+
+    /**
+     * Encodes the specified URL for use in the
+     * <code>sendRedirect</code> method or, if encoding is not needed,
+     * returns the URL unchanged.  The implementation of this method
+     * should include the logic to determine whether the session ID
+     * needs to be encoded in the URL.  Because the rules for making
+     * this determination differ from those used to decide whether to
+     * encode a normal link, this method is seperate from the
+     * <code>encodeUrl</code> method.
+     **/
+    public String encodeRedirectURL( String url ) {
+        return url;
+    }
+
+
+    /**
+     * Sends a temporary redirect response to the client using the
+     * specified redirect location URL.  The URL must be absolute (for
+     * example, <code><em>https://hostname/path/file.html</em></code>).
+     * Relative URLs are not permitted here.
+     */
+    public void sendRedirect( String location ) throws IOException {
+        setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY );
+        setHeader( "Location", location );
+    }
+
+
+    /**
+     * Sends an error response to the client using the specified status
+     * code and descriptive message.  If setStatus has previously been
+     * called, it is reset to the error status code.  The message is
+     * sent as the body of an HTML page, which is returned to the user
+     * to describe the problem.  The page is sent with a default HTML
+     * header; the message is enclosed in simple body tags
+     * (&lt;body&gt;&lt;/body&gt;).
+     **/
+    public void sendError( int sc ) throws IOException {
+        sendError( sc, "" );
+    }
+
+
+    /**
+     * Sends an error response to the client using the specified status
+     * code and descriptive message.  If setStatus has previously been
+     * called, it is reset to the error status code.  The message is
+     * sent as the body of an HTML page, which is returned to the user
+     * to describe the problem.  The page is sent with a default HTML
+     * header; the message is enclosed in simple body tags
+     * (&lt;body&gt;&lt;/body&gt;).
+     **/
+    public void sendError(int sc, String msg) throws IOException {
+        setStatus( sc );
+        _statusMessage = msg;
+
+        _writer = null;
+        _servletStream = null;
+
+        setContentType( "text/html" );
+        getWriter().println( "<html><head><title>" + msg + "</title></head><body>" + msg + "</body></html>" );
+    }
+
+
+    /**
+     * Sets the status code for this response.  This method is used to
+     * set the return status code when there is no error (for example,
+     * for the status codes SC_OK or SC_MOVED_TEMPORARILY).  If there
+     * is an error, the <code>sendError</code> method should be used
+     * instead.
+     **/
+    public void setStatus( int sc ) {
+        _status = sc;
+    }
+
+
+    /**
+     * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
+     * To set a status code use setStatus(int), to send an error with a description
+     * use sendError(int, String). Sets the status code and message for this response.
+     **/
+    public void setStatus( int sc, String msg ) {
+        setStatus( sc );
+    }
+
+
+    /**
+     * Adds a field to the response header with the given name and value.
+     * If the field had already been set, the new value overwrites the
+     * previous one.  The <code>containsHeader</code> method can be
+     * used to test for the presence of a header before setting its
+     * value.
+     **/
+    public void setHeader( String name, String value ) {
+        ArrayList values = new ArrayList();
+        values.add( value );
+        synchronized (_headers) {
+            _headers.put( name.toUpperCase(), values );
+        }
+    }
+
+
+    /**
+     * Adds a field to the response header with the given name and
+     * integer value.  If the field had already been set, the new value
+     * overwrites the previous one.  The <code>containsHeader</code>
+     * method can be used to test for the presence of a header before
+     * setting its value.
+     **/
+    public void setIntHeader( String name, int value ) {
+        setHeader( name, asHeaderValue( value ) );
+    }
+
+
+    private String asHeaderValue( int value ) {
+        return Integer.toString( value );
+    }
+
+
+    /**
+     * Adds a field to the response header with the given name and
+     * date-valued field.  The date is specified in terms of
+     * milliseconds since the epoch.  If the date field had already
+     * been set, the new value overwrites the previous one.  The
+     * <code>containsHeader</code> method can be used to test for the
+     * presence of a header before setting its value.
+     **/
+    public void setDateHeader( String name, long date ) {
+        setHeader( name, asDateHeaderValue( date ) );
+    }
+
+
+    private String asDateHeaderValue( long date ) {
+        Date value = new Date( date );
+        SimpleDateFormat formatter = new SimpleDateFormat( RFC1123_DATE_SPEC, Locale.US );
+        formatter.setTimeZone( TimeZone.getTimeZone( "Greenwich Mean Time" ) );
+        return formatter.format( value );
+    }
+
+
+    /**
+     * Returns the name of the character set encoding used for
+     * the MIME body sent by this response.
+     **/
+    public String getCharacterEncoding() {
+        return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET : _encoding;
+    }
+
+
+    /**
+     * Sets the content type of the response the server sends to
+     * the client. The content type may include the type of character
+     * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
+     *
+     * <p>You can only use this method once, and you should call it
+     * before you obtain a <code>PrintWriter</code> or
+     * {@link ServletOutputStream} object to return a response.
+     **/
+    public void setContentType( String type ) {
+        String[] typeAndEncoding = HttpUnitUtils.parseContentTypeHeader( type );
+
+        _contentType = typeAndEncoding[0];
+        if (typeAndEncoding[1] != null) _encoding = typeAndEncoding[1];
+    }
+
+
+    /**
+     * Returns a {@link ServletOutputStream} suitable for writing binary
+     * data in the response. The servlet engine does not encode the
+     * binary data.
+     *
+     * @exception IllegalStateException if you have already called the <code>getWriter</code> method
+     **/
+    public ServletOutputStream getOutputStream() throws IOException {
+        if (_writer != null) throw new IllegalStateException( "Tried to create output stream; writer already exists" );
+        if (_servletStream == null) {
+            _outputStream = new ByteArrayOutputStream();
+            _servletStream = new ServletUnitOutputStream( _outputStream );
+        }
+        return _servletStream;
+    }
+
+
+    /**
+     * Returns a <code>PrintWriter</code> object that you
+     * can use to send character text to the client.
+     * The character encoding used is the one specified
+     * in the <code>charset=</code> property of the
+     * {@link #setContentType} method, which you must call
+     * <i>before</i> you call this method.
+     *
+     * <p>If necessary, the MIME type of the response is
+     * modified to reflect the character encoding used.
+     *
+     * <p> You cannot use this method if you have already
+     * called {@link #getOutputStream} for this
+     * <code>ServletResponse</code> object.
+     *
+     * @exception UnsupportedEncodingException  if the character encoding specified in
+     *                                         <code>setContentType</code> cannot be
+     *                                         used
+     *
+     * @exception IllegalStateException        if the <code>getOutputStream</code>
+     *                                                 method has already been called for this
+     *                                         response object; in that case, you can't
+     *                                         use this method
+     *
+     **/
+    public PrintWriter getWriter() throws UnsupportedEncodingException {
+        if (_servletStream != null) throw new IllegalStateException( "Tried to create writer; output stream already exists" );
+        if (_writer == null) {
+            _outputStream = new ByteArrayOutputStream();
+            _writer = new PrintWriter( new OutputStreamWriter( _outputStream, getCharacterEncoding() ) );
+        }
+        return _writer;
+    }
+
+
+    /**
+     * Sets the length of the content the server returns
+     * to the client. In HTTP servlets, this method sets the
+     * HTTP Content-Length header.
+     **/
+    public void setContentLength( int len ) {
+        setIntHeader( "Content-Length", len );
+    }
+
+
+//------------------------------- the following methods are new in JSDK 2.2 ----------------------
+
+
+    /**
+     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+     **/
+    public void addHeader( String name, String value ) {
+        synchronized (_headers) {
+            String key = name.toUpperCase();
+            ArrayList values = (ArrayList) _headers.get( key );
+            if (values == null) {
+                values = new ArrayList();
+                _headers.put( key, values );
+            }
+            values.add( value );
+        }
+    }
+
+
+    /**
+     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+     **/
+    public void addIntHeader( String name, int value ) {
+        addHeader( name, asHeaderValue( value ) );
+    }
+
+
+    /**
+     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+     **/
+    public void addDateHeader( String name, long value ) {
+        addHeader( name, asDateHeaderValue( value ) );
+    }
+
+
+    /**
+     * Sets the preferred buffer size for the body of the response. The servlet container
+     * will use a buffer at least as large as the size requested. The actual buffer size
+     * used can be found using getBufferSize.
+     **/
+    public void setBufferSize( int size ) {
+        if (getContents().length != 0) throw new IllegalStateException( "May not set buffer size after data is written" );
+    }
+
+
+    /**
+     * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
+     **/
+    public int getBufferSize() {
+        return 0;
+    }
+
+
+    /**
+     * Returns a boolean indicating if the response has been committed. A committed response has
+     * already had its status code and headers written.
+     **/
+    public boolean isCommitted() {
+        return _committed;
+    }
+
+
+    /**
+     * Forces any content in the buffer to be written to the client. A call to this method automatically
+     * commits the response, meaning the status code and headers will be written.
+     **/
+    public void flushBuffer() throws IOException {
+        _committed = true;
+    }
+
+
+    /**
+     * Clears any data that exists in the buffer as well as the status code and headers.
+     * If the response has been committed, this method throws an IllegalStateException.
+     **/
+    public void reset() {
+        resetBuffer();
+        _headers.clear();
+        _headersComplete = false;
+        _status = SC_OK;
+    }
+
+
+    /**
+     * Sets the locale of the response, setting the headers (including the Content-Type's charset)
+     * as appropriate. This method should be called before a call to getWriter().
+     * By default, the response locale is the default locale for the server.
+     **/
+    public void setLocale( Locale locale ) {
+        _locale = locale;
+        if (_encoding == null) {
+            for (Iterator it = ENCODING_MAP.entrySet().iterator(); it.hasNext();) {
+                Map.Entry entry = (Map.Entry) it.next();
+                String locales = (String) entry.getValue();
+                if (locales.indexOf( locale.getLanguage() ) >= 0 || locales.indexOf( locale.toString() ) >= 0) {
+                    _encoding = (String) entry.getKey();
+                    return;
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Returns the locale assigned to the response.
+     **/
+    public Locale getLocale() {
+        return _locale;
+    }
+
+
+//----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
+
+
+    /**
+     * Clears the content of the underlying buffer in the response without clearing headers or status code.
+     * If the response has been committed, this method throws an IllegalStateException.
+     *
+     * @since 1.3
+     */
+    public void resetBuffer() {
+        if (_committed) throw new IllegalStateException( "May not resetBuffer after response is committed" );
+        _outputStream = null;
+        _servletStream = null;
+        _writer = null;
+    }
+
+
+//---------------------------------------------- package methods --------------------------------------------------
+
+    /**
+     * Returns the contents of this response.
+     **/
+    byte[] getContents() {
+        if (_outputStream == null) {
+            return new byte[0];
+        } else {
+            if (_writer != null) _writer.flush();
+            return _outputStream.toByteArray();
+        }
+    }
+
+
+    /**
+     * Returns the status of this response.
+     **/
+    int getStatus() {
+        return _status;
+    }
+
+
+    /**
+     * Returns the message associated with this response's status.
+     **/
+    String getMessage() {
+        return _statusMessage;
+    }
+
+
+    public String[] getHeaderFieldNames() {
+        if (!_headersComplete) completeHeaders();
+        Vector names = new Vector();
+        for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
+            names.addElement( e.nextElement() );
+        }
+        String[] result = new String[ names.size() ];
+        names.copyInto( result );
+        return result;
+    }
+
+
+    /**
+     * Returns the headers defined for this response.
+     **/
+    String getHeaderField( String name ) {
+        if (!_headersComplete) completeHeaders();
+
+        ArrayList values;
+        synchronized (_headers) {
+            values = (ArrayList) _headers.get( name.toUpperCase() );
+        }
+
+        return values == null ? null : (String) values.get( 0 );
+     }
+
+
+     /**
+     * Return an array of all the header values associated with the
+     * specified header name, or an zero-length array if there are no such
+     * header values.
+     *
+     * @param name Header name to look up
+     */
+    public String[] getHeaderFields(String name) {
+        if (!_headersComplete) completeHeaders();
+        ArrayList values;
+        synchronized (_headers) {
+            values = (ArrayList) _headers.get(name.toUpperCase());
+        }
+        if (values == null)
+            return (new String[0]);
+        String results[] = new String[values.size()];
+        return ((String[]) values.toArray(results));
+
+    }
+
+//--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
+
+    public void setCharacterEncoding(String string) {
+        _encoding = string;
+    }
+
+    /**
+     * Returns the content type defined for this response.
+     **/
+    public String getContentType() {
+        return _contentType;
+    }
+
+
+//------------------------------------------- private members ------------------------------------
+
+
+    private String _contentType = "text/plain";
+
+    private String _encoding;
+
+    private PrintWriter  _writer;
+
+    private ServletOutputStream _servletStream;
+
+    private ByteArrayOutputStream _outputStream;
+
+    private int _status = SC_OK;
+
+    private String _statusMessage = "OK";
+
+    private final Hashtable _headers = new Hashtable();
+
+    private boolean _headersComplete;
+
+    private Vector  _cookies = new Vector();
+
+
+    private void completeHeaders() {
+        if (_headersComplete) return;
+        addCookieHeader();
+        setHeader( "Content-Type", _contentType + "; charset=" + getCharacterEncoding() );
+        _headersComplete = true;
+    }
+
+
+    private void addCookieHeader() {
+        if (_cookies.isEmpty()) return;
+
+        StringBuffer sb = new StringBuffer();
+        for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
+            Cookie cookie = (Cookie) e.nextElement();
+            sb.append( cookie.getName() ).append( '=' ).append( cookie.getValue() );
+            if (cookie.getPath() != null) sb.append( ";path=" ).append( cookie.getPath() );
+            if (cookie.getDomain() != null) sb.append( ";domain=" ).append( cookie.getDomain() );
+            if (e.hasMoreElements()) sb.append( ',' );
+        }
+        setHeader( "Set-Cookie", sb.toString() );
+    }
+
+
+
+    static {
+        ENCODING_MAP.put( "iso-8859-1", "ca da de en es fi fr is it nl no pt sv " );
+        ENCODING_MAP.put( "iso-8859-2", "cs hr hu pl ro sh sk sl sq " );
+        ENCODING_MAP.put( "iso-8859-4", "et lt lv ");
+        ENCODING_MAP.put( "iso-8859-5", "be bg mk ru sr uk " );
+        ENCODING_MAP.put( "iso-8859-6", "ar " );
+        ENCODING_MAP.put( "iso-8859-7", "el " );
+        ENCODING_MAP.put( "iso-8859-8", "iw he " );
+        ENCODING_MAP.put( "iso-8859-9", "tr " );
+
+        ENCODING_MAP.put("Shift_JIS", "ja ");
+        ENCODING_MAP.put("EUC-KR",    "ko ");
+        ENCODING_MAP.put("TIS-620",   "th ");
+        ENCODING_MAP.put("GB2312",    "zh " );
+        ENCODING_MAP.put("Big5",      "zh_TW zh_HK " );
+    }
+
+}
+
+
+
+class ServletUnitOutputStream extends ServletOutputStream {
+
+    ServletUnitOutputStream( ByteArrayOutputStream stream ) {
+        _stream = stream;
+    }
+
+
+    public void write( int aByte ) throws IOException {
+        _stream.write( aByte );
+    }
+
+    private ByteArrayOutputStream _stream;
+}
diff --git a/accelerator/src/test/java/de/juplo/accelerator/ParameterGuessingTest.java b/accelerator/src/test/java/de/juplo/accelerator/ParameterGuessingTest.java
new file mode 100644 (file)
index 0000000..342a8f2
--- /dev/null
@@ -0,0 +1,575 @@
+package de.juplo.accelerator;
+
+import com.meterware.httpunit.WebResponse;
+import de.juplo.testingtools.HttpTestCase;
+import java.net.URLEncoder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import org.junit.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {
+  "classpath:/config.xml"
+})
+public class ParameterGuessingTest extends HttpTestCase {
+  private final static Logger log = LoggerFactory.getLogger(ParameterGuessingTest.class);
+
+
+  public ParameterGuessingTest() {
+    super("src/test/resources/web.xml");
+  }
+
+
+  @Test
+  public void testNothingSet() throws Exception {
+
+    log.info("-------- Test: Servlet does not implement getLastModified() and sets no Headers...");
+
+    WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16");
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+  }
+
+  @Test
+  public void testSetUnfilteredHeaders() throws Exception {
+
+    log.info("-------- Test: Servlet sets unfiltered Headers...");
+
+    WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16&X-Debug=bla&Age=34&Content-Language=de");
+    Assert.assertEquals("bla", response.getHeaderField("X-Debug"));
+    Assert.assertEquals("34", response.getHeaderField("Age"));
+    Assert.assertEquals("de", response.getHeaderField("Content-Language"));
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+  }
+
+  @Test
+  public void testETagSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header \"ETag\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    WebResponse response;
+    long date, expires;
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bla\"", "UTF-8"));
+    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bÄl\"a\"", "UTF-8"));
+    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("bla", "UTF-8"));
+    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("bÄl\"a", "UTF-8"));
+    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/\"blub\"", "UTF-8"));
+    Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/\"bÄl\"ub\"", "UTF-8"));
+    Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/blub", "UTF-8"));
+    Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/bÄl\"ub", "UTF-8"));
+    Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+  }
+
+  @Test
+  public void testLastModifiedImplemented() throws Exception {
+
+    log.info("-------- Test: Servlet implements getLastModified()");
+
+    WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16&l=1324162929861");
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Sat, 17 Dec 2011 23:02:09 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+    long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+  }
+
+  @Test
+  public void testCacheControlSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header \"Cache-Control\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    StringBuilder uri;
+    WebResponse response;
+    Date date;
+    long expires;
+    Set<String> params;
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    /** max-age=120 */
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("max-age=120", "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=120", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+    expires = (date.getTime()/1000l + 120l) * 1000l;
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** max-age=120, s-max-age=60, private, must-revalidate  */
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("max-age=120, s-max-age=60, must-revalidate", "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    params = new HashSet<String>();
+    for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+      for (String part : param.split(","))
+        params.add(part.trim());
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=120\" nicht!", params.contains("max-age=120"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"s-max-age=60\" nicht!", params.contains("s-max-age=60"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+    expires = (date.getTime()/1000l + 120l) * 1000l;
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** max-age=120, s-max-age=60, private, must-revalidate, BUT: several other values are set before  */
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("no-store", "UTF-8"));
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("max-age=360, s-max-age=600, private", "UTF-8"));
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("public", "UTF-8"));
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("max-age=120, s-max-age=60, must-revalidate", "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    params = new HashSet<String>();
+    for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+      for (String part : param.split(","))
+        params.add(part.trim());
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=120\" nicht!", params.contains("max-age=120"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"s-max-age=60\" nicht!", params.contains("s-max-age=60"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+    expires = (date.getTime()/1000l + 120l) * 1000l;
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+  }
+
+  @Test
+  public void testDateSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header \"Date\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    StringBuilder uri;
+    WebResponse response;
+    Date date, expires;
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    /** Date ca NOW -1m */
+    calendar.add(Calendar.MINUTE, -1);
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** Date ca NOW -1m, BUT: is set to some garbage values before */
+    calendar.add(Calendar.MINUTE, -1);
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    calendar.add(Calendar.MINUTE, 10);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Date=");
+    calendar.add(Calendar.HOUR, -2);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Date=");
+    calendar.add(Calendar.DATE, 1);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+  }
+
+  @Test
+  public void testExpiresSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header \"Expires\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    StringBuilder uri;
+    WebResponse response;
+    Date date, expires;
+    long age;
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    /** Expires ca. NOW + 10m */
+    calendar.add(Calendar.MINUTE, 10);
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+    age = (expires.getTime() - date.getTime())/1000l;
+    Assert.assertEquals("max-age=" + age, response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** Expires ca. NOW + 10m, BUT: is set to some garbage values before */
+    calendar.add(Calendar.MINUTE, 10);
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Expires=");
+    calendar.add(Calendar.MINUTE, 10);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Expires=");
+    calendar.add(Calendar.HOUR, -2);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Expires=");
+    calendar.add(Calendar.DATE, 1);
+    uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+    age = (expires.getTime() - date.getTime())/1000l;
+    Assert.assertEquals("max-age=" + age, response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+  }
+
+  @Test
+  public void testDateAndExpiresSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header's \"Date\" and \"Expires\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    StringBuilder uri;
+    WebResponse response;
+    Date date, expires, garbage;
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    /** Expires = Date + 30m */
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 30);
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertEquals("max-age=1800", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** Expires = Date + 30m, BUT: Date is set to Date - 2h first and Expires to Date */
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 30);
+    expires = calendar.getTime();
+    calendar.add(Calendar.HOUR, -2);
+    garbage = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(garbage), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertEquals("max-age=1800", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** Expires = Date - 1h --> will be ignored! */
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, -60);
+    garbage = calendar.getTime();
+    calendar.setTime(date);
+    calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(garbage), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+  }
+
+  @Test
+  public void testCacheControlDateAndExpiresSet() throws Exception {
+
+    log.info("-------- Test: Servlet sets Header's \"Cache-Control\", \"Date\" and \"Expires\"");
+
+    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+    StringBuilder uri;
+    WebResponse response;
+    Date date, expires, expected;
+    Set<String> params;
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.MILLISECOND, 0);
+
+    /** Expires = Date + 30m, Cache-Control: must-revalidate, no-store */
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 30);
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("must-revalidate, no-store", "UTF-8"));
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    params = new HashSet<String>();
+    for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+      for (String part : param.split(","))
+        params.add(part.trim());
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=1800\" nicht!", params.contains("max-age=1800"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+    Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"no-store\" nicht!", params.contains("no-store"));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+    /** Expires = Date + 30m, BUT: max-age is set to 600s */
+    date = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 10);
+    expected = calendar.getTime();
+    calendar.add(Calendar.MINUTE, 20);
+    expires = calendar.getTime();
+    uri = new StringBuilder();
+    uri.append("http://localhost/parameter-guessing");
+    uri.append("?n=16");
+    uri.append("&Date=");
+    uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+    uri.append("&Expires=");
+    uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+    uri.append("&Cache-Control=");
+    uri.append(URLEncoder.encode("max-age=600", "UTF-8"));
+    response = executeRequest(uri.toString());
+    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+    Assert.assertEquals("max-age=600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+    Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+    Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expected.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+  }
+}
diff --git a/accelerator/src/test/java/de/juplo/accelerator/RequestSizeTest.java b/accelerator/src/test/java/de/juplo/accelerator/RequestSizeTest.java
new file mode 100644 (file)
index 0000000..6b94d2b
--- /dev/null
@@ -0,0 +1,237 @@
+package de.juplo.accelerator;
+
+import com.meterware.httpunit.WebResponse;
+import de.juplo.testingtools.HttpTestCase;
+import de.juplo.testingtools.LoggingHttpServletResponseWrapper;
+import java.net.URLEncoder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import org.junit.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {
+  "classpath:/config.xml"
+})
+public class RequestSizeTest extends HttpTestCase {
+  private final static Logger log = LoggerFactory.getLogger(RequestSizeTest.class);
+
+
+  public RequestSizeTest() {
+    super("src/test/resources/web.xml");
+  }
+
+
+  @Test
+  public void testSimpleRequestWithGzip() throws Exception {
+
+    log.info("-------- Test: gzipped simple request");
+
+    client.getClientProperties().setAcceptGzip(true);
+
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      WebResponse response = executeRequest("http://localhost/request-size?n=" + i*128);
+      Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+      Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+      if (i==0)
+        Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      else
+        Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+      Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+      Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+      Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+      SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+      long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+      long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+      Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+    }
+  }
+
+  @Test
+  public void testSimpleRequestWithoutGzip() throws Exception {
+
+    log.info("-------- Test: uncompressed simple request");
+
+    client.getClientProperties().setAcceptGzip(false);
+
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      WebResponse response = executeRequest("http://localhost/request-size?n=" + i*128);
+      Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+      Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+      Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+      Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+      Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+      Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+      SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+      long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+      long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+      Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+    }
+  }
+
+  @Test
+  public void testForwardWithGzip() throws Exception {
+
+    log.info("-------- Test: gzipped request with forward");
+
+    client.getClientProperties().setAcceptGzip(true);
+
+    /**
+     * Auf den Fehler bei einem Forward nach Überschreitung der Puffer-Größe
+     * des ursprünglichen Requests wird hier nicht geprüft, weil der Puffer
+     * durch die Komprimierung bei den hier gewählten Test-Parametern nie
+     * vollgeschrieben wird, so dass er stets ohne Fehler zurückgesetzt
+     * werden kann...
+     *
+     * Dafür wird hier zusätzlich geprüft, ob die Komprimierung korrekt nur
+     * dann unterdrückt wird, wenn die gesamte Antwort leer ist (und nicht nur
+     * der initiale Request, der geforwarded wird).
+     */
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      StringBuilder uri = new StringBuilder();
+      uri.append("http://localhost/request-size");
+      uri.append("?n=");
+      uri.append(i%7*128);
+      uri.append("&f=");
+      uri.append(URLEncoder.encode("/forwarded?n=" + i*128, "UTF-8"));
+      WebResponse response = executeRequest(uri.toString());
+      Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+      Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+      if (i==0)
+        Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      else
+        Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+      Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+      Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+      Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+      SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+      long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+      long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+      Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+    }
+  }
+
+  @Test
+  public void testForwardWithoutGzip() throws Exception {
+
+    log.info("-------- Test: uncompressed request with forward");
+
+    client.getClientProperties().setAcceptGzip(false);
+
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      StringBuilder uri = new StringBuilder();
+      uri.append("http://localhost/request-size");
+      uri.append("?n=");
+      uri.append(i*128);
+      uri.append("&f=");
+      uri.append(URLEncoder.encode("/forwarded?n=" + i*128, "UTF-8"));
+      try {
+        WebResponse response = executeRequest(uri.toString());
+        if (i*128 > LoggingHttpServletResponseWrapper.DEFAULT_BUFFER_SIZE)
+          Assert.fail("Error expected while forwarding after " + i*128 + " bytes written!");
+        Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+        Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+        Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+        Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+        Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+        Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+        Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+        SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+        long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+        long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+        Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+      }
+      catch (IllegalStateException e) {
+        if (i*128 > LoggingHttpServletResponseWrapper.DEFAULT_BUFFER_SIZE)
+          log.debug("Expected error while forwarding after {} bytes written: {}", i*128, e.getMessage());
+        else
+          Assert.fail("Unexpected error while forwarding after " + i*128 + " bytes written: " + e.getMessage());
+      }
+    }
+  }
+
+  @Test
+  public void testIncludeWithGzip() throws Exception {
+
+    log.info("-------- Test: gzipped request with includes");
+
+    client.getClientProperties().setAcceptGzip(true);
+
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      StringBuilder uri = new StringBuilder();
+      uri.append("http://localhost/request-size");
+      uri.append("?n=");
+      uri.append(i%7*128);
+      for (int j=0; j < i%4+1; j++) {
+        uri.append("&i=");
+        uri.append(URLEncoder.encode("/included?n=" + i*32*(4-j), "UTF-8"));
+      }
+      WebResponse response = executeRequest(uri.toString());
+      Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+      Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+      if (i==0)
+        Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      else
+        Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+      Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+      Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+      Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+      SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+      long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+      long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+      Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+    }
+  }
+
+  @Test
+  public void testIncludeWithoutGzip() throws Exception {
+
+    log.info("-------- Test: uncompressed request with includes");
+
+    client.getClientProperties().setAcceptGzip(false);
+
+    for (int i=0; i<33; i++) {
+      /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+      StringBuilder uri = new StringBuilder();
+      uri.append("http://localhost/request-size");
+      uri.append("?n=");
+      uri.append(i%7*128);
+      for (int j=0; j < i%4+1; j++) {
+        uri.append("&i=");
+        uri.append(URLEncoder.encode("/included?n=" + i*32*(4-j), "UTF-8"));
+      }
+      WebResponse response = executeRequest(uri.toString());
+      Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+      Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+      Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+      Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+      Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+      Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+      Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+      SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+      long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+      long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+      Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+    }
+  }
+}
diff --git a/accelerator/src/test/java/de/juplo/accelerator/TestServlet.java b/accelerator/src/test/java/de/juplo/accelerator/TestServlet.java
new file mode 100644 (file)
index 0000000..769269c
--- /dev/null
@@ -0,0 +1,81 @@
+package de.juplo.accelerator;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Ignore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+@Ignore
+public class TestServlet extends HttpServlet {
+  private final static Logger log = LoggerFactory.getLogger(TestServlet.class);
+
+  private static final String FORWARDED = TestServlet.class.getName() + ".FORWARDED";
+  private static final String INCLUDED = TestServlet.class.getName() + ".INCLUDED";
+
+  @Override
+  protected long getLastModified(HttpServletRequest req) {
+    try {
+      /** Der Reqeust-Parameter "lm" wird als Wert für Last-Modified zurückgegeben */
+      return Long.parseLong(req.getParameter("l"));
+    }
+    catch (Exception e) {
+      return -1l;
+    }
+  }
+
+  @Override
+  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+    /** Angeforderte Header setzen */
+    Map<String, String[]> map = request.getParameterMap();
+    for (String param : map.keySet()) {
+      if (param.equals("n") || param.equals("l") || param.equals("f") || param.equals("i"))
+        continue;
+      /**
+       * Alle Request-Parameter außer die Sonder-Parameter "n" und "lm"
+       * werden als Schlüssel/Wert-Paare für die Antwort-Header interpretiert!
+       */
+      for (String value : map.get(param))
+        response.setHeader(param, value);
+    }
+
+    int n = 0;
+    try {
+      /**
+       * Wenn der Parameter n gesetzt ist, wird ein Antwort-Body erzeugt, der
+       * exakt die Anzahl der geforderten Bytes enthält.
+       */
+      n = Integer.parseInt(request.getParameter("n"));
+    }
+    catch(Exception e) {}
+    log.debug("GET {} bytes: {}", n, request.getRequestURI());
+    ServletOutputStream out = response.getOutputStream();
+    for (int i=0; i<n; i++)
+      out.write(i%2 + 48); /** ASCII-Codes für "0" und "1" */
+
+    if (request.getParameter("i") != null && request.getAttribute(INCLUDED) == null) {
+      for (String include : request.getParameterValues("i")) {
+        RequestDispatcher dispatcher = request.getRequestDispatcher(include);
+        request.setAttribute(INCLUDED, include);
+        dispatcher.include(request, response);
+      }
+    }
+
+    if (request.getParameter("f") != null && request.getAttribute(FORWARDED) == null) {
+      String forward = request.getParameter("f");
+      request.setAttribute(FORWARDED, forward);
+      RequestDispatcher dispatcher = request.getRequestDispatcher(forward);
+      dispatcher.forward(request, response);
+    }
+  }
+}
diff --git a/accelerator/src/test/resources/config.xml b/accelerator/src/test/resources/config.xml
new file mode 100644 (file)
index 0000000..0b4ebb5
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="
+           http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+  <!-- Activates the AspectJ-Weaver -->
+  <context:component-scan base-package="de.juplo"/>
+  <context:spring-configured/>
+  <!--<context:load-time-weaver/>-->
+
+  <bean id="eTag" class="java.lang.String">
+    <constructor-arg value="Hallo Welt!"/>
+  </bean>
+
+  <bean id="weak" class="java.lang.Boolean">
+    <constructor-arg value="true"/>
+  </bean>
+
+  <bean id="lastModified" class="java.lang.Long">
+    <constructor-arg value="0"/>
+  </bean>
+
+  <bean id="cacheSeconds" class="java.lang.Integer">
+    <constructor-arg value="3600"/>
+  </bean>
+
+</beans>
diff --git a/accelerator/src/test/resources/log4j.xml b/accelerator/src/test/resources/log4j.xml
new file mode 100644 (file)
index 0000000..16e81f0
--- /dev/null
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+  
+  <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+    <layout class="org.apache.log4j.PatternLayout">
+      <param name="ConversionPattern"
+                value="%p - %C{1}.%M(%L) | %m%n"/>
+    </layout>
+  </appender>
+  
+  <logger name="de.juplo">
+    <level value="debug" />
+  </logger>
+
+  <root>
+    <level value="INFO"/>
+    <appender-ref ref="CONSOLE"/>
+  </root>
+  
+</log4j:configuration>
diff --git a/accelerator/src/test/resources/web.xml b/accelerator/src/test/resources/web.xml
new file mode 100644 (file)
index 0000000..01ce9b7
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+  <filter>
+    <filter-name>accelerator</filter-name>
+    <filter-class>de.juplo.accelerator.AcceleratorFilter</filter-class>
+  </filter>
+
+  <filter-mapping>
+    <filter-name>accelerator</filter-name>
+    <url-pattern>/*</url-pattern>
+  </filter-mapping>
+
+  <servlet>
+    <servlet-name>test-servlet</servlet-name>
+    <servlet-class>de.juplo.accelerator.TestServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>test-servlet</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+</web-app>
diff --git a/cachecontrol/pom.xml b/cachecontrol/pom.xml
deleted file mode 100644 (file)
index 686432e..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<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/maven-v4_0_0.xsd">
-
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>de.halbekunst</groupId>
-    <artifactId>juplo</artifactId>
-    <version>2.0-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>${pom.parent.artifactId}-cachecontrol</artifactId>
-  <name>Juplo - CacheControl</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-webmvc</artifactId>
-      <version>${springframework.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-aspects</artifactId>
-      <version>${springframework.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-tx</artifactId>
-      <version>${springframework.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.aspectj</groupId>
-      <artifactId>aspectjrt</artifactId>
-      <version>${aspectj.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.servlet</groupId>
-      <artifactId>servlet-api</artifactId>
-      <version>${servlet-api.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>javax.persistence</groupId>
-      <artifactId>persistence-api</artifactId>
-      <version>${jpa.version}</version>
-      <scope>provided</scope>
-    </dependency>
-    <dependency>
-      <groupId>${pom.parent.groupId}</groupId>
-      <artifactId>${pom.parent.artifactId}-test</artifactId>
-      <version>${pom.parent.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>${slf4j.binding}</artifactId>
-      <version>${slf4j.version}</version>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>aspectj-maven-plugin</artifactId>
-        <configuration>
-          <complianceLevel>1.6</complianceLevel>
-          <aspectLibraries>
-            <aspectLibrary>
-              <groupId>org.springframework</groupId>
-              <artifactId>spring-aspects</artifactId>
-            </aspectLibrary>
-          </aspectLibraries>
-        </configuration>
-        <executions>
-          <execution>
-            <goals>
-              <goal>compile</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-  </build>
-
-</project>
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/AcceleratorFilter.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/AcceleratorFilter.java
deleted file mode 100644 (file)
index 8e63222..0000000
+++ /dev/null
@@ -1,724 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.zip.GZIPOutputStream;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowire;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Configurable;
-import org.springframework.beans.factory.annotation.Qualifier;
-
-
-
-/**
- *
- * @author kai
- */
-@Configurable(autowire=Autowire.BY_NAME)
-public class AcceleratorFilter implements Filter {
-  private final static Logger log = LoggerFactory.getLogger(AcceleratorFilter.class);
-
-  private final static Map<String,String> EMPTY = Collections.unmodifiableMap(new HashMap<String,String>());
-
-  public final static Integer DEFAULT_BUFFER_SIZE = 1024;
-  public final static String REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
-  public final static String RESPONSE_WRAPPER = AcceleratorFilter.class.getName() + ".RESPONSE_WRAPPER";
-
-
-  @Autowired CacheControl cacheControl;
-  @Autowired(required=false) @Qualifier("defaultBufferSize") Integer defaultBufferSize = DEFAULT_BUFFER_SIZE;
-  @Autowired String eTag;
-  @Autowired Boolean weak;
-  @Autowired Long lastModified;
-  @Autowired Integer cacheSeconds;
-
-
-  @Override
-  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
-    if (!(request instanceof HttpServletRequest)) {
-      log.error("AcceleratorFilter can only handle HTTP-requests");
-      chain.doFilter(request, response);
-      return;
-    }
-
-    HttpServletRequest httpRequest = (HttpServletRequest)request;
-    HttpServletResponse httpResponse = (HttpServletResponse)response;
-
-    AccelerationWrapper wrapper;
-
-    wrapper = (AccelerationWrapper)request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER);
-    if (wrapper != null) {
-      if (wrapper.getFilter() == this) {
-        /** Ignore multiple mappings of the same filter-instance */
-        log.warn("Ignoring multiple mappings on same URL: {}", httpRequest.getRequestURI());
-        chain.doFilter(request, response);
-        return;
-      }
-      else {
-        log.error("Only one instance of AcceleratorFilter must be mapped to any URL: {}", httpRequest.getRequestURI());
-        httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Only one instance of AcceleratorFilter must be mapped to any URL!");
-        return;
-      }
-    }
-
-    wrapper = new AccelerationWrapper(httpRequest, httpResponse);
-    httpRequest.setAttribute(RESPONSE_WRAPPER, wrapper);
-    cacheControl.init(wrapper);
-    try {
-      chain.doFilter(request, wrapper);
-      wrapper.finish();
-    }
-    catch (NotModifiedException nm) {
-      log.trace("Not modified: {}", httpRequest.getRequestURI());
-    }
-  }
-
-  @Override
-  public void init(FilterConfig filterConfig) throws ServletException {
-  }
-
-  @Override
-  public void destroy() {
-  }
-
-
-  class AccelerationWrapper implements HttpServletResponse, CacheMethodHandle {
-
-    private final HttpServletRequest request;
-    private final HttpServletResponse response;
-
-    private final AcceleratorServletOutputStream out;
-
-    private ServletOutputStream stream;
-    private PrintWriter writer;
-
-    private boolean guessing = true;
-    protected boolean zipped = false; // << CacheControll greift direkt auf diese Variable zu!
-
-    private long now;
-    private int status;
-    private int cacheSeconds;
-    private boolean cacheSecondsSet = false;
-    private long lastModified, expires = 0l;
-    private String eTag;
-    private boolean weak;
-    private Map<String,String> cacheParams;
-
-    /** Für den AcceleratorOutputStream */
-    private boolean committed = false;
-    private OutputStream os = null;
-    private int bufferSize;
-    private byte[] buffer;
-    private int pos = 0;
-    private int size = 0;
-
-
-
-    AccelerationWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
-
-      this.request = request;
-      this.response = response;
-
-      now = System.currentTimeMillis();
-      status = HttpServletResponse.SC_OK;
-      cacheSeconds = AcceleratorFilter.this.cacheSeconds;
-      lastModified = AcceleratorFilter.this.lastModified;
-      eTag = AcceleratorFilter.this.eTag;
-      weak = AcceleratorFilter.this.weak;
-      cacheParams = new HashMap<String,String>();
-
-      Enumeration values = request.getHeaders(Headers.HEADER_ACCEPT_ENCODING);
-      while (values.hasMoreElements()) {
-        String value = (String) values.nextElement();
-        if (value.indexOf("gzip") != -1) {
-          zipped = true;
-          break;
-        }
-      }
-
-      out = new AcceleratorServletOutputStream();
-    }
-
-
-    private AcceleratorFilter getFilter() {
-      return AcceleratorFilter.this;
-    }
-
-    private void finish() throws IOException {
-      flushBuffer();
-      out.close();
-    }
-
-    @Override
-    public void setStatus(int sc) {
-      response.setStatus(sc);
-      status = sc;
-    }
-
-    @Override
-    public void setStatus(int sc, String sm) {
-      response.setStatus(sc,sm);
-      status = sc;
-    }
-
-    @Override
-    public void addDateHeader(String name, long value) {
-
-      if (!guessing) {
-        response.addDateHeader(name, value);
-        return;
-      }
-
-      if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
-        now = value;
-        calculateCacheSeconds();
-        return;
-      }
-
-      if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
-        expires = value;
-        calculateCacheSeconds();
-        return;
-      }
-
-      if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
-        lastModified = value;
-        return;
-      }
-
-      /** Unknown header: pass throug! */
-      response.addDateHeader(name, value);
-    }
-
-    @Override
-    public void addHeader(String name, String value) {
-
-      if (!guessing) {
-        response.addHeader(name, value);
-        return;
-      }
-
-      if (value == null)
-        return;
-      analyzeHeader(name, value, false);
-    }
-
-    @Override
-    public void addIntHeader(String name, int value) {
-
-      if (!guessing) {
-        response.addIntHeader(name, value);
-        return;
-      }
-
-      analyzeHeader(name, Integer.toString(value), false);
-    }
-
-    @Override
-    public void setDateHeader(String name, long value) {
-
-      if (!guessing) {
-        response.setDateHeader(name, value);
-        return;
-      }
-
-      if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
-        now = value;
-        calculateCacheSeconds();
-        return;
-      }
-
-      if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
-        expires = value;
-        calculateCacheSeconds();
-        return;
-      }
-
-      if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
-        lastModified = value;
-        return;
-      }
-
-      /** Unknown header: pass throug! */
-      response.setDateHeader(name, value);
-    }
-
-    @Override
-    public void setHeader(String name, String value) {
-
-      if (!guessing) {
-        response.setHeader(name, value);
-        return;
-      }
-
-      analyzeHeader(name, value, true);
-    }
-
-    @Override
-    public void setIntHeader(String name, int value) {
-
-      if (!guessing) {
-        response.setIntHeader(name, value);
-        return;
-      }
-
-      analyzeHeader(name, Integer.toString(value), true);
-    }
-
-    @Override
-    public ServletOutputStream getOutputStream() throws IOException {
-
-      if (writer != null)
-        throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
-
-      if (stream == null) {
-        stream = out;
-      }
-
-      return out;
-    }
-
-    @Override
-    public PrintWriter getWriter() throws IOException {
-
-      if (stream != null)
-        throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
-
-      if (writer == null) {
-        writer = new PrintWriter(new OutputStreamWriter(out, response.getCharacterEncoding()));
-      }
-
-      return writer;
-    }
-
-    @Override
-    public void setContentLength(int len) {
-      if (zipped)
-        log.info("Supressing explicit content-length {} for request {}, because content will be zipped!", len, request.getRequestURI());
-      else
-        response.setContentLength(len);
-    }
-
-    @Override
-    public int getBufferSize() {
-      return bufferSize;
-    }
-
-    @Override
-    public void setBufferSize(int size) {
-      if (this.size > 0)
-        throw new IllegalStateException("cannot change buffer size, because content was already written!");
-      bufferSize = size;
-      buffer = new byte[size];
-    }
-
-    @Override
-    public void resetBuffer() {
-      if (committed)
-        throw new IllegalStateException("cannot reset buffer, because response is already commited!");
-      pos = 0;
-      stream = null;
-      writer = null;
-    }
-
-    @Override
-    public void reset() {
-      if (committed)
-        throw new IllegalStateException("cannot reset response, because response is already commited!");
-      /**
-       * Da committed==false gilt, wurde die Dekoration noch nicht angestßen
-       * und muss entsprechend auch nicht rückgängig gemacht werden!
-       */
-      response.reset();
-      pos = 0;
-      size = 0;
-      stream = null;
-      writer = null;
-    }
-
-    @Override
-    public void flushBuffer() throws IOException {
-      if (writer != null)
-        writer.flush();
-      else if (stream != null)
-        stream.flush();
-    }
-
-    @Override
-    public void addCookie(Cookie cookie) {
-      // TODO: Je nach Vary-Einstellung ETag anpassen?
-      response.addCookie(cookie);
-    }
-
-    @Override
-    public boolean containsHeader(String name) {
-      return response.containsHeader(name);
-    }
-
-    @Override
-    public String encodeURL(String url) {
-      return response.encodeURL(url);
-    }
-
-    @Override
-    public String encodeRedirectURL(String url) {
-      return response.encodeRedirectURL(url);
-    }
-
-    @Override
-    public String encodeUrl(String url) {
-      return response.encodeUrl(url);
-    }
-
-    @Override
-    public String encodeRedirectUrl(String url) {
-      return response.encodeRedirectUrl(url);
-    }
-
-    @Override
-    public void sendError(int sc, String msg) throws IOException {
-      response.sendError(sc,msg);
-    }
-
-    @Override
-    public void sendError(int sc) throws IOException {
-      response.sendError(sc);
-    }
-
-    @Override
-    public void sendRedirect(String location) throws IOException {
-      response.sendRedirect(location);
-    }
-
-    @Override
-    public String getCharacterEncoding() {
-      return response.getCharacterEncoding();
-    }
-
-    @Override
-    public String getContentType() {
-      return response.getContentType();
-    }
-
-    @Override
-    public void setCharacterEncoding(String charset) {
-      // TODO: Je nach Vary-Einstellung ETag anpassen?
-      response.setCharacterEncoding(charset);
-    }
-
-    @Override
-    public void setContentType(String type) {
-      // TODO: Je nach Vary-Einstellung ETag anpassen?
-      response.setContentType(type);
-    }
-
-    @Override
-    public boolean isCommitted() {
-      return committed;
-    }
-
-    @Override
-    public void setLocale(Locale loc) {
-      // TODO: Je nach Vary-Einstellung ETag anpassen?
-      response.setLocale(loc);
-    }
-
-    @Override
-    public Locale getLocale() {
-      return getLocale();
-    }
-
-
-
-    @Override
-    public boolean isZipped() {
-      return zipped;
-    }
-
-    @Override
-    public long getTimestamp() {
-      return now;
-    }
-
-    @Override
-    public int accepts(HttpServletRequest request) {
-      return status;
-    }
-
-    @Override
-    public int getCacheSeconds(HttpServletRequest request) {
-      return cacheSeconds;
-    }
-
-    @Override
-    public long getLastModified(HttpServletRequest request) {
-      return lastModified;
-    }
-
-    @Override
-    public String getETag(HttpServletRequest request) {
-      return eTag;
-    }
-
-    @Override
-    public boolean isETagWeak() {
-      return weak;
-    }
-
-    @Override
-    public void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap) {
-      cacheControlMap.putAll(cacheParams);
-    }
-
-    @Override
-    public Map<String,String> getAdditionalHeaders(HttpServletRequest request) {
-      return EMPTY;
-    }
-
-    public void supressGuessing() {
-      guessing = false;
-    }
-
-
-    private void analyzeHeader(String name, String value, boolean overwrite) {
-      if (name == null)
-        return;
-      name = name.trim();
-
-      if (name.equalsIgnoreCase(Headers.HEADER_DATE)) {
-        if (value == null) {
-          if (overwrite) {
-            now = System.currentTimeMillis();
-            cacheSeconds = AcceleratorFilter.this.cacheSeconds;
-          }
-          return;
-        }
-        try {
-          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-          now = parser.parse(value).getTime();
-          calculateCacheSeconds();
-        }
-        catch (ParseException e) {
-          log.warn("ignoring date for header \"Date\" in invalid format: {}", value);
-        }
-        return;
-      }
-
-      if (name.equalsIgnoreCase(Headers.HEADER_EXPIRES)) {
-        if (value == null) {
-          if (overwrite) {
-            expires = 0;
-            cacheSeconds = AcceleratorFilter.this.cacheSeconds;
-          }
-          return;
-        }
-        try {
-          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-          expires = parser.parse(value).getTime();
-          calculateCacheSeconds();
-        }
-        catch (ParseException e) {
-          log.warn("ignoring date for header \"Expires\" in invalid format: {}", value);
-        }
-        return;
-      }
-
-      if (name.equalsIgnoreCase(Headers.HEADER_LAST_MODIFIED)) {
-        if (value == null) {
-          if (overwrite)
-            lastModified = AcceleratorFilter.this.lastModified;
-          return;
-        }
-        try {
-          SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-          lastModified = parser.parse(value).getTime();
-        }
-        catch (ParseException e) {
-          log.warn("ignoring date for header \"Last-Modified\" in invalid format: {}", value);
-        }
-        return;
-      }
-
-      if (name.equalsIgnoreCase(Headers.HEADER_ETAG)) {
-        if (value == null) {
-          if (overwrite) {
-            eTag = AcceleratorFilter.this.eTag;
-            weak = AcceleratorFilter.this.weak;
-          }
-          return;
-        }
-        value = value.trim();
-        int start = 0;
-        int end = value.length();
-        if (value.startsWith("W/")) {
-          weak = true;
-          start = 2;
-        }
-        else {
-          weak = false;
-        }
-        if (value.charAt(start) == '"')
-          start++;
-        else
-          log.warn("Quote at the beginning ov ETag is missing: {}", value);
-        if (value.charAt(end -1) == '"')
-          end--;
-        else
-          log.warn("Quote at the end of ETag is missing: {}", value);
-        eTag = value.substring(start, end);
-        String filtered = eTag.replaceAll("[^\\x00-\\x21\\x23-\\x7F]+","");
-        if (filtered.length() < eTag.length()) {
-          log.warn("filtering out illegal characters in ETag: \"{}\" -> \"{}\"", eTag, filtered);
-          eTag = filtered;
-        }
-      }
-
-      if (name.equalsIgnoreCase(Headers.HEADER_CACHE_CONTROL)) {
-        if (overwrite)
-          cacheParams.clear();
-        if (value == null)
-          return;
-        for (String param : value.split(",")) {
-          param = param.trim();
-          int pos = param.indexOf("=");
-          if (pos < 0) {
-            cacheParams.put(param, null);
-          }
-          else {
-            String paramName = param.substring(0, pos).trim();
-            if (paramName.equalsIgnoreCase("max-age")) {
-              try {
-                cacheSeconds = Integer.parseInt(param.substring(pos + 1));
-                cacheSecondsSet = true;
-              }
-              catch (NumberFormatException e) {
-                log.warn("illegal value for Header \"Cache-Control\":", param);
-              }
-            }
-            else {
-              cacheParams.put(paramName, param.substring(pos + 1));
-            }
-          }
-        }
-        return;
-      }
-
-      if (name.equalsIgnoreCase(Headers.HEADER_PRAGMA)) {
-        if (value != null && value.trim().equalsIgnoreCase("no-cache"))
-          cacheSeconds = 0;
-        return;
-      }
-
-      /** Pass header through, if no value from intrest was found */
-      if (overwrite)
-        response.setHeader(name, value);
-      else
-        response.addHeader(name, value);
-    }
-
-    private void calculateCacheSeconds() {
-      if (!cacheSecondsSet && expires >= now) {
-        cacheSeconds = (int)(expires/1000 - now/1000);
-        log.debug("calculating cache-seconds from DATE and EXPIRES: {}", cacheSeconds);
-      }
-    }
-
-
-    private class AcceleratorServletOutputStream extends ServletOutputStream  {
-
-      private final ServletOutputStream sos;
-
-
-      private AcceleratorServletOutputStream() throws IOException {
-        bufferSize = defaultBufferSize;
-        buffer = new byte[bufferSize];
-        sos = AccelerationWrapper.this.response.getOutputStream();
-      }
-
-
-      private OutputStream out() throws IOException {
-        if (os == null)
-          os = zipped ? new GZIPOutputStream(sos) : sos;
-        return os;
-      }
-
-      @Override
-      public void write(int i) throws IOException {
-        if (pos == bufferSize) {
-          out().write(buffer);
-          committed = true;
-          /** Dekoration nur beim ersten Schreib-Schub anstoßen */
-          if (pos == size) {
-            if (!cacheControl.decorate(request, response)) {
-              zipped = false;
-              os = null;
-              pos = 0;
-              throw new NotModifiedException();
-            }
-          }
-          pos = 0;
-        }
-        buffer[pos++] = (byte) i;
-        size++;
-      }
-
-      @Override
-      public void flush() throws IOException {
-        if (pos == 0)
-          return;
-
-        committed = true;
-        /** Dekoration nur beim ersten Schreib-Schub anstoßen */
-        if (pos == size) {
-          if (!cacheControl.decorate(request, response)) {
-            zipped = false;
-            os = null;
-            pos = 0;
-            throw new NotModifiedException();
-          }
-        }
-        out().write(buffer, 0, pos);
-        out().flush();
-        pos = 0;
-      }
-
-      @Override
-      public void close() throws IOException {
-        if (size == 0) {
-          committed = true;
-          zipped = false;
-          if (!cacheControl.decorate(request, response))
-            throw new NotModifiedException();
-          sos.close();
-        }
-        else {
-          flush();
-          out().close();
-        }
-      }
-    }
-  }
-}
-
-class NotModifiedException extends IOException {}
\ No newline at end of file
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControl.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControl.java
deleted file mode 100644 (file)
index 458979d..0000000
+++ /dev/null
@@ -1,548 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-import de.halbekunst.juplo.cachecontrol.AcceleratorFilter.AccelerationWrapper;
-import de.halbekunst.juplo.cachecontrol.annotations.CacheSeconds;
-import de.halbekunst.juplo.cachecontrol.annotations.Accepts;
-import de.halbekunst.juplo.cachecontrol.annotations.AdditionalHeaders;
-import de.halbekunst.juplo.cachecontrol.annotations.LastModified;
-import de.halbekunst.juplo.cachecontrol.annotations.ETag;
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Method;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.TreeMap;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.stereotype.Component;
-
-/**
- *
- * @author kai
- */
-@Component
-public class CacheControl {
-  private final static Logger log = LoggerFactory.getLogger(CacheControl.class);
-
-  private static final ThreadLocal<CacheMethodHandle> tl = new ThreadLocal<CacheMethodHandle>();
-
-  @Autowired @Qualifier("cacheSeconds") private Integer defaultCacheSeconds;
-  @Autowired @Qualifier("lastModified") private Long defaultLastModified;
-
-
-  public void init(CacheMethodHandle handle) {
-    CacheControl.tl.set(handle);
-  }
-
-  void init(Object handler, AccelerationWrapper wrapper) throws NoSuchMethodException {
-    CacheControl.tl.set(new ReflectionCacheMethodHandle(handler, wrapper == null ? false : wrapper.zipped));
-  }
-
-  public boolean decorate(
-      HttpServletRequest request,
-      HttpServletResponse response
-      )
-  {
-    try {
-    CacheMethodHandle handle = CacheControl.tl.get();
-
-    /** Doppelte Ausführung verhindern... */
-    if (handle == null) {
-      /** Dekoration wurde bereits durchgeführt! */
-      return true;
-    }
-
-    /**
-     * Alle Antworten (insbesondere auch 304) sollen nach dem {@plainlink
-     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 RFC 2616,
-     * Abschnitt 14.18} einen Date-Header enthalten
-     */
-    response.setDateHeader(Headers.HEADER_DATE, handle.getTimestamp());
-
-    /** Besondere Maßnahmen für besondere HTTP-Status-Codes ?!? */
-    int status = handle.accepts(request);
-    switch (status) {
-      case HttpServletResponse.SC_OK: // 200
-      case HttpServletResponse.SC_NO_CONTENT: // 204
-      case HttpServletResponse.SC_PARTIAL_CONTENT: // 206
-        /** Normale Antwort! Antwort dekorieren... */
-        break;
-      case HttpServletResponse.SC_MOVED_PERMANENTLY: // 301
-      case HttpServletResponse.SC_MOVED_TEMPORARILY: // 302
-      case HttpServletResponse.SC_SEE_OTHER: // 303
-      case HttpServletResponse.SC_NOT_MODIFIED: // 304
-      case HttpServletResponse.SC_USE_PROXY: // 305
-      case HttpServletResponse.SC_TEMPORARY_REDIRECT: // 307
-        /** Redirect-Antwort! Antwort dekodieren... */
-        // TODO: Kann das wirklich nicht zu Protokoll-Verletzungen führen?
-        break;
-      case HttpServletResponse.SC_BAD_REQUEST: // 400
-      case HttpServletResponse.SC_UNAUTHORIZED: // 401
-      case HttpServletResponse.SC_PAYMENT_REQUIRED: // 402
-      case HttpServletResponse.SC_FORBIDDEN: // 403
-      case HttpServletResponse.SC_NOT_FOUND: // 404
-      case HttpServletResponse.SC_METHOD_NOT_ALLOWED: // 405
-      case HttpServletResponse.SC_NOT_ACCEPTABLE: // 406
-      case HttpServletResponse.SC_PROXY_AUTHENTICATION_REQUIRED: // 407
-      case HttpServletResponse.SC_REQUEST_TIMEOUT: // 408
-      case HttpServletResponse.SC_CONFLICT: // 409
-      case HttpServletResponse.SC_GONE: // 410
-      case HttpServletResponse.SC_LENGTH_REQUIRED: // 411
-      case HttpServletResponse.SC_PRECONDITION_FAILED: // 412
-      case HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE: // 413
-      case HttpServletResponse.SC_REQUEST_URI_TOO_LONG: // 414
-      case HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE: // 415
-      case HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE: // 416
-      case HttpServletResponse.SC_INTERNAL_SERVER_ERROR: // 500
-      case HttpServletResponse.SC_NOT_IMPLEMENTED: // 501
-      case HttpServletResponse.SC_SERVICE_UNAVAILABLE: // 503
-      case HttpServletResponse.SC_HTTP_VERSION_NOT_SUPPORTED: // 505
-      default:
-        /**
-         * Es ist nicht klar, was der Handler noch machen wird/muss:
-         * Antwort nicht dekorieren und Kontroller an den Handler übergeben...
-         */
-        return true;
-    }
-
-    Map<String,String> headers = handle.getAdditionalHeaders(request);
-    for (String name : headers.keySet())
-      response.addHeader(name, headers.get(name));
-
-    String url = null;
-    if (log.isDebugEnabled()) {
-      if (request.getQueryString() == null) {
-        url = request.getRequestURI();
-      }
-      else {
-        StringBuilder builder = new StringBuilder();
-        builder.append(request.getRequestURI());
-        builder.append('?');
-        builder.append(request.getQueryString());
-        url = builder.toString();
-      }
-    }
-
-    int cacheSeconds = handle.getCacheSeconds(request);
-    if (cacheSeconds < 0) {
-      log.debug("{}: caching disabled!", url);
-      response.setDateHeader(Headers.HEADER_DATE, handle.getTimestamp());
-      response.setDateHeader(Headers.HEADER_EXPIRES, 0);
-      response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, "private");
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-cache");
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-store");
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, "max-age=0");
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, "s-max-age=0");
-      if (handle.isZipped())
-        response.addHeader(Headers.HEADER_CONTENT_ENCODING, "gzip");
-      return true;
-    }
-
-    long ifModifiedSince = -1;
-    try {
-      ifModifiedSince = request.getDateHeader(Headers.HEADER_IF_MODIFIED_SINCE);
-    }
-    catch (Exception e) {
-      log.error("Exception while fetching If-Modified-Since: {}", e);
-    }
-
-    long lastModified = handle.getLastModified(request);
-
-    /**
-     * Sicherstellen, dass der Wert keine Millisekunden enthält, da die
-     * Zeitangabe aus dem Modified-Since-Header keine Millisekunden enthalten
-     * kann und der Test unten dann stets fehlschlagen würde!
-     */
-    lastModified = lastModified - (lastModified % 1000);
-
-    String ifNoneMatch = request.getHeader(Headers.HEADER_IF_NONE_MATCH);
-    String eTag = handle.getETag(request);
-
-    /**
-     * 304-Antworten sollen nach dem {@plainlink
-     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 RFC
-     * 2616, Abschnitt 10.3.5} einen ETag-Header enthalten, wenn auch die
-     * 200-Antwort einen enthalten hätte.
-     */
-    if (eTag != null) {
-      StringBuilder builder = new StringBuilder();
-      if (handle.isETagWeak())
-        builder.append("W/");
-      builder.append('"');
-      builder.append(eTag);
-      builder.append('"');
-      response.setHeader(Headers.HEADER_ETAG, builder.toString());
-    }
-
-
-    if (ifModifiedSince >= lastModified && lastModified > 0) {
-      /**
-       * request.getDateHeader liefert die Zeit als long, oder -1, wenn der
-       * Header nicht existiert. D.h., wenn "If-Modified-Since" nicht gesetzt
-       * ist, wird die komplette Seite ausgeliefert.
-       * Der zusätzliche Test, ob lastModified größer 0 ist, ist nötig, um
-       * Fehler auszuschließen, wenn die Implementierung von Cachable
-       * negative Werte für Last-Modified zurückliefert.
-       */
-      if (log.isDebugEnabled())
-        log.debug("{}: Not modified since {}", url, new Date(ifModifiedSince));
-
-      if (ifNoneMatch == null) {
-        /** Neue Anfrage oder HTTP/1.0 Client! */
-        log.debug("{}: ETag nicht gesetzt -> 304", url);
-        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-        return false;
-      }
-    }
-
-    if (ifNoneMatch != null) {
-      boolean weak = false;
-      if (ifNoneMatch.startsWith("W/")) {
-        weak = true;
-        ifNoneMatch = ifNoneMatch.substring(3, ifNoneMatch.length() - 1);
-      }
-      else {
-        ifNoneMatch = ifNoneMatch.substring(1, ifNoneMatch.length() - 1);
-      }
-
-      if (!weak || (request.getMethod().equals("GET") && request.getHeader(Headers.HEADER_RANGE) == null)) {
-        /**
-         * Die Gleichheit gilt nur, wenn die ETag's der Anfrage _und_ der
-         * Antwort stark sind (starke Gleichheit!), oder wenn die Antwort nur
-         * schwache Gleichheit fordert...
-         */
-        if (ifNoneMatch.equals(eTag) && (handle.isETagWeak() || !weak)) {
-          log.debug("{}: ETag {} not changed -> 304 ", url, ifNoneMatch);
-          response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
-          return false;
-        }
-      }
-      else {
-        log.warn("{}: ignoring weak ETag W/\"{}\", because the request was no GET-request or the Range-Header was present!", url, ifNoneMatch);
-      }
-    }
-
-
-    log.debug("{}: first up!", url);
-
-    if (handle.isZipped())
-      response.addHeader(Headers.HEADER_CONTENT_ENCODING, "gzip");
-
-    /** HTTP/1.1-Caching-Header richtig setzen!! */
-    response.setDateHeader(Headers.HEADER_LAST_MODIFIED, lastModified);
-
-    /** Cache-Control für HTTP/1.1-Clients generieren */
-    Map<String, String> cacheControl = new TreeMap<String, String>();
-
-    /**
-     * Wenn eins JSESSIONID in der URL enthalten ist, darf die Anfrage nur vom
-     * Browser gecached werden!
-     */
-    if (request.isRequestedSessionIdFromURL()) {
-      cacheControl.put("private", null);
-    }
-    else {
-      /**
-       * Hier muss nicht geprüft werden, ob cacheSeconds > 0 gilt, da in diesem
-       * Fall oben bereits No-Cache-Header generiert und <code>false</code>
-       * zurückgeliefert werden!
-       *
-       * Den Wert als <code>max-age</code> zu den Schlüssel-Wert-Paaren für den
-       * <code>Cache-Control</code>-Header hinzufügen und einen entsprechenden
-       * <code>Expires</code>-Header für HTTP/1.0-Clients setzen.
-       */
-      cacheControl.put("max-age", Integer.toString(cacheSeconds));
-      response.setDateHeader(Headers.HEADER_EXPIRES, (handle.getTimestamp() + (long) cacheSeconds * 1000));
-    }
-
-    /** Dem Handler die Gelegenheit geben, den Cache-Controll-Header anzupassen */
-    handle.cacheControl(request, cacheControl);
-
-
-    if (cacheControl.containsKey("private")) {
-      /**
-       * HTTP/1.0 Caches davon abhalten, die Ressource zu cachen (vgl.: RFC
-       * 2616, {@plainlink
-       * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3
-       * Abschnitt 14.9.3} und {@plainlink
-       * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32
-       * Abschnitt 14.32})
-       */
-      response.setDateHeader(Headers.HEADER_EXPIRES, 0l);
-      response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
-    }
-
-    StringBuilder builder = new StringBuilder();
-    for (Entry<String, String> entry : cacheControl.entrySet()) {
-      builder.setLength(0);
-      builder.append(entry.getKey());
-      if (entry.getValue() != null) {
-        builder.append('=');
-        builder.append(entry.getValue());
-      }
-      response.addHeader(Headers.HEADER_CACHE_CONTROL, builder.toString());
-    }
-
-    return true;
-    }
-    finally {
-      /**
-       * Thread-Locale-Variable zurücksetzen, damit
-       * 1.) ein doppelter Aufruf dieser Methode pro Request erkannt werden kann
-       * 2.) der nächste Request nicht mit dem selben Handle weiterarbeitet
-       */
-      CacheControl.tl.set(null);
-    }
-  }
-
-  public void release() {
-    CacheControl.tl.set(null);
-  }
-
-
-  class ReflectionCacheMethodHandle implements CacheMethodHandle {
-
-    private Object handler;
-    private long now = System.currentTimeMillis();
-    private Integer cacheSeconds;
-    private Long lastModified;
-    private String eTag;
-    private Map<String,String> additionalHeaders;
-    private Method acceptsMethod;
-    private Method cacheSecondsMethod;
-    private Method lastModifiedMethod;
-    private Method eTagMethod;
-    private Method cacheControlMethod;
-    private Method additionalHeadersMethod;
-    private boolean isAcceptsMethodDefined;
-    private boolean isCacheSecondsMethodDefined;
-    private boolean isLastModifiedMethodDefined;
-    private boolean isETagMethodDefined;
-    private boolean isCacheControlMethodDefined;
-    private boolean isAdditionalHeadersMethodDefined;
-    private boolean weak;
-    private boolean zipped;
-
-
-    ReflectionCacheMethodHandle(Object handler, boolean zipped) throws NoSuchMethodException {
-
-      this.handler = handler;
-      this.zipped = zipped;
-
-      cacheSeconds = CacheControl.this.defaultCacheSeconds;
-      lastModified = CacheControl.this.defaultLastModified;
-
-      /** Class-Level-Annotations auslesen */
-      for (Annotation annotation : handler.getClass().getAnnotations()) {
-        if (annotation.annotationType().equals(CacheSeconds.class)) {
-          cacheSeconds = ((CacheSeconds)annotation).value();
-          isCacheSecondsMethodDefined = true;
-          continue;
-        }
-        if (annotation.annotationType().equals(LastModified.class)) {
-          lastModified = ((LastModified)annotation).value();
-          if (lastModified < 1) {
-            /**
-             * Ein Last-Modified-Header wurde angefordert, aber es wurde kein
-             * statischer Wert spezifiziert:
-             * globalen statischen Default-Wert benutzen!
-             */
-            lastModified = defaultLastModified;
-          }
-          isLastModifiedMethodDefined = true;
-          continue;
-        }
-        if (annotation.annotationType().equals(ETag.class)) {
-          ETag eTagAnnotation = (ETag)annotation;
-          eTag = eTagAnnotation.value();
-          weak = eTagAnnotation.weak();
-          isETagMethodDefined = true;
-          continue;
-        }
-        if (annotation.annotationType().equals(AdditionalHeaders.class)) {
-          AdditionalHeaders additionalHeadersAnnotation = (AdditionalHeaders)annotation;
-          additionalHeaders = new HashMap<String,String>();
-          for (String header : additionalHeadersAnnotation.value()) {
-            int i = header.indexOf(':');
-            if (i < 0) {
-              log.error("invalid header: [{}]", header);
-            }
-            else {
-              String name = header.substring(0,i).trim();
-              String value = header.substring(i+1,header.length()).trim();
-              additionalHeaders.put(name, value);
-            }
-          }
-          isAdditionalHeadersMethodDefined = true;
-          continue;
-        }
-      }
-
-      /** Method-Level-Annotations auslesen */
-      for (Method method : handler.getClass().getMethods()) {
-        for (Annotation annotation : method.getAnnotations()) {
-          if (annotation.annotationType().equals(Accepts.class)) {
-            if (isAcceptsMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @Accept wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            acceptsMethod = method;
-            isAcceptsMethodDefined = true;
-            continue;
-          }
-          if (annotation.annotationType().equals(CacheSeconds.class)) {
-            if (isCacheSecondsMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @CacheSeconds wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            cacheSecondsMethod = method;
-            isCacheSecondsMethodDefined = true;
-            continue;
-          }
-          if (annotation.annotationType().equals(LastModified.class)) {
-            if (isLastModifiedMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @LastModified wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            lastModifiedMethod = method;
-            isLastModifiedMethodDefined = true;
-            continue;
-          }
-          if (annotation.annotationType().equals(ETag.class)) {
-            if (isETagMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @ETag wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            eTagMethod = method;
-            weak = ((ETag)annotation).weak();
-            isETagMethodDefined = true;
-            continue;
-          }
-          if (annotation.annotationType().equals(de.halbekunst.juplo.cachecontrol.annotations.CacheControl.class)) {
-            if (isCacheControlMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @CacheControl wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            cacheControlMethod = method;
-            isCacheControlMethodDefined = true;
-            continue;
-          }
-          if (annotation.annotationType().equals(AdditionalHeaders.class)) {
-            if (isAdditionalHeadersMethodDefined)
-              throw new IllegalArgumentException("Die Annotation @AdditionalHeaders wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
-            additionalHeadersMethod = method;
-            isAdditionalHeadersMethodDefined = true;
-            continue;
-          }
-        }
-      }
-
-      if (!isAdditionalHeadersMethodDefined)
-        additionalHeaders = new HashMap<String,String>();
-    }
-
-
-    @Override
-    public boolean isZipped() {
-      return zipped;
-    }
-
-    @Override
-    public long getTimestamp() {
-      return now;
-    }
-
-    @Override
-    public int accepts(HttpServletRequest request) throws IllegalArgumentException {
-      if (acceptsMethod == null) {
-        return HttpServletResponse.SC_OK;
-      }
-      else {
-        try {
-          return (Integer)acceptsMethod.invoke(handler, request);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-
-    @Override
-    public int getCacheSeconds(HttpServletRequest request) throws IllegalArgumentException {
-      if (cacheSecondsMethod == null) {
-        return cacheSeconds;
-      }
-      else {
-        try {
-          return (Integer)cacheSecondsMethod.invoke(handler, request);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-
-    @Override
-    public long getLastModified(HttpServletRequest request) throws IllegalArgumentException {
-      if (lastModifiedMethod == null) {
-        return lastModified;
-      }
-      else {
-        try {
-          return (Long)lastModifiedMethod.invoke(handler, request);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-
-    @Override
-    public String getETag(HttpServletRequest request) throws IllegalArgumentException {
-      if (eTagMethod == null) {
-        return eTag;
-      }
-      else {
-        try {
-          return (String)eTagMethod.invoke(handler, request);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-
-    @Override
-    public boolean isETagWeak() {
-      return weak;
-    }
-
-    @Override
-    public void cacheControl(
-        HttpServletRequest request,
-        Map<String, String> cacheControlMap
-        )
-        throws IllegalArgumentException
-    {
-      if (cacheControlMethod != null) {
-        try {
-          cacheControlMethod.invoke(handler, request, cacheControlMap);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-
-    @Override
-    public Map<String,String> getAdditionalHeaders(HttpServletRequest request) throws IllegalArgumentException {
-      if (additionalHeadersMethod == null) {
-        return additionalHeaders;
-      }
-      else {
-        try {
-          return (Map<String,String>)additionalHeadersMethod.invoke(handler, request);
-        }
-        catch (Exception e) {
-          throw new IllegalArgumentException(e);
-        }
-      }
-    }
-  }
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControlInterceptor.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControlInterceptor.java
deleted file mode 100644 (file)
index c32183f..0000000
+++ /dev/null
@@ -1,83 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-import de.halbekunst.juplo.cachecontrol.AcceleratorFilter.AccelerationWrapper;
-import de.halbekunst.juplo.cachecontrol.annotations.Cacheable;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.servlet.HandlerInterceptor;
-import org.springframework.web.servlet.ModelAndView;
-
-/**
- *
- * @author kai
- */
-public class CacheControlInterceptor implements HandlerInterceptor {
-  private final static Logger log = LoggerFactory.getLogger(CacheControlInterceptor.class);
-
-
-  private CacheControl cacheControl;
-
-
-  @Override
-  public boolean preHandle(
-      HttpServletRequest request,
-      HttpServletResponse response,
-      Object handler
-      ) throws Exception
-  {
-    Cacheable cacheable = handler.getClass().getAnnotation(Cacheable.class);
-    if (cacheable == null) {
-      /** Der Handler ist nicht mit @Cacheable annotiert: keine Dekorationen anbringen! */
-      return true;
-    }
-
-    AccelerationWrapper wrapper = (AccelerationWrapper)request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER);
-    if (wrapper != null)
-      wrapper.supressGuessing();
-
-    /** CacheControll initialisieren (Handler nach annotierte Methoden scannen etc.) */
-    cacheControl.init(handler, wrapper);
-
-    if (cacheable.eager()) {
-      return cacheControl.decorate(request, response);
-    }
-    else {
-      return true;
-    }
-  }
-
-  @Override
-  public void postHandle(
-      HttpServletRequest request,
-      HttpServletResponse response,
-      Object handler,
-      ModelAndView modelAndView
-      ) throws Exception
-  {
-    /**
-     * Dekoration nur dann anstossen, wenn sie nicht bereits von dem
-     * AcceleratorFilter ausgelöst wird.
-     */
-    if (request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER) == null)
-      cacheControl.decorate(request, response);
-  }
-
-  @Override
-  public void afterCompletion(
-      HttpServletRequest request,
-      HttpServletResponse response,
-      Object handler, Exception ex
-      ) throws Exception
-  {
-    cacheControl.release();
-  }
-
-
-  @Autowired
-  public void setCacheControl(CacheControl cacheControl) {
-    this.cacheControl = cacheControl;
-  }
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheMethodHandle.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheMethodHandle.java
deleted file mode 100644 (file)
index a36196e..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-import java.util.Map;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- *
- * @author kai
- */
-public interface CacheMethodHandle {
-  boolean isZipped();
-  long getTimestamp();
-  int accepts(HttpServletRequest request);
-  int getCacheSeconds(HttpServletRequest request);
-  long getLastModified(HttpServletRequest request);
-  String getETag(HttpServletRequest request);
-  boolean isETagWeak();
-  void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap);
-  Map<String,String> getAdditionalHeaders(HttpServletRequest request);
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/Headers.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/Headers.java
deleted file mode 100644 (file)
index 6f7579a..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-/**
- *
- * @author kai
- */
-public abstract class Headers {
-
-  public static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
-
-  public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
-  public static final String HEADER_CACHE_CONTROL = "Cache-Control";
-  public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
-  public static final String HEADER_CONTENT_TYPE = "Content-Type";
-  public static final String HEADER_DATE = "Date";
-  public static final String HEADER_ETAG = "ETag";
-  public static final String HEADER_EXPIRES = "Expires";
-  public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
-  public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
-  public static final String HEADER_LAST_MODIFIED = "Last-Modified";
-  public static final String HEADER_PRAGMA = "Pragma";
-  public static final String HEADER_RANGE = "Range";
-
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Accepts.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Accepts.java
deleted file mode 100644 (file)
index 879cfd3..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mit dieser Methode kann eine Methode annotiert werden, die Auskunft darüber
- * erteilt, mit welchem HTTP-Status-Code der Handler die Anfrage beatnworten
- * wird.
- * <p>
- * Die Methode muss eine Instanz von {@link HttpServletRequest} als (einziges!)
- * Argument akzeptieren und einen Wert liefern, der sich nach
- * <code>int</code> casten lässt.
- * <p>
- * Eine mit dieser Annotation markierte Methode wird nur benötigt, wenn die
- * Caching-Dekoration im Modus <code>eager=true</code> ausgeführt wird. Sie
- * wird in diesem Fall benötigt, weil die Entscheidungen zur Cache-Dekoration
- * dann getroffen werden müssen, <em>bevor</em> die verarbeitende Klasse die
- * Anfrage verarbeitet hat.
- * Wenn die Cache-Dekoration im Modus <code>eager=true</code> betrieben wird
- * und keine Methode mit dieser Annotation annotiert ist, geht {@link CacheControl}
- * davon aus, dass die verarbeitende Klasse alle Anfragen annimmt.
- *
- * @author kai
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface Accepts {}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/AdditionalHeaders.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/AdditionalHeaders.java
deleted file mode 100644 (file)
index f8b9266..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- *
- * @author kai
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE, ElementType.METHOD })
-public @interface AdditionalHeaders {
-
-  String[] value() default {};
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheControl.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheControl.java
deleted file mode 100644 (file)
index ab5c4a3..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mit dieser Annotation kann eine Methode markiert werden, die die von Juplo-
- * CacheControl für den Header <code>Cache-Control</code> generierten
- * Schlüssel/Wert-Kombinationen manipulieren oder ergänzen kann, bevor der
- * Header an den Client ausgeliefert wird (s. {@plainlink
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3 RFC2616, Abschnitt 14.9.3}).
- * <p>
- * Die Methode muss zwei Parameter akzeptieren.
- * Als ersten Parameter eine Instanz von {@link HttpServletRequest}.
- * Als zweiten Parameter eine <code>Map<String,String></code>, die die von
- * Juplo-CacheControl erzeugten Schlüssel/Wert-Paare enthält.
- * <p>
- * Diese Methode liefert eine Map mit Schlüssel-Wert-Paaren für den
- * HTTP/1.1-Header <code>Cache-Control</code> (s. {@plainlink
- * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.3 RFC2616,
- * Abschnitt 14.9.3}).
- *
- * @author kai
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface CacheControl {
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheSeconds.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheSeconds.java
deleted file mode 100644 (file)
index e076e08..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
- * <p>
- * Wenn eine Methode markiert wird, muss diese eine Instanz von
- * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
- * Wert liefern, der sich nach <code>int</code> casten lässt.
- * Die annotierte Methode ermöglicht eine einfache, zentrale aber Request-
- * Abhängige Steuerung des Caching-Verhaltens.
- * <p>
- * Wenn eine Klasse annotiert wird, muss der Anotation die dann statisch für
- * alle von der Klasse erzeugten Antworten gültige Cache-Zeit als Argument
- * übergeben werden.
- * Wird keine Cache-Zeit spezifiziert, wird der Wert <code>86400</code>
- * (ein Tag) verwendet.
- * <ul>
- * <li>Wenn negativer Wert als Cache-Seconds festgelet wird, werden Cache-Header
- * erzeugt, die das Cachen der Antwort für HTTP/1.0 und HTTP/1.1 vollständig
- * untersagen.</li>
- * <li>Wenn einen Wert größer oder gleich <code>0</code> festgelegt wird, wird
- * für HTTP/1.0-Clients ein <code>Expires</code>-Header generiert und für
- * HTTP/1.1-Clients ein <code>Cache-Control</code>-Header mit einem
- * entsprechenden <code>max-age</code>-Eintrag. Dies reicht in Kombination mit
- * der Annotation {@link LastModified} vollständig für ein einfaches aber
- * effektives Caching aus.</li>
- * </ul>
- * <p>
- * TODO
- * <strong>Zu beachten:</strong> Wenn die Methode
- * {@link #getCacheControl(javax.servlet.http.HttpServletRequest)} weitere
- * Schlüssel-Wert-Paare für den <code>Cache-Control</code>-Header liefert,
- * werden diese ergänzt. Wenn in der Rückgabe ein Wert für
- * <code>max-age</code> enthalten ist, wir er allerdings von dem durch diese
- * Methode vorgegebenen Wert überschrieben!
- *
- * @author kai
- * @See Cacheable
- * @See LastModified
- * @See CacheControl
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE, ElementType.METHOD })
-public @interface CacheSeconds {
-  int value() default 86400; /** Default: 1 Tag */
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Cacheable.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Cacheable.java
deleted file mode 100644 (file)
index 7a1be26..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-
-/**
- * Marker-Annotation für Handler (i.A. eine Impelementierung von
- * {@link Controller}), deren Antworten vom {@link CachingInterceptor} mit
- *  HTTP/1.1-Caching-Header nach
- * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html RFC 2616}
- * dekoriert werden sollen.
- * <p>
- * Wenn der Parameter <code>eager</code> auf <code>true</code> gesetzt wird,
- * ermittelt der {@link CachingInterceptor} die Cache-Parameter über die
- * annotierten Methoden vorab.
- * <strong>Achtung:</strong>
- * Dies bedeutet, dass die annotierten Methoden aufgerufen werden <em>bevor</em>
- * die eigentliche Verarbeitungs-Routine der markierten Klasse aufgerufen wird!
- * Wenn sich dabei ergiebt, dass die Antwort nicht erneut ausgeliefert werden
- * muss, wird die eigentliche Verarbeitungs-Routine <em>gar nicht aufgerufen</em>.
- * <p>
- * Wenn der Parameter <code>eager</code> nicht gesetzt ist (oder explizit auf
- * <code>false</code> gesetzt wurde), kapselt der {@link CachingInterceptor}
- * den Request und den Ausgabestrom für den Response-Body und trifft die
- * Entscheidung über die zu ergänzenden Header, wenn der Status des
- * {@link HttpServletResponse} gesetzt oder mit dem Schreiben des Response-Body
- * begonnen wird.
- *
- * @see CacheControl
- * @see Accepts
- * @see CacheSeconds
- * @see LastModified
- * @see ETag
- * @see CacheControl
- * @author kai
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Cacheable {
-  boolean eager() default false;
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/ETag.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/ETag.java
deleted file mode 100644 (file)
index d2542dc..0000000
+++ /dev/null
@@ -1,84 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Über diese Annotation kann der Inhalt des <code>ETag/code>-Headers
- * gesteuert werden.
- * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
- * <p>
- * Wenn eine Methode annotiert wird, muss diese eine Instanz von
- * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
- * <code>String</code> liefern.
- * <p>
- * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den
- * <code>ETag</code>-Header übergeben werden.
- * Da dieser Wert somit statisch ist, macht es nur Sinn, Klassen mit dieser
- * Annotation zu markieren, die ausschließlich statische Ressourcen ausliefern,
- * die sich nur mit der Neuinstallation der Webanwendung ändern.
- * Wenn sich (z.B. nach einer Neuinstallation der Webanwendung) die statischen
- * Ressourcen geändert haben, muss der übergebene statische ETag geändert
- * werden, da Caches sonst weiterhin die alten Ressourcen ausliefern!
- * </p>
- * Frei wählbares ETag nach
- * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19 RFC 2616, Abschnitt 14.19 ETag}.
- * Der gelieferte Wert darf die vom RFC geforderten Anführungszeichen noch nicht
- * enthalten, da er, wenn <code>vary</code> gesetzt ist, noch um je nach
- * erfolgter Content-Negotiation varriierende Teile ergänzt wird.
- * <p>
- * Die erzeugten <code>ETag</code>'s können über die Annotations-Parameter
- * <code>weak</code> und <code>vary</code> weiter gesteuert werden.
- * <ul>
- * <li>
- * Wenn der Parameter <strong>weak</strong> auf den wert <code>true</code>
- * gesetzt wird, wird ein schwaches <code>ETag</code> erezeugt und der
- * Vergleichs-Algorithmus verhält sich entsprechend anders (siehe:
- * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3 RFC 2616, Abschnitt 13.3.3 Weak and Strong Validators}).
- * </li>
- * <li>
- * Über den Parameter <strong>vary</strong> kann Juplo-CacheControl damit
- * beauftragt werden, die Nötigen Maßnahmen für korrektes Content-Negotiating
- * zu ergreifen (siehe:
- * {@linkplain http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.6 RFC 2616, Abschnitt 13.6 Caching Negotiated Responses}).
- * Als Eingabe werden die Header-Namen erwertet, die zu unterschiedlichen
- * Ergebnissen der Content-Negotiation führen können (hier können folgende
- * Header angegeben werden: <code>Accept</code>, <code>Accept-Charset</code>,
- * <code>Accept-Encoding</code> und <code>Accept-Language</code>).
- * Juplo-CacheControl modifizert den übergebenen <code>ETag</code> dann so,
- * dass unterschiedliche Resultate der Content-Negotiation unterschieden
- * werden können.
- * Außerdem wird der <code>Vary</code>-Header entsprechend gesetzt.
- *</li>
- * </ul>
- * <strong>Zu beachten:</strong>
- * Wenn zugleich die Annotation {@link CacheSeconds} verwendet wird, wird
- * die mit dieser Annotation markierte Methode nur aufgerufen, wenn die mit
- * der Annotation {@link CacheSeconds} markierte Methode einen Wert größer
- * oder gleich <code>0</code> liefert, bzw. für die mit Annotation
- * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich
- * <code>0</code> festgelegt wurde.
- *
- * @see #getCacheSeconds(javax.servlet.http.HttpServletRequest)
- *
- * @author kai
- * @see Cacheable
- * @see CacheSeconds
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE, ElementType.METHOD })
-public @interface ETag {
-
-  public final static String ACCEPT = "Accept";
-  public final static String ACCEPT_CHARSET = "Accept-Charset";
-  public final static String ACCEPT_ENCODING = "Accept-Encoding";
-  public final static String ACCEPT_LANGUAGE = "Accept-Language";
-
-
-  String value() default "X";
-  boolean weak() default false;
-  String[] vary() default {};
-}
diff --git a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/LastModified.java b/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/LastModified.java
deleted file mode 100644 (file)
index be28e0e..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-package de.halbekunst.juplo.cachecontrol.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * Über diese Annotation kann der Inhalt des <code>Last-Modified</code>-Headers
- * gesteuert werden.
- * Mit dieser Annotation können Klassen oder Methoden merkiert werden.
- * <p>
- * Wenn eine Methode annotiert wird, muss diese eine Instanz von
- * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen
- * Wert liefern, der sich nach <code>long</code> casten lässt.
- * Die Signatur der Methode entspricht der Methode
- * {@link HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)}
- * aus dem <code>HttpServlet</code>-Interface.
- * Um das Cache-Verhalten ein existierendes Servlet, das diese Methode bereits
- * implementiert, mit Juplo-CacheControll zu verbessern, kann als erste
- * Maßnahme daher einfach diese Methode mit dieser Annotation markiert werden.
- * <p>
- * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den
- * <code>Last-Modified</code>-Header übergeben werden.
- * Da dieser Wert somit statisch ist, macht es nur Sinn, Klassen mit dieser
- * Annotation zu markieren, die ausschließlich statische Ressourcen ausliefern,
- * die sich nur mit der Neuinstallation der Webanwendung ändern.
- * </p>
- * Über diese Annotation wird der Zeitpunkt gesteuert, zu dem die gelieferte
- * Ressource zuletzt verändert wurde.
- * Erwartet wird eine Zeitangabe in Millisekunden seit dem Unix-0-Zeitpunkt,
- * die dann an {@link HttpServletResponse#setDateHeader(String, long)}
- * weitergegeben wird.
- * <p>
- * <strong>Zu beachten:</strong>
- * Wenn zugleich die Annotation {@link CacheSeconds} verwendet wird, wird
- * die mit dieser Annotation markierte Methode nur aufgerufen, wenn die mit
- * der Annotation {@link CacheSeconds} markierte Methode einen Wert größer
- * oder gleich <code>0</code> liefert, bzw. für die mit Annotation
- * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich
- * <code>0</code> festgelegt wurde.
- *
- * @author kai
- * @see Cacheable
- * @see CacheSeconds
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ ElementType.TYPE, ElementType.METHOD })
-public @interface LastModified {
-  long value() default 0;
-}
diff --git a/cachecontrol/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java b/cachecontrol/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java
deleted file mode 100644 (file)
index 285d173..0000000
+++ /dev/null
@@ -1,621 +0,0 @@
-package com.meterware.servletunit;
-/********************************************************************************************************************
-* $Id: ServletUnitHttpResponse.java 751 2006-03-24 19:59:12Z russgold $
-*
-* Copyright (c) 2000-2004,2006, Russell Gold
-*
-* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
-* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
-* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
-* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-*
-* The above copyright notice and this permission notice shall be included in all copies or substantial portions
-* of the Software.
-*
-* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
-* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
-* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
-* DEALINGS IN THE SOFTWARE.
-*
-*******************************************************************************************************************/
-import com.meterware.httpunit.HttpUnitUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.UnsupportedEncodingException;
-
-import java.util.*;
-import java.text.SimpleDateFormat;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletResponse;
-
-
-class ServletUnitHttpResponse implements HttpServletResponse {
-
-    // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
-    private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy HH:mm:ss z";
-    private boolean _committed;
-    private Locale _locale = Locale.getDefault();
-
-    private static final Hashtable ENCODING_MAP = new Hashtable();
-
-    /**
-     * @deprecated Use encodeURL(String url)
-     */
-    public String encodeUrl( String url ) {
-        return encodeURL( url );
-    }
-
-
-    /**
-     * Adds the specified cookie to the response.  It can be called
-     * multiple times to set more than one cookie.
-     */
-    public void addCookie( Cookie cookie ) {
-        _cookies.addElement( cookie );
-    }
-
-
-    /**
-     * Checks whether the response message header has a field with
-     * the specified name.
-     */
-    public boolean containsHeader( String name ) {
-        return _headers.containsKey( name.toUpperCase() );
-    }
-
-
-    /**
-     * @deprecated Use encodeRedirectURL(String url)
-     **/
-    public String encodeRedirectUrl( String url ) {
-        return encodeRedirectURL( url );
-    }
-
-
-    /**
-     * Encodes the specified URL by including the session ID in it,
-     * or, if encoding is not needed, returns the URL unchanged.
-     * The implementation of this method should include the logic to
-     * determine whether the session ID needs to be encoded in the URL.
-     * For example, if the browser supports cookies, or session
-     * tracking is turned off, URL encoding is unnecessary.
-     **/
-    public String encodeURL( String url ) {
-        return url;
-    }
-
-
-    /**
-     * Encodes the specified URL for use in the
-     * <code>sendRedirect</code> method or, if encoding is not needed,
-     * returns the URL unchanged.  The implementation of this method
-     * should include the logic to determine whether the session ID
-     * needs to be encoded in the URL.  Because the rules for making
-     * this determination differ from those used to decide whether to
-     * encode a normal link, this method is seperate from the
-     * <code>encodeUrl</code> method.
-     **/
-    public String encodeRedirectURL( String url ) {
-        return url;
-    }
-
-
-    /**
-     * Sends a temporary redirect response to the client using the
-     * specified redirect location URL.  The URL must be absolute (for
-     * example, <code><em>https://hostname/path/file.html</em></code>).
-     * Relative URLs are not permitted here.
-     */
-    public void sendRedirect( String location ) throws IOException {
-        setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY );
-        setHeader( "Location", location );
-    }
-
-
-    /**
-     * Sends an error response to the client using the specified status
-     * code and descriptive message.  If setStatus has previously been
-     * called, it is reset to the error status code.  The message is
-     * sent as the body of an HTML page, which is returned to the user
-     * to describe the problem.  The page is sent with a default HTML
-     * header; the message is enclosed in simple body tags
-     * (&lt;body&gt;&lt;/body&gt;).
-     **/
-    public void sendError( int sc ) throws IOException {
-        sendError( sc, "" );
-    }
-
-
-    /**
-     * Sends an error response to the client using the specified status
-     * code and descriptive message.  If setStatus has previously been
-     * called, it is reset to the error status code.  The message is
-     * sent as the body of an HTML page, which is returned to the user
-     * to describe the problem.  The page is sent with a default HTML
-     * header; the message is enclosed in simple body tags
-     * (&lt;body&gt;&lt;/body&gt;).
-     **/
-    public void sendError(int sc, String msg) throws IOException {
-        setStatus( sc );
-        _statusMessage = msg;
-
-        _writer = null;
-        _servletStream = null;
-
-        setContentType( "text/html" );
-        getWriter().println( "<html><head><title>" + msg + "</title></head><body>" + msg + "</body></html>" );
-    }
-
-
-    /**
-     * Sets the status code for this response.  This method is used to
-     * set the return status code when there is no error (for example,
-     * for the status codes SC_OK or SC_MOVED_TEMPORARILY).  If there
-     * is an error, the <code>sendError</code> method should be used
-     * instead.
-     **/
-    public void setStatus( int sc ) {
-        _status = sc;
-    }
-
-
-    /**
-     * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
-     * To set a status code use setStatus(int), to send an error with a description
-     * use sendError(int, String). Sets the status code and message for this response.
-     **/
-    public void setStatus( int sc, String msg ) {
-        setStatus( sc );
-    }
-
-
-    /**
-     * Adds a field to the response header with the given name and value.
-     * If the field had already been set, the new value overwrites the
-     * previous one.  The <code>containsHeader</code> method can be
-     * used to test for the presence of a header before setting its
-     * value.
-     **/
-    public void setHeader( String name, String value ) {
-        ArrayList values = new ArrayList();
-        values.add( value );
-        synchronized (_headers) {
-            _headers.put( name.toUpperCase(), values );
-        }
-    }
-
-
-    /**
-     * Adds a field to the response header with the given name and
-     * integer value.  If the field had already been set, the new value
-     * overwrites the previous one.  The <code>containsHeader</code>
-     * method can be used to test for the presence of a header before
-     * setting its value.
-     **/
-    public void setIntHeader( String name, int value ) {
-        setHeader( name, asHeaderValue( value ) );
-    }
-
-
-    private String asHeaderValue( int value ) {
-        return Integer.toString( value );
-    }
-
-
-    /**
-     * Adds a field to the response header with the given name and
-     * date-valued field.  The date is specified in terms of
-     * milliseconds since the epoch.  If the date field had already
-     * been set, the new value overwrites the previous one.  The
-     * <code>containsHeader</code> method can be used to test for the
-     * presence of a header before setting its value.
-     **/
-    public void setDateHeader( String name, long date ) {
-        setHeader( name, asDateHeaderValue( date ) );
-    }
-
-
-    private String asDateHeaderValue( long date ) {
-        Date value = new Date( date );
-        SimpleDateFormat formatter = new SimpleDateFormat( RFC1123_DATE_SPEC, Locale.US );
-        formatter.setTimeZone( TimeZone.getTimeZone( "Greenwich Mean Time" ) );
-        return formatter.format( value );
-    }
-
-
-    /**
-     * Returns the name of the character set encoding used for
-     * the MIME body sent by this response.
-     **/
-    public String getCharacterEncoding() {
-        return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET : _encoding;
-    }
-
-
-    /**
-     * Sets the content type of the response the server sends to
-     * the client. The content type may include the type of character
-     * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
-     *
-     * <p>You can only use this method once, and you should call it
-     * before you obtain a <code>PrintWriter</code> or
-     * {@link ServletOutputStream} object to return a response.
-     **/
-    public void setContentType( String type ) {
-        String[] typeAndEncoding = HttpUnitUtils.parseContentTypeHeader( type );
-
-        _contentType = typeAndEncoding[0];
-        if (typeAndEncoding[1] != null) _encoding = typeAndEncoding[1];
-    }
-
-
-    /**
-     * Returns a {@link ServletOutputStream} suitable for writing binary
-     * data in the response. The servlet engine does not encode the
-     * binary data.
-     *
-     * @exception IllegalStateException if you have already called the <code>getWriter</code> method
-     **/
-    public ServletOutputStream getOutputStream() throws IOException {
-        if (_writer != null) throw new IllegalStateException( "Tried to create output stream; writer already exists" );
-        if (_servletStream == null) {
-            _outputStream = new ByteArrayOutputStream();
-            _servletStream = new ServletUnitOutputStream( _outputStream );
-        }
-        return _servletStream;
-    }
-
-
-    /**
-     * Returns a <code>PrintWriter</code> object that you
-     * can use to send character text to the client.
-     * The character encoding used is the one specified
-     * in the <code>charset=</code> property of the
-     * {@link #setContentType} method, which you must call
-     * <i>before</i> you call this method.
-     *
-     * <p>If necessary, the MIME type of the response is
-     * modified to reflect the character encoding used.
-     *
-     * <p> You cannot use this method if you have already
-     * called {@link #getOutputStream} for this
-     * <code>ServletResponse</code> object.
-     *
-     * @exception UnsupportedEncodingException  if the character encoding specified in
-     *                                         <code>setContentType</code> cannot be
-     *                                         used
-     *
-     * @exception IllegalStateException        if the <code>getOutputStream</code>
-     *                                                 method has already been called for this
-     *                                         response object; in that case, you can't
-     *                                         use this method
-     *
-     **/
-    public PrintWriter getWriter() throws UnsupportedEncodingException {
-        if (_servletStream != null) throw new IllegalStateException( "Tried to create writer; output stream already exists" );
-        if (_writer == null) {
-            _outputStream = new ByteArrayOutputStream();
-            _writer = new PrintWriter( new OutputStreamWriter( _outputStream, getCharacterEncoding() ) );
-        }
-        return _writer;
-    }
-
-
-    /**
-     * Sets the length of the content the server returns
-     * to the client. In HTTP servlets, this method sets the
-     * HTTP Content-Length header.
-     **/
-    public void setContentLength( int len ) {
-        setIntHeader( "Content-Length", len );
-    }
-
-
-//------------------------------- the following methods are new in JSDK 2.2 ----------------------
-
-
-    /**
-     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
-     **/
-    public void addHeader( String name, String value ) {
-        synchronized (_headers) {
-            String key = name.toUpperCase();
-            ArrayList values = (ArrayList) _headers.get( key );
-            if (values == null) {
-                values = new ArrayList();
-                _headers.put( key, values );
-            }
-            values.add( value );
-        }
-    }
-
-
-    /**
-     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
-     **/
-    public void addIntHeader( String name, int value ) {
-        addHeader( name, asHeaderValue( value ) );
-    }
-
-
-    /**
-     * Adds a response header with the given name and value. This method allows response headers to have multiple values.
-     **/
-    public void addDateHeader( String name, long value ) {
-        addHeader( name, asDateHeaderValue( value ) );
-    }
-
-
-    /**
-     * Sets the preferred buffer size for the body of the response. The servlet container
-     * will use a buffer at least as large as the size requested. The actual buffer size
-     * used can be found using getBufferSize.
-     **/
-    public void setBufferSize( int size ) {
-        if (getContents().length != 0) throw new IllegalStateException( "May not set buffer size after data is written" );
-    }
-
-
-    /**
-     * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
-     **/
-    public int getBufferSize() {
-        return 0;
-    }
-
-
-    /**
-     * Returns a boolean indicating if the response has been committed. A committed response has
-     * already had its status code and headers written.
-     **/
-    public boolean isCommitted() {
-        return _committed;
-    }
-
-
-    /**
-     * Forces any content in the buffer to be written to the client. A call to this method automatically
-     * commits the response, meaning the status code and headers will be written.
-     **/
-    public void flushBuffer() throws IOException {
-        _committed = true;
-    }
-
-
-    /**
-     * Clears any data that exists in the buffer as well as the status code and headers.
-     * If the response has been committed, this method throws an IllegalStateException.
-     **/
-    public void reset() {
-        resetBuffer();
-        _headers.clear();
-        _headersComplete = false;
-        _status = SC_OK;
-    }
-
-
-    /**
-     * Sets the locale of the response, setting the headers (including the Content-Type's charset)
-     * as appropriate. This method should be called before a call to getWriter().
-     * By default, the response locale is the default locale for the server.
-     **/
-    public void setLocale( Locale locale ) {
-        _locale = locale;
-        if (_encoding == null) {
-            for (Iterator it = ENCODING_MAP.entrySet().iterator(); it.hasNext();) {
-                Map.Entry entry = (Map.Entry) it.next();
-                String locales = (String) entry.getValue();
-                if (locales.indexOf( locale.getLanguage() ) >= 0 || locales.indexOf( locale.toString() ) >= 0) {
-                    _encoding = (String) entry.getKey();
-                    return;
-                }
-            }
-        }
-    }
-
-
-    /**
-     * Returns the locale assigned to the response.
-     **/
-    public Locale getLocale() {
-        return _locale;
-    }
-
-
-//----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
-
-
-    /**
-     * Clears the content of the underlying buffer in the response without clearing headers or status code.
-     * If the response has been committed, this method throws an IllegalStateException.
-     *
-     * @since 1.3
-     */
-    public void resetBuffer() {
-        if (_committed) throw new IllegalStateException( "May not resetBuffer after response is committed" );
-        _outputStream = null;
-        _servletStream = null;
-        _writer = null;
-    }
-
-
-//---------------------------------------------- package methods --------------------------------------------------
-
-    /**
-     * Returns the contents of this response.
-     **/
-    byte[] getContents() {
-        if (_outputStream == null) {
-            return new byte[0];
-        } else {
-            if (_writer != null) _writer.flush();
-            return _outputStream.toByteArray();
-        }
-    }
-
-
-    /**
-     * Returns the status of this response.
-     **/
-    int getStatus() {
-        return _status;
-    }
-
-
-    /**
-     * Returns the message associated with this response's status.
-     **/
-    String getMessage() {
-        return _statusMessage;
-    }
-
-
-    public String[] getHeaderFieldNames() {
-        if (!_headersComplete) completeHeaders();
-        Vector names = new Vector();
-        for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
-            names.addElement( e.nextElement() );
-        }
-        String[] result = new String[ names.size() ];
-        names.copyInto( result );
-        return result;
-    }
-
-
-    /**
-     * Returns the headers defined for this response.
-     **/
-    String getHeaderField( String name ) {
-        if (!_headersComplete) completeHeaders();
-
-        ArrayList values;
-        synchronized (_headers) {
-            values = (ArrayList) _headers.get( name.toUpperCase() );
-        }
-
-        return values == null ? null : (String) values.get( 0 );
-     }
-
-
-     /**
-     * Return an array of all the header values associated with the
-     * specified header name, or an zero-length array if there are no such
-     * header values.
-     *
-     * @param name Header name to look up
-     */
-    public String[] getHeaderFields(String name) {
-        if (!_headersComplete) completeHeaders();
-        ArrayList values;
-        synchronized (_headers) {
-            values = (ArrayList) _headers.get(name.toUpperCase());
-        }
-        if (values == null)
-            return (new String[0]);
-        String results[] = new String[values.size()];
-        return ((String[]) values.toArray(results));
-
-    }
-
-//--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
-
-    public void setCharacterEncoding(String string) {
-        _encoding = string;
-    }
-
-    /**
-     * Returns the content type defined for this response.
-     **/
-    public String getContentType() {
-        return _contentType;
-    }
-
-
-//------------------------------------------- private members ------------------------------------
-
-
-    private String _contentType = "text/plain";
-
-    private String _encoding;
-
-    private PrintWriter  _writer;
-
-    private ServletOutputStream _servletStream;
-
-    private ByteArrayOutputStream _outputStream;
-
-    private int _status = SC_OK;
-
-    private String _statusMessage = "OK";
-
-    private final Hashtable _headers = new Hashtable();
-
-    private boolean _headersComplete;
-
-    private Vector  _cookies = new Vector();
-
-
-    private void completeHeaders() {
-        if (_headersComplete) return;
-        addCookieHeader();
-        setHeader( "Content-Type", _contentType + "; charset=" + getCharacterEncoding() );
-        _headersComplete = true;
-    }
-
-
-    private void addCookieHeader() {
-        if (_cookies.isEmpty()) return;
-
-        StringBuffer sb = new StringBuffer();
-        for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
-            Cookie cookie = (Cookie) e.nextElement();
-            sb.append( cookie.getName() ).append( '=' ).append( cookie.getValue() );
-            if (cookie.getPath() != null) sb.append( ";path=" ).append( cookie.getPath() );
-            if (cookie.getDomain() != null) sb.append( ";domain=" ).append( cookie.getDomain() );
-            if (e.hasMoreElements()) sb.append( ',' );
-        }
-        setHeader( "Set-Cookie", sb.toString() );
-    }
-
-
-
-    static {
-        ENCODING_MAP.put( "iso-8859-1", "ca da de en es fi fr is it nl no pt sv " );
-        ENCODING_MAP.put( "iso-8859-2", "cs hr hu pl ro sh sk sl sq " );
-        ENCODING_MAP.put( "iso-8859-4", "et lt lv ");
-        ENCODING_MAP.put( "iso-8859-5", "be bg mk ru sr uk " );
-        ENCODING_MAP.put( "iso-8859-6", "ar " );
-        ENCODING_MAP.put( "iso-8859-7", "el " );
-        ENCODING_MAP.put( "iso-8859-8", "iw he " );
-        ENCODING_MAP.put( "iso-8859-9", "tr " );
-
-        ENCODING_MAP.put("Shift_JIS", "ja ");
-        ENCODING_MAP.put("EUC-KR",    "ko ");
-        ENCODING_MAP.put("TIS-620",   "th ");
-        ENCODING_MAP.put("GB2312",    "zh " );
-        ENCODING_MAP.put("Big5",      "zh_TW zh_HK " );
-    }
-
-}
-
-
-
-class ServletUnitOutputStream extends ServletOutputStream {
-
-    ServletUnitOutputStream( ByteArrayOutputStream stream ) {
-        _stream = stream;
-    }
-
-
-    public void write( int aByte ) throws IOException {
-        _stream.write( aByte );
-    }
-
-    private ByteArrayOutputStream _stream;
-}
diff --git a/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/ParameterGuessingTest.java b/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/ParameterGuessingTest.java
deleted file mode 100644 (file)
index e7b64f6..0000000
+++ /dev/null
@@ -1,575 +0,0 @@
-package de.halbekunst.juplo.cachecontrol;
-
-import com.meterware.httpunit.WebResponse;
-import de.halbekunst.juplo.test.HttpTestCase;
-import java.net.URLEncoder;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Locale;
-import java.util.Set;
-import org.junit.Assert;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.test.context.ContextConfiguration;
-import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
-
-
-
-/**
- *
- * @author kai
- */
-@RunWith(SpringJUnit4ClassRunner.class)
-@ContextConfiguration(locations = {
-  "classpath:/config.xml"
-})
-public class ParameterGuessingTest extends HttpTestCase {
-  private final static Logger log = LoggerFactory.getLogger(ParameterGuessingTest.class);
-
-
-  public ParameterGuessingTest() {
-    super("src/test/resources/web.xml");
-  }
-
-
-  @Test
-  public void testNothingSet() throws Exception {
-
-    log.info("-------- Test: Servlet does not implement getLastModified() and sets no Headers...");
-
-    WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16");
-    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
-    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
-    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
-    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
-    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
-    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
-    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
-    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-    long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
-    long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
-    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
-  }
-
-  @Test
-  public void testSetUnfilteredHeaders() throws Exception {
-
-    log.info("-------- Test: Servlet sets unfiltered Headers...");
-
-    WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16&X-Debug=bla&Age=34&Content-Language=de");
-    Assert.assertEquals("bla", response.getHeaderField("X-Debug"));
-    Assert.assertEquals("34", response.getHeaderField("Age"));
-    Assert.assertEquals("de", response.getHeaderField("Content-Language"));
-    Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
-    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
-    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
-    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
-    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
-    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
-    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
-    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-    long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
-    long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
-    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
-  }
-
-  @Test
-  public void testETagSet() throws Exception {
-
-    log.info("-------- Test: Servlet sets Header \"ETag\"");
-
-    SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
-    WebResponse response;
-    long date, expires;
-
-    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bla\"", "UTF-8"));
-    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
-    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
-    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
-    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
-    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
-    Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
-    Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
-    date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
-    expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
-    Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
-
-    response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bÄl\"a\"", "UTF-8"));
-    Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
-    Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
-    Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
-    Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
-    Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
-    Assert.asse