From: Kai Moritz Date: Thu, 23 Aug 2012 20:20:53 +0000 (+0200) Subject: Projekt von de.halbekunst.juplo nach de.juplo verschoben und aufgeräumt X-Git-Url: http://juplo.de/gitweb/?a=commitdiff_plain;h=a6e0e65cce68acb20abc6ca935471611a740c342;p=percentcodec Projekt von de.halbekunst.juplo nach de.juplo verschoben und aufgeräumt --- diff --git a/accelerator-examples/jsp/jetty.sh b/accelerator-examples/jsp/jetty.sh new file mode 100755 index 00000000..4950796b --- /dev/null +++ b/accelerator-examples/jsp/jetty.sh @@ -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 index 00000000..dc99eb68 --- /dev/null +++ b/accelerator-examples/jsp/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + de.juplo + juplo-accelerator-examples + 0.1-SNAPSHOT + + + juplo-accelerator-examples-jsp + war + Juplo - Accelerator-Examples: JSP-Pages + + diff --git a/accelerator-examples/jsp/src/main/resources/config.xml b/accelerator-examples/jsp/src/main/resources/config.xml new file mode 100644 index 00000000..a04b130c --- /dev/null +++ b/accelerator-examples/jsp/src/main/resources/config.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator-examples/jsp/src/main/resources/log4j.xml b/accelerator-examples/jsp/src/main/resources/log4j.xml new file mode 100644 index 00000000..b1e35f82 --- /dev/null +++ b/accelerator-examples/jsp/src/main/resources/log4j.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + 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 index 00000000..4b85b448 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/WEB-INF/c.tld @@ -0,0 +1,578 @@ + + + + + + JSTL 1.1 core library + JSTL core + 1.1 + c + http://java.sun.com/jsp/jstl/core + + + + Provides core validation features for JSTL tags. + + + org.apache.taglibs.standard.tlv.JstlCoreTLV + + + + + + Catches any Throwable that occurs in its body and optionally + exposes it. + + catch + org.apache.taglibs.standard.tag.common.core.CatchTag + JSP + + +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. + + var + false + false + + + + + + Simple conditional tag that establishes a context for + mutually exclusive conditional operations, marked by + <when> and <otherwise> + + choose + org.apache.taglibs.standard.tag.common.core.ChooseTag + JSP + + + + + 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 + + if + org.apache.taglibs.standard.tag.rt.core.IfTag + JSP + + +The test condition that determines whether or +not the body content should be processed. + + test + true + true + boolean + + + +Name of the exported scoped variable for the +resulting value of the test condition. The type +of the scoped variable is Boolean. + + var + false + false + + + +Scope for var. + + scope + false + false + + + + + + Retrieves an absolute or relative URL and exposes its contents + to either the page, a String in 'var', or a Reader in 'varReader'. + + import + org.apache.taglibs.standard.tag.rt.core.ImportTag + org.apache.taglibs.standard.tei.ImportTEI + JSP + + +The URL of the resource to import. + + url + true + true + + + +Name of the exported scoped variable for the +resource's content. The type of the scoped +variable is String. + + var + false + false + + + +Scope for var. + + scope + false + false + + + +Name of the exported scoped variable for the +resource's content. The type of the scoped +variable is Reader. + + varReader + false + false + + + +Name of the context when accessing a relative +URL resource that belongs to a foreign +context. + + context + false + true + + + +Character encoding of the content at the input +resource. + + charEncoding + false + true + + + + + + The basic iteration tag, accepting many different + collection types and supporting subsetting and other + functionality + + forEach + org.apache.taglibs.standard.tag.rt.core.ForEachTag + org.apache.taglibs.standard.tei.ForEachTEI + JSP + + +Collection of items to iterate over. + + items + false + true + java.lang.Object + + java.lang.Object + + + + +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. + + begin + false + true + int + + + +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. + + end + false + true + int + + + +Iteration will only process every step items of +the collection, starting with the first one. + + step + false + true + int + + + +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. + + var + false + false + + + +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. + + varStatus + false + false + + + + + + Iterates over tokens, separated by the supplied delimeters + + forTokens + org.apache.taglibs.standard.tag.rt.core.ForTokensTag + JSP + + +String of tokens to iterate over. + + items + true + true + java.lang.String + + java.lang.String + + + + +The set of delimiters (the characters that +separate the tokens in the string). + + delims + true + true + java.lang.String + + + +Iteration begins at the token located at the +specified index. First token has index 0. + + begin + false + true + int + + + +Iteration ends at the token located at the +specified index (inclusive). + + end + false + true + int + + + +Iteration will only process every step tokens +of the string, starting with the first one. + + step + false + true + int + + + +Name of the exported scoped variable for the +current item of the iteration. This scoped +variable has nested visibility. + + var + false + false + + + +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. + + varStatus + false + false + + + + + + Like <%= ... >, but for expressions. + + out + org.apache.taglibs.standard.tag.rt.core.OutTag + JSP + + +Expression to be evaluated. + + value + true + true + + + +Default value if the resulting value is null. + + default + false + true + + + +Determines whether characters <,>,&,'," in the +resulting string should be converted to their +corresponding character entity codes. Default value is +true. + + escapeXml + false + true + + + + + + + Subtag of <choose> that follows <when> tags + and runs only if all of the prior conditions evaluated to + 'false' + + otherwise + org.apache.taglibs.standard.tag.common.core.OtherwiseTag + JSP + + + + + Adds a parameter to a containing 'import' tag's URL. + + param + org.apache.taglibs.standard.tag.rt.core.ParamTag + JSP + + +Name of the query string parameter. + + name + true + true + + + +Value of the parameter. + + value + false + true + + + + + + Redirects to a new URL. + + redirect + org.apache.taglibs.standard.tag.rt.core.RedirectTag + JSP + + +The URL of the resource to redirect to. + + url + false + true + + + +Name of the context when redirecting to a relative URL +resource that belongs to a foreign context. + + context + false + true + + + + + + Removes a scoped variable (from a particular scope, if specified). + + remove + org.apache.taglibs.standard.tag.common.core.RemoveTag + empty + + +Name of the scoped variable to be removed. + + var + true + false + + + +Scope for var. + + scope + false + false + + + + + + Sets the result of an expression evaluation in a 'scope' + + set + org.apache.taglibs.standard.tag.rt.core.SetTag + JSP + + +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. + + var + false + false + + + +Expression to be evaluated. + + value + false + true + + java.lang.Object + + + + +Target object whose property will be set. Must evaluate to +a JavaBeans object with setter property property, or to a +java.util.Map object. + + target + false + true + + + +Name of the property to be set in the target object. + + property + false + true + + + +Scope for var. + + scope + false + false + + + + + + Creates a URL with optional query parameters. + + url + org.apache.taglibs.standard.tag.rt.core.UrlTag + JSP + + +Name of the exported scoped variable for the +processed url. The type of the scoped variable is +String. + + var + false + false + + + +Scope for var. + + scope + false + false + + + +URL to be processed. + + value + false + true + + + +Name of the context when specifying a relative URL +resource that belongs to a foreign context. + + context + false + true + + + + + + Subtag of <choose> that includes its body if its + condition evalutes to 'true' + + when + org.apache.taglibs.standard.tag.rt.core.WhenTag + JSP + + +The test condition that determines whether or not the +body content should be processed. + + test + true + true + boolean + + + + 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 index 00000000..1931d893 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/WEB-INF/included.jsp @@ -0,0 +1,2 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%> +

Hello World, again...

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 index 00000000..938240c7 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,46 @@ + + + + + + + contextConfigLocation + classpath:/config.xml + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + + accelerator + de.juplo.accelerator.AcceleratorFilter + + + + logger + de.juplo.testingtools.LoggingHttpServletResponseFilter + + + + + + + logger + *.jsp + + + + accelerator + *.jsp + + + 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 index 00000000..e40c2130 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/faulty-page.jsp @@ -0,0 +1,21 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> +<%@taglib uri="/WEB-INF/c.tld" prefix="c"%> + + + + + + Faulty Page + + +

Faulty Page

+

+ This page will raise an error. + after a while! +

+

+ Ecactly, NOW: + <% if (true) throw new RuntimeException("Oh no!"); %> +

+ + diff --git a/accelerator-examples/jsp/src/main/webapp/index.html b/accelerator-examples/jsp/src/main/webapp/index.html new file mode 100644 index 00000000..2246263f --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/index.html @@ -0,0 +1,17 @@ + + + + + + JSP Examples + + +

JSP Examples

+ + + 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 index 00000000..35e49058 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/page-with-forward.jsp @@ -0,0 +1,14 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> + + + + + + Page with forward + + +

Hello World!

+

This should not be seen, because the page is forwardes to /simple-page.jsp

+ + + 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 index 00000000..3347af77 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/page-with-include.jsp @@ -0,0 +1,14 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> +<%@taglib uri="/WEB-INF/c.tld" prefix="c"%> + + + + + + Page with include + + +

Hello World!

+ ${i}: + + 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 index 00000000..99d92d73 --- /dev/null +++ b/accelerator-examples/jsp/src/main/webapp/simple-page.jsp @@ -0,0 +1,13 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> + + + + + + Simple Page + + +

Simple Page

+

This page is a simple jsp-page

+ + 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 index 00000000..5000a9c5 --- /dev/null +++ b/accelerator-examples/jsp/src/test/java/de/juplo/accelerator/examples/JspTest.java @@ -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 index 00000000..4378a128 --- /dev/null +++ b/accelerator-examples/jsp/tomcat.sh @@ -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 index 00000000..fcd9e1d9 --- /dev/null +++ b/accelerator-examples/pom.xml @@ -0,0 +1,202 @@ + + + + 4.0.0 + + de.juplo + juplo-accelerator-examples + Juplo - Accelerator-Examples + 0.1-SNAPSHOT + pom + http://www.juplo.de/accelerator-examples + + + 2.0.6 + + + + + kai + Kai Moritz + kai@juplo.de + + + + + jsp + static + servlet + spring + + + + + UTF-8 + + 1.6.11 + 1.3.2 + 6.0.29 + 1.0 + 1.2 + 4.8.1 + 1.0 + 0.1-SNAPSHOT + 1.2.16 + 2.5 + slf4j-log4j12 + 1.6.1 + 3.0.6.RELEASE + + + + + + de.juplo + juplo-accelerator + ${juplo-accelerator.version} + + + + javax.servlet + jstl + ${jstl.version} + runtime + + + + org.springframework + spring-webmvc + ${springframework.version} + runtime + + + org.springframework + spring-context + ${springframework.version} + runtime + + + + commons-logging + commons-logging + + + + + org.springframework + spring-aspects + ${springframework.version} + runtime + + + org.aspectj + aspectjrt + ${aspectj.version} + runtime + + + + org.slf4j + ${slf4j.binding} + ${slf4j.version} + runtime + + + + de.juplo + juplo-testingtools + ${juplo-testingtools.version} + + + org.apache.tomcat + jasper + ${jasper.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + utf8 + true + + + + org.mortbay.jetty + jetty-maven-plugin + + + + 0.0.0.0 + 8080 + 2 + + + 0 + + + ${project.basedir}/src/main/webapp/WEB-INF + + **/*.jsp + + + **/*.properties + **/*.xml + + + + + + + org.codehaus.mojo + tomcat-maven-plugin + 1.1 + + / + UTF-8 + + + + + + + + + maven-changes-plugin + + + maven-checkstyle-plugin + + + org.codehaus.mojo + cobertura-maven-plugin + 2.0 + + + maven-javadoc-plugin + + + maven-jxr-plugin + + + maven-pmd-plugin + + true + 1.5 + + + + maven-surefire-report-plugin + 2.7.2 + + + + + diff --git a/accelerator-examples/servlet/jetty.sh b/accelerator-examples/servlet/jetty.sh new file mode 100755 index 00000000..4950796b --- /dev/null +++ b/accelerator-examples/servlet/jetty.sh @@ -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 index 00000000..92a5e6fc --- /dev/null +++ b/accelerator-examples/servlet/pom.xml @@ -0,0 +1,17 @@ + + + + 4.0.0 + + + de.juplo + juplo-accelerator-examples + 0.1-SNAPSHOT + + + juplo-accelerator-examples-servlet + war + Juplo - Accelerator-Examples: Servlet + + 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 index 00000000..d9eee5fb --- /dev/null +++ b/accelerator-examples/servlet/src/main/java/de/juplo/accelerator/examples/FaultyServlet.java @@ -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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator-examples/servlet/src/main/resources/log4j.xml b/accelerator-examples/servlet/src/main/resources/log4j.xml new file mode 100644 index 00000000..b1e35f82 --- /dev/null +++ b/accelerator-examples/servlet/src/main/resources/log4j.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + 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 index 00000000..994a904e --- /dev/null +++ b/accelerator-examples/servlet/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,78 @@ + + + + + + + contextConfigLocation + classpath:/config.xml + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + + accelerator + de.juplo.accelerator.AcceleratorFilter + + + + logger + de.juplo.testingtools.LoggingHttpServletResponseFilter + + + + + + + logger + /test-servlet + + + logger + /faulty-servlet + + + + accelerator + /test-servlet + + + accelerator + /faulty-servlet + + + + + + + test-servlet + de.juplo.testintools.TestServlet + + + faulty-servlet + de.juplo.accelerator.examples.FaultyServlet + + + + + + + test-servlet + /test-servlet + + + faulty-servlet + /faulty-servlet + + + diff --git a/accelerator-examples/servlet/src/main/webapp/index.html b/accelerator-examples/servlet/src/main/webapp/index.html new file mode 100644 index 00000000..c08ba7c0 --- /dev/null +++ b/accelerator-examples/servlet/src/main/webapp/index.html @@ -0,0 +1,40 @@ + + + + + + Servlet Examples + + +

Servlet Examples

+

This page is a static HTML-page

+ + + + 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 index 00000000..77592ab0 --- /dev/null +++ b/accelerator-examples/servlet/src/test/java/de/juplo/accelerator/examples/ServletTest.java @@ -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 index 00000000..4378a128 --- /dev/null +++ b/accelerator-examples/servlet/tomcat.sh @@ -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 index 00000000..4950796b --- /dev/null +++ b/accelerator-examples/spring/jetty.sh @@ -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 index 00000000..4eddbc9a --- /dev/null +++ b/accelerator-examples/spring/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + + de.juplo + juplo-accelerator-examples + 0.1-SNAPSHOT + + + juplo-accelerator-examples-spring + war + Juplo - Accelerator-Examples: Spring-MVC + + + + + + org.springframework + spring-context + ${springframework.version} + + + org.springframework + spring-web + ${springframework.version} + + + org.springframework + spring-webmvc + ${springframework.version} + + + + + 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 index 00000000..58c45c66 --- /dev/null +++ b/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/FaultyController.java @@ -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 index 00000000..66716587 --- /dev/null +++ b/accelerator-examples/spring/src/main/java/de/juplo/accelerator/examples/spring/SpringController.java @@ -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 requests = new TreeMap(); + private Map accessTimes = new HashMap(); + + + @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 index 00000000..fc1de60e --- /dev/null +++ b/accelerator-examples/spring/src/main/resources/config.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator-examples/spring/src/main/resources/log4j.xml b/accelerator-examples/spring/src/main/resources/log4j.xml new file mode 100644 index 00000000..b6030f96 --- /dev/null +++ b/accelerator-examples/spring/src/main/resources/log4j.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 index 00000000..26abdbdb --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp @@ -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"%> + + + + + + Page, that is handled by a controller + + + <% Map requests = (Map)request.getAttribute("requests"); + if (requests == null) { %> +

This page associated with a controller and should be viewed as such!

+

Go to: /spring-controller.html

+ <% } else { %> +

Controlled Page

+
+ + <%= request.getParameter("max-age") %><% } %>" /> +
+
+

(Uncached) requests so far:

+
    + <% for(Map.Entry entry : requests.entrySet()) { %> +
  1. + <%= entry.getKey() %>: + <% if (entry.getValue().equals("")) { %> + No parameters... + <% } else { %> + + <% for (String parameter : entry.getValue().split("&")) { %> + <%= parameter %> + <% } %> + + <% } %> +
  2. + <% } %> +
+ <% } %> +

This page was delivered via SPRING!

+ + 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 index 00000000..8718f54f --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp @@ -0,0 +1,17 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> + + + + + + Simple Page + + +

Faulty Page

+

This page will raise an error!

+

+ Ecactly, NOW: + <% if (true) throw new RuntimeException("Oh no!"); %> +

+ + 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 index 00000000..a329acd3 --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/index.jsp @@ -0,0 +1,35 @@ + + + + + + Simple Spring-MVC Examples + + +

Simple Spring-MVC Examples

+ +

This page was delivered via SPRING!

+

Note:

+

+ Since the org.springframework.web.servlet.DispatcherServlet + ist configured to handle all *.html-requests, the path + /index.html points to a view. +

+

+ Therefore, this page must be stored under + /WEB-INF/views/index.jsp in order to be served as + /index.html +

+

