Skip to content
Browse files

First cut at SM3 on Webbit. It nearly worlks. Nearly.

  • Loading branch information...
0 parents commit 7075c628f122119c1068dd645ccdba6111bc8990 Joe Walnes committed Aug 10, 2011
5 .gitignore
@@ -0,0 +1,5 @@
+*.iws
+*.ipr
+*.iml
+target
+build
37 LICENSE
@@ -0,0 +1,37 @@
+(BSD License: http://www.opensource.org/licenses/bsd-license)
+
+Copyright (c) 2011, Joe Walnes and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+ copyright notice, this list of conditions and the
+ following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the
+ following disclaimer in the documentation and/or other
+ materials provided with the distribution.
+
+* Neither the name of the Webbit nor the names of
+ its contributors may be used to endorse or promote products
+ derived from this software without specific prior written
+ permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
53 Makefile
@@ -0,0 +1,53 @@
+# Common tasks:
+# make -- Full build
+# make clean -- Clean up built files
+
+LIBRARY=webbit-sitemesh
+CLASSPATH=$(shell echo $(wildcard lib/*.jar) | sed -e 's/ /:/g')
+
+# Non file targets
+.PHONY: all jar test clean sample
+
+# Default target: Compile, run tests and build tarball
+all: jar test
+jar: build/$(LIBRARY).jar build/$(LIBRARY)-src.jar
+test: build/.tests-pass
+
+# Run sample
+sample: build/$(LIBRARY)-tests.jar
+ java -cp $(CLASSPATH):build/$(LIBRARY).jar:build/$(LIBRARY)-tests.jar samples.Main
+
+# Function to find files in directory with suffix. $(call find,dir,ext)
+find = $(shell find $(1) -name '*.$(2)')
+
+# Function to extract Test class names from a jar. $(call extracttests,foo.jar)
+extracttests = $(shell jar tf $(1) | grep 'Test.class$$' | sed -e 's|/|.|g;s|.class$$||')
+
+# Compile Jar
+build/$(LIBRARY).jar: $(call find,src/main/java,java)
+ @mkdir -p build/main/classes
+ cp -R src/main/resources/* build/main/classes
+ javac -g -cp $(CLASSPATH) -d build/main/classes $(call find,src/main/java,java)
+ jar cf $@ -C build/main/classes .
+
+# Assemble source jar
+build/$(LIBRARY)-src.jar: $(call find,src/main/java,java)
+ @mkdir -p build
+ jar cf $@ -C src/main/java .
+
+# Compile tests
+build/$(LIBRARY)-tests.jar: build/$(LIBRARY).jar $(call find,src/test/java,java)
+ @mkdir -p build/test/classes
+ javac -g -cp $(CLASSPATH):build/$(LIBRARY).jar -d build/test/classes $(call find,src/test/java,java)
+ jar cf $@ -C build/test/classes .
+
+# Run tests, and create .tests-pass if they succeed
+build/.tests-pass: build/$(LIBRARY)-tests.jar
+ @rm -f $@
+ java -cp $(CLASSPATH):build/$(LIBRARY).jar:build/$(LIBRARY)-tests.jar org.junit.runner.JUnitCore $(call extracttests,build/$(LIBRARY)-tests.jar)
+ @touch $@
+
+# Clean up
+clean:
+ rm -rf build out
+
4 README.md
@@ -0,0 +1,4 @@
+# Webbit SiteMesh
+
+Allows SiteMesh3 to run on Webbit.
+
74 pom.xml
@@ -0,0 +1,74 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.webbitserver</groupId>
+ <artifactId>webbit-sitemesh</artifactId>
+ <name>${project.artifactId}</name>
+ <description>Adds SiteMesh page decorations to Webbit.</description>
+ <url>http://github.com/joewalnes/webbit-sitemesh</url>
+ <version>0.1-SNAPSHOT</version>
+ <packaging>jar</packaging>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>6</version>
+ </parent>
+ <licenses>
+ <license>
+ <name>BSD License</name>
+ <url>http://www.opensource.org/licenses/bsd-license</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>scm:git:git://github.com/joewalnes/webbit-sitemesh.git</connection>
+ <developerConnection>scm:git:git@github.com:joewalnes/webbit-sitemesh.git</developerConnection>
+ <url>git://github.com/joewalnes/webbit-sitemesh.git</url>
+ </scm>
+ <dependencies>
+ <dependency>
+ <groupId>org.webbitserver</groupId>
+ <artifactId>webbit</artifactId>
+ <version>0.1.13</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.sitemesh</groupId>
+ <artifactId>sitemesh</artifactId>
+ <version>3.0-alpha-2</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.8.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.1</version>
+ <configuration>
+ <useAgent>true</useAgent>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>2.2</version>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
120 src/main/java/org/webbitserver/sitemesh/BaseSiteMeshHandlerBuilder.java
@@ -0,0 +1,120 @@
+package org.webbitserver.sitemesh;
+
+import org.sitemesh.builder.BaseSiteMeshBuilder;
+import org.sitemesh.config.PathMapper;
+import org.webbitserver.HttpHandler;
+import org.webbitserver.HttpRequest;
+import org.webbitserver.sitemesh.contentbuffer.BasicSelector;
+import org.webbitserver.sitemesh.contentbuffer.Selector;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Functionality for building a {@link SiteMeshHandler}.
+ * Inherits common functionality from {@link org.sitemesh.builder.BaseSiteMeshBuilder}.
+ *
+ * <p>Clients should use the concrete {@link SiteMeshHandlerBuilder} implementation.</p>
+ *
+ * @see BaseSiteMeshBuilder
+ * @see SiteMeshHandler
+ *
+ * @param <BUILDER> The type to return from the builder methods. Subclasses
+ * should type this as their own class type.
+ *
+ * @author Joe Walnes
+ */
+public abstract class BaseSiteMeshHandlerBuilder<BUILDER extends BaseSiteMeshBuilder>
+ extends BaseSiteMeshBuilder<BUILDER, WebbitSiteMeshContext, HttpHandler> {
+
+ private Collection<String> mimeTypes;
+
+ private PathMapper<Boolean> excludesMapper = new PathMapper<Boolean>();
+ private Selector customSelector;
+
+ /**
+ * Create the SiteMesh Handler.
+ */
+ @Override
+ public abstract HttpHandler create();
+
+ /**
+ * See {@link BaseSiteMeshHandlerBuilder#setupDefaults()}.
+ * In addition to the parent setup, this also calls {@link #setMimeTypes(String[])} with
+ * <code>{"text/html"}</code>.
+ */
+ @Override
+ protected void setupDefaults() {
+ super.setupDefaults();
+ setMimeTypes("text/html");
+ }
+
+ // --------------------------------------------------------------
+ // Selector setup.
+
+ /**
+ * Add a path to be excluded by SiteMesh.
+ */
+ public BUILDER addExcludedPath(String exclude) {
+ excludesMapper.put(exclude, true);
+ return self();
+ }
+
+ // --------------------------------------------------------------
+ // Selector setup.
+
+ /**
+ * Set MIME types that the Filter should intercept. The default
+ * is <code>{"text/html"}</code>.
+ *
+ * <p>Note: The MIME types are ignored if {@link #setCustomSelector(Selector)} is called.</p>
+ */
+ public BUILDER setMimeTypes(String... mimeTypes) {
+ this.mimeTypes = Arrays.asList(mimeTypes);
+ return self();
+ }
+
+ /**
+ * Set MIME types that the Filter should intercept. The default
+ * is <code>{"text/html"}</code>.
+ *
+ * <p>Note: The MIME types are ignored if {@link #setCustomSelector(Selector)} is called.</p>
+ */
+ public BUILDER setMimeTypes(List<String> mimeTypes) {
+ this.mimeTypes = mimeTypes;
+ return self();
+ }
+
+ /**
+ * Set a custom {@link Selector}.
+ *
+ * <p>Note: If this is called, it will override any MIME types
+ * passed to {@link #setMimeTypes(String[])} as these are specific
+ * to the default Selector.</p>
+ */
+ public BUILDER setCustomSelector(Selector selector) {
+ this.customSelector = selector;
+ return self();
+ }
+
+ /**
+ * Get configured {@link Selector}.
+ */
+ public Selector getSelector() {
+ if (customSelector != null) {
+ return customSelector;
+ } else {
+ String[] mimeTypesArray = mimeTypes.toArray(new String[mimeTypes.size()]);
+ return new BasicSelector(mimeTypesArray) {
+ @Override
+ public boolean shouldBufferForRequest(HttpRequest request) {
+ String requestPath = WebbitSiteMeshContext.getRequestPath(request);
+ return super.shouldBufferForRequest(request)
+ && excludesMapper.get(requestPath) == null;
+ }
+ };
+ }
+ }
+
+}
60 src/main/java/org/webbitserver/sitemesh/SiteMeshHandler.java
@@ -0,0 +1,60 @@
+package org.webbitserver.sitemesh;
+
+import org.sitemesh.DecoratorSelector;
+import org.sitemesh.content.Content;
+import org.sitemesh.content.ContentProcessor;
+import org.webbitserver.HttpRequest;
+import org.webbitserver.HttpResponse;
+import org.webbitserver.sitemesh.contentbuffer.ContentBufferingHandler;
+import org.webbitserver.sitemesh.contentbuffer.Selector;
+
+import java.io.IOException;
+import java.nio.CharBuffer;
+
+public class SiteMeshHandler extends ContentBufferingHandler {
+
+ private final ContentProcessor contentProcessor;
+ private final DecoratorSelector<WebbitSiteMeshContext> decoratorSelector;
+
+ public SiteMeshHandler(Selector selector,
+ ContentProcessor contentProcessor,
+ DecoratorSelector<WebbitSiteMeshContext> decoratorSelector) {
+ super(selector);
+ this.contentProcessor = contentProcessor;
+ this.decoratorSelector = decoratorSelector;
+ }
+
+ protected void postProcessBuffer(HttpRequest httpRequest, HttpResponse httpResponse, CharBuffer buffer)
+ throws IOException {
+
+ if (buffer == null) {
+
+ }
+ WebbitSiteMeshContext context = createContext(httpRequest, contentProcessor);
+
+ // Parse page contents
+ Content content = contentProcessor.build(buffer, context);
+
+ // Apply decorators
+ if (content != null) {
+ for (String decoratorPath : decoratorSelector.selectDecoratorPaths(content, context)) {
+ // TODO: Make this bit non-blocking
+ content = context.decorate(decoratorPath, content);
+ }
+ }
+
+ System.out.println("content = " + content);
+
+ if (content != null) {
+ // Write decorated response
+ httpResponse.content(content.getData().getValue());
+ } else {
+ // Write original content
+ httpResponse.content(buffer.toString());
+ }
+ }
+
+ protected WebbitSiteMeshContext createContext(HttpRequest request, ContentProcessor contentProcessor) {
+ return new WebbitSiteMeshContext(request, contentProcessor);
+ }
+}
61 src/main/java/org/webbitserver/sitemesh/SiteMeshHandlerBuilder.java
@@ -0,0 +1,61 @@
+package org.webbitserver.sitemesh;
+
+import org.webbitserver.HttpHandler;
+
+/**
+ * Convenient API for building the main SiteMesh {@link HttpHandler}.
+ *
+ * <p>This follows the API builder pattern - each method returns a reference to the original builder
+ * so they can be chained together. When configured, call the {@link #create()} method which will
+ * return the final immutable {@link HttpHandler}.</p>
+ *
+ * <h3>Examples</h3>
+ *
+ * <pre>
+ * // Simplest example...
+ * HttpHandler siteMeshHandler = new SiteMeshHandlerBuilder()
+ * .addDecoratorPath("/*", "/decorator.html")
+ * .create();
+ *
+ * // A few more options (shows applying multiple decorators to a single page)...
+ * HttpHandler siteMeshHandler = new SiteMeshHandlerBuilder()
+ * .addDecoratorPaths("/*", "/decorators/main-layout.html", "/decorators-common-style.html")
+ * .addDecoratorPaths("/admin/*", "/decorators/admin-layout.html", "/decorators-common-style.html")
+ * .addTagRuleBundle(new MyLinkRewriterBundle())
+ * .addExcludePath("/javadoc/*")
+ * .addExcludePath("/portfolio/*")
+ * .create();
+ *
+ * // If you want to get a bit crazy and totally customize SiteMesh...
+ * HttpHandler siteMeshHandler = new SiteMeshHandlerBuilder()
+ * .setMimeTypes("image/svg+xml")
+ * .setCustomContentProcessor(new MySvgContentProcessor())
+ * .setCustomDecoratorSelector(new MyDatabaseDrivenDecoratorSelector())
+ * .create();
+ * </pre>
+ *
+ * <h3>Custom implementations (advanced)</h3>
+ *
+ * <p>This is only for advanced users who need to change the behavior of the builder...</p>
+ *
+ * <p>If you ever find the need to subclass SiteMeshHandlerBuilder (e.g. to add more convenience
+ * methods, to change the implementation returned, or add new functionality), it is instead recommended
+ * that you extend {@link BaseSiteMeshHandlerBuilder}. This way, the generic type signature can
+ * be altered.</p>
+ *
+ * @author Joe Walnes
+ */
+public class SiteMeshHandlerBuilder
+ extends BaseSiteMeshHandlerBuilder<SiteMeshHandlerBuilder> {
+
+ /**
+ * Create the SiteMesh Handler.
+ */
+ public HttpHandler create() {
+ return new SiteMeshHandler(
+ getSelector(),
+ getContentProcessor(),
+ getDecoratorSelector());
+ }
+
+}
53 src/main/java/org/webbitserver/sitemesh/WebbitSiteMeshContext.java
@@ -0,0 +1,53 @@
+package org.webbitserver.sitemesh;
+
+import org.sitemesh.BaseSiteMeshContext;
+import org.sitemesh.SiteMeshContext;
+import org.sitemesh.content.Content;
+import org.sitemesh.content.ContentProcessor;
+import org.webbitserver.HttpRequest;
+
+import java.io.IOException;
+import java.io.Writer;
+
+public class WebbitSiteMeshContext extends BaseSiteMeshContext {
+
+ /**
+ * Key that the {@link org.sitemesh.content.ContentProperty} is stored under in the {@link org.webbitserver.HttpRequest}
+ * attribute. It is "org.sitemesh.content.Content".
+ */
+ public static final String CONTENT_KEY = Content.class.getName();
+
+ /**
+ * Key that the {@link WebbitSiteMeshContext} is stored under in the {@link org.webbitserver.HttpRequest}
+ * attribute. It is "org.sitemesh.SiteMeshContext".
+ */
+ public static final String CONTEXT_KEY = SiteMeshContext.class.getName();
+
+ private final HttpRequest request;
+
+ public WebbitSiteMeshContext(HttpRequest request, ContentProcessor contentProcessor) {
+ super(contentProcessor);
+ this.request = request;
+ }
+
+ /**
+ * Dispatches to another {@link org.webbitserver.HttpHandler} to render a decorator.
+ *
+ * <p>The end point can access the {@link org.sitemesh.content.ContentProperty} and {@link SiteMeshContext} by using
+ * looking them up as {@link org.webbitserver.HttpRequest} attributes under the keys
+ * {@link #CONTENT_KEY} and {@link #CONTEXT_KEY} respectively.</p>
+ */
+ @Override
+ protected void decorate(String decoratorPath, Content content, Writer out) throws IOException {
+ // TODO
+ System.out.println("decoratorPath = " + decoratorPath);
+ }
+
+ public String getPath() {
+ return getRequestPath(request);
+ }
+
+ public static String getRequestPath(HttpRequest request) {
+ return request.uri();
+ }
+}
59 src/main/java/org/webbitserver/sitemesh/contentbuffer/BasicSelector.java
@@ -0,0 +1,59 @@
+package org.webbitserver.sitemesh.contentbuffer;
+
+import org.webbitserver.HttpRequest;
+
+/**
+ * Basic implementation of {@link Selector}. Will select OK responses that match a particular
+ * MIME type, and (optionally) error pages. It will also only kick in once per request.
+ *
+ * <p>For more control, this can be subclassed, or replaced with a different implementation of
+ * {@link Selector}.
+ *
+ * @author Joe Walnes
+ */
+public class BasicSelector implements Selector {
+
+ private static final String ALREADY_APPLIED_KEY = BasicSelector.class.getName() + ".APPLIED_ONCE";
+
+ private final String[] mimeTypesToBuffer;
+ private final boolean includeErrorPages;
+
+ public BasicSelector(String... mimeTypesToBuffer) {
+ this(false, mimeTypesToBuffer);
+ }
+
+ public BasicSelector(boolean includeErrorPages, String... mimeTypesToBuffer) {
+ this.mimeTypesToBuffer = mimeTypesToBuffer;
+ this.includeErrorPages = includeErrorPages;
+ }
+
+ public boolean shouldBufferForContentType(String contentType, String mimeType, String encoding) {
+ if (mimeType == null) {
+ return false;
+ }
+ for (String mimeTypeToBuffer : mimeTypesToBuffer) {
+ if (mimeTypeToBuffer.equalsIgnoreCase(mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean shouldAbortBufferingForHttpStatusCode(int statusCode) {
+ return !(statusCode == 200 || includeErrorPages && statusCode >= 400);
+ }
+
+ public boolean shouldBufferForRequest(HttpRequest request) {
+ return !filterAlreadyAppliedForRequest(request);
+ }
+
+ protected boolean filterAlreadyAppliedForRequest(HttpRequest request) {
+ if (Boolean.TRUE.equals(request.data(ALREADY_APPLIED_KEY))) {
+ return true;
+ } else {
+ request.data(ALREADY_APPLIED_KEY, true);
+ return false;
+ }
+ }
+
+}
145 src/main/java/org/webbitserver/sitemesh/contentbuffer/BufferedResponse.java
@@ -0,0 +1,145 @@
+package org.webbitserver.sitemesh.contentbuffer;
+
+import org.sitemesh.webapp.contentfilter.io.HttpContentType;
+import org.webbitserver.HttpResponse;
+import org.webbitserver.wrapper.HttpResponseWrapper;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+public abstract class BufferedResponse extends HttpResponseWrapper {
+
+ protected abstract void postProcess(CharBuffer buffer);
+
+ private static final Charset UTF8 = Charset.forName("UTF8");
+
+ private final Selector selector;
+
+ private ByteArrayOutputStream buffer = null;
+ private boolean bufferingWasDisabled = false;
+
+ public BufferedResponse(Selector selector, HttpResponse response) {
+ super(response);
+ this.selector = selector;
+ }
+
+ /**
+ * Enable buffering for this request. Subsequent content will be written to the buffer
+ * instead of the original response.
+ */
+ protected void enableBuffering() {
+ if (buffer != null) {
+ return; // Already buffering.
+ }
+ buffer = new ByteArrayOutputStream(1024);
+ }
+
+ /**
+ * Disable buffering for this request. Subsequent content will be written to the original
+ * response.
+ */
+ protected void disableBuffering() {
+ buffer = null;
+ bufferingWasDisabled = true;
+ }
+
+ @Override
+ public HttpResponseWrapper header(String name, String value) {
+ String lower = name.toLowerCase();
+ if (lower.equals("content-type")) {
+ setContentType(value);
+ } else if (buffer == null || !lower.equals("content-length")) {
+ super.header(name, value);
+ }
+ return this;
+ }
+
+ private void setContentType(String type) {
+ HttpContentType httpContentType = new HttpContentType(type);
+ if (selector.shouldBufferForContentType(type, httpContentType.getType(), httpContentType.getEncoding())) {
+ enableBuffering();
+ } else {
+ disableBuffering();
+ }
+ }
+
+ @Override
+ public HttpResponseWrapper header(String name, long value) {
+ String lower = name.toLowerCase();
+ if (buffer == null || !lower.equals("content-length")) {
+ return super.header(name, value);
+ }
+ return this;
+ }
+
+ @Override
+ public HttpResponseWrapper status(int status) {
+ abortBufferingIfBadStatusCode(status);
+ return super.status(status);
+ }
+
+ @Override
+ public HttpResponseWrapper error(Throwable error) {
+ abortBufferingIfBadStatusCode(500);
+ return super.error(error);
+ }
+
+ @Override
+ public HttpResponseWrapper content(String content) {
+ try {
+ if (buffer == null) {
+ return super.content(content);
+ } else {
+ buffer.write(content.getBytes(charset()));
+ return this;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public HttpResponseWrapper write(String content) {
+ try {
+ if (buffer == null) {
+ return super.write(content);
+ } else {
+ buffer.write(content.getBytes(UTF8));
+ return this;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public HttpResponseWrapper content(byte[] content) {
+ try {
+ if (buffer == null) {
+ return super.content(content);
+ } else {
+ buffer.write(content);
+ return this;
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public HttpResponseWrapper end() {
+ postProcess(buffer == null ?
+ null :
+ CharBuffer.wrap(new String(buffer.toByteArray(), charset())));
+ return super.end();
+ }
+
+ protected void abortBufferingIfBadStatusCode(int statusCode) {
+ if (selector.shouldAbortBufferingForHttpStatusCode(statusCode)) {
+ disableBuffering();
+ }
+ }
+
+}
47 src/main/java/org/webbitserver/sitemesh/contentbuffer/ContentBufferingHandler.java
@@ -0,0 +1,47 @@
+package org.webbitserver.sitemesh.contentbuffer;
+
+import org.webbitserver.HttpControl;
+import org.webbitserver.HttpHandler;
+import org.webbitserver.HttpRequest;
+import org.webbitserver.HttpResponse;
+
+import java.io.IOException;
+import java.nio.CharBuffer;
+
+public abstract class ContentBufferingHandler implements HttpHandler {
+
+ protected abstract void postProcessBuffer(HttpRequest httpRequest, HttpResponse httpResponse, CharBuffer buffer)
+ throws IOException;
+
+ private final Selector selector;
+
+ public ContentBufferingHandler(Selector selector) {
+ this.selector = selector;
+ }
+
+ public Selector selector() {
+ return selector;
+ }
+
+ public void handleHttpRequest(final HttpRequest httpRequest, final HttpResponse httpResponse, HttpControl httpControl) throws Exception {
+
+ if (!selector.shouldBufferForRequest(httpRequest)) {
+ // Fast bail out, for obviously non-SiteMeshable requests
+ httpControl.nextHandler();
+ return;
+ }
+
+ httpControl.nextHandler(httpRequest, new BufferedResponse(selector(), httpResponse) {
+ @Override
+ public void postProcess(CharBuffer buffer) {
+ try {
+ postProcessBuffer(httpRequest, httpResponse, buffer);
+ } catch (IOException e) {
+ httpResponse.error(e);
+ }
+ }
+ });
+ }
+
+}
+
38 src/main/java/org/webbitserver/sitemesh/contentbuffer/Selector.java
@@ -0,0 +1,38 @@
+package org.webbitserver.sitemesh.contentbuffer;
+
+import org.webbitserver.HttpRequest;
+
+/**
+ * Rules that will be used by the {@link ContentBufferingHandler} and {@link BufferedResponse}
+ * to determine whether the response should be buffered.
+ *
+ * For a basic implementation, use {@link BasicSelector}.
+ *
+ * @author Joe Walnes
+ */
+public interface Selector {
+
+ /**
+ * Determine whether buffering should be used for a particular content-type. Use
+ * this to ensure that only content-types you care about are intercepted.
+ *
+ * @param contentType e.g. "text/html; charset=iso-8859-1"
+ * @param mimeType e.g "text/html"
+ * @param encoding e.g. "iso-8859-1" (may be null)
+ */
+ boolean shouldBufferForContentType(String contentType, String mimeType, String encoding);
+
+ /**
+ * Determine whether buffering should be used for a particular HTTP status code.
+ * For example, some applications may choose to rewrite content of 404 error pages.
+ *
+ * @param statusCode e.g. 200, 302, 404, 500, etc.
+ */
+ boolean shouldAbortBufferingForHttpStatusCode(int statusCode);
+
+ /**
+ * Determine whether buffering should be used for a particular request. For example,
+ * elements like path, attributes, cookies, etc may influence this.
+ */
+ boolean shouldBufferForRequest(HttpRequest request);
+}
26 src/test/java/samples/Main.java
@@ -0,0 +1,26 @@
+package samples;
+
+import org.webbitserver.handler.StringHttpHandler;
+import org.webbitserver.sitemesh.SiteMeshHandlerBuilder;
+
+import java.io.IOException;
+
+import static org.webbitserver.WebServers.createWebServer;
+
+public class Main {
+
+ public static void main(String[] args) throws IOException {
+ System.out.println("Listening on: " +
+ createWebServer(1234)
+ .add(new SiteMeshHandlerBuilder()
+ .addDecoratorPath("/*", "/decorator")
+ .create())
+ .add("/hello", new StringHttpHandler("text/html",
+ "<html><body><h1>Hello</h1></body></html>"))
+ .add("/decorator", new StringHttpHandler("text/html",
+ "<html><style>body { background-color: #ffff00 }</style>" +
+ "<body><sitemesh:write property='body'/></body></html>"))
+ .start()
+ .getUri());
+ }
+}

0 comments on commit 7075c62

Please sign in to comment.
Something went wrong with that request. Please try again.