Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

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

0 comments on commit 7075c62

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