+ Additionatly, a file /index.jsp in the root-directory of + the webappliction is needed, to forward unqualified requests + (like http://HOSTNAME/) to the welcome-page served by + the Spring-Dispatcher-Servlet. +

+ + 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 index 00000000..892bf1a6 --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp @@ -0,0 +1,14 @@ +<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> + + + + + + Simple Page + + +

Hello World!

+

This is a really simple page...

+

This page was delivered via SPRING!

+ + 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 index 00000000..683aa66d --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,68 @@ + + + + + + + contextConfigLocation + classpath:/config.xml + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + + accelerator + de.juplo.accelerator.AcceleratorFilter + + + + logger + de.juplo.testingtools.LoggingHttpServletResponseFilter + + + + + + + logger + *.html + + + + accelerator + *.html + + + + + + + dispatcher-servlet + org.springframework.web.servlet.DispatcherServlet + + contextConfigLocation + + + + 1 + + + + + + + dispatcher-servlet + *.html + + + diff --git a/accelerator-examples/spring/src/main/webapp/index.jsp b/accelerator-examples/spring/src/main/webapp/index.jsp new file mode 100644 index 00000000..90820004 --- /dev/null +++ b/accelerator-examples/spring/src/main/webapp/index.jsp @@ -0,0 +1,8 @@ +<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" session="false" %> + +<%-- + +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 index 00000000..f2ab9f1e --- /dev/null +++ b/accelerator-examples/spring/src/test/java/de/juplo/accelerator/examples/SpringMVCTest.java @@ -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 index 00000000..4378a128 --- /dev/null +++ b/accelerator-examples/spring/tomcat.sh @@ -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 index 00000000..4950796b --- /dev/null +++ b/accelerator-examples/static/jetty.sh @@ -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 index 00000000..a4af6bac --- /dev/null +++ b/accelerator-examples/static/pom.xml @@ -0,0 +1,26 @@ + + + + 4.0.0 + + + de.juplo + juplo-accelerator-examples + 0.1-SNAPSHOT + + + juplo-accelerator-examples-static + war + Juplo - Accelerator-Examples: Static Content + + + + org.apache.commons + commons-io + ${commons-io.version} + test + + + + diff --git a/accelerator-examples/static/src/main/resources/config.xml b/accelerator-examples/static/src/main/resources/config.xml new file mode 100644 index 00000000..a04b130c --- /dev/null +++ b/accelerator-examples/static/src/main/resources/config.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator-examples/static/src/main/resources/log4j.xml b/accelerator-examples/static/src/main/resources/log4j.xml new file mode 100644 index 00000000..b1e35f82 --- /dev/null +++ b/accelerator-examples/static/src/main/resources/log4j.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + 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 index 00000000..5edc75fd --- /dev/null +++ b/accelerator-examples/static/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,63 @@ + + + + + + + contextConfigLocation + classpath:/config.xml + + + + + + + org.springframework.web.context.ContextLoaderListener + + + + + + + accelerator + de.juplo.accelerator.AcceleratorFilter + + + + logger + de.juplo.testingtools.LoggingHttpServletResponseFilter + + + + + + + logger + /static/* + + + + accelerator + /static/* + + + + + + + default + org.eclipse.jetty.servlet.DefaultServlet + + + + + + + default + / + + + + diff --git a/accelerator-examples/static/src/main/webapp/index.html b/accelerator-examples/static/src/main/webapp/index.html new file mode 100644 index 00000000..22c61d08 --- /dev/null +++ b/accelerator-examples/static/src/main/webapp/index.html @@ -0,0 +1,15 @@ + + + + + + Examples for Static Content + + +

Examples for Static Content

+ + + 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 index 00000000..f8517959 --- /dev/null +++ b/accelerator-examples/static/src/main/webapp/static/page.html @@ -0,0 +1,13 @@ + + + + + + A Static Page + + + +

A Static Page

+

This page is a static HTML-page

+ + 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 index 00000000..e04bba27 --- /dev/null +++ b/accelerator-examples/static/src/main/webapp/static/stylesheets.css @@ -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 index 00000000..f27fec4c --- /dev/null +++ b/accelerator-examples/static/src/test/java/de/juplo/accelerator/examples/StaticTest.java @@ -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 index 00000000..0173a1fc --- /dev/null +++ b/accelerator-examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -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 index 00000000..fd36da9b --- /dev/null +++ b/accelerator/pom.xml @@ -0,0 +1,211 @@ + + + + 4.0.0 + + de.juplo + juplo-accelerator + Juplo - Accelerator + 0.1-SNAPSHOT + http://www.juplo.de/accelerator + + + 2.0.6 + + + + + kai + Kai Moritz + kai@juplo.de + + + + + + UTF-8 + + 1.6.11 + 1.0 + 4.8.1 + 1.0 + 1.2.16 + 2.5 + slf4j-log4j12 + 1.6.1 + 3.0.6.RELEASE + + + + + org.springframework + spring-webmvc + ${springframework.version} + provided + + + org.springframework + spring-aspects + ${springframework.version} + provided + + + org.springframework + spring-tx + ${springframework.version} + provided + + + org.aspectj + aspectjrt + ${aspectj.version} + provided + + + javax.servlet + servlet-api + ${servlet-api.version} + provided + + + javax.persistence + persistence-api + ${jpa.version} + provided + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + de.juplo + juplo-testingtools + ${juplo-testingtools.version} + test + + + junit + junit + ${junit.version} + test + + + org.slf4j + ${slf4j.binding} + ${slf4j.version} + test + + + + + + juplo.internal + Internal Release Repository + http://juplo.de/archiva/repository/internal/ + + + juplo.snapshots + Internal Snapshot Repository + http://juplo.de/archiva/repository/snapshots/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + utf8 + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + install + install + + sources + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar + + + + + + org.codehaus.mojo + aspectj-maven-plugin + + 1.6 + + + org.springframework + spring-aspects + + + + + + + compile + + + + + + + + + + + maven-changes-plugin + + + maven-checkstyle-plugin + + + org.codehaus.mojo + cobertura-maven-plugin + 2.0 + + + maven-javadoc-plugin + + + maven-jxr-plugin + + + maven-pmd-plugin + + true + 1.5 + + + + maven-surefire-report-plugin + 2.7.2 + + + + + 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 index 00000000..7b1e3062 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/AcceleratorFilter.java @@ -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 EMPTY = Collections.unmodifiableMap(new HashMap()); + + 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 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(); + + 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 cacheControlMap) { + cacheControlMap.putAll(cacheParams); + } + + @Override + public Map 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 index 00000000..f119b600 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/CacheControl.java @@ -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 tl = new ThreadLocal(); + + @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 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 cacheControl = new TreeMap(); + + /** + * 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 false + * zurückgeliefert werden! + * + * Den Wert als max-age zu den Schlüssel-Wert-Paaren für den + * Cache-Control-Header hinzufügen und einen entsprechenden + * Expires-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 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 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(); + 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(); + } + + + @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 cacheControlMap + ) + throws IllegalArgumentException + { + if (cacheControlMethod != null) { + try { + cacheControlMethod.invoke(handler, request, cacheControlMap); + } + catch (Exception e) { + throw new IllegalArgumentException(e); + } + } + } + + @Override + public Map getAdditionalHeaders(HttpServletRequest request) throws IllegalArgumentException { + if (additionalHeadersMethod == null) { + return additionalHeaders; + } + else { + try { + return (Map)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 index 00000000..10aaee1f --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/CacheControlInterceptor.java @@ -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 index 00000000..96f529ba --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/CacheMethodHandle.java @@ -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 cacheControlMap); + Map 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 index 00000000..49f19fe1 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/Headers.java @@ -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 index 00000000..2f44afc0 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/Accepts.java @@ -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. + *

+ * Die Methode muss eine Instanz von {@link HttpServletRequest} als (einziges!) + * Argument akzeptieren und einen Wert liefern, der sich nach + * int casten lässt. + *

+ * Eine mit dieser Annotation markierte Methode wird nur benötigt, wenn die + * Caching-Dekoration im Modus eager=true ausgeführt wird. Sie + * wird in diesem Fall benötigt, weil die Entscheidungen zur Cache-Dekoration + * dann getroffen werden müssen, bevor die verarbeitende Klasse die + * Anfrage verarbeitet hat. + * Wenn die Cache-Dekoration im Modus eager=true 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 index 00000000..2f379208 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/AdditionalHeaders.java @@ -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 index 00000000..6f7705db --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheControl.java @@ -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 Cache-Control 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}). + *

+ * Die Methode muss zwei Parameter akzeptieren. + * Als ersten Parameter eine Instanz von {@link HttpServletRequest}. + * Als zweiten Parameter eine Map, die die von + * Juplo-CacheControl erzeugten Schlüssel/Wert-Paare enthält. + *

+ * Diese Methode liefert eine Map mit Schlüssel-Wert-Paaren für den + * HTTP/1.1-Header Cache-Control (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 index 00000000..04036462 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/CacheSeconds.java @@ -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. + *

+ * Wenn eine Methode markiert wird, muss diese eine Instanz von + * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen + * Wert liefern, der sich nach int casten lässt. + * Die annotierte Methode ermöglicht eine einfache, zentrale aber Request- + * Abhängige Steuerung des Caching-Verhaltens. + *

+ * 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 86400 + * (ein Tag) verwendet. + *

    + *
  • 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.
  • + *
  • Wenn einen Wert größer oder gleich 0 festgelegt wird, wird + * für HTTP/1.0-Clients ein Expires-Header generiert und für + * HTTP/1.1-Clients ein Cache-Control-Header mit einem + * entsprechenden max-age-Eintrag. Dies reicht in Kombination mit + * der Annotation {@link LastModified} vollständig für ein einfaches aber + * effektives Caching aus.
  • + *
+ *

+ * TODO + * Zu beachten: Wenn die Methode + * {@link #getCacheControl(javax.servlet.http.HttpServletRequest)} weitere + * Schlüssel-Wert-Paare für den Cache-Control-Header liefert, + * werden diese ergänzt. Wenn in der Rückgabe ein Wert für + * max-age 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 index 00000000..a5960fc3 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/Cacheable.java @@ -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. + *

+ * Wenn der Parameter eager auf true gesetzt wird, + * ermittelt der {@link CachingInterceptor} die Cache-Parameter über die + * annotierten Methoden vorab. + * Achtung: + * Dies bedeutet, dass die annotierten Methoden aufgerufen werden bevor + * 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 gar nicht aufgerufen. + *

+ * Wenn der Parameter eager nicht gesetzt ist (oder explizit auf + * false 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 index 00000000..81b6d16a --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/ETag.java @@ -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 ETag/code>-Headers + * gesteuert werden. + * Mit dieser Annotation können Klassen oder Methoden merkiert werden. + *

+ * Wenn eine Methode annotiert wird, muss diese eine Instanz von + * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen + * String liefern. + *

+ * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den + * ETag-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! + *

+ * 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 vary gesetzt ist, noch um je nach + * erfolgter Content-Negotiation varriierende Teile ergänzt wird. + *

+ * Die erzeugten ETag's können über die Annotations-Parameter + * weak und vary weiter gesteuert werden. + *

    + *
  • + * Wenn der Parameter weak auf den wert true + * gesetzt wird, wird ein schwaches ETag 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}). + *
  • + *
  • + * Über den Parameter vary 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: Accept, Accept-Charset, + * Accept-Encoding und Accept-Language). + * Juplo-CacheControl modifizert den übergebenen ETag dann so, + * dass unterschiedliche Resultate der Content-Negotiation unterschieden + * werden können. + * Außerdem wird der Vary-Header entsprechend gesetzt. + *
  • + *
+ * Zu beachten: + * 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 0 liefert, bzw. für die mit Annotation + * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich + * 0 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 index 00000000..c5e86a34 --- /dev/null +++ b/accelerator/src/main/java/de/juplo/accelerator/annotations/LastModified.java @@ -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 Last-Modified-Headers + * gesteuert werden. + * Mit dieser Annotation können Klassen oder Methoden merkiert werden. + *

+ * Wenn eine Methode annotiert wird, muss diese eine Instanz von + * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen + * Wert liefern, der sich nach long casten lässt. + * Die Signatur der Methode entspricht der Methode + * {@link HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)} + * aus dem HttpServlet-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. + *

+ * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den + * Last-Modified-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. + *

+ * Ü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. + *

+ * Zu beachten: + * 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 0 liefert, bzw. für die mit Annotation + * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich + * 0 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 index 00000000..285d1737 --- /dev/null +++ b/accelerator/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java @@ -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 + * sendRedirect 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 + * encodeUrl 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, https://hostname/path/file.html). + * 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 + * (<body></body>). + **/ + 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 + * (<body></body>). + **/ + public void sendError(int sc, String msg) throws IOException { + setStatus( sc ); + _statusMessage = msg; + + _writer = null; + _servletStream = null; + + setContentType( "text/html" ); + getWriter().println( "" + msg + "" + msg + "" ); + } + + + /** + * 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 sendError 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 containsHeader 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 containsHeader + * 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 + * containsHeader 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, text/html; charset=ISO-8859-4. + * + *

You can only use this method once, and you should call it + * before you obtain a PrintWriter 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 getWriter 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 PrintWriter object that you + * can use to send character text to the client. + * The character encoding used is the one specified + * in the charset= property of the + * {@link #setContentType} method, which you must call + * before you call this method. + * + *

If necessary, the MIME type of the response is + * modified to reflect the character encoding used. + * + *

You cannot use this method if you have already + * called {@link #getOutputStream} for this + * ServletResponse object. + * + * @exception UnsupportedEncodingException if the character encoding specified in + * setContentType cannot be + * used + * + * @exception IllegalStateException if the getOutputStream + * 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 index 00000000..342a8f2f --- /dev/null +++ b/accelerator/src/test/java/de/juplo/accelerator/ParameterGuessingTest.java @@ -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 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(); + 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(); + 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 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(); + 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 index 00000000..6b94d2ba --- /dev/null +++ b/accelerator/src/test/java/de/juplo/accelerator/RequestSizeTest.java @@ -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 index 00000000..769269c7 --- /dev/null +++ b/accelerator/src/test/java/de/juplo/accelerator/TestServlet.java @@ -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 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 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator/src/test/resources/log4j.xml b/accelerator/src/test/resources/log4j.xml new file mode 100644 index 00000000..16e81f00 --- /dev/null +++ b/accelerator/src/test/resources/log4j.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/accelerator/src/test/resources/web.xml b/accelerator/src/test/resources/web.xml new file mode 100644 index 00000000..01ce9b79 --- /dev/null +++ b/accelerator/src/test/resources/web.xml @@ -0,0 +1,26 @@ + + + + + accelerator + de.juplo.accelerator.AcceleratorFilter + + + + accelerator + /* + + + + test-servlet + de.juplo.accelerator.TestServlet + + + + test-servlet + /* + + + diff --git a/cachecontrol/pom.xml b/cachecontrol/pom.xml deleted file mode 100644 index 686432ed..00000000 --- a/cachecontrol/pom.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo - 2.0-SNAPSHOT - - - ${pom.parent.artifactId}-cachecontrol - Juplo - CacheControl - - - - org.springframework - spring-webmvc - ${springframework.version} - provided - - - org.springframework - spring-aspects - ${springframework.version} - provided - - - org.springframework - spring-tx - ${springframework.version} - provided - - - org.aspectj - aspectjrt - ${aspectj.version} - provided - - - javax.servlet - servlet-api - ${servlet-api.version} - provided - - - javax.persistence - persistence-api - ${jpa.version} - provided - - - ${pom.parent.groupId} - ${pom.parent.artifactId}-test - ${pom.parent.version} - test - - - org.slf4j - ${slf4j.binding} - ${slf4j.version} - test - - - - - - - org.codehaus.mojo - aspectj-maven-plugin - - 1.6 - - - org.springframework - spring-aspects - - - - - - - compile - - - - - - - - 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 index 8e63222a..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/AcceleratorFilter.java +++ /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 EMPTY = Collections.unmodifiableMap(new HashMap()); - - 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 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(); - - 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 cacheControlMap) { - cacheControlMap.putAll(cacheParams); - } - - @Override - public Map 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 index 458979d9..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControl.java +++ /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 tl = new ThreadLocal(); - - @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 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 cacheControl = new TreeMap(); - - /** - * 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 false - * zurückgeliefert werden! - * - * Den Wert als max-age zu den Schlüssel-Wert-Paaren für den - * Cache-Control-Header hinzufügen und einen entsprechenden - * Expires-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 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 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(); - 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(); - } - - - @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 cacheControlMap - ) - throws IllegalArgumentException - { - if (cacheControlMethod != null) { - try { - cacheControlMethod.invoke(handler, request, cacheControlMap); - } - catch (Exception e) { - throw new IllegalArgumentException(e); - } - } - } - - @Override - public Map getAdditionalHeaders(HttpServletRequest request) throws IllegalArgumentException { - if (additionalHeadersMethod == null) { - return additionalHeaders; - } - else { - try { - return (Map)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 index c32183f6..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheControlInterceptor.java +++ /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 index a36196e6..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/CacheMethodHandle.java +++ /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 cacheControlMap); - Map 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 index 6f7579a1..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/Headers.java +++ /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 index 879cfd3e..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Accepts.java +++ /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. - *

- * Die Methode muss eine Instanz von {@link HttpServletRequest} als (einziges!) - * Argument akzeptieren und einen Wert liefern, der sich nach - * int casten lässt. - *

- * Eine mit dieser Annotation markierte Methode wird nur benötigt, wenn die - * Caching-Dekoration im Modus eager=true ausgeführt wird. Sie - * wird in diesem Fall benötigt, weil die Entscheidungen zur Cache-Dekoration - * dann getroffen werden müssen, bevor die verarbeitende Klasse die - * Anfrage verarbeitet hat. - * Wenn die Cache-Dekoration im Modus eager=true 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 index f8b92662..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/AdditionalHeaders.java +++ /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 index ab5c4a36..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheControl.java +++ /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 Cache-Control 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}). - *

- * Die Methode muss zwei Parameter akzeptieren. - * Als ersten Parameter eine Instanz von {@link HttpServletRequest}. - * Als zweiten Parameter eine Map, die die von - * Juplo-CacheControl erzeugten Schlüssel/Wert-Paare enthält. - *

- * Diese Methode liefert eine Map mit Schlüssel-Wert-Paaren für den - * HTTP/1.1-Header Cache-Control (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 index e076e087..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/CacheSeconds.java +++ /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. - *

- * Wenn eine Methode markiert wird, muss diese eine Instanz von - * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen - * Wert liefern, der sich nach int casten lässt. - * Die annotierte Methode ermöglicht eine einfache, zentrale aber Request- - * Abhängige Steuerung des Caching-Verhaltens. - *

- * 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 86400 - * (ein Tag) verwendet. - *

    - *
  • 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.
  • - *
  • Wenn einen Wert größer oder gleich 0 festgelegt wird, wird - * für HTTP/1.0-Clients ein Expires-Header generiert und für - * HTTP/1.1-Clients ein Cache-Control-Header mit einem - * entsprechenden max-age-Eintrag. Dies reicht in Kombination mit - * der Annotation {@link LastModified} vollständig für ein einfaches aber - * effektives Caching aus.
  • - *
- *

- * TODO - * Zu beachten: Wenn die Methode - * {@link #getCacheControl(javax.servlet.http.HttpServletRequest)} weitere - * Schlüssel-Wert-Paare für den Cache-Control-Header liefert, - * werden diese ergänzt. Wenn in der Rückgabe ein Wert für - * max-age 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 index 7a1be267..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/Cacheable.java +++ /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. - *

- * Wenn der Parameter eager auf true gesetzt wird, - * ermittelt der {@link CachingInterceptor} die Cache-Parameter über die - * annotierten Methoden vorab. - * Achtung: - * Dies bedeutet, dass die annotierten Methoden aufgerufen werden bevor - * 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 gar nicht aufgerufen. - *

- * Wenn der Parameter eager nicht gesetzt ist (oder explizit auf - * false 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 index d2542dc8..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/ETag.java +++ /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 ETag/code>-Headers - * gesteuert werden. - * Mit dieser Annotation können Klassen oder Methoden merkiert werden. - *

- * Wenn eine Methode annotiert wird, muss diese eine Instanz von - * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen - * String liefern. - *

- * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den - * ETag-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! - *

- * 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 vary gesetzt ist, noch um je nach - * erfolgter Content-Negotiation varriierende Teile ergänzt wird. - *

- * Die erzeugten ETag's können über die Annotations-Parameter - * weak und vary weiter gesteuert werden. - *

    - *
  • - * Wenn der Parameter weak auf den wert true - * gesetzt wird, wird ein schwaches ETag 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}). - *
  • - *
  • - * Über den Parameter vary 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: Accept, Accept-Charset, - * Accept-Encoding und Accept-Language). - * Juplo-CacheControl modifizert den übergebenen ETag dann so, - * dass unterschiedliche Resultate der Content-Negotiation unterschieden - * werden können. - * Außerdem wird der Vary-Header entsprechend gesetzt. - *
  • - *
- * Zu beachten: - * 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 0 liefert, bzw. für die mit Annotation - * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich - * 0 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 index be28e0ea..00000000 --- a/cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/annotations/LastModified.java +++ /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 Last-Modified-Headers - * gesteuert werden. - * Mit dieser Annotation können Klassen oder Methoden merkiert werden. - *

- * Wenn eine Methode annotiert wird, muss diese eine Instanz von - * {@link HttpServletRequest} als (einziges!) Argument akzeptieren und einen - * Wert liefern, der sich nach long casten lässt. - * Die Signatur der Methode entspricht der Methode - * {@link HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)} - * aus dem HttpServlet-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. - *

- * Wenn eine Klasse Annotiert wird, muss der Annotation der Wert für den - * Last-Modified-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. - *

- * Ü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. - *

- * Zu beachten: - * 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 0 liefert, bzw. für die mit Annotation - * {@link CacheSeconds} markierte Klasse eine Cache-Zeit größer oder gleich - * 0 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 index 285d1737..00000000 --- a/cachecontrol/src/test/java/com/meterware/servletunit/ServletUnitHttpResponse.java +++ /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 - * sendRedirect 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 - * encodeUrl 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, https://hostname/path/file.html). - * 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 - * (<body></body>). - **/ - 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 - * (<body></body>). - **/ - public void sendError(int sc, String msg) throws IOException { - setStatus( sc ); - _statusMessage = msg; - - _writer = null; - _servletStream = null; - - setContentType( "text/html" ); - getWriter().println( "" + msg + "" + msg + "" ); - } - - - /** - * 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 sendError 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 containsHeader 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 containsHeader - * 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 - * containsHeader 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, text/html; charset=ISO-8859-4. - * - *

You can only use this method once, and you should call it - * before you obtain a PrintWriter 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 getWriter 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 PrintWriter object that you - * can use to send character text to the client. - * The character encoding used is the one specified - * in the charset= property of the - * {@link #setContentType} method, which you must call - * before you call this method. - * - *

If necessary, the MIME type of the response is - * modified to reflect the character encoding used. - * - *

You cannot use this method if you have already - * called {@link #getOutputStream} for this - * ServletResponse object. - * - * @exception UnsupportedEncodingException if the character encoding specified in - * setContentType cannot be - * used - * - * @exception IllegalStateException if the getOutputStream - * 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 index e7b64f68..00000000 --- a/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/ParameterGuessingTest.java +++ /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.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 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(); - 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(); - 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 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(); - 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/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/RequestSizeTest.java b/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/RequestSizeTest.java deleted file mode 100644 index 2b3fec85..00000000 --- a/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/RequestSizeTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package de.halbekunst.juplo.cachecontrol; - -import com.meterware.httpunit.WebResponse; -import de.halbekunst.juplo.test.HttpTestCase; -import de.halbekunst.juplo.test.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/cachecontrol/src/test/resources/config.xml b/cachecontrol/src/test/resources/config.xml deleted file mode 100644 index f9e3e0fc..00000000 --- a/cachecontrol/src/test/resources/config.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cachecontrol/src/test/resources/log4j.xml b/cachecontrol/src/test/resources/log4j.xml deleted file mode 100644 index 833d85c9..00000000 --- a/cachecontrol/src/test/resources/log4j.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/cachecontrol/src/test/resources/web.xml b/cachecontrol/src/test/resources/web.xml deleted file mode 100644 index 4838a1a3..00000000 --- a/cachecontrol/src/test/resources/web.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - accelerator - de.halbekunst.juplo.cachecontrol.AcceleratorFilter - - - - accelerator - /* - - - - test-servlet - de.halbekunst.juplo.test.TestServlet - - - - test-servlet - /* - - - diff --git a/examples/jsp/catalog.xml b/examples/jsp/catalog.xml deleted file mode 100644 index 94e80903..00000000 --- a/examples/jsp/catalog.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/examples/jsp/jetty.sh b/examples/jsp/jetty.sh deleted file mode 100755 index 4950796b..00000000 --- a/examples/jsp/jetty.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/jsp/pom.xml b/examples/jsp/pom.xml deleted file mode 100644 index 7eaed0bd..00000000 --- a/examples/jsp/pom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo-examples - 2.0-SNAPSHOT - - - juplo-examples-jsp - war - Juplo - Examples: JSP-Pages - - diff --git a/examples/jsp/src/main/resources/config.xml b/examples/jsp/src/main/resources/config.xml deleted file mode 100644 index e414ce93..00000000 --- a/examples/jsp/src/main/resources/config.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/jsp/src/main/resources/log4j.xml b/examples/jsp/src/main/resources/log4j.xml deleted file mode 100644 index d3414bdb..00000000 --- a/examples/jsp/src/main/resources/log4j.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/jsp/src/main/webapp/WEB-INF/c.tld b/examples/jsp/src/main/webapp/WEB-INF/c.tld deleted file mode 100644 index 4b85b448..00000000 --- a/examples/jsp/src/main/webapp/WEB-INF/c.tld +++ /dev/null @@ -1,578 +0,0 @@ - - - - - - JSTL 1.1 core library - JSTL core - 1.1 - c - http://java.sun.com/jsp/jstl/core - - - - Provides core validation features for JSTL tags. - - - org.apache.taglibs.standard.tlv.JstlCoreTLV - - - - - - Catches any Throwable that occurs in its body and optionally - exposes it. - - catch - org.apache.taglibs.standard.tag.common.core.CatchTag - JSP - - -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. - - var - false - false - - - - - - Simple conditional tag that establishes a context for - mutually exclusive conditional operations, marked by - <when> and <otherwise> - - choose - org.apache.taglibs.standard.tag.common.core.ChooseTag - JSP - - - - - 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 - - if - org.apache.taglibs.standard.tag.rt.core.IfTag - JSP - - -The test condition that determines whether or -not the body content should be processed. - - test - true - true - boolean - - - -Name of the exported scoped variable for the -resulting value of the test condition. The type -of the scoped variable is Boolean. - - var - false - false - - - -Scope for var. - - scope - false - false - - - - - - Retrieves an absolute or relative URL and exposes its contents - to either the page, a String in 'var', or a Reader in 'varReader'. - - import - org.apache.taglibs.standard.tag.rt.core.ImportTag - org.apache.taglibs.standard.tei.ImportTEI - JSP - - -The URL of the resource to import. - - url - true - true - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -Name of the exported scoped variable for the -resource's content. The type of the scoped -variable is Reader. - - varReader - false - false - - - -Name of the context when accessing a relative -URL resource that belongs to a foreign -context. - - context - false - true - - - -Character encoding of the content at the input -resource. - - charEncoding - false - true - - - - - - The basic iteration tag, accepting many different - collection types and supporting subsetting and other - functionality - - forEach - org.apache.taglibs.standard.tag.rt.core.ForEachTag - org.apache.taglibs.standard.tei.ForEachTEI - JSP - - -Collection of items to iterate over. - - items - false - true - java.lang.Object - - java.lang.Object - - - - -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. - - begin - false - true - int - - - -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. - - end - false - true - int - - - -Iteration will only process every step items of -the collection, starting with the first one. - - step - false - true - int - - - -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. - - var - false - false - - - -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. - - varStatus - false - false - - - - - - Iterates over tokens, separated by the supplied delimeters - - forTokens - org.apache.taglibs.standard.tag.rt.core.ForTokensTag - JSP - - -String of tokens to iterate over. - - items - true - true - java.lang.String - - java.lang.String - - - - -The set of delimiters (the characters that -separate the tokens in the string). - - delims - true - true - java.lang.String - - - -Iteration begins at the token located at the -specified index. First token has index 0. - - begin - false - true - int - - - -Iteration ends at the token located at the -specified index (inclusive). - - end - false - true - int - - - -Iteration will only process every step tokens -of the string, starting with the first one. - - step - false - true - int - - - -Name of the exported scoped variable for the -current item of the iteration. This scoped -variable has nested visibility. - - var - false - false - - - -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. - - varStatus - false - false - - - - - - Like <%= ... >, but for expressions. - - out - org.apache.taglibs.standard.tag.rt.core.OutTag - JSP - - -Expression to be evaluated. - - value - true - true - - - -Default value if the resulting value is null. - - default - false - true - - - -Determines whether characters <,>,&,'," in the -resulting string should be converted to their -corresponding character entity codes. Default value is -true. - - escapeXml - false - true - - - - - - - Subtag of <choose> that follows <when> tags - and runs only if all of the prior conditions evaluated to - 'false' - - otherwise - org.apache.taglibs.standard.tag.common.core.OtherwiseTag - JSP - - - - - Adds a parameter to a containing 'import' tag's URL. - - param - org.apache.taglibs.standard.tag.rt.core.ParamTag - JSP - - -Name of the query string parameter. - - name - true - true - - - -Value of the parameter. - - value - false - true - - - - - - Redirects to a new URL. - - redirect - org.apache.taglibs.standard.tag.rt.core.RedirectTag - JSP - - -The URL of the resource to redirect to. - - url - false - true - - - -Name of the context when redirecting to a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Removes a scoped variable (from a particular scope, if specified). - - remove - org.apache.taglibs.standard.tag.common.core.RemoveTag - empty - - -Name of the scoped variable to be removed. - - var - true - false - - - -Scope for var. - - scope - false - false - - - - - - Sets the result of an expression evaluation in a 'scope' - - set - org.apache.taglibs.standard.tag.rt.core.SetTag - JSP - - -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. - - var - false - false - - - -Expression to be evaluated. - - value - false - true - - java.lang.Object - - - - -Target object whose property will be set. Must evaluate to -a JavaBeans object with setter property property, or to a -java.util.Map object. - - target - false - true - - - -Name of the property to be set in the target object. - - property - false - true - - - -Scope for var. - - scope - false - false - - - - - - Creates a URL with optional query parameters. - - url - org.apache.taglibs.standard.tag.rt.core.UrlTag - JSP - - -Name of the exported scoped variable for the -processed url. The type of the scoped variable is -String. - - var - false - false - - - -Scope for var. - - scope - false - false - - - -URL to be processed. - - value - false - true - - - -Name of the context when specifying a relative URL -resource that belongs to a foreign context. - - context - false - true - - - - - - Subtag of <choose> that includes its body if its - condition evalutes to 'true' - - when - org.apache.taglibs.standard.tag.rt.core.WhenTag - JSP - - -The test condition that determines whether or not the -body content should be processed. - - test - true - true - boolean - - - - diff --git a/examples/jsp/src/main/webapp/WEB-INF/included.jsp b/examples/jsp/src/main/webapp/WEB-INF/included.jsp deleted file mode 100644 index 1931d893..00000000 --- a/examples/jsp/src/main/webapp/WEB-INF/included.jsp +++ /dev/null @@ -1,2 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false"%> -

Hello World, again...

diff --git a/examples/jsp/src/main/webapp/WEB-INF/web.xml b/examples/jsp/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index c5661f84..00000000 --- a/examples/jsp/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - contextConfigLocation - classpath:/config.xml - - - - - - - org.springframework.web.context.ContextLoaderListener - - - - - - - accelerator - de.halbekunst.juplo.cachecontrol.AcceleratorFilter - - - - logger - de.halbekunst.juplo.test.LoggingHttpServletResponseFilter - - - - - - - logger - *.jsp - - - - accelerator - *.jsp - - - diff --git a/examples/jsp/src/main/webapp/faulty-page.jsp b/examples/jsp/src/main/webapp/faulty-page.jsp deleted file mode 100644 index e40c2130..00000000 --- a/examples/jsp/src/main/webapp/faulty-page.jsp +++ /dev/null @@ -1,21 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> -<%@taglib uri="/WEB-INF/c.tld" prefix="c"%> - - - - - - Faulty Page - - -

Faulty Page

-

- This page will raise an error. - after a while! -

-

- Ecactly, NOW: - <% if (true) throw new RuntimeException("Oh no!"); %> -

- - diff --git a/examples/jsp/src/main/webapp/index.html b/examples/jsp/src/main/webapp/index.html deleted file mode 100644 index 2246263f..00000000 --- a/examples/jsp/src/main/webapp/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - JSP Examples - - -

JSP Examples

- - - diff --git a/examples/jsp/src/main/webapp/page-with-forward.jsp b/examples/jsp/src/main/webapp/page-with-forward.jsp deleted file mode 100644 index 35e49058..00000000 --- a/examples/jsp/src/main/webapp/page-with-forward.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> - - - - - - Page with forward - - -

Hello World!

-

This should not be seen, because the page is forwardes to /simple-page.jsp

- - - diff --git a/examples/jsp/src/main/webapp/page-with-include.jsp b/examples/jsp/src/main/webapp/page-with-include.jsp deleted file mode 100644 index 3347af77..00000000 --- a/examples/jsp/src/main/webapp/page-with-include.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> -<%@taglib uri="/WEB-INF/c.tld" prefix="c"%> - - - - - - Page with include - - -

Hello World!

- ${i}: - - diff --git a/examples/jsp/src/main/webapp/simple-page.jsp b/examples/jsp/src/main/webapp/simple-page.jsp deleted file mode 100644 index 99d92d73..00000000 --- a/examples/jsp/src/main/webapp/simple-page.jsp +++ /dev/null @@ -1,13 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> - - - - - - Simple Page - - -

Simple Page

-

This page is a simple jsp-page

- - diff --git a/examples/jsp/src/test/java/de/halbekunst/cachecontrol/examples/JspTest.java b/examples/jsp/src/test/java/de/halbekunst/cachecontrol/examples/JspTest.java deleted file mode 100644 index a7331b63..00000000 --- a/examples/jsp/src/test/java/de/halbekunst/cachecontrol/examples/JspTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.halbekunst.cachecontrol.examples; - -import de.halbekunst.juplo.test.HttpTestCase; -import com.meterware.httpunit.WebResponse; -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/examples/jsp/tomcat.sh b/examples/jsp/tomcat.sh deleted file mode 100755 index 4378a128..00000000 --- a/examples/jsp/tomcat.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/pom.xml b/examples/pom.xml deleted file mode 100644 index 1905f525..00000000 --- a/examples/pom.xml +++ /dev/null @@ -1,138 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo - 2.0-SNAPSHOT - - - juplo-examples - pom - Juplo - Examples - - - jsp - static - servlet - spring - - - - - - - ${pom.parent.groupId} - juplo-cachecontrol - ${pom.parent.version} - - - - - javax.servlet - jstl - ${jstl.version} - runtime - - - - - org.springframework - spring-webmvc - ${springframework.version} - runtime - - - org.springframework - spring-context - ${springframework.version} - runtime - - - - commons-logging - commons-logging - - - - - org.springframework - spring-aspects - ${springframework.version} - runtime - - - org.aspectj - aspectjrt - ${aspectj.version} - runtime - - - - - org.slf4j - ${slf4j.binding} - ${slf4j.version} - runtime - - - - - ${pom.parent.groupId} - juplo-test - ${pom.parent.version} - - - org.apache.tomcat - jasper - ${jasper.version} - test - - - - - - - - org.mortbay.jetty - jetty-maven-plugin - ${jetty.version} - - - - 0.0.0.0 - 8080 - 2 - - - 0 - - - ${project.basedir}/src/main/webapp/WEB-INF - - **/*.jsp - - - **/*.properties - **/*.xml - - - - - - - org.codehaus.mojo - tomcat-maven-plugin - 1.1 - - / - UTF-8 - - - - - - diff --git a/examples/servlet/jetty.sh b/examples/servlet/jetty.sh deleted file mode 100755 index 4950796b..00000000 --- a/examples/servlet/jetty.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/servlet/pom.xml b/examples/servlet/pom.xml deleted file mode 100644 index 313fe7c1..00000000 --- a/examples/servlet/pom.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo-examples - 2.0-SNAPSHOT - - - juplo-examples-servlet - war - Juplo - Examples: Servlet - - diff --git a/examples/servlet/src/main/java/de/halbekunst/cachecontrol/examples/FaultyServlet.java b/examples/servlet/src/main/java/de/halbekunst/cachecontrol/examples/FaultyServlet.java deleted file mode 100644 index bc382db9..00000000 --- a/examples/servlet/src/main/java/de/halbekunst/cachecontrol/examples/FaultyServlet.java +++ /dev/null @@ -1,43 +0,0 @@ -package de.halbekunst.cachecontrol.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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/servlet/src/main/resources/log4j.xml b/examples/servlet/src/main/resources/log4j.xml deleted file mode 100644 index d3414bdb..00000000 --- a/examples/servlet/src/main/resources/log4j.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/servlet/src/main/webapp/WEB-INF/web.xml b/examples/servlet/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index dc503d07..00000000 --- a/examples/servlet/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - contextConfigLocation - classpath:/config.xml - - - - - - - org.springframework.web.context.ContextLoaderListener - - - - - - - accelerator - de.halbekunst.juplo.cachecontrol.AcceleratorFilter - - - - logger - de.halbekunst.juplo.test.LoggingHttpServletResponseFilter - - - - - - - logger - /test-servlet - - - logger - /faulty-servlet - - - - accelerator - /test-servlet - - - accelerator - /faulty-servlet - - - - - - - test-servlet - de.halbekunst.juplo.test.TestServlet - - - faulty-servlet - de.halbekunst.cachecontrol.examples.FaultyServlet - - - - - - - test-servlet - /test-servlet - - - faulty-servlet - /faulty-servlet - - - diff --git a/examples/servlet/src/main/webapp/index.html b/examples/servlet/src/main/webapp/index.html deleted file mode 100644 index c08ba7c0..00000000 --- a/examples/servlet/src/main/webapp/index.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - Servlet Examples - - -

Servlet Examples

-

This page is a static HTML-page

- - - - diff --git a/examples/servlet/src/test/java/de/halbekunst/cachecontrol/examples/ServletTest.java b/examples/servlet/src/test/java/de/halbekunst/cachecontrol/examples/ServletTest.java deleted file mode 100644 index 32b13e11..00000000 --- a/examples/servlet/src/test/java/de/halbekunst/cachecontrol/examples/ServletTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.halbekunst.cachecontrol.examples; - -import de.halbekunst.juplo.test.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/examples/servlet/tomcat.sh b/examples/servlet/tomcat.sh deleted file mode 100755 index 4378a128..00000000 --- a/examples/servlet/tomcat.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/spring/jetty.sh b/examples/spring/jetty.sh deleted file mode 100755 index 4950796b..00000000 --- a/examples/spring/jetty.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/spring/pom.xml b/examples/spring/pom.xml deleted file mode 100644 index b6924e41..00000000 --- a/examples/spring/pom.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo-examples - 2.0-SNAPSHOT - - - juplo-examples-spring - war - Juplo - Examples: Spring-MVC - - - - - - org.springframework - spring-context - ${springframework.version} - - - org.springframework - spring-web - ${springframework.version} - - - org.springframework - spring-webmvc - ${springframework.version} - - - - - diff --git a/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/FaultyController.java b/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/FaultyController.java deleted file mode 100644 index 21ec8236..00000000 --- a/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/FaultyController.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.halbekunst.juplo.examples.spring; - -import de.halbekunst.juplo.cachecontrol.annotations.CacheSeconds; -import de.halbekunst.juplo.cachecontrol.annotations.Cacheable; -import de.halbekunst.juplo.cachecontrol.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 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/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/SpringController.java b/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/SpringController.java deleted file mode 100644 index 2ce8f4e3..00000000 --- a/examples/spring/src/main/java/de/halbekunst/juplo/examples/spring/SpringController.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.halbekunst.juplo.examples.spring; - -import de.halbekunst.juplo.cachecontrol.annotations.CacheSeconds; -import de.halbekunst.juplo.cachecontrol.annotations.Cacheable; -import de.halbekunst.juplo.cachecontrol.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 requests = new TreeMap(); - private Map accessTimes = new HashMap(); - - - @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/examples/spring/src/main/resources/config.xml b/examples/spring/src/main/resources/config.xml deleted file mode 100644 index f77073c1..00000000 --- a/examples/spring/src/main/resources/config.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/spring/src/main/resources/log4j.xml b/examples/spring/src/main/resources/log4j.xml deleted file mode 100644 index 13ec197b..00000000 --- a/examples/spring/src/main/resources/log4j.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp b/examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp deleted file mode 100644 index 26abdbdb..00000000 --- a/examples/spring/src/main/webapp/WEB-INF/views/controller-view.jsp +++ /dev/null @@ -1,43 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> -<%@page import="java.util.Map" %> -<%@page import="java.util.Date"%> - - - - - - Page, that is handled by a controller - - - <% Map requests = (Map)request.getAttribute("requests"); - if (requests == null) { %> -

This page associated with a controller and should be viewed as such!

-

Go to: /spring-controller.html

- <% } else { %> -

Controlled Page

-
- - <%= request.getParameter("max-age") %><% } %>" /> -
-
-

(Uncached) requests so far:

-
    - <% for(Map.Entry entry : requests.entrySet()) { %> -
  1. - <%= entry.getKey() %>: - <% if (entry.getValue().equals("")) { %> - No parameters... - <% } else { %> - - <% for (String parameter : entry.getValue().split("&")) { %> - <%= parameter %> - <% } %> - - <% } %> -
  2. - <% } %> -
- <% } %> -

This page was delivered via SPRING!

- - diff --git a/examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp b/examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp deleted file mode 100644 index 8718f54f..00000000 --- a/examples/spring/src/main/webapp/WEB-INF/views/faulty-page.jsp +++ /dev/null @@ -1,17 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> - - - - - - Simple Page - - -

Faulty Page

-

This page will raise an error!

-

- Ecactly, NOW: - <% if (true) throw new RuntimeException("Oh no!"); %> -

- - diff --git a/examples/spring/src/main/webapp/WEB-INF/views/index.jsp b/examples/spring/src/main/webapp/WEB-INF/views/index.jsp deleted file mode 100644 index a329acd3..00000000 --- a/examples/spring/src/main/webapp/WEB-INF/views/index.jsp +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - Simple Spring-MVC Examples - - -

Simple Spring-MVC Examples

- -

This page was delivered via SPRING!

-

Note:

-

- Since the org.springframework.web.servlet.DispatcherServlet - ist configured to handle all *.html-requests, the path - /index.html points to a view. -

-

- Therefore, this page must be stored under - /WEB-INF/views/index.jsp in order to be served as - /index.html -

-

- Additionatly, a file /index.jsp in the root-directory of - the webappliction is needed, to forward unqualified requests - (like http://HOSTNAME/) to the welcome-page served by - the Spring-Dispatcher-Servlet. -

- - diff --git a/examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp b/examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp deleted file mode 100644 index 892bf1a6..00000000 --- a/examples/spring/src/main/webapp/WEB-INF/views/spring-page.jsp +++ /dev/null @@ -1,14 +0,0 @@ -<%@page contentType="text/html" pageEncoding="UTF-8" session="false" buffer="1kb" %> - - - - - - Simple Page - - -

Hello World!

-

This is a really simple page...

-

This page was delivered via SPRING!

- - diff --git a/examples/spring/src/main/webapp/WEB-INF/web.xml b/examples/spring/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 4f26f015..00000000 --- a/examples/spring/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - contextConfigLocation - classpath:/config.xml - - - - - - - org.springframework.web.context.ContextLoaderListener - - - - - - - accelerator - de.halbekunst.juplo.cachecontrol.AcceleratorFilter - - - - logger - de.halbekunst.juplo.test.LoggingHttpServletResponseFilter - - - - - - - logger - *.html - - - - accelerator - *.html - - - - - - - dispatcher-servlet - org.springframework.web.servlet.DispatcherServlet - - contextConfigLocation - - - - 1 - - - - - - - dispatcher-servlet - *.html - - - diff --git a/examples/spring/src/main/webapp/index.jsp b/examples/spring/src/main/webapp/index.jsp deleted file mode 100644 index 90820004..00000000 --- a/examples/spring/src/main/webapp/index.jsp +++ /dev/null @@ -1,8 +0,0 @@ -<%@page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" session="false" %> - -<%-- - -Unfortionatly, the welcome-mechanism in web.xml does not work with a page -served by a servlet... - ---%> diff --git a/examples/spring/src/test/java/de/halbekunst/cachecontrol/examples/SpringMVCTest.java b/examples/spring/src/test/java/de/halbekunst/cachecontrol/examples/SpringMVCTest.java deleted file mode 100644 index f90d16e6..00000000 --- a/examples/spring/src/test/java/de/halbekunst/cachecontrol/examples/SpringMVCTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package de.halbekunst.cachecontrol.examples; - -import de.halbekunst.juplo.test.HttpTestCase; -import com.meterware.httpunit.WebResponse; -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/examples/spring/tomcat.sh b/examples/spring/tomcat.sh deleted file mode 100755 index 4378a128..00000000 --- a/examples/spring/tomcat.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/static/jetty.sh b/examples/static/jetty.sh deleted file mode 100755 index 4950796b..00000000 --- a/examples/static/jetty.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/examples/static/pom.xml b/examples/static/pom.xml deleted file mode 100644 index 5cf0634f..00000000 --- a/examples/static/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo-examples - 2.0-SNAPSHOT - - - juplo-examples-static - war - Juplo - Examples: Static Content - - - - org.apache.commons - commons-io - ${commons-io.version} - test - - - - diff --git a/examples/static/src/main/resources/config.xml b/examples/static/src/main/resources/config.xml deleted file mode 100644 index e414ce93..00000000 --- a/examples/static/src/main/resources/config.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/static/src/main/resources/log4j.xml b/examples/static/src/main/resources/log4j.xml deleted file mode 100644 index d3414bdb..00000000 --- a/examples/static/src/main/resources/log4j.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/static/src/main/webapp/WEB-INF/web.xml b/examples/static/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 2df85a54..00000000 --- a/examples/static/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - contextConfigLocation - classpath:/config.xml - - - - - - - org.springframework.web.context.ContextLoaderListener - - - - - - - accelerator - de.halbekunst.juplo.cachecontrol.AcceleratorFilter - - - - logger - de.halbekunst.juplo.test.LoggingHttpServletResponseFilter - - - - - - - logger - /static/* - - - - accelerator - /static/* - - - - - - - default - org.eclipse.jetty.servlet.DefaultServlet - - - - - - - default - / - - - - diff --git a/examples/static/src/main/webapp/index.html b/examples/static/src/main/webapp/index.html deleted file mode 100644 index 22c61d08..00000000 --- a/examples/static/src/main/webapp/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Examples for Static Content - - -

Examples for Static Content

- - - diff --git a/examples/static/src/main/webapp/static/page.html b/examples/static/src/main/webapp/static/page.html deleted file mode 100644 index f8517959..00000000 --- a/examples/static/src/main/webapp/static/page.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - A Static Page - - - -

A Static Page

-

This page is a static HTML-page

- - diff --git a/examples/static/src/main/webapp/static/stylesheets.css b/examples/static/src/main/webapp/static/stylesheets.css deleted file mode 100644 index e04bba27..00000000 --- a/examples/static/src/main/webapp/static/stylesheets.css +++ /dev/null @@ -1,7 +0,0 @@ -body { - background-color: #ccc; - color: #444; -} -h1,h2,h3,h4 { - color: #000; -} \ No newline at end of file diff --git a/examples/static/src/test/java/de/halbekunst/cachecontrol/examples/StaticTest.java b/examples/static/src/test/java/de/halbekunst/cachecontrol/examples/StaticTest.java deleted file mode 100644 index e66a5087..00000000 --- a/examples/static/src/test/java/de/halbekunst/cachecontrol/examples/StaticTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.halbekunst.cachecontrol.examples; - -import de.halbekunst.juplo.test.HttpTestCase; -import com.meterware.httpunit.WebResponse; -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/examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java b/examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java deleted file mode 100644 index 0173a1fc..00000000 --- a/examples/static/src/test/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ /dev/null @@ -1,28 +0,0 @@ -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/maven-plugins/hibernate4/pom.xml b/maven-plugins/hibernate4/pom.xml new file mode 100644 index 00000000..a5f7e010 --- /dev/null +++ b/maven-plugins/hibernate4/pom.xml @@ -0,0 +1,172 @@ + + + + 4.0.0 + + de.juplo + hibernate4-maven-plugin + Juplo - Maven-Plugins: Hibernate 4 Maven Mojo + 1.0 + maven-plugin + http://www.juplo.de/maven-plugins/hibernate4 + + + 2.0.6 + + + + + kai + Kai Moritz + kai@juplo.de + + + + + + UTF-8 + + 4.1.5.SP1 + 3.0.4 + 1.0.1 + 1.0.2 + + + + + org.apache.maven + maven-core + ${maven.version} + + + org.codehaus.plexus + plexus-utils + + + + + org.apache.maven + maven-plugin-api + ${maven.version} + + + org.hibernate + hibernate-core + ${hibernate-core.version} + + + org.jboss.logging + jboss-logging + + + + + net.sf.scannotation + scannotation + ${scannotation.version} + + + com.pyx4j + maven-plugin-log4j + ${maven-plugin-log4j.version} + + + org.apache.maven + maven-artifact + + + org.apache.maven + maven-plugin-api + + + + + + + + juplo.internal + Internal Release Repository + http://juplo.de/archiva/repository/internal/ + + + juplo.snapshots + Internal Snapshot Repository + http://juplo.de/archiva/repository/snapshots/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + utf8 + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + install + install + + sources + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar + + + + + + + + + + + maven-changes-plugin + + + maven-checkstyle-plugin + + + org.codehaus.mojo + cobertura-maven-plugin + 2.0 + + + maven-javadoc-plugin + + + maven-jxr-plugin + + + maven-pmd-plugin + + true + 1.5 + + + + maven-surefire-report-plugin + 2.7.2 + + + + + diff --git a/maven-plugins/hibernate4/src/main/java/de/juplo/plugins/hibernate4/Hbm2DdlMojo.java b/maven-plugins/hibernate4/src/main/java/de/juplo/plugins/hibernate4/Hbm2DdlMojo.java new file mode 100644 index 00000000..f2fd2320 --- /dev/null +++ b/maven-plugins/hibernate4/src/main/java/de/juplo/plugins/hibernate4/Hbm2DdlMojo.java @@ -0,0 +1,433 @@ +package de.juplo.plugins.hibernate4; + +/* + * Copyright 2001-2005 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.pyx4j.log4j.MavenLogAppender; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.Embeddable; +import javax.persistence.MappedSuperclass; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.project.MavenProject; +import org.hibernate.cfg.Configuration; +import org.hibernate.tool.hbm2ddl.SchemaExport; +import org.hibernate.tool.hbm2ddl.SchemaExport.Type; +import org.hibernate.tool.hbm2ddl.Target; +import org.scannotation.AnnotationDB; + + +/** + * Goal which extracts the hibernate-mapping-configuration and + * exports an according SQL-database-schema. + * + * @goal export + * @phase process-classes + * @threadSafe + * @requiresDependencyResolution runtime + */ +public class Hbm2DdlMojo extends AbstractMojo +{ + /** + * The project whose project files to create. + * + * @parameter expression="${project}" + * @required + * @readonly + */ + private MavenProject project; + + /** + * Directories to scan. + * + * @parameter expression="${project.build.outputDirectory}" + */ + private String outputDirectory; + + /** + * Skip execution + * + * @parameter default-value="false" + */ + private boolean skip; + + /** + * SQL-Driver name. + * + * @parameter expression="${hibernate.connection.driver_class} + */ + private String driverClassName; + + /** + * Database URL. + * + * @parameter expression="${hibernate.connection.url}" + */ + private String url; + + /** + * Database username + * + * @parameter expression="${hibernate.connection.username}" + */ + private String username; + + /** + * Database password + * + * @parameter expression="${hibernate.connection.password}" + */ + private String password; + + /** + * Hibernate dialect. + * + * @parameter expression="${hibernate.dialect}" + */ + private String hibernateDialect; + + /** + * Hibernate configuration file. + * + * @parameter default-value="${project.build.outputDirectory}/hibernate.properties" + */ + private String hibernateProperties; + + /** + * Target of execution: + *
    + *
  • NONE do nothing - just validate the configuration
  • + *
  • EXPORT create database (DEFAULT!)
  • + *
  • SCRIPT export schema to SQL-script
  • + *
  • BOTH
  • + *
+ * @parameter default-value="EXPORT" + */ + private String target; + + /** + * Type of export. + *
    + *
  • NONE do nothing - just validate the configuration
  • + *
  • CREATE create database-schema
  • + *
  • DROP drop database-schema
  • + *
  • BOTH (DEFAULT!)
  • + *
+ * @parameter default-value="BOTH" + */ + private String type; + + /** + * Output file. + * + * @parameter default-value="${project.build.outputDirectory}/schema.sql" + */ + private String outputFile; + + /** + * Delimiter in output-file. + * + * @parameter default-value=";" + */ + private String delimiter; + + /** + * Format output-file. + * + * @parameter default-value="true" + */ + private boolean format; + + + @Override + public void execute() + throws + MojoFailureException, + MojoExecutionException + { + if (skip) + return; + + File dir = new File(outputDirectory); + if (!dir.exists()) + throw new MojoExecutionException("Cannot scan for annotated classes in " + outputDirectory + ": directory does not exist!"); + + + Set classes = new HashSet(); + URL dirUrl = null; + try { + AnnotationDB db = new AnnotationDB(); + getLog().info("Scanning directory " + outputDirectory + " for annotated classes..."); + dirUrl = dir.toURI().toURL(); + db.scanArchives(dirUrl); + if (db.getAnnotationIndex().containsKey(Entity.class.getName())) + classes.addAll(db.getAnnotationIndex().get(Entity.class.getName())); + if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName())) + classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName())); + if (db.getAnnotationIndex().containsKey(Embeddable.class.getName())) + classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName())); + } + catch (IOException e) { + getLog().error("Error while scanning!", e); + throw new MojoFailureException(e.getMessage()); + } + if (classes.isEmpty()) + throw new MojoFailureException("No annotated classes found in directory " + outputDirectory); + + Properties properties = new Properties(); + + /** Try to read configuration from properties-file */ + try { + File file = new File(hibernateProperties); + if (file.exists()) { + getLog().info("Reading properties from file " + hibernateProperties + "..."); + properties.load(new FileInputStream(file)); + } + else + getLog().info("Ignoring nonexistent properties-file " + hibernateProperties + "!"); + } + catch (IOException e) { + getLog().error("Error while reading properties!", e); + throw new MojoExecutionException(e.getMessage()); + } + + /** Overwrite values from propertie-file or set if given */ + if (driverClassName != null) + properties.setProperty("hibernate.connection.driver_class", driverClassName); + if (url != null) + properties.setProperty("hibernate.connection.url", url); + if (username != null) + properties.setProperty("hibernate.connection.username", username); + if (password != null) + properties.setProperty("hibernate.connection.password", password); + if (hibernateDialect != null) + properties.setProperty("hibernate.dialect", hibernateDialect); + + if (properties.isEmpty()) + getLog().warn("No properties set!"); + for (Entry entry : properties.entrySet()) + getLog().debug(entry.getKey() + " = " + entry.getValue()); + + ClassLoader classLoader = null; + try { + getLog().debug("Creating ClassLoader for project-dependencies..."); + List classpathFiles = project.getCompileClasspathElements(); + URL[] urls = new URL[classpathFiles.size()]; + for (int i = 0; i < classpathFiles.size(); ++i) { + getLog().debug("Dependency: " + classpathFiles.get(i)); + urls[i] = new File(classpathFiles.get(i)).toURI().toURL(); + } + classLoader = new URLClassLoader(urls, getClass().getClassLoader()); + } + catch (Exception e) { + getLog().error("Error while creating ClassLoader!", e); + throw new MojoExecutionException(e.getMessage()); + } + + Configuration config = new Configuration(); + config.setProperties(properties); + try { + getLog().debug("Adding annotated classes to hibernate-mapping-configuration..."); + for (String annotatedClass : classes) { + getLog().debug("Class " + annotatedClass); + config.addAnnotatedClass(classLoader.loadClass(annotatedClass)); + } + } + catch (ClassNotFoundException e) { + getLog().error("Error while adding annotated classes!", e); + throw new MojoExecutionException(e.getMessage()); + } + + Target target = null; + try { + target = Target.valueOf(this.target); + } + catch (IllegalArgumentException e) { + getLog().error("Invalid value for configuration-option \"target\": " + this.target); + getLog().error("Valid values are: NONE, SCRIPT, EXPORT, BOTH"); + throw new MojoExecutionException("Invalid value for configuration-option \"target\""); + } + Type type = null; + try { + type = Type.valueOf(this.type); + } + catch (IllegalArgumentException e) { + getLog().error("Invalid value for configuration-option \"type\": " + this.type); + getLog().error("Valid values are: NONE, CREATE, DROP, BOTH"); + throw new MojoExecutionException("Invalid value for configuration-option \"type\""); + } + + Connection connection = null; + try { + /** + * The connection must be established outside of hibernate, because + * hibernate does not use the context-classloader of the current + * thread and, hence, would not be able to resolve the driver-class! + */ + switch (target) { + case EXPORT: + case BOTH: + switch (type) { + case CREATE: + case DROP: + case BOTH: + Class driverClass = classLoader.loadClass(driverClassName); + getLog().debug("Registering JDBC-driver " + driverClass.getName()); + DriverManager.registerDriver(new DriverProxy((Driver)driverClass.newInstance())); + getLog().debug("Opening JDBC-connection to " + url + " as " + username + " with password " + password); + connection = DriverManager.getConnection(url, username, password); + } + } + } + catch (ClassNotFoundException e) { + getLog().error("Dependency for driver-class " + driverClassName + " is missing!"); + throw new MojoExecutionException(e.getMessage()); + } + catch (Exception e) { + getLog().error("Cannot establish connection to database!"); + Enumeration drivers = DriverManager.getDrivers(); + if (!drivers.hasMoreElements()) + getLog().error("No drivers registered!"); + while (drivers.hasMoreElements()) + getLog().debug("Driver: " + drivers.nextElement()); + throw new MojoExecutionException(e.getMessage()); + } + + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + MavenLogAppender.startPluginLog(this); + try { + /** + * Change class-loader of current thread, so that hibernate can + * see all dependencies! + */ + Thread.currentThread().setContextClassLoader(classLoader); + + SchemaExport export = new SchemaExport(config, connection); + export.setOutputFile(outputFile); + export.setDelimiter(delimiter); + export.setFormat(format); + export.execute(target, type); + + for (Object exception : export.getExceptions()) + getLog().debug(exception.toString()); + } + finally { + /** Stop Log-Capturing */ + MavenLogAppender.endPluginLog(this); + + /** Restore the old class-loader (TODO: is this really necessary?) */ + Thread.currentThread().setContextClassLoader(contextClassLoader); + + /** Close the connection */ + try { + connection.close(); + } + catch (SQLException e) { + getLog().error("Error while closing connection: " + e.getMessage()); + } + } + } + + /** + * Needed, because DriverManager won't pick up drivers, that were not + * loaded by the system-classloader! + * See: + * http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location + */ + static final class DriverProxy implements Driver { + + private final Driver target; + + DriverProxy(Driver target) { + if (target == null) { + throw new NullPointerException(); + } + this.target = target; + } + + public java.sql.Driver getTarget() { + return target; + } + + @Override + public boolean acceptsURL(String url) throws SQLException { + return target.acceptsURL(url); + } + + @Override + public java.sql.Connection connect( + String url, java.util.Properties info) throws SQLException { + return target.connect(url, info); + } + + @Override + public int getMajorVersion() { + return target.getMajorVersion(); + } + + @Override + public int getMinorVersion() { + return target.getMinorVersion(); + } + + @Override + public DriverPropertyInfo[] getPropertyInfo( + String url, Properties info) throws SQLException { + return target.getPropertyInfo(url, info); + } + + @Override + public boolean jdbcCompliant() { + return target.jdbcCompliant(); + } + + @Override + public String toString() { + return "Proxy: " + target; + } + + @Override + public int hashCode() { + return target.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DriverProxy)) { + return false; + } + DriverProxy other = (DriverProxy) obj; + return this.target.equals(other.target); + } +} +} diff --git a/maven-plugins/pom.xml b/maven-plugins/pom.xml new file mode 100644 index 00000000..84841728 --- /dev/null +++ b/maven-plugins/pom.xml @@ -0,0 +1,20 @@ + + + + 4.0.0 + + + de.juplo + juplo + 1.0-SNAPSHOT + + + maven-plugins + pom + Juplo - Maven-Plugins + + + hibernate4 + + + diff --git a/maven/hibernate4-maven-plugin/pom.xml b/maven/hibernate4-maven-plugin/pom.xml deleted file mode 100644 index 2c480b97..00000000 --- a/maven/hibernate4-maven-plugin/pom.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo-maven - 2.0-SNAPSHOT - - - hibernate4-maven-plugin - maven-plugin - Juplo - Maven-Plugins: Hibernate 4 Maven Mojo - http://www.halbekunst.de - - - - org.apache.maven - maven-core - 3.0.4 - - - org.apache.maven - maven-plugin-api - 3.0.4 - - - org.hibernate - hibernate-core - 4.1.5.SP1 - - - net.sf.scannotation - scannotation - 1.0.2 - - - com.pyx4j - maven-plugin-log4j - 1.0.1 - - - - diff --git a/maven/hibernate4-maven-plugin/src/main/java/de/halbekunst/Hbm2DdlMojo.java b/maven/hibernate4-maven-plugin/src/main/java/de/halbekunst/Hbm2DdlMojo.java deleted file mode 100644 index 5844ee5d..00000000 --- a/maven/hibernate4-maven-plugin/src/main/java/de/halbekunst/Hbm2DdlMojo.java +++ /dev/null @@ -1,433 +0,0 @@ -package de.halbekunst; - -/* - * Copyright 2001-2005 The Apache Software Foundation. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import com.pyx4j.log4j.MavenLogAppender; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.DriverPropertyInfo; -import java.sql.SQLException; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; -import javax.persistence.Entity; -import javax.persistence.Embeddable; -import javax.persistence.MappedSuperclass; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.project.MavenProject; -import org.hibernate.cfg.Configuration; -import org.hibernate.tool.hbm2ddl.SchemaExport; -import org.hibernate.tool.hbm2ddl.SchemaExport.Type; -import org.hibernate.tool.hbm2ddl.Target; -import org.scannotation.AnnotationDB; - - -/** - * Goal which extracts the hibernate-mapping-configuration and - * exports an according SQL-database-schema. - * - * @goal export - * @phase process-classes - * @threadSafe - * @requiresDependencyResolution runtime - */ -public class Hbm2DdlMojo extends AbstractMojo -{ - /** - * The project whose project files to create. - * - * @parameter expression="${project}" - * @required - * @readonly - */ - private MavenProject project; - - /** - * Directories to scan. - * - * @parameter expression="${project.build.outputDirectory}" - */ - private String outputDirectory; - - /** - * Skip execution - * - * @parameter default-value="false" - */ - private boolean skip; - - /** - * SQL-Driver name. - * - * @parameter expression="${hibernate.connection.driver_class} - */ - private String driverClassName; - - /** - * Database URL. - * - * @parameter expression="${hibernate.connection.url}" - */ - private String url; - - /** - * Database username - * - * @parameter expression="${hibernate.connection.username}" - */ - private String username; - - /** - * Database password - * - * @parameter expression="${hibernate.connection.password}" - */ - private String password; - - /** - * Hibernate dialect. - * - * @parameter expression="${hibernate.dialect}" - */ - private String hibernateDialect; - - /** - * Hibernate configuration file. - * - * @parameter default-value="${project.build.outputDirectory}/hibernate.properties" - */ - private String hibernateProperties; - - /** - * Target of execution: - *
    - *
  • NONE do nothing - just validate the configuration
  • - *
  • EXPORT create database (DEFAULT!)
  • - *
  • SCRIPT export schema to SQL-script
  • - *
  • BOTH
  • - *
- * @parameter default-value="EXPORT" - */ - private String target; - - /** - * Type of export. - *
    - *
  • NONE do nothing - just validate the configuration
  • - *
  • CREATE create database-schema
  • - *
  • DROP drop database-schema
  • - *
  • BOTH (DEFAULT!)
  • - *
- * @parameter default-value="BOTH" - */ - private String type; - - /** - * Output file. - * - * @parameter default-value="${project.build.outputDirectory}/schema.sql" - */ - private String outputFile; - - /** - * Delimiter in output-file. - * - * @parameter default-value=";" - */ - private String delimiter; - - /** - * Format output-file. - * - * @parameter default-value="true" - */ - private boolean format; - - - @Override - public void execute() - throws - MojoFailureException, - MojoExecutionException - { - if (skip) - return; - - File dir = new File(outputDirectory); - if (!dir.exists()) - throw new MojoExecutionException("Cannot scan for annotated classes in " + outputDirectory + ": directory does not exist!"); - - - Set classes = new HashSet(); - URL dirUrl = null; - try { - AnnotationDB db = new AnnotationDB(); - getLog().info("Scanning directory " + outputDirectory + " for annotated classes..."); - dirUrl = dir.toURI().toURL(); - db.scanArchives(dirUrl); - if (db.getAnnotationIndex().containsKey(Entity.class.getName())) - classes.addAll(db.getAnnotationIndex().get(Entity.class.getName())); - if (db.getAnnotationIndex().containsKey(MappedSuperclass.class.getName())) - classes.addAll(db.getAnnotationIndex().get(MappedSuperclass.class.getName())); - if (db.getAnnotationIndex().containsKey(Embeddable.class.getName())) - classes.addAll(db.getAnnotationIndex().get(Embeddable.class.getName())); - } - catch (IOException e) { - getLog().error("Error while scanning!", e); - throw new MojoFailureException(e.getMessage()); - } - if (classes.isEmpty()) - throw new MojoFailureException("No annotated classes found in directory " + outputDirectory); - - Properties properties = new Properties(); - - /** Try to read configuration from properties-file */ - try { - File file = new File(hibernateProperties); - if (file.exists()) { - getLog().info("Reading properties from file " + hibernateProperties + "..."); - properties.load(new FileInputStream(file)); - } - else - getLog().info("Ignoring nonexistent properties-file " + hibernateProperties + "!"); - } - catch (IOException e) { - getLog().error("Error while reading properties!", e); - throw new MojoExecutionException(e.getMessage()); - } - - /** Overwrite values from propertie-file or set if given */ - if (driverClassName != null) - properties.setProperty("hibernate.connection.driver_class", driverClassName); - if (url != null) - properties.setProperty("hibernate.connection.url", url); - if (username != null) - properties.setProperty("hibernate.connection.username", username); - if (password != null) - properties.setProperty("hibernate.connection.password", password); - if (hibernateDialect != null) - properties.setProperty("hibernate.dialect", hibernateDialect); - - if (properties.isEmpty()) - getLog().warn("No properties set!"); - for (Entry entry : properties.entrySet()) - getLog().debug(entry.getKey() + " = " + entry.getValue()); - - ClassLoader classLoader = null; - try { - getLog().debug("Creating ClassLoader for project-dependencies..."); - List classpathFiles = project.getCompileClasspathElements(); - URL[] urls = new URL[classpathFiles.size()]; - for (int i = 0; i < classpathFiles.size(); ++i) { - getLog().debug("Dependency: " + classpathFiles.get(i)); - urls[i] = new File(classpathFiles.get(i)).toURI().toURL(); - } - classLoader = new URLClassLoader(urls, getClass().getClassLoader()); - } - catch (Exception e) { - getLog().error("Error while creating ClassLoader!", e); - throw new MojoExecutionException(e.getMessage()); - } - - Configuration config = new Configuration(); - config.setProperties(properties); - try { - getLog().debug("Adding annotated classes to hibernate-mapping-configuration..."); - for (String annotatedClass : classes) { - getLog().debug("Class " + annotatedClass); - config.addAnnotatedClass(classLoader.loadClass(annotatedClass)); - } - } - catch (ClassNotFoundException e) { - getLog().error("Error while adding annotated classes!", e); - throw new MojoExecutionException(e.getMessage()); - } - - Target target = null; - try { - target = Target.valueOf(this.target); - } - catch (IllegalArgumentException e) { - getLog().error("Invalid value for configuration-option \"target\": " + this.target); - getLog().error("Valid values are: NONE, SCRIPT, EXPORT, BOTH"); - throw new MojoExecutionException("Invalid value for configuration-option \"target\""); - } - Type type = null; - try { - type = Type.valueOf(this.type); - } - catch (IllegalArgumentException e) { - getLog().error("Invalid value for configuration-option \"type\": " + this.type); - getLog().error("Valid values are: NONE, CREATE, DROP, BOTH"); - throw new MojoExecutionException("Invalid value for configuration-option \"type\""); - } - - Connection connection = null; - try { - /** - * The connection must be established outside of hibernate, because - * hibernate does not use the context-classloader of the current - * thread and, hence, would not be able to resolve the driver-class! - */ - switch (target) { - case EXPORT: - case BOTH: - switch (type) { - case CREATE: - case DROP: - case BOTH: - Class driverClass = classLoader.loadClass(driverClassName); - getLog().debug("Registering JDBC-driver " + driverClass.getName()); - DriverManager.registerDriver(new DriverProxy((Driver)driverClass.newInstance())); - getLog().debug("Opening JDBC-connection to " + url + " as " + username + " with password " + password); - connection = DriverManager.getConnection(url, username, password); - } - } - } - catch (ClassNotFoundException e) { - getLog().error("Dependency for driver-class " + driverClassName + " is missing!"); - throw new MojoExecutionException(e.getMessage()); - } - catch (Exception e) { - getLog().error("Cannot establish connection to database!"); - Enumeration drivers = DriverManager.getDrivers(); - if (!drivers.hasMoreElements()) - getLog().error("No drivers registered!"); - while (drivers.hasMoreElements()) - getLog().debug("Driver: " + drivers.nextElement()); - throw new MojoExecutionException(e.getMessage()); - } - - ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); - MavenLogAppender.startPluginLog(this); - try { - /** - * Change class-loader of current thread, so that hibernate can - * see all dependencies! - */ - Thread.currentThread().setContextClassLoader(classLoader); - - SchemaExport export = new SchemaExport(config, connection); - export.setOutputFile(outputFile); - export.setDelimiter(delimiter); - export.setFormat(format); - export.execute(target, type); - - for (Object exception : export.getExceptions()) - getLog().debug(exception.toString()); - } - finally { - /** Stop Log-Capturing */ - MavenLogAppender.endPluginLog(this); - - /** Restore the old class-loader (TODO: is this really necessary?) */ - Thread.currentThread().setContextClassLoader(contextClassLoader); - - /** Close the connection */ - try { - connection.close(); - } - catch (SQLException e) { - getLog().error("Error while closing connection: " + e.getMessage()); - } - } - } - - /** - * Needed, because DriverManager won't pick up drivers, that were not - * loaded by the system-classloader! - * See: - * http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location - */ - static final class DriverProxy implements Driver { - - private final Driver target; - - DriverProxy(Driver target) { - if (target == null) { - throw new NullPointerException(); - } - this.target = target; - } - - public java.sql.Driver getTarget() { - return target; - } - - @Override - public boolean acceptsURL(String url) throws SQLException { - return target.acceptsURL(url); - } - - @Override - public java.sql.Connection connect( - String url, java.util.Properties info) throws SQLException { - return target.connect(url, info); - } - - @Override - public int getMajorVersion() { - return target.getMajorVersion(); - } - - @Override - public int getMinorVersion() { - return target.getMinorVersion(); - } - - @Override - public DriverPropertyInfo[] getPropertyInfo( - String url, Properties info) throws SQLException { - return target.getPropertyInfo(url, info); - } - - @Override - public boolean jdbcCompliant() { - return target.jdbcCompliant(); - } - - @Override - public String toString() { - return "Proxy: " + target; - } - - @Override - public int hashCode() { - return target.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof DriverProxy)) { - return false; - } - DriverProxy other = (DriverProxy) obj; - return this.target.equals(other.target); - } -} -} diff --git a/maven/pom.xml b/maven/pom.xml deleted file mode 100644 index 76b05d62..00000000 --- a/maven/pom.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo - 2.0-SNAPSHOT - - - ${pom.parent.artifactId}-maven - pom - Juplo - Maven-Plugins - - - hibernate4-maven-plugin - - \ No newline at end of file diff --git a/percentcodec/pom.xml b/percentcodec/pom.xml new file mode 100644 index 00000000..a0d7e56d --- /dev/null +++ b/percentcodec/pom.xml @@ -0,0 +1,166 @@ + + + + 4.0.0 + + de.juplo + juplo-percentcodec + Juplo - Percent-Codec + 1.0.1 + http://www.juplo.de/percentcodec + + + 2.0.6 + + + + + kai + Kai Moritz + kai@juplo.de + + + + + + UTF-8 + + 4.8.1 + 1.2.16 + 1.6.1 + + + + + junit + junit + ${junit.version} + test + + + org.slf4j + slf4j-log4j12 + ${slf4j.version} + + + log4j + log4j + + + test + + + log4j + log4j + ${log4j.version} + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + test + + + + + + juplo.internal + Internal Release Repository + http://juplo.de/archiva/repository/internal/ + + + juplo.snapshots + Internal Snapshot Repository + http://juplo.de/archiva/repository/snapshots/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + utf8 + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + install + install + + sources + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar + + + + + + + + + + + maven-changes-plugin + + + maven-checkstyle-plugin + + + org.codehaus.mojo + cobertura-maven-plugin + 2.0 + + + maven-javadoc-plugin + + + maven-jxr-plugin + + + maven-pmd-plugin + + true + 1.5 + + + + maven-surefire-report-plugin + 2.7.2 + + + + + diff --git a/percentcodec/src/main/java/de/juplo/percentcodec/PercentCodec.java b/percentcodec/src/main/java/de/juplo/percentcodec/PercentCodec.java new file mode 100644 index 00000000..1a23c097 --- /dev/null +++ b/percentcodec/src/main/java/de/juplo/percentcodec/PercentCodec.java @@ -0,0 +1,198 @@ +package de.juplo.percentcodec; + +import java.nio.charset.Charset; + +/** + * This class performes percent-encoding/-decoding like described in RFC 3986. + *

+ * Complete URI's are not handled by this implementation. + * That is done best with the original {@linkplain java.net.URI}-class from core Java. + * The purpose of this class is to have a simple tool to encode/decode the + * inner parts of an URI, like a segment of the URI-path (the part between two + * forward slashes) or a name or value segment of the query, where all reserved + * characters must be encoded/decoded. + * + * @author kai + */ +public class PercentCodec { + private final Charset charset; + + + public PercentCodec(String encoding) { + charset = Charset.forName(encoding); + } + + + public String encode(CharSequence in) { + StringBuilder out = new StringBuilder(); + int i = 0; + int length = in.length(); + while (i < length) { + int codePoint = Character.codePointAt(in, i); + i += Character.charCount(codePoint); + switch (codePoint) { + case 'a': + case 'A': + case 'b': + case 'B': + case 'c': + case 'C': + case 'd': + case 'D': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'h': + case 'H': + case 'i': + case 'I': + case 'j': + case 'J': + case 'k': + case 'K': + case 'l': + case 'L': + case 'm': + case 'M': + case 'n': + case 'N': + case 'o': + case 'O': + case 'p': + case 'P': + case 'q': + case 'Q': + case 'r': + case 'R': + case 's': + case 'S': + case 't': + case 'T': + case 'u': + case 'U': + case 'v': + case 'V': + case 'w': + case 'W': + case 'x': + case 'X': + case 'y': + case 'Y': + case 'z': + case 'Z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '-': + case '_': + case '.': + case '~': + /** + * Unreserved characters can (and should!) stay unchanged! + * (See {@link http://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters}) + */ + out.append(Character.toChars(codePoint)); + break; + default: + /** + * All other characters are reserved or special characters and, + * hence, must be encoded! + */ + String encoded = new String(Character.toChars(codePoint)); + byte[] bytes = encoded.getBytes(charset); + for (int j = 0; j < bytes.length; j++) { + out.append('%'); + out.append(Character.forDigit((bytes[j] >> 4) & 0xF, 16)); + out.append(Character.forDigit((bytes[j]) & 0xF, 16)); + } + } + } + return out.toString(); + } + + public String decode(CharSequence in) { + StringBuilder out = new StringBuilder(); + int i = 0; + int length = in.length(); + while (i < length) { + char c = in.charAt(i); + if (c != '%') { + out.append(c); + i++; + } + else { + byte[] bytes = new byte[length-i/3]; + int pos = 0; + while (i+2 < length && in.charAt(i) == '%' ) { + int b = 0; + switch (in.charAt(i+1)) { + case '0': break; + case '1': b = 16*1; break; + case '2': b = 16*2; break; + case '3': b = 16*3; break; + case '4': b = 16*4; break; + case '5': b = 16*5; break; + case '6': b = 16*6; break; + case '7': b = 16*7; break; + case '8': b = 16*8; break; + case '9': b = 16*9; break; + case 'a': + case 'A': b = 16*10; break; + case 'b': + case 'B': b = 16*11; break; + case 'c': + case 'C': b = 16*12; break; + case 'd': + case 'D': b = 16*13; break; + case 'e': + case 'E': b = 16*14; break; + case 'f': + case 'F': b = 16*15; break; + default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3)); + } + switch (in.charAt(i+2)) { + case '0': break; + case '1': b += 1; break; + case '2': b += 2; break; + case '3': b += 3; break; + case '4': b += 4; break; + case '5': b += 5; break; + case '6': b += 6; break; + case '7': b += 7; break; + case '8': b += 8; break; + case '9': b += 9; break; + case 'a': + case 'A': b += 10; break; + case 'b': + case 'B': b += 11; break; + case 'c': + case 'C': b += 12; break; + case 'd': + case 'D': b += 13; break; + case 'e': + case 'E': b += 14; break; + case 'f': + case 'F': b += 15; break; + default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3)); + } + bytes[pos++] = (byte)b; + i += 3; + } + out.append(new String(bytes, 0, pos, charset)); + if (i < length && in.charAt(i) == '%') + throw new IllegalArgumentException("Incomplete escape-sequence: %" + in.subSequence(i, length)); + } + } + return out.toString(); + } +} diff --git a/percentcodec/src/test/java/de/juplo/percentcodec/PercentCodecTest.java b/percentcodec/src/test/java/de/juplo/percentcodec/PercentCodecTest.java new file mode 100644 index 00000000..bec35fa3 --- /dev/null +++ b/percentcodec/src/test/java/de/juplo/percentcodec/PercentCodecTest.java @@ -0,0 +1,115 @@ +package de.juplo.percentcodec; + +import de.juplo.percentcodec.PercentCodec; +import junit.framework.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class PercentCodecTest { + private final static Logger log = LoggerFactory.getLogger(PercentCodecTest.class); + + public final static char[] decoded = { ' ', '+', 'q', 's', '8', '0', 'x', 'ä', 'ß', 'à', '€', '¢', '@', '/', '?', '#', ';','.', '&', '%' }; + public final static String[] encoded_latin1 = { "%20", "%2b", "q", "s", "8", "0", "x", "%e4", "%df", "%e0", "%3f", "%a2", "%40", "%2f", "%3f", "%23", "%3b",".", "%26", "%25" }; + public final static String[] encoded_utf8 = { "%20", "%2b", "q", "s", "8", "0", "x", "%c3%a4", "%c3%9f", "%c3%a0", "%e2%82%ac", "%c2%a2", "%40", "%2f", "%3f", "%23", "%3b",".", "%26", "%25" }; + + + @Test + public void testEncodeLatin1() throws Exception { + PercentCodec codec = new PercentCodec("latin1"); + + for (int a = 0; a < decoded.length; a++) { + for (int b = 0; b < decoded.length; b++) { + for (int c = 0; c < decoded.length; c++) { + /** Das Zeichen '€' existiert in Latin1 nicht! */ + if (a == 10 || b == 10 || c == 10) + continue; + StringBuilder input = new StringBuilder(); + input.append(decoded[a]); + input.append(decoded[b]); + input.append(decoded[c]); + StringBuilder expected = new StringBuilder(); + expected.append(encoded_latin1[a]); + expected.append(encoded_latin1[b]); + expected.append(encoded_latin1[c]); + String output = codec.encode(input); + log.debug("{}\t-> {}", input, output); + Assert.assertEquals("\"" + input + "\" was encoded falsely", expected.toString(), output); + } + } + } + } + + @Test + public void testDecodeLatin1() throws Exception { + PercentCodec codec = new PercentCodec("latin1"); + + for (int a = 0; a < decoded.length; a++) { + for (int b = 0; b < decoded.length; b++) { + for (int c = 0; c < decoded.length; c++) { + /** Das Zeichen '€' existiert in Latin1 nicht! */ + if (a == 10 || b == 10 || c == 10) + continue; + StringBuilder input = new StringBuilder(); + input.append(encoded_latin1[a]); + input.append(encoded_latin1[b]); + input.append(encoded_latin1[c]); + StringBuilder expected = new StringBuilder(); + expected.append(decoded[a]); + expected.append(decoded[b]); + expected.append(decoded[c]); + String output = codec.decode(input); + log.debug("{}\t-> {}", input, output); + Assert.assertEquals("\"" + input + "\" was decoded falsely", expected.toString(), output); + } + } + } + } + + @Test + public void testEncodeUtf8() throws Exception { + PercentCodec codec = new PercentCodec("UTF-8"); + + for (int a = 0; a < decoded.length; a++) { + for (int b = 0; b < decoded.length; b++) { + for (int c = 0; c < decoded.length; c++) { + StringBuilder input = new StringBuilder(); + input.append(decoded[a]); + input.append(decoded[b]); + input.append(decoded[c]); + StringBuilder expected = new StringBuilder(); + expected.append(encoded_utf8[a]); + expected.append(encoded_utf8[b]); + expected.append(encoded_utf8[c]); + String output = codec.encode(input); + log.debug("{}\t-> {}", input, output); + Assert.assertEquals("\"" + input + "\" was encoded falsely", expected.toString(), output); + } + } + } + } + + @Test + public void testDecodeUtf8() throws Exception { + PercentCodec codec = new PercentCodec("UTF-8"); + + for (int a = 0; a < decoded.length; a++) { + for (int b = 0; b < decoded.length; b++) { + for (int c = 0; c < decoded.length; c++) { + StringBuilder input = new StringBuilder(); + input.append(encoded_utf8[a]); + input.append(encoded_utf8[b]); + input.append(encoded_utf8[c]); + StringBuilder expected = new StringBuilder(); + expected.append(decoded[a]); + expected.append(decoded[b]); + expected.append(decoded[c]); + String output = codec.decode(input); + log.debug("{}\t-> {}", input, output); + Assert.assertEquals("\"" + input + "\" was decoded falsely", expected.toString(), output); + } + } + } + } +} diff --git a/percentcodec/src/test/resources/log4j.xml b/percentcodec/src/test/resources/log4j.xml new file mode 100644 index 00000000..18822a58 --- /dev/null +++ b/percentcodec/src/test/resources/log4j.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 8c63f411..ec6d8305 100644 --- a/pom.xml +++ b/pom.xml @@ -4,12 +4,12 @@ 4.0.0 - de.halbekunst + de.juplo juplo - 2.0-SNAPSHOT + 1.0-SNAPSHOT Juplo pom - http://www.halbekunst.de + http://www.juplo.de 2.0.6 @@ -19,126 +19,36 @@ kai Kai Moritz - kai@ich-geh-kaputt.de + kai@juplo.de - test - cachecontrol - examples + testingtools + percentcodec + maven-plugins + accelerator + accelerator-examples - UTF-8 - - - 1.6.11 - 1.3.2 - 1.7.1 - 6.0.29 - 8.1.4.v20120524 - 1.0 - 1.2 - 4.8.1 - 1.2.16 - 2.5 - slf4j-log4j12 - 1.6.1 - 3.0.6.RELEASE - - - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - - junit - junit - ${junit.version} - test - - - - - - - - org.jboss.repository.maven - http://repository.jboss.org/maven2 - false - - - halbekunst.internal - Internal Release Repository - http://halbekunst.de/archiva/repository/internal/ - false - - - - halbekunst.internal + juplo.internal Internal Release Repository - http://halbekunst.de/archiva/repository/internal/ + http://juplo.de/archiva/repository/internal/ - halbekunst.snapshots + juplo.snapshots Internal Snapshot Repository - http://halbekunst.de/archiva/repository/snapshots/ + http://juplo.de/archiva/repository/snapshots/ - - - - org.apache.maven.plugins - maven-compiler-plugin - - 1.6 - 1.6 - utf8 - true - - - - org.apache.maven.plugins - maven-dependency-plugin - - - install - install - - sources - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - verify - - jar - - - - - - - @@ -147,27 +57,6 @@ maven-checkstyle-plugin - - org.codehaus.mojo - cobertura-maven-plugin - 2.0 - - - maven-javadoc-plugin - - - maven-jxr-plugin - - - maven-pmd-plugin - - true - 1.5 - - - - maven-surefire-report-plugin - diff --git a/test/pom.xml b/test/pom.xml deleted file mode 100644 index 7c3a4b48..00000000 --- a/test/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo - 2.0-SNAPSHOT - - - ${pom.parent.artifactId}-test - Juplo - Test - - - - - - httpunit - httpunit - ${httpunit.version} - - - junit - junit - - - javax.servlet - servlet-api - - - - - junit - junit - ${junit.version} - - - org.springframework - spring-test - ${springframework.version} - provided - - - org.eclipse.jetty - jetty-servlet - ${jetty.version} - - - - - org.springframework - spring-beans - ${springframework.version} - provided - - - org.springframework - spring-context - ${springframework.version} - provided - - - javax.servlet - servlet-api - ${servlet-api.version} - provided - - - - diff --git a/test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java b/test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java deleted file mode 100644 index 76c46468..00000000 --- a/test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java +++ /dev/null @@ -1,84 +0,0 @@ -package de.halbekunst.juplo.test; - -import com.meterware.httpunit.WebResponse; -import com.meterware.servletunit.InvocationContext; -import com.meterware.servletunit.ServletRunner; -import com.meterware.servletunit.ServletUnitClient; -import java.io.File; -import java.util.Enumeration; -import javax.servlet.http.HttpServletRequest; -import org.junit.Before; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author kai - */ -public abstract class HttpTestCase { - - private final static Logger log = LoggerFactory.getLogger(HttpTestCase.class); - - private ServletRunner sr; - private File config; - public ServletUnitClient client; - - - public HttpTestCase(String config) { - this(new File(config)); - } - - public HttpTestCase(File config) { - if (!config.exists()) - throw new RuntimeException("web.xml is missing: " + config + " does not exist."); - this.config = config; - } - - - @Before - public void init() throws Exception { - sr = new ServletRunner(config, ""); // Dies ist der einzige Konstruktor, der die Context-Root der Webapp im Testfall korrekt initialisiert :/ - client = sr.newClient(); - } - - public WebResponse executeRequest(String uri) throws Exception { - log.debug("---------- GET: {}", uri); - InvocationContext invocation = client.newInvocation(uri); - HttpServletRequest request = invocation.getRequest(); - log.debug("Request - {}: {}", request.getMethod(), request.getProtocol()); - Enumeration headers = request.getHeaderNames(); - while (headers.hasMoreElements()) { - String header = headers.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - log.debug("Request - {}: {}", header, values.nextElement()); - } - - log.debug("Invocing service method."); - - /** - * We cannot call invocation.service(), because we have to wrap the - * response. Therefore this was coppied from InvocationContextImpl. - */ - LoggingHttpServletResponseWrapper wrappedResponse = - new LoggingHttpServletResponseWrapper(uri, invocation.getResponse()); - if (invocation.isFilterActive()) { - invocation.getFilter().doFilter(invocation.getRequest(), wrappedResponse, invocation.getFilterChain()); - } - else { - invocation.getServlet().service(invocation.getRequest(), wrappedResponse); - } - long count = wrappedResponse.close(); - - WebResponse response = invocation.getServletResponse(); - log.debug("Response - {}: {}", response.getResponseCode(), response.getResponseMessage()); - log.debug("Response - {}, {} bytes", response.getContentType(), count); - for (String header : response.getHeaderFieldNames()) { - for (String value : response.getHeaderFields(header)) { - log.debug("Response - {}: {}", header, value); - } - } - return response; - } -} - diff --git a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java b/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java deleted file mode 100644 index b9dad518..00000000 --- a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -package de.halbekunst.juplo.test; - -import java.io.IOException; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author kai - */ -public class LoggingHttpServletResponseFilter implements Filter { - - private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class); - - - @Override - public void init(FilterConfig filterConfig) throws ServletException { - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest)req; - HttpServletResponse response = (HttpServletResponse)res; - log.info("counting request {}", request.getRequestURI()); - LoggingHttpServletResponseWrapper wrappedResponse = - new LoggingHttpServletResponseWrapper(request.getRequestURI(), response); - chain.doFilter(request, wrappedResponse); - log.info("response-size: {}", wrappedResponse.close()); - } - - @Override - public void destroy() { - } -} diff --git a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java b/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java deleted file mode 100644 index 075e6a79..00000000 --- a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java +++ /dev/null @@ -1,359 +0,0 @@ -package de.halbekunst.juplo.test; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.util.Collection; -import java.util.Date; -import java.util.Locale; -import javax.servlet.ServletOutputStream; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * @author kai - */ -public class LoggingHttpServletResponseWrapper implements HttpServletResponse { - - private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class); - - public final static int DEFAULT_BUFFER_SIZE = 1024; - - private static long count = 0; - - private final Long no; - private final HttpServletResponse response; - private CountingServletOutputStream out; - private ServletOutputStream stream; - private PrintWriter writer; - private int buffer = DEFAULT_BUFFER_SIZE; - private boolean committed = false; - - - LoggingHttpServletResponseWrapper(String name, HttpServletResponse response) { - no = ++count; - log.debug("New request {}: {}", no, name); - this.response = response; - } - - - public long close() throws IOException { - if (out == null) { - return -1l; - } - else { - if (writer != null) - writer.close(); - else - stream.close(); - long result = out.count; - out = null; - buffer = DEFAULT_BUFFER_SIZE; - return result; - } - } - - @Override - public void flushBuffer() throws IOException { - log.debug("{} -- flushing buffer", no); - committed = true; - response.flushBuffer(); - } - - @Override - public int getBufferSize() { - log.trace("{} -- getting buffer size: {}", no, buffer); - return buffer; - } - - @Override - public boolean isCommitted() { - Boolean result = committed || response.isCommitted(); - log.trace("{} -- commited? {}", no, result); - return result; - } - - @Override - public void reset() { - log.debug("{} -- reset!", no); - if (committed) - throw new IllegalStateException("call to reset() after response has been commited!"); - if (out != null) - out.count = 0; - response.reset(); - } - - @Override - public void resetBuffer() { - log.debug("{} -- resetting buffer", no); - if (committed) - throw new IllegalStateException("call to resetBuffer() after response has been commited!"); - if (out != null) - out.count = 0; - response.resetBuffer(); - } - - @Override - public void setBufferSize(int size) { - log.debug("{} -- setting buffer size to {}", no, size); - if (out != null && out.count > 0) - throw new IllegalStateException("call to setBuffer() after content has been written!"); - response.setBufferSize(size); - buffer = size; - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - log.debug("{} -- getting output stream", no); - - if (writer != null) - throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!"); - - if (stream == null) { - log.debug("{} -- creating new servlet output stream", no); - out = new CountingServletOutputStream(response.getOutputStream()); - stream = out; - } - - return stream; - } - - @Override - public PrintWriter getWriter() throws IOException { - log.debug("{} -- getting print writer", no); - - if (stream != null) - throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!"); - - if (writer == null) { - log.debug("{} -- creating new print writer", no); - out = new CountingServletOutputStream(response.getOutputStream()); - OutputStreamWriter streamWriter = new OutputStreamWriter(out, response.getCharacterEncoding()); - writer = new PrintWriter(streamWriter); - } - - return writer; - } - - @Override - public void addCookie(Cookie cookie) { - log.debug("{} -- adding cookie: {}", no, cookie); - response.addCookie(cookie); - } - - @Override - public boolean containsHeader(String name) { - Boolean result = response.containsHeader(name); - log.trace("{} -- contains header {}? {}", new Object[] { no, name, result }); - return result; - } - - @Override - public String encodeURL(String url) { - log.trace("{} -- encoding url {}", no, url); - return response.encodeURL(url); - } - - @Override - public String encodeRedirectURL(String url) { - log.trace("{} -- encoding redirect url {}", no, url); - return response.encodeRedirectURL(url); - } - - @Override - public String encodeUrl(String url) { - log.trace("{} -- encoding url {}", no, url); - return response.encodeUrl(url); - } - - @Override - public String encodeRedirectUrl(String url) { - log.trace("{} -- encoding redirect url {}", no, url); - return response.encodeRedirectUrl(url); - } - - @Override - public void sendError(int sc, String msg) throws IOException { - log.debug("{} -- sending error: {}. {}", new Object[] { no, sc, msg }); - response.sendError(sc, msg); - } - - @Override - public void sendError(int sc) throws IOException { - log.debug("{} -- sending error: {}", no, sc); - } - - @Override - public void sendRedirect(String location) throws IOException { - log.debug("{} -- sending redirect: {}", no, location); - response.sendRedirect(location); - } - - @Override - public void setDateHeader(String name, long date) { - log.debug("{} -- setting date header {} to {}", new Object[] { no, name, new Date(date) }); - response.setDateHeader(name, date); - } - - @Override - public void addDateHeader(String name, long date) { - log.debug("{} -- adding date header {}: {}", new Object[] { no, name, new Date(date) }); - response.addDateHeader(name, date); - } - - @Override - public void setHeader(String name, String value) { - log.debug("{} -- setting header {} to {}", new Object[] { no, name, value }); - response.setHeader(name, value); - } - - @Override - public void addHeader(String name, String value) { - log.debug("{} -- adding header {}: {}", new Object[] { no, name, value }); - response.addHeader(name, value); - } - - @Override - public void setIntHeader(String name, int value) { - log.debug("{} -- seting int header {} to {}", new Object[] { no, name, value }); - response.setIntHeader(name, value); - } - - @Override - public void addIntHeader(String name, int value) { - log.debug("{} -- adding int header {}: {}", new Object[] { no, name, value }); - response.addIntHeader(name, value); - } - - @Override - public void setStatus(int sc) { - log.debug("{} -- setting status to {}", no, sc); - response.setStatus(sc); - } - - @Override - public void setStatus(int sc, String sm) { - log.debug("{} -- setting status to {} (message: {})", new Object[] { no, sc, sm }); - response.setStatus(sc, sm); - } - - @Override - public String getCharacterEncoding() { - String result = response.getCharacterEncoding(); - log.trace("{} -- character encoding: {}", no, result); - return result; - } - - @Override - public String getContentType() { - String result = response.getContentType(); - log.trace("{} -- content type: {}", no, result); - return result; - } - - @Override - public void setCharacterEncoding(String charset) { - log.debug("{} -- setting character encoding to {}", no, charset); - response.setCharacterEncoding(charset); - } - - @Override - public void setContentLength(int len) { - log.debug("{} -- setting content length to {}", no, len); - response.setContentLength(len); - } - - @Override - public void setContentType(String type) { - log.debug("{} -- setting content type to {}", no, type); - response.setContentType(type); - } - - @Override - public void setLocale(Locale loc) { - log.debug("{} -- setting locale to {}", no, loc); - response.setLocale(loc); - } - - @Override - public Locale getLocale() { - Locale locale = response.getLocale(); - log.trace("{} -- locale: {}", no, locale); - return locale; - } - - @Override - public int getStatus() { - Integer status = response.getStatus(); - log.trace("{} -- status: {}", no, status); - return status; - } - - @Override - public String getHeader(String name) { - String value = response.getHeader(name); - log.trace("{} -- header \"{}\": {}", new Object[] { no, name, value }); - return value; - } - - @Override - public Collection getHeaders(String name) { - Collection values = response.getHeaders(name); - if (log.isTraceEnabled()) { - StringBuilder builder = new StringBuilder(); - builder.append(no); - builder.append(" -- headers \""); - builder.append(name); - builder.append("\":"); - for (String value : values) { - builder.append(' '); - builder.append(value); - } - log.trace(builder.toString()); - } - return values; - } - - @Override - public Collection getHeaderNames() { - Collection values = response.getHeaderNames(); - if (log.isTraceEnabled()) { - StringBuilder builder = new StringBuilder(); - builder.append(no); - builder.append(" -- header-names:"); - for (String value : values) { - builder.append(' '); - builder.append(value); - } - log.trace(builder.toString()); - } - return values; - } - - - class CountingServletOutputStream extends ServletOutputStream { - - private ServletOutputStream out; - long count = 0l; - - - CountingServletOutputStream(ServletOutputStream out) { - this.out = out; - } - - - @Override - public void write(int i) throws IOException { - count++; - /** Simulate commit, when count is getting bigger then buffer */ - if (count == buffer + 1) { - log.info("{} -- simulating commit because buffer overflow! buffer: {}, count: {}", new Object[] { no, buffer, count }); - committed = true; - } - log.trace("{} -- writing byte {}: {}", new Object[] { no, count, (char)i }); - out.write(i); - } - } -} diff --git a/test/src/main/java/de/halbekunst/juplo/test/TestServlet.java b/test/src/main/java/de/halbekunst/juplo/test/TestServlet.java deleted file mode 100644 index b2b29f44..00000000 --- a/test/src/main/java/de/halbekunst/juplo/test/TestServlet.java +++ /dev/null @@ -1,79 +0,0 @@ -package de.halbekunst.juplo.test; - -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.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * - * @author kai - */ -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 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 - * Source: http://stackoverflow.com/questions/2411343/request-scoped-beans-in-spring-testing - *

- * Anwendung: - * - * @RunWith(SpringJUnit4ClassRunner.class) - * @ContextConfiguration(locations = "classpath:spring/TestScopedBeans-context.xml") - * @TestExecutionListeners({ - * WebContextTestExecutionListener.class, - * DependencyInjectionTestExecutionListener.class, - * DirtiesContextTestExecutionListener.class }) - * public class TestScopedBeans { - * ... - * - */ -public class WebContextTestExecutionListener extends AbstractTestExecutionListener { - @Override - public void prepareTestInstance(TestContext testContext) throws Exception { - - if (testContext.getApplicationContext() instanceof GenericApplicationContext) { - GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext(); - ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); - Scope requestScope = new SimpleThreadScope(); - beanFactory.registerScope("request", requestScope); - Scope sessionScope = new SimpleThreadScope(); - beanFactory.registerScope("session", sessionScope); - } - } -} diff --git a/testingtools/pom.xml b/testingtools/pom.xml new file mode 100644 index 00000000..93e23930 --- /dev/null +++ b/testingtools/pom.xml @@ -0,0 +1,184 @@ + + + + 4.0.0 + + de.juplo + juplo-testingtools + Juplo - Testing-Tools + 1.0 + http://www.juplo.de/testingtools + + + 2.0.6 + + + + + kai + Kai Moritz + kai@juplo.de + + + + + + + UTF-8 + + 1.7.1 + 8.1.4.v20120524 + 4.8.1 + 2.5 + 1.6.1 + 3.0.6.RELEASE + + + + + + httpunit + httpunit + ${httpunit.version} + + + junit + junit + + + javax.servlet + servlet-api + + + + + junit + junit + ${junit.version} + + + org.springframework + spring-test + ${springframework.version} + provided + + + org.eclipse.jetty + jetty-servlet + ${jetty.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.springframework + spring-beans + ${springframework.version} + provided + + + org.springframework + spring-context + ${springframework.version} + provided + + + javax.servlet + servlet-api + ${servlet-api.version} + provided + + + + + + juplo.internal + Internal Release Repository + http://juplo.de/archiva/repository/internal/ + + + juplo.snapshots + Internal Snapshot Repository + http://juplo.de/archiva/repository/snapshots/ + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + utf8 + true + + + + org.apache.maven.plugins + maven-dependency-plugin + + + install + install + + sources + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + verify + + jar + + + + + + + + + + + maven-changes-plugin + + + maven-checkstyle-plugin + + + org.codehaus.mojo + cobertura-maven-plugin + 2.0 + + + maven-javadoc-plugin + + + maven-jxr-plugin + + + maven-pmd-plugin + + true + 1.5 + + + + maven-surefire-report-plugin + 2.7.2 + + + + + diff --git a/testingtools/src/main/java/de/juplo/testingtools/HttpTestCase.java b/testingtools/src/main/java/de/juplo/testingtools/HttpTestCase.java new file mode 100644 index 00000000..a243bc32 --- /dev/null +++ b/testingtools/src/main/java/de/juplo/testingtools/HttpTestCase.java @@ -0,0 +1,84 @@ +package de.juplo.testingtools; + +import com.meterware.httpunit.WebResponse; +import com.meterware.servletunit.InvocationContext; +import com.meterware.servletunit.ServletRunner; +import com.meterware.servletunit.ServletUnitClient; +import java.io.File; +import java.util.Enumeration; +import javax.servlet.http.HttpServletRequest; +import org.junit.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author kai + */ +public abstract class HttpTestCase { + + private final static Logger log = LoggerFactory.getLogger(HttpTestCase.class); + + private ServletRunner sr; + private File config; + public ServletUnitClient client; + + + public HttpTestCase(String config) { + this(new File(config)); + } + + public HttpTestCase(File config) { + if (!config.exists()) + throw new RuntimeException("web.xml is missing: " + config + " does not exist."); + this.config = config; + } + + + @Before + public void init() throws Exception { + sr = new ServletRunner(config, ""); // Dies ist der einzige Konstruktor, der die Context-Root der Webapp im Testfall korrekt initialisiert :/ + client = sr.newClient(); + } + + public WebResponse executeRequest(String uri) throws Exception { + log.debug("---------- GET: {}", uri); + InvocationContext invocation = client.newInvocation(uri); + HttpServletRequest request = invocation.getRequest(); + log.debug("Request - {}: {}", request.getMethod(), request.getProtocol()); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String header = headers.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + log.debug("Request - {}: {}", header, values.nextElement()); + } + + log.debug("Invocing service method."); + + /** + * We cannot call invocation.service(), because we have to wrap the + * response. Therefore this was coppied from InvocationContextImpl. + */ + LoggingHttpServletResponseWrapper wrappedResponse = + new LoggingHttpServletResponseWrapper(uri, invocation.getResponse()); + if (invocation.isFilterActive()) { + invocation.getFilter().doFilter(invocation.getRequest(), wrappedResponse, invocation.getFilterChain()); + } + else { + invocation.getServlet().service(invocation.getRequest(), wrappedResponse); + } + long count = wrappedResponse.close(); + + WebResponse response = invocation.getServletResponse(); + log.debug("Response - {}: {}", response.getResponseCode(), response.getResponseMessage()); + log.debug("Response - {}, {} bytes", response.getContentType(), count); + for (String header : response.getHeaderFieldNames()) { + for (String value : response.getHeaderFields(header)) { + log.debug("Response - {}: {}", header, value); + } + } + return response; + } +} + diff --git a/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseFilter.java b/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseFilter.java new file mode 100644 index 00000000..4181f205 --- /dev/null +++ b/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseFilter.java @@ -0,0 +1,42 @@ +package de.juplo.testingtools; + +import java.io.IOException; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * + * @author kai + */ +public class LoggingHttpServletResponseFilter implements Filter { + + private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class); + + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)res; + log.info("counting request {}", request.getRequestURI()); + LoggingHttpServletResponseWrapper wrappedResponse = + new LoggingHttpServletResponseWrapper(request.getRequestURI(), response); + chain.doFilter(request, wrappedResponse); + log.info("response-size: {}", wrappedResponse.close()); + } + + @Override + public void destroy() { + } +} diff --git a/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseWrapper.java b/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseWrapper.java new file mode 100644 index 00000000..eb6a0675 --- /dev/null +++ b/testingtools/src/main/java/de/juplo/testingtools/LoggingHttpServletResponseWrapper.java @@ -0,0 +1,359 @@ +package de.juplo.testingtools; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.util.Collection; +import java.util.Date; +import java.util.Locale; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author kai + */ +public class LoggingHttpServletResponseWrapper implements HttpServletResponse { + + private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class); + + public final static int DEFAULT_BUFFER_SIZE = 1024; + + private static long count = 0; + + private final Long no; + private final HttpServletResponse response; + private CountingServletOutputStream out; + private ServletOutputStream stream; + private PrintWriter writer; + private int buffer = DEFAULT_BUFFER_SIZE; + private boolean committed = false; + + + LoggingHttpServletResponseWrapper(String name, HttpServletResponse response) { + no = ++count; + log.debug("New request {}: {}", no, name); + this.response = response; + } + + + public long close() throws IOException { + if (out == null) { + return -1l; + } + else { + if (writer != null) + writer.close(); + else + stream.close(); + long result = out.count; + out = null; + buffer = DEFAULT_BUFFER_SIZE; + return result; + } + } + + @Override + public void flushBuffer() throws IOException { + log.debug("{} -- flushing buffer", no); + committed = true; + response.flushBuffer(); + } + + @Override + public int getBufferSize() { + log.trace("{} -- getting buffer size: {}", no, buffer); + return buffer; + } + + @Override + public boolean isCommitted() { + Boolean result = committed || response.isCommitted(); + log.trace("{} -- commited? {}", no, result); + return result; + } + + @Override + public void reset() { + log.debug("{} -- reset!", no); + if (committed) + throw new IllegalStateException("call to reset() after response has been commited!"); + if (out != null) + out.count = 0; + response.reset(); + } + + @Override + public void resetBuffer() { + log.debug("{} -- resetting buffer", no); + if (committed) + throw new IllegalStateException("call to resetBuffer() after response has been commited!"); + if (out != null) + out.count = 0; + response.resetBuffer(); + } + + @Override + public void setBufferSize(int size) { + log.debug("{} -- setting buffer size to {}", no, size); + if (out != null && out.count > 0) + throw new IllegalStateException("call to setBuffer() after content has been written!"); + response.setBufferSize(size); + buffer = size; + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + log.debug("{} -- getting output stream", no); + + if (writer != null) + throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!"); + + if (stream == null) { + log.debug("{} -- creating new servlet output stream", no); + out = new CountingServletOutputStream(response.getOutputStream()); + stream = out; + } + + return stream; + } + + @Override + public PrintWriter getWriter() throws IOException { + log.debug("{} -- getting print writer", no); + + if (stream != null) + throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!"); + + if (writer == null) { + log.debug("{} -- creating new print writer", no); + out = new CountingServletOutputStream(response.getOutputStream()); + OutputStreamWriter streamWriter = new OutputStreamWriter(out, response.getCharacterEncoding()); + writer = new PrintWriter(streamWriter); + } + + return writer; + } + + @Override + public void addCookie(Cookie cookie) { + log.debug("{} -- adding cookie: {}", no, cookie); + response.addCookie(cookie); + } + + @Override + public boolean containsHeader(String name) { + Boolean result = response.containsHeader(name); + log.trace("{} -- contains header {}? {}", new Object[] { no, name, result }); + return result; + } + + @Override + public String encodeURL(String url) { + log.trace("{} -- encoding url {}", no, url); + return response.encodeURL(url); + } + + @Override + public String encodeRedirectURL(String url) { + log.trace("{} -- encoding redirect url {}", no, url); + return response.encodeRedirectURL(url); + } + + @Override + public String encodeUrl(String url) { + log.trace("{} -- encoding url {}", no, url); + return response.encodeUrl(url); + } + + @Override + public String encodeRedirectUrl(String url) { + log.trace("{} -- encoding redirect url {}", no, url); + return response.encodeRedirectUrl(url); + } + + @Override + public void sendError(int sc, String msg) throws IOException { + log.debug("{} -- sending error: {}. {}", new Object[] { no, sc, msg }); + response.sendError(sc, msg); + } + + @Override + public void sendError(int sc) throws IOException { + log.debug("{} -- sending error: {}", no, sc); + } + + @Override + public void sendRedirect(String location) throws IOException { + log.debug("{} -- sending redirect: {}", no, location); + response.sendRedirect(location); + } + + @Override + public void setDateHeader(String name, long date) { + log.debug("{} -- setting date header {} to {}", new Object[] { no, name, new Date(date) }); + response.setDateHeader(name, date); + } + + @Override + public void addDateHeader(String name, long date) { + log.debug("{} -- adding date header {}: {}", new Object[] { no, name, new Date(date) }); + response.addDateHeader(name, date); + } + + @Override + public void setHeader(String name, String value) { + log.debug("{} -- setting header {} to {}", new Object[] { no, name, value }); + response.setHeader(name, value); + } + + @Override + public void addHeader(String name, String value) { + log.debug("{} -- adding header {}: {}", new Object[] { no, name, value }); + response.addHeader(name, value); + } + + @Override + public void setIntHeader(String name, int value) { + log.debug("{} -- seting int header {} to {}", new Object[] { no, name, value }); + response.setIntHeader(name, value); + } + + @Override + public void addIntHeader(String name, int value) { + log.debug("{} -- adding int header {}: {}", new Object[] { no, name, value }); + response.addIntHeader(name, value); + } + + @Override + public void setStatus(int sc) { + log.debug("{} -- setting status to {}", no, sc); + response.setStatus(sc); + } + + @Override + public void setStatus(int sc, String sm) { + log.debug("{} -- setting status to {} (message: {})", new Object[] { no, sc, sm }); + response.setStatus(sc, sm); + } + + @Override + public String getCharacterEncoding() { + String result = response.getCharacterEncoding(); + log.trace("{} -- character encoding: {}", no, result); + return result; + } + + @Override + public String getContentType() { + String result = response.getContentType(); + log.trace("{} -- content type: {}", no, result); + return result; + } + + @Override + public void setCharacterEncoding(String charset) { + log.debug("{} -- setting character encoding to {}", no, charset); + response.setCharacterEncoding(charset); + } + + @Override + public void setContentLength(int len) { + log.debug("{} -- setting content length to {}", no, len); + response.setContentLength(len); + } + + @Override + public void setContentType(String type) { + log.debug("{} -- setting content type to {}", no, type); + response.setContentType(type); + } + + @Override + public void setLocale(Locale loc) { + log.debug("{} -- setting locale to {}", no, loc); + response.setLocale(loc); + } + + @Override + public Locale getLocale() { + Locale locale = response.getLocale(); + log.trace("{} -- locale: {}", no, locale); + return locale; + } + + @Override + public int getStatus() { + Integer status = response.getStatus(); + log.trace("{} -- status: {}", no, status); + return status; + } + + @Override + public String getHeader(String name) { + String value = response.getHeader(name); + log.trace("{} -- header \"{}\": {}", new Object[] { no, name, value }); + return value; + } + + @Override + public Collection getHeaders(String name) { + Collection values = response.getHeaders(name); + if (log.isTraceEnabled()) { + StringBuilder builder = new StringBuilder(); + builder.append(no); + builder.append(" -- headers \""); + builder.append(name); + builder.append("\":"); + for (String value : values) { + builder.append(' '); + builder.append(value); + } + log.trace(builder.toString()); + } + return values; + } + + @Override + public Collection getHeaderNames() { + Collection values = response.getHeaderNames(); + if (log.isTraceEnabled()) { + StringBuilder builder = new StringBuilder(); + builder.append(no); + builder.append(" -- header-names:"); + for (String value : values) { + builder.append(' '); + builder.append(value); + } + log.trace(builder.toString()); + } + return values; + } + + + class CountingServletOutputStream extends ServletOutputStream { + + private ServletOutputStream out; + long count = 0l; + + + CountingServletOutputStream(ServletOutputStream out) { + this.out = out; + } + + + @Override + public void write(int i) throws IOException { + count++; + /** Simulate commit, when count is getting bigger then buffer */ + if (count == buffer + 1) { + log.info("{} -- simulating commit because buffer overflow! buffer: {}, count: {}", new Object[] { no, buffer, count }); + committed = true; + } + log.trace("{} -- writing byte {}: {}", new Object[] { no, count, (char)i }); + out.write(i); + } + } +} diff --git a/testingtools/src/main/java/de/juplo/testingtools/WebContextTestExecutionListener.java b/testingtools/src/main/java/de/juplo/testingtools/WebContextTestExecutionListener.java new file mode 100644 index 00000000..93095841 --- /dev/null +++ b/testingtools/src/main/java/de/juplo/testingtools/WebContextTestExecutionListener.java @@ -0,0 +1,41 @@ +package de.juplo.testingtools; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.context.support.SimpleThreadScope; +import org.springframework.test.context.TestContext; +import org.springframework.test.context.support.AbstractTestExecutionListener; + +/** + * Diese Klasse ermöglicht es, Beans mit dem Scope REQUEST in JUnit-Testfällen + * zu verwenden. + *

+ * Source: http://stackoverflow.com/questions/2411343/request-scoped-beans-in-spring-testing + *

+ * Anwendung: + * + * @RunWith(SpringJUnit4ClassRunner.class) + * @ContextConfiguration(locations = "classpath:spring/TestScopedBeans-context.xml") + * @TestExecutionListeners({ + * WebContextTestExecutionListener.class, + * DependencyInjectionTestExecutionListener.class, + * DirtiesContextTestExecutionListener.class }) + * public class TestScopedBeans { + * ... + * + */ +public class WebContextTestExecutionListener extends AbstractTestExecutionListener { + @Override + public void prepareTestInstance(TestContext testContext) throws Exception { + + if (testContext.getApplicationContext() instanceof GenericApplicationContext) { + GenericApplicationContext context = (GenericApplicationContext) testContext.getApplicationContext(); + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + Scope requestScope = new SimpleThreadScope(); + beanFactory.registerScope("request", requestScope); + Scope sessionScope = new SimpleThreadScope(); + beanFactory.registerScope("session", sessionScope); + } + } +} diff --git a/utils/pom.xml b/utils/pom.xml deleted file mode 100644 index bb58b1fc..00000000 --- a/utils/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - 4.0.0 - - - de.halbekunst - juplo - 1.0.1 - - - ${pom.parent.artifactId}-utils - Juplo - Utils - - - - junit - junit - ${junit.version} - test - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - - - log4j - log4j - - - test - - - log4j - log4j - ${log4j.version} - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - test - - - - diff --git a/utils/src/main/java/de/halbekunst/juplo/utils/PercentCodec.java b/utils/src/main/java/de/halbekunst/juplo/utils/PercentCodec.java deleted file mode 100644 index d4c53ad1..00000000 --- a/utils/src/main/java/de/halbekunst/juplo/utils/PercentCodec.java +++ /dev/null @@ -1,198 +0,0 @@ -package de.halbekunst.juplo.utils; - -import java.nio.charset.Charset; - -/** - * This class performes percent-encoding/-decoding like described in RFC 3986. - *

- * Complete URI's are not handled by this implementation. - * That is done best with the original {@linkplain java.net.URI}-class from core Java. - * The purpose of this class is to have a simple tool to encode/decode the - * inner parts of an URI, like a segment of the URI-path (the part between two - * forward slashes) or a name or value segment of the query, where all reserved - * characters must be encoded/decoded. - * - * @author kai - */ -public class PercentCodec { - private final Charset charset; - - - public PercentCodec(String encoding) { - charset = Charset.forName(encoding); - } - - - public String encode(CharSequence in) { - StringBuilder out = new StringBuilder(); - int i = 0; - int length = in.length(); - while (i < length) { - int codePoint = Character.codePointAt(in, i); - i += Character.charCount(codePoint); - switch (codePoint) { - case 'a': - case 'A': - case 'b': - case 'B': - case 'c': - case 'C': - case 'd': - case 'D': - case 'e': - case 'E': - case 'f': - case 'F': - case 'g': - case 'G': - case 'h': - case 'H': - case 'i': - case 'I': - case 'j': - case 'J': - case 'k': - case 'K': - case 'l': - case 'L': - case 'm': - case 'M': - case 'n': - case 'N': - case 'o': - case 'O': - case 'p': - case 'P': - case 'q': - case 'Q': - case 'r': - case 'R': - case 's': - case 'S': - case 't': - case 'T': - case 'u': - case 'U': - case 'v': - case 'V': - case 'w': - case 'W': - case 'x': - case 'X': - case 'y': - case 'Y': - case 'z': - case 'Z': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - case '_': - case '.': - case '~': - /** - * Unreserved characters can (and should!) stay unchanged! - * (See {@link http://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters}) - */ - out.append(Character.toChars(codePoint)); - break; - default: - /** - * All other characters are reserved or special characters and, - * hence, must be encoded! - */ - String encoded = new String(Character.toChars(codePoint)); - byte[] bytes = encoded.getBytes(charset); - for (int j = 0; j < bytes.length; j++) { - out.append('%'); - out.append(Character.forDigit((bytes[j] >> 4) & 0xF, 16)); - out.append(Character.forDigit((bytes[j]) & 0xF, 16)); - } - } - } - return out.toString(); - } - - public String decode(CharSequence in) { - StringBuilder out = new StringBuilder(); - int i = 0; - int length = in.length(); - while (i < length) { - char c = in.charAt(i); - if (c != '%') { - out.append(c); - i++; - } - else { - byte[] bytes = new byte[length-i/3]; - int pos = 0; - while (i+2 < length && in.charAt(i) == '%' ) { - int b = 0; - switch (in.charAt(i+1)) { - case '0': break; - case '1': b = 16*1; break; - case '2': b = 16*2; break; - case '3': b = 16*3; break; - case '4': b = 16*4; break; - case '5': b = 16*5; break; - case '6': b = 16*6; break; - case '7': b = 16*7; break; - case '8': b = 16*8; break; - case '9': b = 16*9; break; - case 'a': - case 'A': b = 16*10; break; - case 'b': - case 'B': b = 16*11; break; - case 'c': - case 'C': b = 16*12; break; - case 'd': - case 'D': b = 16*13; break; - case 'e': - case 'E': b = 16*14; break; - case 'f': - case 'F': b = 16*15; break; - default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3)); - } - switch (in.charAt(i+2)) { - case '0': break; - case '1': b += 1; break; - case '2': b += 2; break; - case '3': b += 3; break; - case '4': b += 4; break; - case '5': b += 5; break; - case '6': b += 6; break; - case '7': b += 7; break; - case '8': b += 8; break; - case '9': b += 9; break; - case 'a': - case 'A': b += 10; break; - case 'b': - case 'B': b += 11; break; - case 'c': - case 'C': b += 12; break; - case 'd': - case 'D': b += 13; break; - case 'e': - case 'E': b += 14; break; - case 'f': - case 'F': b += 15; break; - default: throw new IllegalArgumentException("Illegal escape-sequence: %" + in.subSequence(i, i+3)); - } - bytes[pos++] = (byte)b; - i += 3; - } - out.append(new String(bytes, 0, pos, charset)); - if (i < length && in.charAt(i) == '%') - throw new IllegalArgumentException("Incomplete escape-sequence: %" + in.subSequence(i, length)); - } - } - return out.toString(); - } -} diff --git a/utils/src/test/java/de/halbekunst/juplo/utils/PercentCodecTest.java b/utils/src/test/java/de/halbekunst/juplo/utils/PercentCodecTest.java deleted file mode 100644 index a06784e5..00000000 --- a/utils/src/test/java/de/halbekunst/juplo/utils/PercentCodecTest.java +++ /dev/null @@ -1,114 +0,0 @@ -package de.halbekunst.juplo.utils; - -import junit.framework.Assert; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class PercentCodecTest { - private final static Logger log = LoggerFactory.getLogger(PercentCodecTest.class); - - public final static char[] decoded = { ' ', '+', 'q', 's', '8', '0', 'x', 'ä', 'ß', 'à', '€', '¢', '@', '/', '?', '#', ';','.', '&', '%' }; - public final static String[] encoded_latin1 = { "%20", "%2b", "q", "s", "8", "0", "x", "%e4", "%df", "%e0", "%3f", "%a2", "%40", "%2f", "%3f", "%23", "%3b",".", "%26", "%25" }; - public final static String[] encoded_utf8 = { "%20", "%2b", "q", "s", "8", "0", "x", "%c3%a4", "%c3%9f", "%c3%a0", "%e2%82%ac", "%c2%a2", "%40", "%2f", "%3f", "%23", "%3b",".", "%26", "%25" }; - - - @Test - public void testEncodeLatin1() throws Exception { - PercentCodec codec = new PercentCodec("latin1"); - - for (int a = 0; a < decoded.length; a++) { - for (int b = 0; b < decoded.length; b++) { - for (int c = 0; c < decoded.length; c++) { - /** Das Zeichen '€' existiert in Latin1 nicht! */ - if (a == 10 || b == 10 || c == 10) - continue; - StringBuilder input = new StringBuilder(); - input.append(decoded[a]); - input.append(decoded[b]); - input.append(decoded[c]); - StringBuilder expected = new StringBuilder(); - expected.append(encoded_latin1[a]); - expected.append(encoded_latin1[b]); - expected.append(encoded_latin1[c]); - String output = codec.encode(input); - log.debug("{}\t-> {}", input, output); - Assert.assertEquals("\"" + input + "\" was encoded falsely", expected.toString(), output); - } - } - } - } - - @Test - public void testDecodeLatin1() throws Exception { - PercentCodec codec = new PercentCodec("latin1"); - - for (int a = 0; a < decoded.length; a++) { - for (int b = 0; b < decoded.length; b++) { - for (int c = 0; c < decoded.length; c++) { - /** Das Zeichen '€' existiert in Latin1 nicht! */ - if (a == 10 || b == 10 || c == 10) - continue; - StringBuilder input = new StringBuilder(); - input.append(encoded_latin1[a]); - input.append(encoded_latin1[b]); - input.append(encoded_latin1[c]); - StringBuilder expected = new StringBuilder(); - expected.append(decoded[a]); - expected.append(decoded[b]); - expected.append(decoded[c]); - String output = codec.decode(input); - log.debug("{}\t-> {}", input, output); - Assert.assertEquals("\"" + input + "\" was decoded falsely", expected.toString(), output); - } - } - } - } - - @Test - public void testEncodeUtf8() throws Exception { - PercentCodec codec = new PercentCodec("UTF-8"); - - for (int a = 0; a < decoded.length; a++) { - for (int b = 0; b < decoded.length; b++) { - for (int c = 0; c < decoded.length; c++) { - StringBuilder input = new StringBuilder(); - input.append(decoded[a]); - input.append(decoded[b]); - input.append(decoded[c]); - StringBuilder expected = new StringBuilder(); - expected.append(encoded_utf8[a]); - expected.append(encoded_utf8[b]); - expected.append(encoded_utf8[c]); - String output = codec.encode(input); - log.debug("{}\t-> {}", input, output); - Assert.assertEquals("\"" + input + "\" was encoded falsely", expected.toString(), output); - } - } - } - } - - @Test - public void testDecodeUtf8() throws Exception { - PercentCodec codec = new PercentCodec("UTF-8"); - - for (int a = 0; a < decoded.length; a++) { - for (int b = 0; b < decoded.length; b++) { - for (int c = 0; c < decoded.length; c++) { - StringBuilder input = new StringBuilder(); - input.append(encoded_utf8[a]); - input.append(encoded_utf8[b]); - input.append(encoded_utf8[c]); - StringBuilder expected = new StringBuilder(); - expected.append(decoded[a]); - expected.append(decoded[b]); - expected.append(decoded[c]); - String output = codec.decode(input); - log.debug("{}\t-> {}", input, output); - Assert.assertEquals("\"" + input + "\" was decoded falsely", expected.toString(), output); - } - } - } - } -} diff --git a/utils/src/test/resources/log4j.xml b/utils/src/test/resources/log4j.xml deleted file mode 100644 index 18822a58..00000000 --- a/utils/src/test/resources/log4j.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - -