From 0db676f1bea77f061e43e7347fc71f989730206b Mon Sep 17 00:00:00 2001
From: Torstein Krause Johansen
Date: Tue, 28 Jun 2016 14:24:25 +0200
Subject: [PATCH] Re-added test case lost in large merge
- Removed imageio-jmagick (again)
- Removed internal Maven repo
- fixed line endings on a number of files to avoid a humongous merge
diff when getting changes into upstream
- Re-enabled a few tests
---
imageio/imageio-jmagick/pom.xml | 38 -
.../plugins/jpeg/JPEGImageReaderTest.java | 2 -
pom.xml | 7 -
.../twelvemonkeys/io/enc/DeflateEncoder.java | 180 +-
.../twelvemonkeys/io/enc/InflateDecoder.java | 218 +-
.../com/twelvemonkeys/lang/NativeLoader.java | 800 +++---
.../util/regex/REWildcardStringParser.java | 796 +++---
.../servlet/image/TextRenderer.java | 670 ++---
.../servlet/jsp/droplet/Droplet.java | 152 +-
.../servlet/jsp/droplet/JspFragment.java | 84 +-
.../servlet/jsp/droplet/Oparam.java | 52 +-
.../servlet/jsp/droplet/Param.java | 82 +-
.../jsp/droplet/taglib/IncludeTag.java | 428 +--
.../jsp/droplet/taglib/NestingHandler.java | 366 +--
.../jsp/droplet/taglib/NestingValidator.java | 204 +-
.../servlet/jsp/droplet/taglib/OparamTag.java | 440 ++--
.../servlet/jsp/droplet/taglib/ParamTag.java | 258 +-
.../jsp/droplet/taglib/ValueOfTEI.java | 94 +-
.../jsp/droplet/taglib/ValueOfTag.java | 296 +--
.../servlet/jsp/taglib/BodyReaderTag.java | 78 +-
.../servlet/jsp/taglib/CSVToTableTag.java | 470 ++--
.../servlet/jsp/taglib/ExBodyTagSupport.java | 580 ++---
.../servlet/jsp/taglib/ExTag.java | 326 +--
.../servlet/jsp/taglib/ExTagSupport.java | 586 ++---
.../servlet/jsp/taglib/LastModifiedTEI.java | 40 +-
.../servlet/jsp/taglib/TrimWhiteSpaceTag.java | 174 +-
.../servlet/jsp/taglib/XMLTransformTag.java | 316 +--
.../jsp/taglib/logic/ConditionalTagBase.java | 276 +-
.../servlet/jsp/taglib/logic/EqualTag.java | 336 +--
.../jsp/taglib/logic/IteratorProviderTEI.java | 80 +-
.../jsp/taglib/logic/IteratorProviderTag.java | 172 +-
.../servlet/jsp/taglib/logic/NotEqualTag.java | 336 +--
.../servlet/log4j/Log4JContextWrapper.java | 366 +--
.../twelvemonkeys/servlet/DebugServlet.java | 236 +-
.../twelvemonkeys/servlet/GenericFilter.java | 764 +++---
.../twelvemonkeys/servlet/GenericServlet.java | 176 +-
.../twelvemonkeys/servlet/HttpServlet.java | 176 +-
.../servlet/OutputStreamAdapter.java | 240 +-
.../twelvemonkeys/servlet/ProxyServlet.java | 870 +++----
.../servlet/ServletConfigException.java | 162 +-
.../servlet/ServletConfigMapAdapter.java | 562 ++--
.../ServletResponseStreamDelegate.java | 228 +-
.../twelvemonkeys/servlet/ServletUtil.java | 1546 +++++------
.../twelvemonkeys/servlet/ThrottleFilter.java | 610 ++---
.../twelvemonkeys/servlet/TimingFilter.java | 222 +-
.../servlet/TrimWhiteSpaceFilter.java | 474 ++--
.../servlet/cache/CacheFilter.java | 398 +--
.../servlet/cache/CacheResponseWrapper.java | 520 ++--
.../servlet/cache/CachedEntity.java | 150 +-
.../servlet/cache/CachedEntityImpl.java | 338 +--
.../servlet/cache/CachedResponse.java | 190 +-
.../servlet/cache/CachedResponseImpl.java | 426 +--
.../servlet/cache/HTTPCache.java | 2298 ++++++++---------
.../cache/SerlvetCacheResponseWrapper.java | 544 ++--
.../servlet/cache/WritableCachedResponse.java | 154 +-
.../cache/WritableCachedResponseImpl.java | 370 +--
.../fileupload/FileSizeExceededException.java | 84 +-
.../fileupload/FileUploadException.java | 104 +-
.../servlet/fileupload/FileUploadFilter.java | 250 +-
.../fileupload/HttpFileUploadRequest.java | 126 +-
.../HttpFileUploadRequestWrapper.java | 306 +--
.../servlet/fileupload/UploadedFile.java | 172 +-
.../servlet/fileupload/UploadedFileImpl.java | 180 +-
.../servlet/gzip/GZIPFilter.java | 282 +-
.../servlet/gzip/GZIPResponseWrapper.java | 294 +--
.../servlet/image/AWTImageFilterAdapter.java | 144 +-
.../servlet/image/BufferedImageOpAdapter.java | 134 +-
.../servlet/image/ColorServlet.java | 422 +--
.../servlet/image/ComposeFilter.java | 118 +-
.../image/ContentNegotiationFilter.java | 878 +++----
.../servlet/image/CropFilter.java | 462 ++--
.../servlet/image/ImageFilter.java | 434 ++--
.../servlet/image/ImageServletException.java | 110 +-
.../servlet/image/ImageServletResponse.java | 386 +--
.../image/ImageServletResponseImpl.java | 1608 ++++++------
.../servlet/image/NullImageFilter.java | 92 +-
.../servlet/image/RotateFilter.java | 404 +--
.../servlet/image/ScaleFilter.java | 644 ++---
.../servlet/image/SourceRenderFilter.java | 306 +--
.../servlet/image/package_info.java | 64 +-
.../twelvemonkeys/servlet/package_info.java | 8 +-
.../servlet/FilterAbstractTestCase.java | 876 +++----
.../servlet/GenericFilterTestCase.java | 314 +--
.../servlet/ServletConfigMapAdapterTest.java | 398 +--
.../servlet/ServletHeadersMapAdapterTest.java | 190 +-
.../ServletParametersMapAdapterTest.java | 192 +-
.../ServletResponseAbsrtactTestCase.java | 46 +-
.../servlet/TrimWhiteSpaceFilterTestCase.java | 230 +-
.../image/aoi/AreaOfInterestTestCase.java | 341 +++
89 files changed, 15925 insertions(+), 15631 deletions(-)
delete mode 100644 imageio/imageio-jmagick/pom.xml
create mode 100644 twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java
diff --git a/imageio/imageio-jmagick/pom.xml b/imageio/imageio-jmagick/pom.xml
deleted file mode 100644
index b2a2e5eb..00000000
--- a/imageio/imageio-jmagick/pom.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-
-
- 4.0.0
-
- com.twelvemonkeys.imageio
- imageio
- 3.0-ece-1
-
- imageio-jmagick
- TwelveMonkeys :: ImageIO :: JMagick Plugin
-
- JMagick Home page
- for more information.]]>
-
-
-
-
- com.twelvemonkeys.imageio
- imageio-core
-
-
- com.twelvemonkeys.imageio
- imageio-core
- tests
-
-
- jmagick
- jmagick
- 6.2.4
- provided
-
-
-
-
diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
index 1f495e35..5b83076f 100644
--- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
+++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java
@@ -951,7 +951,6 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest
-
-
- central
- http://repo.dev.escenic.com/content/repositories/thirdparty-snapshots
- false
-
-
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java
index 5e11f5b9..176cf8b4 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java
@@ -1,90 +1,90 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.io.enc;
-
-import java.io.OutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.zip.Deflater;
-
-/**
- * {@code Encoder} implementation for standard DEFLATE encoding.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java#2 $
- *
- * @see RFC 1951
- * @see Deflater
- * @see InflateDecoder
- * @see java.util.zip.DeflaterOutputStream
- */
-final class DeflateEncoder implements Encoder {
-
- private final Deflater deflater;
- private final byte[] buffer = new byte[1024];
-
- public DeflateEncoder() {
- this(new Deflater(Deflater.DEFAULT_COMPRESSION, true)); // TODO: Should we use "no wrap"?
- }
-
- public DeflateEncoder(final Deflater pDeflater) {
- if (pDeflater == null) {
- throw new IllegalArgumentException("deflater == null");
- }
-
- deflater = pDeflater;
- }
-
- public void encode(final OutputStream stream, ByteBuffer buffer)
- throws IOException
- {
- System.out.println("DeflateEncoder.encode");
- deflater.setInput(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
- flushInputToStream(stream);
- }
-
- private void flushInputToStream(final OutputStream pStream) throws IOException {
- System.out.println("DeflateEncoder.flushInputToStream");
-
- if (deflater.needsInput()) {
- System.out.println("Foo");
- }
-
- while (!deflater.needsInput()) {
- int deflated = deflater.deflate(buffer, 0, buffer.length);
- pStream.write(buffer, 0, deflated);
- System.out.println("flushed " + deflated);
- }
- }
-
-// public void flush() {
-// deflater.finish();
-// }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.io.enc;
+
+import java.io.OutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.zip.Deflater;
+
+/**
+ * {@code Encoder} implementation for standard DEFLATE encoding.
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java#2 $
+ *
+ * @see RFC 1951
+ * @see Deflater
+ * @see InflateDecoder
+ * @see java.util.zip.DeflaterOutputStream
+ */
+final class DeflateEncoder implements Encoder {
+
+ private final Deflater deflater;
+ private final byte[] buffer = new byte[1024];
+
+ public DeflateEncoder() {
+ this(new Deflater(Deflater.DEFAULT_COMPRESSION, true)); // TODO: Should we use "no wrap"?
+ }
+
+ public DeflateEncoder(final Deflater pDeflater) {
+ if (pDeflater == null) {
+ throw new IllegalArgumentException("deflater == null");
+ }
+
+ deflater = pDeflater;
+ }
+
+ public void encode(final OutputStream stream, ByteBuffer buffer)
+ throws IOException
+ {
+ System.out.println("DeflateEncoder.encode");
+ deflater.setInput(buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+ flushInputToStream(stream);
+ }
+
+ private void flushInputToStream(final OutputStream pStream) throws IOException {
+ System.out.println("DeflateEncoder.flushInputToStream");
+
+ if (deflater.needsInput()) {
+ System.out.println("Foo");
+ }
+
+ while (!deflater.needsInput()) {
+ int deflated = deflater.deflate(buffer, 0, buffer.length);
+ pStream.write(buffer, 0, deflated);
+ System.out.println("flushed " + deflated);
+ }
+ }
+
+// public void flush() {
+// deflater.finish();
+// }
+}
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java
index eaeac33e..e69b8b68 100644
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java
@@ -1,110 +1,110 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.io.enc;
-
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.zip.DataFormatException;
-import java.util.zip.Inflater;
-
-/**
- * {@code Decoder} implementation for standard DEFLATE encoding.
- *
- *
- * @see RFC 1951
- *
- * @see Inflater
- * @see DeflateEncoder
- * @see java.util.zip.InflaterInputStream
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java#2 $
- */
-final class InflateDecoder implements Decoder {
-
- private final Inflater inflater;
-
- private final byte[] buffer;
-
- /**
- * Creates an {@code InflateDecoder}
- *
- */
- public InflateDecoder() {
- this(new Inflater(true));
- }
-
- /**
- * Creates an {@code InflateDecoder}
- *
- * @param pInflater the inflater instance to use
- */
- public InflateDecoder(final Inflater pInflater) {
- if (pInflater == null) {
- throw new IllegalArgumentException("inflater == null");
- }
-
- inflater = pInflater;
- buffer = new byte[1024];
- }
-
- public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
- try {
- int decoded;
-
- while ((decoded = inflater.inflate(buffer.array(), buffer.arrayOffset(), buffer.capacity())) == 0) {
- if (inflater.finished() || inflater.needsDictionary()) {
- return 0;
- }
-
- if (inflater.needsInput()) {
- fill(stream);
- }
- }
-
- return decoded;
- }
- catch (DataFormatException e) {
- String message = e.getMessage();
- throw new DecodeException(message != null ? message : "Invalid ZLIB data format", e);
- }
- }
-
- private void fill(final InputStream pStream) throws IOException {
- int available = pStream.read(buffer, 0, buffer.length);
-
- if (available == -1) {
- throw new EOFException("Unexpected end of ZLIB stream");
- }
-
- inflater.setInput(buffer, 0, available);
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.io.enc;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * {@code Decoder} implementation for standard DEFLATE encoding.
+ *
+ *
+ * @see RFC 1951
+ *
+ * @see Inflater
+ * @see DeflateEncoder
+ * @see java.util.zip.InflaterInputStream
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java#2 $
+ */
+final class InflateDecoder implements Decoder {
+
+ private final Inflater inflater;
+
+ private final byte[] buffer;
+
+ /**
+ * Creates an {@code InflateDecoder}
+ *
+ */
+ public InflateDecoder() {
+ this(new Inflater(true));
+ }
+
+ /**
+ * Creates an {@code InflateDecoder}
+ *
+ * @param pInflater the inflater instance to use
+ */
+ public InflateDecoder(final Inflater pInflater) {
+ if (pInflater == null) {
+ throw new IllegalArgumentException("inflater == null");
+ }
+
+ inflater = pInflater;
+ buffer = new byte[1024];
+ }
+
+ public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
+ try {
+ int decoded;
+
+ while ((decoded = inflater.inflate(buffer.array(), buffer.arrayOffset(), buffer.capacity())) == 0) {
+ if (inflater.finished() || inflater.needsDictionary()) {
+ return 0;
+ }
+
+ if (inflater.needsInput()) {
+ fill(stream);
+ }
+ }
+
+ return decoded;
+ }
+ catch (DataFormatException e) {
+ String message = e.getMessage();
+ throw new DecodeException(message != null ? message : "Invalid ZLIB data format", e);
+ }
+ }
+
+ private void fill(final InputStream pStream) throws IOException {
+ int available = pStream.read(buffer, 0, buffer.length);
+
+ if (available == -1) {
+ throw new EOFException("Unexpected end of ZLIB stream");
+ }
+
+ inflater.setInput(buffer, 0, available);
+ }
}
\ No newline at end of file
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java
index 8c24ed25..6d097cfa 100755
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/lang/NativeLoader.java
@@ -1,401 +1,401 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.lang;
-
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.util.FilterIterator;
-import com.twelvemonkeys.util.service.ServiceRegistry;
-
-import java.io.*;
-import java.util.Collections;
-import java.util.Iterator;
-
-/**
- * NativeLoader
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/NativeLoader.java#2 $
- */
-final class NativeLoader {
- // TODO: Considerations:
- // - Rename all libs like the current code, to .(so|dll|dylib)?
- // - Keep library filename from jar, and rather store a separate
- // properties-file with the library->library-file mappings?
- // - As all invocations are with library file name, we could probably skip
- // both renaming and properties-file altogether...
-
- // TODO: The real trick here, is how to load the correct library for the
- // current platform...
- // - Change String pResource to String[] pResources?
- // - NativeResource class, that has a list of multiple resources?
- // NativeResource(Map) os->native lib mapping
-
- // TODO: Consider exposing the method from SystemUtil
-
- // TODO: How about a SPI based solution?!
- // public interface com.twelvemonkeys.lang.NativeResourceProvider
- //
- // imlementations return a pointer to the correct resource for a given (by
- // this class) OS.
- //
- // String getResourceName(...)
- //
- // See http://tolstoy.com/samizdat/sysprops.html
- // System properties:
- // "os.name"
- // Windows, Linux, Solaris/SunOS,
- // Mac OS/Mac OS X/Rhapsody (aka Mac OS X Server)
- // General Unix (AIX, Digital Unix, FreeBSD, HP-UX, Irix)
- // OS/2
- // "os.arch"
- // Windows: x86
- // Linux: x86, i386, i686, x86_64, ia64,
- // Solaris: sparc, sparcv9, x86
- // Mac OS: PowerPC, ppc, i386
- // AIX: x86, ppc
- // Digital Unix: alpha
- // FreeBSD: x86, sparc
- // HP-UX: PA-RISC
- // Irix: mips
- // OS/2: x86
- // "os.version"
- // Windows: 4.0 -> NT/95, 5.0 -> 2000, 5.1 -> XP (don't care about old versions, CE etc)
- // Mac OS: 8.0, 8.1, 10.0 -> OS X, 10.x.x -> OS X, 5.6 -> Rhapsody (!)
- //
- // Normalize os.name, os.arch and os.version?!
-
-
- ///** Normalized operating system constant */
- //static final OperatingSystem OS_NAME = normalizeOperatingSystem();
- //
- ///** Normalized system architecture constant */
- //static final Architecture OS_ARCHITECTURE = normalizeArchitecture();
- //
- ///** Unormalized operating system version constant (for completeness) */
- //static final String OS_VERSION = System.getProperty("os.version");
-
- static final NativeResourceRegistry sRegistry = new NativeResourceRegistry();
-
- private NativeLoader() {
- }
-
-/*
- private static Architecture normalizeArchitecture() {
- String arch = System.getProperty("os.arch");
- if (arch == null) {
- throw new IllegalStateException("System property \"os.arch\" == null");
- }
-
- arch = arch.toLowerCase();
- if (OS_NAME == OperatingSystem.Windows
- && (arch.startsWith("x86") || arch.startsWith("i386"))) {
- return Architecture.X86;
- // TODO: 64 bit
- }
- else if (OS_NAME == OperatingSystem.Linux) {
- if (arch.startsWith("x86") || arch.startsWith("i386")) {
- return Architecture.I386;
- }
- else if (arch.startsWith("i686")) {
- return Architecture.I686;
- }
- // TODO: More Linux options?
- // TODO: 64 bit
- }
- else if (OS_NAME == OperatingSystem.MacOS) {
- if (arch.startsWith("power") || arch.startsWith("ppc")) {
- return Architecture.PPC;
- }
- else if (arch.startsWith("i386")) {
- return Architecture.I386;
- }
- }
- else if (OS_NAME == OperatingSystem.Solaris) {
- if (arch.startsWith("sparc")) {
- return Architecture.SPARC;
- }
- if (arch.startsWith("x86")) {
- // TODO: Should we use i386 as Linux and Mac does?
- return Architecture.X86;
- }
- // TODO: 64 bit
- }
-
- return Architecture.Unknown;
- }
-*/
-
-/*
- private static OperatingSystem normalizeOperatingSystem() {
- String os = System.getProperty("os.name");
- if (os == null) {
- throw new IllegalStateException("System property \"os.name\" == null");
- }
-
- os = os.toLowerCase();
- if (os.startsWith("windows")) {
- return OperatingSystem.Windows;
- }
- else if (os.startsWith("linux")) {
- return OperatingSystem.Linux;
- }
- else if (os.startsWith("mac os")) {
- return OperatingSystem.MacOS;
- }
- else if (os.startsWith("solaris") || os.startsWith("sunos")) {
- return OperatingSystem.Solaris;
- }
-
- return OperatingSystem.Unknown;
- }
-*/
-
- // TODO: We could actually have more than one resource for each lib...
- private static String getResourceFor(String pLibrary) {
- Iterator providers = sRegistry.providers(pLibrary);
- while (providers.hasNext()) {
- NativeResourceSPI resourceSPI = providers.next();
-
- try {
- return resourceSPI.getClassPathResource(Platform.get());
- }
- catch (Throwable t) {
- // Dergister and try next
- sRegistry.deregister(resourceSPI);
- }
- }
-
- return null;
- }
-
- /**
- * Loads a native library.
- *
- * @param pLibrary name of the library
- *
- * @throws UnsatisfiedLinkError
- */
- public static void loadLibrary(String pLibrary) {
- loadLibrary0(pLibrary, null, null);
- }
-
- /**
- * Loads a native library.
- *
- * @param pLibrary name of the library
- * @param pLoader the class loader to use
- *
- * @throws UnsatisfiedLinkError
- */
- public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
- loadLibrary0(pLibrary, null, pLoader);
- }
-
- /**
- * Loads a native library.
- *
- * @param pLibrary name of the library
- * @param pResource name of the resource
- * @param pLoader the class loader to use
- *
- * @throws UnsatisfiedLinkError
- */
- static void loadLibrary0(String pLibrary, String pResource, ClassLoader pLoader) {
- if (pLibrary == null) {
- throw new IllegalArgumentException("library == null");
- }
-
- // Try loading normal way
- UnsatisfiedLinkError unsatisfied;
- try {
- System.loadLibrary(pLibrary);
- return;
- }
- catch (UnsatisfiedLinkError err) {
- // Ignore
- unsatisfied = err;
- }
-
- final ClassLoader loader = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
- final String resource = pResource != null ? pResource : getResourceFor(pLibrary);
-
- // TODO: resource may be null, and that MIGHT be okay, IFF the resource
- // is allready unpacked to the user dir... However, we then need another
- // way to resolve the library extension...
- // Right now we just fail in a predictable way (no NPE)!
- if (resource == null) {
- throw unsatisfied;
- }
-
- // Default to load/store from user.home
- File dir = new File(System.getProperty("user.home") + "/.twelvemonkeys/lib");
- dir.mkdirs();
- //File libraryFile = new File(dir.getAbsolutePath(), pLibrary + LIBRARY_EXTENSION);
- File libraryFile = new File(dir.getAbsolutePath(), pLibrary + "." + FileUtil.getExtension(resource));
-
- if (!libraryFile.exists()) {
- try {
- extractToUserDir(resource, libraryFile, loader);
- }
- catch (IOException ioe) {
- UnsatisfiedLinkError err = new UnsatisfiedLinkError("Unable to extract resource to dir: " + libraryFile.getAbsolutePath());
- err.initCause(ioe);
- throw err;
- }
- }
-
- // Try to load the library from the file we just wrote
- System.load(libraryFile.getAbsolutePath());
- }
-
- private static void extractToUserDir(String pResource, File pLibraryFile, ClassLoader pLoader) throws IOException {
- // Get resource from classpath
- InputStream in = pLoader.getResourceAsStream(pResource);
- if (in == null) {
- throw new FileNotFoundException("Unable to locate classpath resource: " + pResource);
- }
-
- // Write to file in user dir
- FileOutputStream fileOut = null;
- try {
- fileOut = new FileOutputStream(pLibraryFile);
-
- byte[] tmp = new byte[1024];
- // copy the contents of our resource out to the destination
- // dir 1K at a time. 1K may seem arbitrary at first, but today
- // is a Tuesday, so it makes perfect sense.
- int bytesRead = in.read(tmp);
- while (bytesRead != -1) {
- fileOut.write(tmp, 0, bytesRead);
- bytesRead = in.read(tmp);
- }
- }
- finally {
- FileUtil.close(fileOut);
- FileUtil.close(in);
- }
- }
-
- // TODO: Validate OS names?
- // Windows
- // Linux
- // Solaris
- // Mac OS (OSX+)
- // Generic Unix?
- // Others?
-
- // TODO: OSes that support different architectures might require different
- // resources for each architecture.. Need a namespace/flavour system
- // TODO: 32 bit/64 bit issues?
- // Eg: Windows, Windows/32, Windows/64, Windows/Intel/64?
- // Solaris/Sparc, Solaris/Intel/64
- // MacOS/PowerPC, MacOS/Intel
- /*
- public static class NativeResource {
- private Map mMap;
-
- public NativeResource(String[] pOSNames, String[] pReourceNames) {
- if (pOSNames == null) {
- throw new IllegalArgumentException("osNames == null");
- }
- if (pReourceNames == null) {
- throw new IllegalArgumentException("resourceNames == null");
- }
- if (pOSNames.length != pReourceNames.length) {
- throw new IllegalArgumentException("osNames.length != resourceNames.length");
- }
-
- Map map = new HashMap();
- for (int i = 0; i < pOSNames.length; i++) {
- map.put(pOSNames[i], pReourceNames[i]);
- }
-
- mMap = Collections.unmodifiableMap(map);
- }
-
- public NativeResource(Map pMap) {
- if (pMap == null) {
- throw new IllegalArgumentException("map == null");
- }
-
- Map map = new HashMap(pMap);
-
- Iterator it = map.keySet().iterator();
- while (it.hasNext()) {
- Map.Entry entry = (Map.Entry) it.next();
- if (!(entry.getKey() instanceof String && entry.getValue() instanceof String)) {
- throw new IllegalArgumentException("map contains non-string entries: " + entry);
- }
- }
-
- mMap = Collections.unmodifiableMap(map);
- }
-
- protected NativeResource() {
- }
-
- public final String resourceForCurrentOS() {
- throw new UnsupportedOperationException();
- }
-
- protected String getResourceName(String pOSName) {
- return (String) mMap.get(pOSName);
- }
- }
- */
-
- private static class NativeResourceRegistry extends ServiceRegistry {
- public NativeResourceRegistry() {
- super(Collections.singletonList(NativeResourceSPI.class).iterator());
- registerApplicationClasspathSPIs();
- }
-
- Iterator providers(final String nativeResource) {
- return new FilterIterator(
- providers(NativeResourceSPI.class),
- new NameFilter(nativeResource)
- );
- }
- }
-
- private static class NameFilter implements FilterIterator.Filter {
- private final String name;
-
- NameFilter(String pName) {
- if (pName == null) {
- throw new IllegalArgumentException("name == null");
- }
- name = pName;
- }
- public boolean accept(NativeResourceSPI pElement) {
- return name.equals(pElement.getResourceName());
- }
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.lang;
+
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.util.FilterIterator;
+import com.twelvemonkeys.util.service.ServiceRegistry;
+
+import java.io.*;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * NativeLoader
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/NativeLoader.java#2 $
+ */
+final class NativeLoader {
+ // TODO: Considerations:
+ // - Rename all libs like the current code, to .(so|dll|dylib)?
+ // - Keep library filename from jar, and rather store a separate
+ // properties-file with the library->library-file mappings?
+ // - As all invocations are with library file name, we could probably skip
+ // both renaming and properties-file altogether...
+
+ // TODO: The real trick here, is how to load the correct library for the
+ // current platform...
+ // - Change String pResource to String[] pResources?
+ // - NativeResource class, that has a list of multiple resources?
+ // NativeResource(Map) os->native lib mapping
+
+ // TODO: Consider exposing the method from SystemUtil
+
+ // TODO: How about a SPI based solution?!
+ // public interface com.twelvemonkeys.lang.NativeResourceProvider
+ //
+ // imlementations return a pointer to the correct resource for a given (by
+ // this class) OS.
+ //
+ // String getResourceName(...)
+ //
+ // See http://tolstoy.com/samizdat/sysprops.html
+ // System properties:
+ // "os.name"
+ // Windows, Linux, Solaris/SunOS,
+ // Mac OS/Mac OS X/Rhapsody (aka Mac OS X Server)
+ // General Unix (AIX, Digital Unix, FreeBSD, HP-UX, Irix)
+ // OS/2
+ // "os.arch"
+ // Windows: x86
+ // Linux: x86, i386, i686, x86_64, ia64,
+ // Solaris: sparc, sparcv9, x86
+ // Mac OS: PowerPC, ppc, i386
+ // AIX: x86, ppc
+ // Digital Unix: alpha
+ // FreeBSD: x86, sparc
+ // HP-UX: PA-RISC
+ // Irix: mips
+ // OS/2: x86
+ // "os.version"
+ // Windows: 4.0 -> NT/95, 5.0 -> 2000, 5.1 -> XP (don't care about old versions, CE etc)
+ // Mac OS: 8.0, 8.1, 10.0 -> OS X, 10.x.x -> OS X, 5.6 -> Rhapsody (!)
+ //
+ // Normalize os.name, os.arch and os.version?!
+
+
+ ///** Normalized operating system constant */
+ //static final OperatingSystem OS_NAME = normalizeOperatingSystem();
+ //
+ ///** Normalized system architecture constant */
+ //static final Architecture OS_ARCHITECTURE = normalizeArchitecture();
+ //
+ ///** Unormalized operating system version constant (for completeness) */
+ //static final String OS_VERSION = System.getProperty("os.version");
+
+ static final NativeResourceRegistry sRegistry = new NativeResourceRegistry();
+
+ private NativeLoader() {
+ }
+
+/*
+ private static Architecture normalizeArchitecture() {
+ String arch = System.getProperty("os.arch");
+ if (arch == null) {
+ throw new IllegalStateException("System property \"os.arch\" == null");
+ }
+
+ arch = arch.toLowerCase();
+ if (OS_NAME == OperatingSystem.Windows
+ && (arch.startsWith("x86") || arch.startsWith("i386"))) {
+ return Architecture.X86;
+ // TODO: 64 bit
+ }
+ else if (OS_NAME == OperatingSystem.Linux) {
+ if (arch.startsWith("x86") || arch.startsWith("i386")) {
+ return Architecture.I386;
+ }
+ else if (arch.startsWith("i686")) {
+ return Architecture.I686;
+ }
+ // TODO: More Linux options?
+ // TODO: 64 bit
+ }
+ else if (OS_NAME == OperatingSystem.MacOS) {
+ if (arch.startsWith("power") || arch.startsWith("ppc")) {
+ return Architecture.PPC;
+ }
+ else if (arch.startsWith("i386")) {
+ return Architecture.I386;
+ }
+ }
+ else if (OS_NAME == OperatingSystem.Solaris) {
+ if (arch.startsWith("sparc")) {
+ return Architecture.SPARC;
+ }
+ if (arch.startsWith("x86")) {
+ // TODO: Should we use i386 as Linux and Mac does?
+ return Architecture.X86;
+ }
+ // TODO: 64 bit
+ }
+
+ return Architecture.Unknown;
+ }
+*/
+
+/*
+ private static OperatingSystem normalizeOperatingSystem() {
+ String os = System.getProperty("os.name");
+ if (os == null) {
+ throw new IllegalStateException("System property \"os.name\" == null");
+ }
+
+ os = os.toLowerCase();
+ if (os.startsWith("windows")) {
+ return OperatingSystem.Windows;
+ }
+ else if (os.startsWith("linux")) {
+ return OperatingSystem.Linux;
+ }
+ else if (os.startsWith("mac os")) {
+ return OperatingSystem.MacOS;
+ }
+ else if (os.startsWith("solaris") || os.startsWith("sunos")) {
+ return OperatingSystem.Solaris;
+ }
+
+ return OperatingSystem.Unknown;
+ }
+*/
+
+ // TODO: We could actually have more than one resource for each lib...
+ private static String getResourceFor(String pLibrary) {
+ Iterator providers = sRegistry.providers(pLibrary);
+ while (providers.hasNext()) {
+ NativeResourceSPI resourceSPI = providers.next();
+
+ try {
+ return resourceSPI.getClassPathResource(Platform.get());
+ }
+ catch (Throwable t) {
+ // Dergister and try next
+ sRegistry.deregister(resourceSPI);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads a native library.
+ *
+ * @param pLibrary name of the library
+ *
+ * @throws UnsatisfiedLinkError
+ */
+ public static void loadLibrary(String pLibrary) {
+ loadLibrary0(pLibrary, null, null);
+ }
+
+ /**
+ * Loads a native library.
+ *
+ * @param pLibrary name of the library
+ * @param pLoader the class loader to use
+ *
+ * @throws UnsatisfiedLinkError
+ */
+ public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
+ loadLibrary0(pLibrary, null, pLoader);
+ }
+
+ /**
+ * Loads a native library.
+ *
+ * @param pLibrary name of the library
+ * @param pResource name of the resource
+ * @param pLoader the class loader to use
+ *
+ * @throws UnsatisfiedLinkError
+ */
+ static void loadLibrary0(String pLibrary, String pResource, ClassLoader pLoader) {
+ if (pLibrary == null) {
+ throw new IllegalArgumentException("library == null");
+ }
+
+ // Try loading normal way
+ UnsatisfiedLinkError unsatisfied;
+ try {
+ System.loadLibrary(pLibrary);
+ return;
+ }
+ catch (UnsatisfiedLinkError err) {
+ // Ignore
+ unsatisfied = err;
+ }
+
+ final ClassLoader loader = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
+ final String resource = pResource != null ? pResource : getResourceFor(pLibrary);
+
+ // TODO: resource may be null, and that MIGHT be okay, IFF the resource
+ // is allready unpacked to the user dir... However, we then need another
+ // way to resolve the library extension...
+ // Right now we just fail in a predictable way (no NPE)!
+ if (resource == null) {
+ throw unsatisfied;
+ }
+
+ // Default to load/store from user.home
+ File dir = new File(System.getProperty("user.home") + "/.twelvemonkeys/lib");
+ dir.mkdirs();
+ //File libraryFile = new File(dir.getAbsolutePath(), pLibrary + LIBRARY_EXTENSION);
+ File libraryFile = new File(dir.getAbsolutePath(), pLibrary + "." + FileUtil.getExtension(resource));
+
+ if (!libraryFile.exists()) {
+ try {
+ extractToUserDir(resource, libraryFile, loader);
+ }
+ catch (IOException ioe) {
+ UnsatisfiedLinkError err = new UnsatisfiedLinkError("Unable to extract resource to dir: " + libraryFile.getAbsolutePath());
+ err.initCause(ioe);
+ throw err;
+ }
+ }
+
+ // Try to load the library from the file we just wrote
+ System.load(libraryFile.getAbsolutePath());
+ }
+
+ private static void extractToUserDir(String pResource, File pLibraryFile, ClassLoader pLoader) throws IOException {
+ // Get resource from classpath
+ InputStream in = pLoader.getResourceAsStream(pResource);
+ if (in == null) {
+ throw new FileNotFoundException("Unable to locate classpath resource: " + pResource);
+ }
+
+ // Write to file in user dir
+ FileOutputStream fileOut = null;
+ try {
+ fileOut = new FileOutputStream(pLibraryFile);
+
+ byte[] tmp = new byte[1024];
+ // copy the contents of our resource out to the destination
+ // dir 1K at a time. 1K may seem arbitrary at first, but today
+ // is a Tuesday, so it makes perfect sense.
+ int bytesRead = in.read(tmp);
+ while (bytesRead != -1) {
+ fileOut.write(tmp, 0, bytesRead);
+ bytesRead = in.read(tmp);
+ }
+ }
+ finally {
+ FileUtil.close(fileOut);
+ FileUtil.close(in);
+ }
+ }
+
+ // TODO: Validate OS names?
+ // Windows
+ // Linux
+ // Solaris
+ // Mac OS (OSX+)
+ // Generic Unix?
+ // Others?
+
+ // TODO: OSes that support different architectures might require different
+ // resources for each architecture.. Need a namespace/flavour system
+ // TODO: 32 bit/64 bit issues?
+ // Eg: Windows, Windows/32, Windows/64, Windows/Intel/64?
+ // Solaris/Sparc, Solaris/Intel/64
+ // MacOS/PowerPC, MacOS/Intel
+ /*
+ public static class NativeResource {
+ private Map mMap;
+
+ public NativeResource(String[] pOSNames, String[] pReourceNames) {
+ if (pOSNames == null) {
+ throw new IllegalArgumentException("osNames == null");
+ }
+ if (pReourceNames == null) {
+ throw new IllegalArgumentException("resourceNames == null");
+ }
+ if (pOSNames.length != pReourceNames.length) {
+ throw new IllegalArgumentException("osNames.length != resourceNames.length");
+ }
+
+ Map map = new HashMap();
+ for (int i = 0; i < pOSNames.length; i++) {
+ map.put(pOSNames[i], pReourceNames[i]);
+ }
+
+ mMap = Collections.unmodifiableMap(map);
+ }
+
+ public NativeResource(Map pMap) {
+ if (pMap == null) {
+ throw new IllegalArgumentException("map == null");
+ }
+
+ Map map = new HashMap(pMap);
+
+ Iterator it = map.keySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry entry = (Map.Entry) it.next();
+ if (!(entry.getKey() instanceof String && entry.getValue() instanceof String)) {
+ throw new IllegalArgumentException("map contains non-string entries: " + entry);
+ }
+ }
+
+ mMap = Collections.unmodifiableMap(map);
+ }
+
+ protected NativeResource() {
+ }
+
+ public final String resourceForCurrentOS() {
+ throw new UnsupportedOperationException();
+ }
+
+ protected String getResourceName(String pOSName) {
+ return (String) mMap.get(pOSName);
+ }
+ }
+ */
+
+ private static class NativeResourceRegistry extends ServiceRegistry {
+ public NativeResourceRegistry() {
+ super(Collections.singletonList(NativeResourceSPI.class).iterator());
+ registerApplicationClasspathSPIs();
+ }
+
+ Iterator providers(final String nativeResource) {
+ return new FilterIterator(
+ providers(NativeResourceSPI.class),
+ new NameFilter(nativeResource)
+ );
+ }
+ }
+
+ private static class NameFilter implements FilterIterator.Filter {
+ private final String name;
+
+ NameFilter(String pName) {
+ if (pName == null) {
+ throw new IllegalArgumentException("name == null");
+ }
+ name = pName;
+ }
+ public boolean accept(NativeResourceSPI pElement) {
+ return name.equals(pElement.getResourceName());
+ }
+ }
}
\ No newline at end of file
diff --git a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java
index fa2f2879..0f56c8fc 100755
--- a/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java
+++ b/sandbox/sandbox-common/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java
@@ -1,398 +1,398 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.util.regex;
-
-import com.twelvemonkeys.util.DebugUtil;
-
-import java.io.PrintStream;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * This class parses arbitrary strings against a wildcard string mask provided.
- * The wildcard characters are '*' and '?'.
- *
- * The string masks provided are treated as case sensitive.
- * Null-valued string masks as well as null valued strings to be parsed, will lead to rejection.
- *
- *
- *
- * This task is performed based on regular expression techniques.
- * The possibilities of string generation with the well-known wildcard characters stated above,
- * represent a subset of the possibilities of string generation with regular expressions.
- * The '*' corresponds to ([Union of all characters in the alphabet])*
- * The '?' corresponds to ([Union of all characters in the alphabet])
- * These expressions are not suited for textual representation at all, I must say. Is there any math tags included in HTML?
- *
- *
- *
- * This class uses the Regexp package from Apache's Jakarta Project, links below.
- *
- *
- *
- * Examples of usage:
- * This example will return "Accepted!".
- *
- * REWildcardStringParser parser = new REWildcardStringParser("*_28????.jp*");
- * if (parser.parseString("gupu_280915.jpg")) {
- * System.out.println("Accepted!");
- * } else {
- * System.out.println("Not accepted!");
- * }
- *
- *
- *
- *
- * @author Eirik Torske
- * @see Jakarta Regexp
- * @see {@code org.apache.regexp.RE}
- * @see com.twelvemonkeys.util.regex.WildcardStringParser
- *
- * @todo Rewrite to use this regex package, and not Jakarta directly!
- */
-public class REWildcardStringParser /*extends EntityObject*/ {
-
- // Constants
-
- /** Field ALPHABET */
- public static final char[] ALPHABET = {
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00e6',
- '\u00f8', '\u00e5', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
- 'Z', '\u00c6', '\u00d8', '\u00c5', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '-'
- };
-
- /** Field FREE_RANGE_CHARACTER */
- public static final char FREE_RANGE_CHARACTER = '*';
-
- /** Field FREE_PASS_CHARACTER */
- public static final char FREE_PASS_CHARACTER = '?';
-
- // Members
- Pattern mRegexpParser;
- String mStringMask;
- boolean mInitialized = false;
- int mTotalNumberOfStringsParsed;
- boolean mDebugging;
- PrintStream out;
-
- // Properties
- // Constructors
-
- /**
- * Creates a wildcard string parser.
- *
- * @param pStringMask the wildcard string mask.
- */
- public REWildcardStringParser(final String pStringMask) {
- this(pStringMask, false);
- }
-
- /**
- * Creates a wildcard string parser.
- *
- * @param pStringMask the wildcard string mask.
- * @param pDebugging {@code true} will cause debug messages to be emitted to {@code System.out}.
- */
- public REWildcardStringParser(final String pStringMask, final boolean pDebugging) {
- this(pStringMask, pDebugging, System.out);
- }
-
- /**
- * Creates a wildcard string parser.
- *
- * @param pStringMask the wildcard string mask.
- * @param pDebugging {@code true} will cause debug messages to be emitted.
- * @param pDebuggingPrintStream the {@code java.io.PrintStream} to which the debug messages will be emitted.
- */
- public REWildcardStringParser(final String pStringMask, final boolean pDebugging, final PrintStream pDebuggingPrintStream) {
-
- this.mStringMask = pStringMask;
- this.mDebugging = pDebugging;
- this.out = pDebuggingPrintStream;
- mInitialized = buildRegexpParser();
- }
-
- // Methods
-
- /**
- * Converts wildcard string mask to regular expression.
- * This method should reside in som utility class, but I don't know how proprietary the regular expression format is...
- * @return the corresponding regular expression or {@code null} if an error occurred.
- */
- private String convertWildcardExpressionToRegularExpression(final String pWildcardExpression) {
-
- if (pWildcardExpression == null) {
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this) + "wildcard expression is null - also returning null as regexp!");
- }
- return null;
- }
- StringBuilder regexpBuffer = new StringBuilder();
- boolean convertingError = false;
-
- for (int i = 0; i < pWildcardExpression.length(); i++) {
- if (convertingError) {
- return null;
- }
-
- // Free-range character '*'
- char stringMaskChar = pWildcardExpression.charAt(i);
-
- if (isFreeRangeCharacter(stringMaskChar)) {
- regexpBuffer.append("(([a-�A-�0-9]|.|_|-)*)");
- }
-
- // Free-pass character '?'
- else if (isFreePassCharacter(stringMaskChar)) {
- regexpBuffer.append("([a-�A_�0-9]|.|_|-)");
- }
-
- // Valid characters
- else if (isInAlphabet(stringMaskChar)) {
- regexpBuffer.append(stringMaskChar);
- }
-
- // Invalid character - aborting
- else {
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this)
- + "one or more characters in string mask are not legal characters - returning null as regexp!");
- }
- convertingError = true;
- }
- }
- return regexpBuffer.toString();
- }
-
- /**
- * Builds the regexp parser.
- */
- private boolean buildRegexpParser() {
-
- // Convert wildcard string mask to regular expression
- String regexp = convertWildcardExpressionToRegularExpression(mStringMask);
-
- if (regexp == null) {
- out.println(DebugUtil.getPrefixErrorMessage(this)
- + "irregularity in regexp conversion - now not able to parse any strings, all strings will be rejected!");
- return false;
- }
-
- // Instantiate a regular expression parser
- try {
- mRegexpParser = Pattern.compile(regexp);
- }
- catch (PatternSyntaxException e) {
- if (mDebugging) {
- out.println(DebugUtil.getPrefixErrorMessage(this) + "RESyntaxException \"" + e.getMessage()
- + "\" caught - now not able to parse any strings, all strings will be rejected!");
- }
- if (mDebugging) {
- e.printStackTrace(System.err);
- }
- return false;
- }
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this) + "regular expression parser from regular expression " + regexp
- + " extracted from wildcard string mask " + mStringMask + ".");
- }
- return true;
- }
-
- /**
- * Simple check of the string to be parsed.
- */
- private boolean checkStringToBeParsed(final String pStringToBeParsed) {
-
- // Check for nullness
- if (pStringToBeParsed == null) {
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this) + "string to be parsed is null - rejection!");
- }
- return false;
- }
-
- // Check if valid character (element in alphabet)
- for (int i = 0; i < pStringToBeParsed.length(); i++) {
- if (!isInAlphabet(pStringToBeParsed.charAt(i))) {
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this)
- + "one or more characters in string to be parsed are not legal characters - rejection!");
- }
- return false;
- }
- }
- return true;
- }
-
- /**
- * Tests if a certain character is a valid character in the alphabet that is applying for this automaton.
- */
- public static boolean isInAlphabet(final char pCharToCheck) {
-
- for (int i = 0; i < ALPHABET.length; i++) {
- if (pCharToCheck == ALPHABET[i]) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Tests if a certain character is the designated "free-range" character ('*').
- */
- public static boolean isFreeRangeCharacter(final char pCharToCheck) {
- return pCharToCheck == FREE_RANGE_CHARACTER;
- }
-
- /**
- * Tests if a certain character is the designated "free-pass" character ('?').
- */
- public static boolean isFreePassCharacter(final char pCharToCheck) {
- return pCharToCheck == FREE_PASS_CHARACTER;
- }
-
- /**
- * Tests if a certain character is a wildcard character ('*' or '?').
- */
- public static boolean isWildcardCharacter(final char pCharToCheck) {
- return ((isFreeRangeCharacter(pCharToCheck)) || (isFreePassCharacter(pCharToCheck)));
- }
-
- /**
- * Gets the string mask that was used when building the parser atomaton.
- *
- * @return the string mask used for building the parser automaton.
- */
- public String getStringMask() {
- return mStringMask;
- }
-
- /**
- * Parses a string.
- *
- *
- * @param pStringToBeParsed
- * @return {@code true} if and only if the string are accepted by the parser.
- */
- public boolean parseString(final String pStringToBeParsed) {
-
- if (mDebugging) {
- out.println();
- }
- if (mDebugging) {
- out.println(DebugUtil.getPrefixDebugMessage(this) + "parsing \"" + pStringToBeParsed + "\"...");
- }
-
- // Update statistics
- mTotalNumberOfStringsParsed++;
-
- // Check string to be parsed
- if (!checkStringToBeParsed(pStringToBeParsed)) {
- return false;
- }
-
- // Perform parsing and return accetance/rejection flag
- if (mInitialized) {
- return mRegexpParser.matcher(pStringToBeParsed).matches();
- } else {
- out.println(DebugUtil.getPrefixErrorMessage(this) + "trying to use non-initialized parser - string rejected!");
- }
- return false;
- }
-
- /*
- * Overriding mandatory methods from EntityObject's.
- */
-
- /**
- * Method toString
- *
- *
- * @return
- *
- */
- public String toString() {
-
- StringBuilder buffer = new StringBuilder();
-
- buffer.append(DebugUtil.getClassName(this));
- buffer.append(": String mask ");
- buffer.append(mStringMask);
- buffer.append("\n");
- return buffer.toString();
- }
-
- // Just taking the lazy, easy and dangerous way out
-
- /**
- * Method equals
- *
- *
- * @param pObject
- *
- * @return
- *
- */
- public boolean equals(Object pObject) {
-
- if (pObject instanceof REWildcardStringParser) {
- REWildcardStringParser externalParser = (REWildcardStringParser) pObject;
-
- return (externalParser.mStringMask == this.mStringMask);
- }
- return ((Object) this).equals(pObject);
- }
-
- // Just taking the lazy, easy and dangerous way out
-
- /**
- * Method hashCode
- *
- *
- * @return
- *
- */
- public int hashCode() {
- return ((Object) this).hashCode();
- }
-
- protected Object clone() throws CloneNotSupportedException {
- return new REWildcardStringParser(mStringMask);
- }
-
- // Just taking the lazy, easy and dangerous way out
- protected void finalize() throws Throwable {}
-}
-
-
-/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
-
-
-/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.util.regex;
+
+import com.twelvemonkeys.util.DebugUtil;
+
+import java.io.PrintStream;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * This class parses arbitrary strings against a wildcard string mask provided.
+ * The wildcard characters are '*' and '?'.
+ *
+ * The string masks provided are treated as case sensitive.
+ * Null-valued string masks as well as null valued strings to be parsed, will lead to rejection.
+ *
+ *
+ *
+ * This task is performed based on regular expression techniques.
+ * The possibilities of string generation with the well-known wildcard characters stated above,
+ * represent a subset of the possibilities of string generation with regular expressions.
+ * The '*' corresponds to ([Union of all characters in the alphabet])*
+ * The '?' corresponds to ([Union of all characters in the alphabet])
+ * These expressions are not suited for textual representation at all, I must say. Is there any math tags included in HTML?
+ *
+ *
+ *
+ * This class uses the Regexp package from Apache's Jakarta Project, links below.
+ *
+ *
+ *
+ * Examples of usage:
+ * This example will return "Accepted!".
+ *
+ * REWildcardStringParser parser = new REWildcardStringParser("*_28????.jp*");
+ * if (parser.parseString("gupu_280915.jpg")) {
+ * System.out.println("Accepted!");
+ * } else {
+ * System.out.println("Not accepted!");
+ * }
+ *
+ *
+ *
+ *
+ * @author Eirik Torske
+ * @see Jakarta Regexp
+ * @see {@code org.apache.regexp.RE}
+ * @see com.twelvemonkeys.util.regex.WildcardStringParser
+ *
+ * @todo Rewrite to use this regex package, and not Jakarta directly!
+ */
+public class REWildcardStringParser /*extends EntityObject*/ {
+
+ // Constants
+
+ /** Field ALPHABET */
+ public static final char[] ALPHABET = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '\u00e6',
+ '\u00f8', '\u00e5', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'N', 'M', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
+ 'Z', '\u00c6', '\u00d8', '\u00c5', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '-'
+ };
+
+ /** Field FREE_RANGE_CHARACTER */
+ public static final char FREE_RANGE_CHARACTER = '*';
+
+ /** Field FREE_PASS_CHARACTER */
+ public static final char FREE_PASS_CHARACTER = '?';
+
+ // Members
+ Pattern mRegexpParser;
+ String mStringMask;
+ boolean mInitialized = false;
+ int mTotalNumberOfStringsParsed;
+ boolean mDebugging;
+ PrintStream out;
+
+ // Properties
+ // Constructors
+
+ /**
+ * Creates a wildcard string parser.
+ *
+ * @param pStringMask the wildcard string mask.
+ */
+ public REWildcardStringParser(final String pStringMask) {
+ this(pStringMask, false);
+ }
+
+ /**
+ * Creates a wildcard string parser.
+ *
+ * @param pStringMask the wildcard string mask.
+ * @param pDebugging {@code true} will cause debug messages to be emitted to {@code System.out}.
+ */
+ public REWildcardStringParser(final String pStringMask, final boolean pDebugging) {
+ this(pStringMask, pDebugging, System.out);
+ }
+
+ /**
+ * Creates a wildcard string parser.
+ *
+ * @param pStringMask the wildcard string mask.
+ * @param pDebugging {@code true} will cause debug messages to be emitted.
+ * @param pDebuggingPrintStream the {@code java.io.PrintStream} to which the debug messages will be emitted.
+ */
+ public REWildcardStringParser(final String pStringMask, final boolean pDebugging, final PrintStream pDebuggingPrintStream) {
+
+ this.mStringMask = pStringMask;
+ this.mDebugging = pDebugging;
+ this.out = pDebuggingPrintStream;
+ mInitialized = buildRegexpParser();
+ }
+
+ // Methods
+
+ /**
+ * Converts wildcard string mask to regular expression.
+ * This method should reside in som utility class, but I don't know how proprietary the regular expression format is...
+ * @return the corresponding regular expression or {@code null} if an error occurred.
+ */
+ private String convertWildcardExpressionToRegularExpression(final String pWildcardExpression) {
+
+ if (pWildcardExpression == null) {
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this) + "wildcard expression is null - also returning null as regexp!");
+ }
+ return null;
+ }
+ StringBuilder regexpBuffer = new StringBuilder();
+ boolean convertingError = false;
+
+ for (int i = 0; i < pWildcardExpression.length(); i++) {
+ if (convertingError) {
+ return null;
+ }
+
+ // Free-range character '*'
+ char stringMaskChar = pWildcardExpression.charAt(i);
+
+ if (isFreeRangeCharacter(stringMaskChar)) {
+ regexpBuffer.append("(([a-�A-�0-9]|.|_|-)*)");
+ }
+
+ // Free-pass character '?'
+ else if (isFreePassCharacter(stringMaskChar)) {
+ regexpBuffer.append("([a-�A_�0-9]|.|_|-)");
+ }
+
+ // Valid characters
+ else if (isInAlphabet(stringMaskChar)) {
+ regexpBuffer.append(stringMaskChar);
+ }
+
+ // Invalid character - aborting
+ else {
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this)
+ + "one or more characters in string mask are not legal characters - returning null as regexp!");
+ }
+ convertingError = true;
+ }
+ }
+ return regexpBuffer.toString();
+ }
+
+ /**
+ * Builds the regexp parser.
+ */
+ private boolean buildRegexpParser() {
+
+ // Convert wildcard string mask to regular expression
+ String regexp = convertWildcardExpressionToRegularExpression(mStringMask);
+
+ if (regexp == null) {
+ out.println(DebugUtil.getPrefixErrorMessage(this)
+ + "irregularity in regexp conversion - now not able to parse any strings, all strings will be rejected!");
+ return false;
+ }
+
+ // Instantiate a regular expression parser
+ try {
+ mRegexpParser = Pattern.compile(regexp);
+ }
+ catch (PatternSyntaxException e) {
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixErrorMessage(this) + "RESyntaxException \"" + e.getMessage()
+ + "\" caught - now not able to parse any strings, all strings will be rejected!");
+ }
+ if (mDebugging) {
+ e.printStackTrace(System.err);
+ }
+ return false;
+ }
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this) + "regular expression parser from regular expression " + regexp
+ + " extracted from wildcard string mask " + mStringMask + ".");
+ }
+ return true;
+ }
+
+ /**
+ * Simple check of the string to be parsed.
+ */
+ private boolean checkStringToBeParsed(final String pStringToBeParsed) {
+
+ // Check for nullness
+ if (pStringToBeParsed == null) {
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this) + "string to be parsed is null - rejection!");
+ }
+ return false;
+ }
+
+ // Check if valid character (element in alphabet)
+ for (int i = 0; i < pStringToBeParsed.length(); i++) {
+ if (!isInAlphabet(pStringToBeParsed.charAt(i))) {
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this)
+ + "one or more characters in string to be parsed are not legal characters - rejection!");
+ }
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests if a certain character is a valid character in the alphabet that is applying for this automaton.
+ */
+ public static boolean isInAlphabet(final char pCharToCheck) {
+
+ for (int i = 0; i < ALPHABET.length; i++) {
+ if (pCharToCheck == ALPHABET[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Tests if a certain character is the designated "free-range" character ('*').
+ */
+ public static boolean isFreeRangeCharacter(final char pCharToCheck) {
+ return pCharToCheck == FREE_RANGE_CHARACTER;
+ }
+
+ /**
+ * Tests if a certain character is the designated "free-pass" character ('?').
+ */
+ public static boolean isFreePassCharacter(final char pCharToCheck) {
+ return pCharToCheck == FREE_PASS_CHARACTER;
+ }
+
+ /**
+ * Tests if a certain character is a wildcard character ('*' or '?').
+ */
+ public static boolean isWildcardCharacter(final char pCharToCheck) {
+ return ((isFreeRangeCharacter(pCharToCheck)) || (isFreePassCharacter(pCharToCheck)));
+ }
+
+ /**
+ * Gets the string mask that was used when building the parser atomaton.
+ *
+ * @return the string mask used for building the parser automaton.
+ */
+ public String getStringMask() {
+ return mStringMask;
+ }
+
+ /**
+ * Parses a string.
+ *
+ *
+ * @param pStringToBeParsed
+ * @return {@code true} if and only if the string are accepted by the parser.
+ */
+ public boolean parseString(final String pStringToBeParsed) {
+
+ if (mDebugging) {
+ out.println();
+ }
+ if (mDebugging) {
+ out.println(DebugUtil.getPrefixDebugMessage(this) + "parsing \"" + pStringToBeParsed + "\"...");
+ }
+
+ // Update statistics
+ mTotalNumberOfStringsParsed++;
+
+ // Check string to be parsed
+ if (!checkStringToBeParsed(pStringToBeParsed)) {
+ return false;
+ }
+
+ // Perform parsing and return accetance/rejection flag
+ if (mInitialized) {
+ return mRegexpParser.matcher(pStringToBeParsed).matches();
+ } else {
+ out.println(DebugUtil.getPrefixErrorMessage(this) + "trying to use non-initialized parser - string rejected!");
+ }
+ return false;
+ }
+
+ /*
+ * Overriding mandatory methods from EntityObject's.
+ */
+
+ /**
+ * Method toString
+ *
+ *
+ * @return
+ *
+ */
+ public String toString() {
+
+ StringBuilder buffer = new StringBuilder();
+
+ buffer.append(DebugUtil.getClassName(this));
+ buffer.append(": String mask ");
+ buffer.append(mStringMask);
+ buffer.append("\n");
+ return buffer.toString();
+ }
+
+ // Just taking the lazy, easy and dangerous way out
+
+ /**
+ * Method equals
+ *
+ *
+ * @param pObject
+ *
+ * @return
+ *
+ */
+ public boolean equals(Object pObject) {
+
+ if (pObject instanceof REWildcardStringParser) {
+ REWildcardStringParser externalParser = (REWildcardStringParser) pObject;
+
+ return (externalParser.mStringMask == this.mStringMask);
+ }
+ return ((Object) this).equals(pObject);
+ }
+
+ // Just taking the lazy, easy and dangerous way out
+
+ /**
+ * Method hashCode
+ *
+ *
+ * @return
+ *
+ */
+ public int hashCode() {
+ return ((Object) this).hashCode();
+ }
+
+ protected Object clone() throws CloneNotSupportedException {
+ return new REWildcardStringParser(mStringMask);
+ }
+
+ // Just taking the lazy, easy and dangerous way out
+ protected void finalize() throws Throwable {}
+}
+
+
+/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
+
+
+/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java
index 1bfda5ff..69530349 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java
@@ -1,336 +1,336 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import java.awt.*;
-import java.awt.geom.Rectangle2D;
-
-/**
- * This servlet is capable of rendereing a text string and output it as an
- * image. The text can be rendered in any given font, size,
- * style or color, into an image, and output it as a GIF, JPEG or PNG image,
- * with optional caching of the rendered image files.
- *
- *
- *
- * Parameters:
- *
- * - {@code text}
- * - string, the text string to render.
- *
- {@code width}
- * - integer, the width of the image
- *
- {@code height}
- * - integer, the height of the image
- *
- {@code fontFamily}
- * - string, the name of the font family.
- * Default is {@code "Helvetica"}.
- *
- {@code fontSize}
- * - integer, the size of the font. Default is {@code 12}.
- *
- {@code fontStyle}
- * - string, the tyle of the font. Can be one of the constants
- * {@code plain} (default), {@code bold}, {@code italic} or
- * {@code bolditalic}. Any other will result in {@code plain}.
- *
- {@code fgcolor}
- * - color (HTML form, {@code #RRGGBB}), or color constant from
- * {@link java.awt.Color}, default is {@code "black"}.
- *
- {@code bgcolor}
- * - color (HTML form, {@code #RRGGBB}), or color constant from
- * {@link java.awt.Color}, default is {@code "transparent"}.
- * Note that the hash character ({@code "#"}) used in colors must be
- * escaped as {@code %23} in the query string. See
- * {@link StringUtil#toColor(String)}, examples.
- *
- *
- *
- *
- {@code cache}
- * - boolean, {@code true} if you want to cache the result
- * to disk (default).
- *
- *
- {@code compression}
- * - float, the optional compression ratio for the output image. For JPEG
- * images, the quality is the inverse of the compression ratio. See
- * {@link #JPEG_DEFAULT_COMPRESSION_LEVEL},
- * {@link #PNG_DEFAULT_COMPRESSION_LEVEL}.
- *
- Applies to JPEG and PNG images only.
- *
- *
- {@code dither}
- * - enumerated, one of {@code NONE}, {@code DEFAULT} or
- * {@code FS}, if you want to dither the result ({@code DEFAULT} is
- * default).
- * {@code FS} will produce the best results, but it's slower.
- *
- Use in conjuction with {@code indexed}, {@code palette}
- * and {@code websafe}.
- *
- Applies to GIF and PNG images only.
- *
- *
- {@code fileName}
- * - string, an optional filename. If not set, the path after the servlet
- * ({@link HttpServletRequest#getPathInfo}) will be used for the cache
- * filename. See {@link #getCacheFile(ServletRequest)},
- * {@link #getCacheRoot}.
- *
- *
- {@code height}
- * - integer, the height of the image.
- *
- *
- {@code width}
- * - integer, the width of the image.
- *
- *
- {@code indexed}
- * - integer, the number of colors in the resulting image, or -1 (default).
- * If the value is set and positive, the image will use an
- * {@code IndexColorModel} with
- * the number of colors specified. Otherwise the image will be true color.
- *
- Applies to GIF and PNG images only.
- *
- *
- {@code palette}
- * - string, an optional filename. If set, the image will use IndexColorModel
- * with a palette read from the given file.
- *
- Applies to GIF and PNG images only.
- *
- *
- {@code websafe}
- * - boolean, {@code true} if you want the result to use the 216 color
- * websafe palette (default is false).
- *
- Applies to GIF and PNG images only.
- *
- *
- * @example
- * <IMG src="/text/test.gif?height=40&width=600
- * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23990033
- * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
- * %20lazy%20dog&cache=false" />
- *
- * @example
- * <IMG src="/text/test.jpg?height=40&width=600
- * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=black
- * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
- * %20lazy%20dog&compression=3&cache=false" />
- *
- * @example
- * <IMG src="/text/test.png?height=40&width=600
- * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23336699
- * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
- * %20lazy%20dog&cache=true" />
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: TextRenderer.java#2 $
- */
-
-class TextRenderer /*extends ImageServlet implements ImagePainterServlet*/ {
- // TODO: Create something usable out of this piece of old junk.. ;-)
- // It just needs a graphics object to write onto
- // Alternatively, defer, and compute the size needed
- // Or, make it a filter...
-
- /** {@code "italic"} */
- public final static String FONT_STYLE_ITALIC = "italic";
- /** {@code "plain"} */
- public final static String FONT_STYLE_PLAIN = "plain";
- /** {@code "bold"} */
- public final static String FONT_STYLE_BOLD = "bold";
-
- /** {@code text} */
- public final static String PARAM_TEXT = "text";
- /** {@code marginLeft} */
- public final static String PARAM_MARGIN_LEFT = "marginLeft";
- /** {@code marginTop} */
- public final static String PARAM_MARGIN_TOP = "marginTop";
- /** {@code fontFamily} */
- public final static String PARAM_FONT_FAMILY = "fontFamily";
- /** {@code fontSize} */
- public final static String PARAM_FONT_SIZE = "fontSize";
- /** {@code fontStyle} */
- public final static String PARAM_FONT_STYLE = "fontStyle";
- /** {@code textRotation} */
- public final static String PARAM_TEXT_ROTATION = "textRotation";
- /** {@code textRotation} */
- public final static String PARAM_TEXT_ROTATION_UNITS = "textRotationUnits";
-
- /** {@code bgcolor} */
- public final static String PARAM_BGCOLOR = "bgcolor";
- /** {@code fgcolor} */
- public final static String PARAM_FGCOLOR = "fgcolor";
-
- protected final static String ROTATION_DEGREES = "DEGREES";
- protected final static String ROTATION_RADIANS = "RADIANS";
-
- /**
- * Creates the TextRender servlet.
- */
-
- public TextRenderer() {
- }
-
- /**
- * Renders the text string for this servlet request.
- */
- private void paint(ServletRequest pReq, Graphics2D pRes, int pWidth, int pHeight)
- throws ImageServletException {
-
- // Get parameters
- String text = pReq.getParameter(PARAM_TEXT);
- String[] lines = StringUtil.toStringArray(text, "\n\r");
-
- String fontFamily = pReq.getParameter(PARAM_FONT_FAMILY);
- String fontSize = pReq.getParameter(PARAM_FONT_SIZE);
- String fontStyle = pReq.getParameter(PARAM_FONT_STYLE);
-
- String bgcolor = pReq.getParameter(PARAM_BGCOLOR);
- String fgcolor = pReq.getParameter(PARAM_FGCOLOR);
-
- // TODO: Make them static..
- pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
- pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
- pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
- // pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
-
- //System.out.println(pRes.getBackground());
-
- // Clear area with bgcolor
- if (!StringUtil.isEmpty(bgcolor)) {
- pRes.setBackground(StringUtil.toColor(bgcolor));
- pRes.clearRect(0, 0, pWidth, pHeight);
-
- //System.out.println(pRes.getBackground());
- }
-
- // Create and set font
- Font font = new Font(
- fontFamily != null ? fontFamily : "Helvetica",
- getFontStyle(fontStyle),
- fontSize != null ? Integer.parseInt(fontSize) : 12
- );
- pRes.setFont(font);
-
- // Set rotation
- double angle = getAngle(pReq);
- pRes.rotate(angle, pWidth / 2.0, pHeight / 2.0);
-
- // Draw string in fgcolor
- pRes.setColor(fgcolor != null ? StringUtil.toColor(fgcolor) : Color.black);
-
- float x = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_LEFT, Float.MIN_VALUE);
- Rectangle2D[] bounds = new Rectangle2D[lines.length];
- if (x <= Float.MIN_VALUE) {
- // Center
- float longest = 0f;
- for (int i = 0; i < lines.length; i++) {
- bounds[i] = font.getStringBounds(lines[i], pRes.getFontRenderContext());
- if (bounds[i].getWidth() > longest) {
- longest = (float) bounds[i].getWidth();
- }
- }
-
- //x = (float) ((pWidth - bounds.getWidth()) / 2f);
- x = (float) ((pWidth - longest) / 2f);
-
- //System.out.println("marginLeft: " + x);
- }
- //else {
- //System.out.println("marginLeft (from param): " + x);
- //}
-
- float y = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_TOP, Float.MIN_VALUE);
- float lineHeight = (float) (bounds[0] != null ?
- bounds[0].getHeight() : font.getStringBounds(lines[0], pRes.getFontRenderContext()).getHeight());
-
- if (y <= Float.MIN_VALUE) {
- // Center
- y = (float) ((pHeight - lineHeight) / 2f)
- - (lineHeight * (lines.length - 2.5f) / 2f);
-
- //System.out.println("marginTop: " + y);
- }
- else {
- // Todo: Correct for font height?
- y += font.getSize2D();
- //System.out.println("marginTop (from param):" + y);
-
- }
-
- //System.out.println("Font size: " + font.getSize2D());
- //System.out.println("Line height: " + lineHeight);
-
- // Draw
- for (int i = 0; i < lines.length; i++) {
- pRes.drawString(lines[i], x, y + lineHeight * i);
- }
- }
-
- /**
- * Returns the font style constant.
- *
- * @param pStyle a string containing either the word {@code "plain"} or one
- * or more of {@code "bold"} and {@code italic}.
- * @return the font style constant as defined in {@link Font}.
- *
- * @see Font#PLAIN
- * @see Font#BOLD
- * @see Font#ITALIC
- */
- private int getFontStyle(String pStyle) {
- if (pStyle == null || StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_PLAIN)) {
- return Font.PLAIN;
- }
-
- // Try to find bold/italic
- int style = Font.PLAIN;
- if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_BOLD)) {
- style |= Font.BOLD;
- }
- if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_ITALIC)) {
- style |= Font.ITALIC;
- }
-
- return style;
- }
-
- /**
- * Gets the angle of rotation from the request.
- *
- * @param pRequest the servlet request to get parameters from
- * @return the angle in radians.
- */
- private double getAngle(ServletRequest pRequest) {
- // Get angle
- double angle = ServletUtil.getDoubleParameter(pRequest, PARAM_TEXT_ROTATION, 0.0);
-
- // Convert to radians, if needed
- String units = pRequest.getParameter(PARAM_TEXT_ROTATION_UNITS);
- if (!StringUtil.isEmpty(units) && ROTATION_DEGREES.equalsIgnoreCase(units)) {
- angle = Math.toRadians(angle);
- }
-
- return angle;
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import java.awt.*;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * This servlet is capable of rendereing a text string and output it as an
+ * image. The text can be rendered in any given font, size,
+ * style or color, into an image, and output it as a GIF, JPEG or PNG image,
+ * with optional caching of the rendered image files.
+ *
+ *
+ *
+ * Parameters:
+ *
+ * - {@code text}
+ * - string, the text string to render.
+ *
- {@code width}
+ * - integer, the width of the image
+ *
- {@code height}
+ * - integer, the height of the image
+ *
- {@code fontFamily}
+ * - string, the name of the font family.
+ * Default is {@code "Helvetica"}.
+ *
- {@code fontSize}
+ * - integer, the size of the font. Default is {@code 12}.
+ *
- {@code fontStyle}
+ * - string, the tyle of the font. Can be one of the constants
+ * {@code plain} (default), {@code bold}, {@code italic} or
+ * {@code bolditalic}. Any other will result in {@code plain}.
+ *
- {@code fgcolor}
+ * - color (HTML form, {@code #RRGGBB}), or color constant from
+ * {@link java.awt.Color}, default is {@code "black"}.
+ *
- {@code bgcolor}
+ * - color (HTML form, {@code #RRGGBB}), or color constant from
+ * {@link java.awt.Color}, default is {@code "transparent"}.
+ * Note that the hash character ({@code "#"}) used in colors must be
+ * escaped as {@code %23} in the query string. See
+ * {@link StringUtil#toColor(String)}, examples.
+ *
+ *
+ *
+ *
- {@code cache}
+ * - boolean, {@code true} if you want to cache the result
+ * to disk (default).
+ *
+ *
- {@code compression}
+ * - float, the optional compression ratio for the output image. For JPEG
+ * images, the quality is the inverse of the compression ratio. See
+ * {@link #JPEG_DEFAULT_COMPRESSION_LEVEL},
+ * {@link #PNG_DEFAULT_COMPRESSION_LEVEL}.
+ *
- Applies to JPEG and PNG images only.
+ *
+ *
- {@code dither}
+ * - enumerated, one of {@code NONE}, {@code DEFAULT} or
+ * {@code FS}, if you want to dither the result ({@code DEFAULT} is
+ * default).
+ * {@code FS} will produce the best results, but it's slower.
+ *
- Use in conjuction with {@code indexed}, {@code palette}
+ * and {@code websafe}.
+ *
- Applies to GIF and PNG images only.
+ *
+ *
- {@code fileName}
+ * - string, an optional filename. If not set, the path after the servlet
+ * ({@link HttpServletRequest#getPathInfo}) will be used for the cache
+ * filename. See {@link #getCacheFile(ServletRequest)},
+ * {@link #getCacheRoot}.
+ *
+ *
- {@code height}
+ * - integer, the height of the image.
+ *
+ *
- {@code width}
+ * - integer, the width of the image.
+ *
+ *
- {@code indexed}
+ * - integer, the number of colors in the resulting image, or -1 (default).
+ * If the value is set and positive, the image will use an
+ * {@code IndexColorModel} with
+ * the number of colors specified. Otherwise the image will be true color.
+ *
- Applies to GIF and PNG images only.
+ *
+ *
- {@code palette}
+ * - string, an optional filename. If set, the image will use IndexColorModel
+ * with a palette read from the given file.
+ *
- Applies to GIF and PNG images only.
+ *
+ *
- {@code websafe}
+ * - boolean, {@code true} if you want the result to use the 216 color
+ * websafe palette (default is false).
+ *
- Applies to GIF and PNG images only.
+ *
+ *
+ * @example
+ * <IMG src="/text/test.gif?height=40&width=600
+ * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23990033
+ * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
+ * %20lazy%20dog&cache=false" />
+ *
+ * @example
+ * <IMG src="/text/test.jpg?height=40&width=600
+ * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=black
+ * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
+ * %20lazy%20dog&compression=3&cache=false" />
+ *
+ * @example
+ * <IMG src="/text/test.png?height=40&width=600
+ * &fontFamily=TimesRoman&fontSize=30&fontStyle=italic&fgcolor=%23336699
+ * &bgcolor=%23cccccc&text=the%20quick%20brown%20fox%20jumps%20over%20the
+ * %20lazy%20dog&cache=true" />
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: TextRenderer.java#2 $
+ */
+
+class TextRenderer /*extends ImageServlet implements ImagePainterServlet*/ {
+ // TODO: Create something usable out of this piece of old junk.. ;-)
+ // It just needs a graphics object to write onto
+ // Alternatively, defer, and compute the size needed
+ // Or, make it a filter...
+
+ /** {@code "italic"} */
+ public final static String FONT_STYLE_ITALIC = "italic";
+ /** {@code "plain"} */
+ public final static String FONT_STYLE_PLAIN = "plain";
+ /** {@code "bold"} */
+ public final static String FONT_STYLE_BOLD = "bold";
+
+ /** {@code text} */
+ public final static String PARAM_TEXT = "text";
+ /** {@code marginLeft} */
+ public final static String PARAM_MARGIN_LEFT = "marginLeft";
+ /** {@code marginTop} */
+ public final static String PARAM_MARGIN_TOP = "marginTop";
+ /** {@code fontFamily} */
+ public final static String PARAM_FONT_FAMILY = "fontFamily";
+ /** {@code fontSize} */
+ public final static String PARAM_FONT_SIZE = "fontSize";
+ /** {@code fontStyle} */
+ public final static String PARAM_FONT_STYLE = "fontStyle";
+ /** {@code textRotation} */
+ public final static String PARAM_TEXT_ROTATION = "textRotation";
+ /** {@code textRotation} */
+ public final static String PARAM_TEXT_ROTATION_UNITS = "textRotationUnits";
+
+ /** {@code bgcolor} */
+ public final static String PARAM_BGCOLOR = "bgcolor";
+ /** {@code fgcolor} */
+ public final static String PARAM_FGCOLOR = "fgcolor";
+
+ protected final static String ROTATION_DEGREES = "DEGREES";
+ protected final static String ROTATION_RADIANS = "RADIANS";
+
+ /**
+ * Creates the TextRender servlet.
+ */
+
+ public TextRenderer() {
+ }
+
+ /**
+ * Renders the text string for this servlet request.
+ */
+ private void paint(ServletRequest pReq, Graphics2D pRes, int pWidth, int pHeight)
+ throws ImageServletException {
+
+ // Get parameters
+ String text = pReq.getParameter(PARAM_TEXT);
+ String[] lines = StringUtil.toStringArray(text, "\n\r");
+
+ String fontFamily = pReq.getParameter(PARAM_FONT_FAMILY);
+ String fontSize = pReq.getParameter(PARAM_FONT_SIZE);
+ String fontStyle = pReq.getParameter(PARAM_FONT_STYLE);
+
+ String bgcolor = pReq.getParameter(PARAM_BGCOLOR);
+ String fgcolor = pReq.getParameter(PARAM_FGCOLOR);
+
+ // TODO: Make them static..
+ pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
+ pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
+ pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY));
+ // pRes.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
+
+ //System.out.println(pRes.getBackground());
+
+ // Clear area with bgcolor
+ if (!StringUtil.isEmpty(bgcolor)) {
+ pRes.setBackground(StringUtil.toColor(bgcolor));
+ pRes.clearRect(0, 0, pWidth, pHeight);
+
+ //System.out.println(pRes.getBackground());
+ }
+
+ // Create and set font
+ Font font = new Font(
+ fontFamily != null ? fontFamily : "Helvetica",
+ getFontStyle(fontStyle),
+ fontSize != null ? Integer.parseInt(fontSize) : 12
+ );
+ pRes.setFont(font);
+
+ // Set rotation
+ double angle = getAngle(pReq);
+ pRes.rotate(angle, pWidth / 2.0, pHeight / 2.0);
+
+ // Draw string in fgcolor
+ pRes.setColor(fgcolor != null ? StringUtil.toColor(fgcolor) : Color.black);
+
+ float x = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_LEFT, Float.MIN_VALUE);
+ Rectangle2D[] bounds = new Rectangle2D[lines.length];
+ if (x <= Float.MIN_VALUE) {
+ // Center
+ float longest = 0f;
+ for (int i = 0; i < lines.length; i++) {
+ bounds[i] = font.getStringBounds(lines[i], pRes.getFontRenderContext());
+ if (bounds[i].getWidth() > longest) {
+ longest = (float) bounds[i].getWidth();
+ }
+ }
+
+ //x = (float) ((pWidth - bounds.getWidth()) / 2f);
+ x = (float) ((pWidth - longest) / 2f);
+
+ //System.out.println("marginLeft: " + x);
+ }
+ //else {
+ //System.out.println("marginLeft (from param): " + x);
+ //}
+
+ float y = ServletUtil.getFloatParameter(pReq, PARAM_MARGIN_TOP, Float.MIN_VALUE);
+ float lineHeight = (float) (bounds[0] != null ?
+ bounds[0].getHeight() : font.getStringBounds(lines[0], pRes.getFontRenderContext()).getHeight());
+
+ if (y <= Float.MIN_VALUE) {
+ // Center
+ y = (float) ((pHeight - lineHeight) / 2f)
+ - (lineHeight * (lines.length - 2.5f) / 2f);
+
+ //System.out.println("marginTop: " + y);
+ }
+ else {
+ // Todo: Correct for font height?
+ y += font.getSize2D();
+ //System.out.println("marginTop (from param):" + y);
+
+ }
+
+ //System.out.println("Font size: " + font.getSize2D());
+ //System.out.println("Line height: " + lineHeight);
+
+ // Draw
+ for (int i = 0; i < lines.length; i++) {
+ pRes.drawString(lines[i], x, y + lineHeight * i);
+ }
+ }
+
+ /**
+ * Returns the font style constant.
+ *
+ * @param pStyle a string containing either the word {@code "plain"} or one
+ * or more of {@code "bold"} and {@code italic}.
+ * @return the font style constant as defined in {@link Font}.
+ *
+ * @see Font#PLAIN
+ * @see Font#BOLD
+ * @see Font#ITALIC
+ */
+ private int getFontStyle(String pStyle) {
+ if (pStyle == null || StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_PLAIN)) {
+ return Font.PLAIN;
+ }
+
+ // Try to find bold/italic
+ int style = Font.PLAIN;
+ if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_BOLD)) {
+ style |= Font.BOLD;
+ }
+ if (StringUtil.containsIgnoreCase(pStyle, FONT_STYLE_ITALIC)) {
+ style |= Font.ITALIC;
+ }
+
+ return style;
+ }
+
+ /**
+ * Gets the angle of rotation from the request.
+ *
+ * @param pRequest the servlet request to get parameters from
+ * @return the angle in radians.
+ */
+ private double getAngle(ServletRequest pRequest) {
+ // Get angle
+ double angle = ServletUtil.getDoubleParameter(pRequest, PARAM_TEXT_ROTATION, 0.0);
+
+ // Convert to radians, if needed
+ String units = pRequest.getParameter(PARAM_TEXT_ROTATION_UNITS);
+ if (!StringUtil.isEmpty(units) && ROTATION_DEGREES.equalsIgnoreCase(units)) {
+ angle = Math.toRadians(angle);
+ }
+
+ return angle;
+ }
}
\ No newline at end of file
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java
index fb068c41..261733c7 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java
@@ -1,76 +1,76 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: Droplet.java,v $
- * Revision 1.3 2003/10/06 14:25:19 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.2 2002/10/18 14:12:16 WMHAKUR
- * Now, it even compiles. :-/
- *
- * Revision 1.1 2002/10/18 14:02:16 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet;
-
-import com.twelvemonkeys.servlet.jsp.droplet.taglib.IncludeTag;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.PageContext;
-import java.io.IOException;
-
-/**
- * Dynamo Droplet like Servlet.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- *
- */
-public abstract class Droplet extends HttpServlet implements JspFragment {
-
- public abstract void service(PageContext pPageContext)
- throws ServletException, IOException;
-
- /**
- * Services a parameter. Programatically equivalent to the
- * JSP tag.
- */
- public void serviceParameter(String pParameter, PageContext pPageContext) throws ServletException, IOException {
- Object param = pPageContext.getRequest().getAttribute(pParameter);
-
- if (param != null) {
- if (param instanceof Param) {
- ((Param) param).service(pPageContext);
- }
- else {
- pPageContext.getOut().print(param);
- }
- }
- else {
- // Try to get value from parameters
- Object obj = pPageContext.getRequest().getParameter(pParameter);
-
- // Print parameter or default value
- pPageContext.getOut().print((obj != null) ? obj : "");
- }
- }
-
- /**
- * "There's no need to override this method." :-)
- */
- final public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
- PageContext pageContext = (PageContext) pRequest.getAttribute(IncludeTag.PAGE_CONTEXT);
-
- // TODO: What if pageContext == null
- service(pageContext);
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: Droplet.java,v $
+ * Revision 1.3 2003/10/06 14:25:19 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.2 2002/10/18 14:12:16 WMHAKUR
+ * Now, it even compiles. :-/
+ *
+ * Revision 1.1 2002/10/18 14:02:16 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet;
+
+import com.twelvemonkeys.servlet.jsp.droplet.taglib.IncludeTag;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.PageContext;
+import java.io.IOException;
+
+/**
+ * Dynamo Droplet like Servlet.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ *
+ */
+public abstract class Droplet extends HttpServlet implements JspFragment {
+
+ public abstract void service(PageContext pPageContext)
+ throws ServletException, IOException;
+
+ /**
+ * Services a parameter. Programatically equivalent to the
+ * JSP tag.
+ */
+ public void serviceParameter(String pParameter, PageContext pPageContext) throws ServletException, IOException {
+ Object param = pPageContext.getRequest().getAttribute(pParameter);
+
+ if (param != null) {
+ if (param instanceof Param) {
+ ((Param) param).service(pPageContext);
+ }
+ else {
+ pPageContext.getOut().print(param);
+ }
+ }
+ else {
+ // Try to get value from parameters
+ Object obj = pPageContext.getRequest().getParameter(pParameter);
+
+ // Print parameter or default value
+ pPageContext.getOut().print((obj != null) ? obj : "");
+ }
+ }
+
+ /**
+ * "There's no need to override this method." :-)
+ */
+ final public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
+ PageContext pageContext = (PageContext) pRequest.getAttribute(IncludeTag.PAGE_CONTEXT);
+
+ // TODO: What if pageContext == null
+ service(pageContext);
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java
index 344dab31..50b11b64 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java
@@ -1,42 +1,42 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: JspFragment.java,v $
- * Revision 1.2 2003/10/06 14:25:36 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/10/18 14:02:16 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet;
-
-import javax.servlet.ServletException;
-import javax.servlet.jsp.PageContext;
-import java.io.IOException;
-
-/**
- * Interface for JSP sub pages or page fragments to implement.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- */
-public interface JspFragment {
-
- /**
- * Services a sub page or a page fragment inside another page
- * (or PageContext).
- *
- * @param pContext the PageContext that is used to render the subpage.
- *
- * @throws ServletException if an exception occurs that interferes with the
- * subpage's normal operation
- * @throws IOException if an input or output exception occurs
- */
- public void service(PageContext pContext) throws ServletException, IOException;
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: JspFragment.java,v $
+ * Revision 1.2 2003/10/06 14:25:36 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/10/18 14:02:16 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet;
+
+import javax.servlet.ServletException;
+import javax.servlet.jsp.PageContext;
+import java.io.IOException;
+
+/**
+ * Interface for JSP sub pages or page fragments to implement.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ */
+public interface JspFragment {
+
+ /**
+ * Services a sub page or a page fragment inside another page
+ * (or PageContext).
+ *
+ * @param pContext the PageContext that is used to render the subpage.
+ *
+ * @throws ServletException if an exception occurs that interferes with the
+ * subpage's normal operation
+ * @throws IOException if an input or output exception occurs
+ */
+ public void service(PageContext pContext) throws ServletException, IOException;
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java
index dfc49f89..87c29ce7 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java
@@ -1,26 +1,26 @@
-package com.twelvemonkeys.servlet.jsp.droplet;
-
-import javax.servlet.ServletException;
-import javax.servlet.jsp.PageContext;
-import java.io.IOException;
-
-/**
- * Oparam (Open parameter)
- */
-public class Oparam extends Param implements JspFragment {
- /**
- * Creates an Oparam.
- *
- * @param pValue the value of the parameter
- */
- public Oparam(String pValue) {
- super(pValue);
- }
-
- public void service(PageContext pContext) throws ServletException, IOException {
- pContext.getServletContext().log("Service subpage " + pContext.getServletContext().getRealPath(value));
-
- pContext.include(value);
- }
-}
-
+package com.twelvemonkeys.servlet.jsp.droplet;
+
+import javax.servlet.ServletException;
+import javax.servlet.jsp.PageContext;
+import java.io.IOException;
+
+/**
+ * Oparam (Open parameter)
+ */
+public class Oparam extends Param implements JspFragment {
+ /**
+ * Creates an Oparam.
+ *
+ * @param pValue the value of the parameter
+ */
+ public Oparam(String pValue) {
+ super(pValue);
+ }
+
+ public void service(PageContext pContext) throws ServletException, IOException {
+ pContext.getServletContext().log("Service subpage " + pContext.getServletContext().getRealPath(value));
+
+ pContext.include(value);
+ }
+}
+
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java
index bfee7a5e..74fe73d9 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java
@@ -1,41 +1,41 @@
-package com.twelvemonkeys.servlet.jsp.droplet;
-
-import javax.servlet.ServletException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.PageContext;
-import java.io.IOException;
-
-/**
- * Param
- */
-public class Param implements JspFragment {
-
- /** The value member field. */
- protected String value = null;
-
- /**
- * Creates a Param.
- *
- * @param pValue the value of the parameter
- */
- public Param(String pValue) {
- value = pValue;
- }
-
- /**
- * Gets the value of the parameter.
- */
- public String getValue() {
- return value;
- }
-
- /**
- * Services the page fragment. This version simply prints the value of
- * this parameter to teh PageContext's out.
- */
- public void service(PageContext pContext)
- throws ServletException, IOException {
- JspWriter writer = pContext.getOut();
- writer.print(value);
- }
-}
+package com.twelvemonkeys.servlet.jsp.droplet;
+
+import javax.servlet.ServletException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import java.io.IOException;
+
+/**
+ * Param
+ */
+public class Param implements JspFragment {
+
+ /** The value member field. */
+ protected String value = null;
+
+ /**
+ * Creates a Param.
+ *
+ * @param pValue the value of the parameter
+ */
+ public Param(String pValue) {
+ value = pValue;
+ }
+
+ /**
+ * Gets the value of the parameter.
+ */
+ public String getValue() {
+ return value;
+ }
+
+ /**
+ * Services the page fragment. This version simply prints the value of
+ * this parameter to teh PageContext's out.
+ */
+ public void service(PageContext pContext)
+ throws ServletException, IOException {
+ JspWriter writer = pContext.getOut();
+ writer.print(value);
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java
index a9c097d1..d25dbda5 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java
@@ -1,214 +1,214 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: IncludeTag.java,v $
- * Revision 1.2 2003/10/06 14:25:36 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.jsp.JspException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * Include tag tag that emulates ATG Dynamo Droplet tag JHTML behaviour for
- * JSP.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- *
- */
-public class IncludeTag extends ExTagSupport {
- /**
- * This will contain the names of all the parameters that have been
- * added to the PageContext.REQUEST_SCOPE scope by this tag.
- */
- private ArrayList parameterNames = null;
-
- /**
- * If any of the parameters we insert for this tag already exist, then
- * we back up the older parameter in this {@code HashMap} and
- * restore them when the tag is finished.
- */
- private HashMap oldParameters = null;
-
- /**
- * This is the URL for the JSP page that the parameters contained in this
- * tag are to be inserted into.
- */
- private String page;
-
- /**
- * The name of the PageContext attribute
- */
- public final static String PAGE_CONTEXT = "com.twelvemonkeys.servlet.jsp.PageContext";
-
- /**
- * Sets the value for the JSP page to insert the parameters into. This
- * will be set by the tag attribute within the original JSP page.
- *
- * @param pPage The URL for the JSP page to insert parameters into.
- */
- public void setPage(String pPage) {
- page = pPage;
- }
-
- /**
- * Adds a parameter to the {@code PageContext.REQUEST_SCOPE} scope.
- * If a parameter with the same name as {@code pName} already exists,
- * then the old parameter is first placed in the {@code OldParameters}
- * member variable. When this tag is finished, the old value will be
- * restored.
- *
- * @param pName The name of the new parameter to be stored in the
- * {@code PageContext.REQUEST_SCOPE} scope.
- * @param pValue The value for the parmeter to be stored in the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- public void addParameter(String pName, Object pValue) {
- // Check that we haven't already saved this parameter
- if (!parameterNames.contains(pName)) {
- parameterNames.add(pName);
-
- // Now check if this parameter already exists in the page.
- Object obj = getRequest().getAttribute(pName);
- if (obj != null) {
- oldParameters.put(pName, obj);
- }
- }
-
- // Finally, insert the parameter in the request scope.
- getRequest().setAttribute(pName, pValue);
- }
-
- /**
- * This is the method called when the JSP interpreter first hits the tag
- * associated with this class. This method will firstly determine whether
- * the page referenced by the {@code page} attribute exists. If the
- * page doesn't exist, this method will throw a {@code JspException}.
- * If the page does exist, this method will hand control over to that JSP
- * page.
- *
- * @exception JspException
- */
- public int doStartTag() throws JspException {
- oldParameters = new HashMap();
- parameterNames = new ArrayList();
-
- return EVAL_BODY_INCLUDE;
- }
-
- /**
- * This method is called when the JSP page compiler hits the end tag. By
- * now all the data should have been passed and parameters entered into
- * the {@code PageContext.REQUEST_SCOPE} scope. This method includes
- * the JSP page whose URL is stored in the {@code mPage} member
- * variable.
- *
- * @exception JspException
- */
- public int doEndTag() throws JspException {
- String msg;
-
- try {
- Iterator iterator;
- String parameterName;
-
- // -- Harald K 20020726
- // Include the page, in place
- //getDispatcher().include(getRequest(), getResponse());
- addParameter(PAGE_CONTEXT, pageContext); // Will be cleared later
- pageContext.include(page);
-
- // Remove all the parameters that were added to the request scope
- // for this insert tag.
- iterator = parameterNames.iterator();
-
- while (iterator.hasNext()) {
- parameterName = iterator.next();
-
- getRequest().removeAttribute(parameterName);
- }
-
- iterator = oldParameters.keySet().iterator();
-
- // Restore the parameters we temporarily replaced (if any).
- while (iterator.hasNext()) {
- parameterName = iterator.next();
-
- getRequest().setAttribute(parameterName, oldParameters.get(parameterName));
- }
-
- return super.doEndTag();
- }
- catch (IOException ioe) {
- msg = "Caught an IOException while including " + page
- + "\n" + ioe.toString();
- log(msg, ioe);
- throw new JspException(msg);
- }
- catch (ServletException se) {
- msg = "Caught a ServletException while including " + page
- + "\n" + se.toString();
- log(msg, se);
- throw new JspException(msg);
- }
- }
-
- /**
- * Free up the member variables that we've used throughout this tag.
- */
- protected void clearServiceState() {
- oldParameters = null;
- parameterNames = null;
- }
-
- /**
- * Returns the request dispatcher for the JSP page whose URL is stored in
- * the {@code mPage} member variable.
- *
- * @return The RequestDispatcher for the JSP page whose URL is stored in
- * the {@code mPage} member variable.
- */
- /*
- private RequestDispatcher getDispatcher() {
- return getRequest().getRequestDispatcher(page);
- }
- */
-
- /**
- * Returns the HttpServletRequest object for the current user request.
- *
- * @return The HttpServletRequest object for the current user request.
- */
- private HttpServletRequest getRequest() {
- return (HttpServletRequest) pageContext.getRequest();
- }
-
- /**
- * Returns the HttpServletResponse object for the current user request.
- *
- * @return The HttpServletResponse object for the current user request.
- */
- private HttpServletResponse getResponse() {
- return (HttpServletResponse) pageContext.getResponse();
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: IncludeTag.java,v $
+ * Revision 1.2 2003/10/06 14:25:36 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.jsp.JspException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Include tag tag that emulates ATG Dynamo Droplet tag JHTML behaviour for
+ * JSP.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ *
+ */
+public class IncludeTag extends ExTagSupport {
+ /**
+ * This will contain the names of all the parameters that have been
+ * added to the PageContext.REQUEST_SCOPE scope by this tag.
+ */
+ private ArrayList parameterNames = null;
+
+ /**
+ * If any of the parameters we insert for this tag already exist, then
+ * we back up the older parameter in this {@code HashMap} and
+ * restore them when the tag is finished.
+ */
+ private HashMap oldParameters = null;
+
+ /**
+ * This is the URL for the JSP page that the parameters contained in this
+ * tag are to be inserted into.
+ */
+ private String page;
+
+ /**
+ * The name of the PageContext attribute
+ */
+ public final static String PAGE_CONTEXT = "com.twelvemonkeys.servlet.jsp.PageContext";
+
+ /**
+ * Sets the value for the JSP page to insert the parameters into. This
+ * will be set by the tag attribute within the original JSP page.
+ *
+ * @param pPage The URL for the JSP page to insert parameters into.
+ */
+ public void setPage(String pPage) {
+ page = pPage;
+ }
+
+ /**
+ * Adds a parameter to the {@code PageContext.REQUEST_SCOPE} scope.
+ * If a parameter with the same name as {@code pName} already exists,
+ * then the old parameter is first placed in the {@code OldParameters}
+ * member variable. When this tag is finished, the old value will be
+ * restored.
+ *
+ * @param pName The name of the new parameter to be stored in the
+ * {@code PageContext.REQUEST_SCOPE} scope.
+ * @param pValue The value for the parmeter to be stored in the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ public void addParameter(String pName, Object pValue) {
+ // Check that we haven't already saved this parameter
+ if (!parameterNames.contains(pName)) {
+ parameterNames.add(pName);
+
+ // Now check if this parameter already exists in the page.
+ Object obj = getRequest().getAttribute(pName);
+ if (obj != null) {
+ oldParameters.put(pName, obj);
+ }
+ }
+
+ // Finally, insert the parameter in the request scope.
+ getRequest().setAttribute(pName, pValue);
+ }
+
+ /**
+ * This is the method called when the JSP interpreter first hits the tag
+ * associated with this class. This method will firstly determine whether
+ * the page referenced by the {@code page} attribute exists. If the
+ * page doesn't exist, this method will throw a {@code JspException}.
+ * If the page does exist, this method will hand control over to that JSP
+ * page.
+ *
+ * @exception JspException
+ */
+ public int doStartTag() throws JspException {
+ oldParameters = new HashMap();
+ parameterNames = new ArrayList();
+
+ return EVAL_BODY_INCLUDE;
+ }
+
+ /**
+ * This method is called when the JSP page compiler hits the end tag. By
+ * now all the data should have been passed and parameters entered into
+ * the {@code PageContext.REQUEST_SCOPE} scope. This method includes
+ * the JSP page whose URL is stored in the {@code mPage} member
+ * variable.
+ *
+ * @exception JspException
+ */
+ public int doEndTag() throws JspException {
+ String msg;
+
+ try {
+ Iterator iterator;
+ String parameterName;
+
+ // -- Harald K 20020726
+ // Include the page, in place
+ //getDispatcher().include(getRequest(), getResponse());
+ addParameter(PAGE_CONTEXT, pageContext); // Will be cleared later
+ pageContext.include(page);
+
+ // Remove all the parameters that were added to the request scope
+ // for this insert tag.
+ iterator = parameterNames.iterator();
+
+ while (iterator.hasNext()) {
+ parameterName = iterator.next();
+
+ getRequest().removeAttribute(parameterName);
+ }
+
+ iterator = oldParameters.keySet().iterator();
+
+ // Restore the parameters we temporarily replaced (if any).
+ while (iterator.hasNext()) {
+ parameterName = iterator.next();
+
+ getRequest().setAttribute(parameterName, oldParameters.get(parameterName));
+ }
+
+ return super.doEndTag();
+ }
+ catch (IOException ioe) {
+ msg = "Caught an IOException while including " + page
+ + "\n" + ioe.toString();
+ log(msg, ioe);
+ throw new JspException(msg);
+ }
+ catch (ServletException se) {
+ msg = "Caught a ServletException while including " + page
+ + "\n" + se.toString();
+ log(msg, se);
+ throw new JspException(msg);
+ }
+ }
+
+ /**
+ * Free up the member variables that we've used throughout this tag.
+ */
+ protected void clearServiceState() {
+ oldParameters = null;
+ parameterNames = null;
+ }
+
+ /**
+ * Returns the request dispatcher for the JSP page whose URL is stored in
+ * the {@code mPage} member variable.
+ *
+ * @return The RequestDispatcher for the JSP page whose URL is stored in
+ * the {@code mPage} member variable.
+ */
+ /*
+ private RequestDispatcher getDispatcher() {
+ return getRequest().getRequestDispatcher(page);
+ }
+ */
+
+ /**
+ * Returns the HttpServletRequest object for the current user request.
+ *
+ * @return The HttpServletRequest object for the current user request.
+ */
+ private HttpServletRequest getRequest() {
+ return (HttpServletRequest) pageContext.getRequest();
+ }
+
+ /**
+ * Returns the HttpServletResponse object for the current user request.
+ *
+ * @return The HttpServletResponse object for the current user request.
+ */
+ private HttpServletResponse getResponse() {
+ return (HttpServletResponse) pageContext.getResponse();
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java
index 8382464b..71278ee6 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java
@@ -1,183 +1,183 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: NestingHandler.java,v $
- * Revision 1.4 2003/10/06 14:25:44 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.3 2003/08/04 15:26:30 WMHAKUR
- * Code clean-up.
- *
- * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
- * Fixed package error.
- *
- * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import com.twelvemonkeys.lang.StringUtil;
-
-import org.xml.sax.*;
-import org.xml.sax.helpers.DefaultHandler;
-
-/**
- * A SAX handler that returns an exception if the nesting of
- * {@code param}, {@code oparam}, {@code droplet} and
- * {@code valueof} is not correct.
- *
- * Based on the NestingHandler.java,
- * taken from More Servlets and JavaServer Pages
- * from Prentice Hall and Sun Microsystems Press,
- * http://www.moreservlets.com/.
- * © 2002 Marty Hall; may be freely used or adapted.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- */
-public class NestingHandler extends DefaultHandler {
- private String includeTagName = "include";
- private String paramTagName = "param";
- private String openParamTagName = "oparam";
-
- //private Stack mParents = new Stack();
-
- private boolean inIncludeTag = false;
-
- private String namespacePrefix = null;
- private String namespaceURI = null;
-
- private NestingValidator validator = null;
-
- public NestingHandler(String pNamespacePrefix, String pNameSpaceURI,
- NestingValidator pValidator) {
- namespacePrefix = pNamespacePrefix;
- namespaceURI = pNameSpaceURI;
-
- validator = pValidator;
- }
-
- public void startElement(String pNamespaceURI, String pLocalName,
- String pQualifiedName, Attributes pAttributes)
- throws SAXException {
- String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
- ? getNSPrefixFromURI(pNamespaceURI)
- : getNamespacePrefix(pQualifiedName);
-
- String localName = !StringUtil.isEmpty(pLocalName)
- ? pLocalName : getLocalName(pQualifiedName);
- /*
- if (namespacePrefix.equals(namespacePrefix)) {
- System.out.println("startElement:\nnamespaceURI=" + pNamespaceURI
- + " namespacePrefix=" + namespacePrefix
- + " localName=" + localName
- + " qName=" + pQualifiedName
- + " attributes=" + pAttributes);
- }
- */
- if (localName.equals(includeTagName)) {
- // include
- //System.out.println("<" + namespacePrefix + ":"
- // + includeTagName + ">");
- if (inIncludeTag) {
- validator.reportError("Cannot nest " + namespacePrefix + ":"
- + includeTagName);
- }
- inIncludeTag = true;
- }
- else if (localName.equals(paramTagName)) {
- // param
- //System.out.println("<" + namespacePrefix + ":"
- // + paramTagName + "/>");
- if (!inIncludeTag) {
- validator.reportError(this.namespacePrefix + ":"
- + paramTagName
- + " can only appear within "
- + this.namespacePrefix + ":"
- + includeTagName);
- }
- }
- else if (localName.equals(openParamTagName)) {
- // oparam
- //System.out.println("<" + namespacePrefix + ":"
- // + openParamTagName + ">");
- if (!inIncludeTag) {
- validator.reportError(this.namespacePrefix + ":"
- + openParamTagName
- + " can only appear within "
- + this.namespacePrefix + ":"
- + includeTagName);
- }
- inIncludeTag = false;
- }
- else {
- // Only jsp:text allowed inside include!
- if (inIncludeTag && !localName.equals("text")) {
- validator.reportError(namespacePrefix + ":" + localName
- + " can not appear within "
- + this.namespacePrefix + ":"
- + includeTagName);
- }
- }
- }
-
- public void endElement(String pNamespaceURI,
- String pLocalName,
- String pQualifiedName)
- throws SAXException {
- String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
- ? getNSPrefixFromURI(pNamespaceURI)
- : getNamespacePrefix(pQualifiedName);
-
- String localName = !StringUtil.isEmpty(pLocalName)
- ? pLocalName : getLocalName(pQualifiedName);
- /*
- if (namespacePrefix.equals(namespacePrefix)) {
- System.out.println("endElement:\nnamespaceURI=" + pNamespaceURI
- + " namespacePrefix=" + namespacePrefix
- + " localName=" + localName
- + " qName=" + pQualifiedName);
- }
- */
- if (namespacePrefix.equals(this.namespacePrefix)
- && localName.equals(includeTagName)) {
-
- //System.out.println("" + namespacePrefix + ":"
- // + includeTagName + ">");
-
- inIncludeTag = false;
- }
- else if (namespacePrefix.equals(this.namespacePrefix)
- && localName.equals(openParamTagName)) {
-
- //System.out.println("" + namespacePrefix + ":"
- // + openParamTagName + ">");
-
- inIncludeTag = true; // assuming no errors before this...
- }
- }
-
- /**
- * Stupid broken namespace-support "fix"..
- */
-
- private String getNSPrefixFromURI(String pNamespaceURI) {
- return (pNamespaceURI.equals(namespaceURI)
- ? namespacePrefix : "");
- }
-
- private String getNamespacePrefix(String pQualifiedName) {
- return pQualifiedName.substring(0, pQualifiedName.indexOf(':'));
- }
-
- private String getLocalName(String pQualifiedName) {
- return pQualifiedName.substring(pQualifiedName.indexOf(':') + 1);
- }
-}
-
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: NestingHandler.java,v $
+ * Revision 1.4 2003/10/06 14:25:44 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.3 2003/08/04 15:26:30 WMHAKUR
+ * Code clean-up.
+ *
+ * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
+ * Fixed package error.
+ *
+ * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import com.twelvemonkeys.lang.StringUtil;
+
+import org.xml.sax.*;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * A SAX handler that returns an exception if the nesting of
+ * {@code param}, {@code oparam}, {@code droplet} and
+ * {@code valueof} is not correct.
+ *
+ * Based on the NestingHandler.java,
+ * taken from More Servlets and JavaServer Pages
+ * from Prentice Hall and Sun Microsystems Press,
+ * http://www.moreservlets.com/.
+ * © 2002 Marty Hall; may be freely used or adapted.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ */
+public class NestingHandler extends DefaultHandler {
+ private String includeTagName = "include";
+ private String paramTagName = "param";
+ private String openParamTagName = "oparam";
+
+ //private Stack mParents = new Stack();
+
+ private boolean inIncludeTag = false;
+
+ private String namespacePrefix = null;
+ private String namespaceURI = null;
+
+ private NestingValidator validator = null;
+
+ public NestingHandler(String pNamespacePrefix, String pNameSpaceURI,
+ NestingValidator pValidator) {
+ namespacePrefix = pNamespacePrefix;
+ namespaceURI = pNameSpaceURI;
+
+ validator = pValidator;
+ }
+
+ public void startElement(String pNamespaceURI, String pLocalName,
+ String pQualifiedName, Attributes pAttributes)
+ throws SAXException {
+ String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
+ ? getNSPrefixFromURI(pNamespaceURI)
+ : getNamespacePrefix(pQualifiedName);
+
+ String localName = !StringUtil.isEmpty(pLocalName)
+ ? pLocalName : getLocalName(pQualifiedName);
+ /*
+ if (namespacePrefix.equals(namespacePrefix)) {
+ System.out.println("startElement:\nnamespaceURI=" + pNamespaceURI
+ + " namespacePrefix=" + namespacePrefix
+ + " localName=" + localName
+ + " qName=" + pQualifiedName
+ + " attributes=" + pAttributes);
+ }
+ */
+ if (localName.equals(includeTagName)) {
+ // include
+ //System.out.println("<" + namespacePrefix + ":"
+ // + includeTagName + ">");
+ if (inIncludeTag) {
+ validator.reportError("Cannot nest " + namespacePrefix + ":"
+ + includeTagName);
+ }
+ inIncludeTag = true;
+ }
+ else if (localName.equals(paramTagName)) {
+ // param
+ //System.out.println("<" + namespacePrefix + ":"
+ // + paramTagName + "/>");
+ if (!inIncludeTag) {
+ validator.reportError(this.namespacePrefix + ":"
+ + paramTagName
+ + " can only appear within "
+ + this.namespacePrefix + ":"
+ + includeTagName);
+ }
+ }
+ else if (localName.equals(openParamTagName)) {
+ // oparam
+ //System.out.println("<" + namespacePrefix + ":"
+ // + openParamTagName + ">");
+ if (!inIncludeTag) {
+ validator.reportError(this.namespacePrefix + ":"
+ + openParamTagName
+ + " can only appear within "
+ + this.namespacePrefix + ":"
+ + includeTagName);
+ }
+ inIncludeTag = false;
+ }
+ else {
+ // Only jsp:text allowed inside include!
+ if (inIncludeTag && !localName.equals("text")) {
+ validator.reportError(namespacePrefix + ":" + localName
+ + " can not appear within "
+ + this.namespacePrefix + ":"
+ + includeTagName);
+ }
+ }
+ }
+
+ public void endElement(String pNamespaceURI,
+ String pLocalName,
+ String pQualifiedName)
+ throws SAXException {
+ String namespacePrefix = !StringUtil.isEmpty(pNamespaceURI)
+ ? getNSPrefixFromURI(pNamespaceURI)
+ : getNamespacePrefix(pQualifiedName);
+
+ String localName = !StringUtil.isEmpty(pLocalName)
+ ? pLocalName : getLocalName(pQualifiedName);
+ /*
+ if (namespacePrefix.equals(namespacePrefix)) {
+ System.out.println("endElement:\nnamespaceURI=" + pNamespaceURI
+ + " namespacePrefix=" + namespacePrefix
+ + " localName=" + localName
+ + " qName=" + pQualifiedName);
+ }
+ */
+ if (namespacePrefix.equals(this.namespacePrefix)
+ && localName.equals(includeTagName)) {
+
+ //System.out.println("" + namespacePrefix + ":"
+ // + includeTagName + ">");
+
+ inIncludeTag = false;
+ }
+ else if (namespacePrefix.equals(this.namespacePrefix)
+ && localName.equals(openParamTagName)) {
+
+ //System.out.println("" + namespacePrefix + ":"
+ // + openParamTagName + ">");
+
+ inIncludeTag = true; // assuming no errors before this...
+ }
+ }
+
+ /**
+ * Stupid broken namespace-support "fix"..
+ */
+
+ private String getNSPrefixFromURI(String pNamespaceURI) {
+ return (pNamespaceURI.equals(namespaceURI)
+ ? namespacePrefix : "");
+ }
+
+ private String getNamespacePrefix(String pQualifiedName) {
+ return pQualifiedName.substring(0, pQualifiedName.indexOf(':'));
+ }
+
+ private String getLocalName(String pQualifiedName) {
+ return pQualifiedName.substring(pQualifiedName.indexOf(':') + 1);
+ }
+}
+
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java
index 6c2b2708..15e5fe7f 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java
@@ -1,102 +1,102 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: NestingValidator.java,v $
- * Revision 1.4 2003/08/04 15:26:40 WMHAKUR
- * Code clean-up.
- *
- * Revision 1.3 2002/11/18 14:12:43 WMHAKUR
- * *** empty log message ***
- *
- * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
- * Fixed package error.
- *
- * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import org.xml.sax.InputSource;
-import org.xml.sax.helpers.DefaultHandler;
-
-import javax.servlet.jsp.tagext.PageData;
-import javax.servlet.jsp.tagext.TagLibraryValidator;
-import javax.servlet.jsp.tagext.ValidationMessage;
-import javax.xml.parsers.SAXParser;
-import javax.xml.parsers.SAXParserFactory;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A validator that verifies that tags follow
- * proper nesting order.
- *
- * Based on NestingValidator.java,
- * taken from More Servlets and JavaServer Pages
- * from Prentice Hall and Sun Microsystems Press,
- * http://www.moreservlets.com/.
- * © 2002 Marty Hall; may be freely used or adapted.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- *
- */
-public class NestingValidator extends TagLibraryValidator {
-
- private List errors = new ArrayList();
-
- /**
- *
- */
- public ValidationMessage[] validate(String pPrefix, String pURI, PageData pPage) {
-
- //System.out.println("Validating " + pPrefix + " (" + pURI + ") for "
- // + pPage + ".");
-
- // Pass the parser factory in on the command line with
- // -D to override the use of the Apache parser.
-
- DefaultHandler handler = new NestingHandler(pPrefix, pURI, this);
- SAXParserFactory factory = SAXParserFactory.newInstance();
-
- try {
- // FileUtil.copy(pPage.getInputStream(), System.out);
-
- SAXParser parser = factory.newSAXParser();
- InputSource source =
- new InputSource(pPage.getInputStream());
-
- // Parse, handler will use callback to report errors
- parser.parse(source, handler);
-
-
- }
- catch (Exception e) {
- String errorMessage = e.getMessage();
-
- reportError(errorMessage);
- }
-
- // Return any errors and exceptions, empty array means okay
- return errors.toArray(new ValidationMessage[errors.size()]);
- }
-
- /**
- * Callback method for the handler to report errors
- */
- public void reportError(String pMessage) {
- // The first argument to the ValidationMessage
- // constructor can be a tag ID. Since tag IDs
- // are not universally supported, use null for
- // portability. The important part is the second
- // argument: the error message.
- errors.add(new ValidationMessage(null, pMessage));
- }
-}
-
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: NestingValidator.java,v $
+ * Revision 1.4 2003/08/04 15:26:40 WMHAKUR
+ * Code clean-up.
+ *
+ * Revision 1.3 2002/11/18 14:12:43 WMHAKUR
+ * *** empty log message ***
+ *
+ * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
+ * Fixed package error.
+ *
+ * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.servlet.jsp.tagext.PageData;
+import javax.servlet.jsp.tagext.TagLibraryValidator;
+import javax.servlet.jsp.tagext.ValidationMessage;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A validator that verifies that tags follow
+ * proper nesting order.
+ *
+ * Based on NestingValidator.java,
+ * taken from More Servlets and JavaServer Pages
+ * from Prentice Hall and Sun Microsystems Press,
+ * http://www.moreservlets.com/.
+ * © 2002 Marty Hall; may be freely used or adapted.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ *
+ */
+public class NestingValidator extends TagLibraryValidator {
+
+ private List errors = new ArrayList();
+
+ /**
+ *
+ */
+ public ValidationMessage[] validate(String pPrefix, String pURI, PageData pPage) {
+
+ //System.out.println("Validating " + pPrefix + " (" + pURI + ") for "
+ // + pPage + ".");
+
+ // Pass the parser factory in on the command line with
+ // -D to override the use of the Apache parser.
+
+ DefaultHandler handler = new NestingHandler(pPrefix, pURI, this);
+ SAXParserFactory factory = SAXParserFactory.newInstance();
+
+ try {
+ // FileUtil.copy(pPage.getInputStream(), System.out);
+
+ SAXParser parser = factory.newSAXParser();
+ InputSource source =
+ new InputSource(pPage.getInputStream());
+
+ // Parse, handler will use callback to report errors
+ parser.parse(source, handler);
+
+
+ }
+ catch (Exception e) {
+ String errorMessage = e.getMessage();
+
+ reportError(errorMessage);
+ }
+
+ // Return any errors and exceptions, empty array means okay
+ return errors.toArray(new ValidationMessage[errors.size()]);
+ }
+
+ /**
+ * Callback method for the handler to report errors
+ */
+ public void reportError(String pMessage) {
+ // The first argument to the ValidationMessage
+ // constructor can be a tag ID. Since tag IDs
+ // are not universally supported, use null for
+ // portability. The important part is the second
+ // argument: the error message.
+ errors.add(new ValidationMessage(null, pMessage));
+ }
+}
+
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java
index 08a42ae5..62bf52d2 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java
@@ -1,220 +1,220 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: OparamTag.java,v $
- * Revision 1.4 2003/10/06 14:25:53 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.3 2002/11/18 14:12:43 WMHAKUR
- * *** empty log message ***
- *
- * Revision 1.2 2002/11/07 12:20:14 WMHAKUR
- * Updated to reflect changes in com.twelvemonkeys.util.*Util
- *
- * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.jsp.droplet.Oparam;
-import com.twelvemonkeys.servlet.jsp.taglib.BodyReaderTag;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.JspException;
-import java.io.File;
-import java.io.IOException;
-
-/**
- * Open parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: jsp/droplet/taglib/OparamTag.java#1 $
- */
-public class OparamTag extends BodyReaderTag {
-
- protected final static String COUNTER = "com.twelvemonkeys.servlet.jsp.taglib.OparamTag.counter";
-
- private File subpage = null;
-
- /**
- * This is the name of the parameter to be inserted into the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- private String parameterName = null;
-
- private String language = null;
-
- private String prefix = null;
-
- /**
- * This method allows the JSP page to set the name for the parameter by
- * using the {@code name} tag attribute.
- *
- * @param pName The name for the parameter to insert into the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- public void setName(String pName) {
- parameterName = pName;
- }
-
- public void setLanguage(String pLanguage) {
- //System.out.println("setLanguage:"+pLanguage);
- language = pLanguage;
- }
-
- public void setPrefix(String pPrefix) {
- //System.out.println("setPrefix:"+pPrefix);
- prefix = pPrefix;
- }
-
- /**
- * Ensure that the tag implemented by this class is enclosed by an {@code IncludeTag}.
- * If the tag is not enclosed by an {@code IncludeTag} then a {@code JspException} is thrown.
- *
- * @return If this tag is enclosed within an {@code IncludeTag}, then the default return value
- * from this method is the {@code TagSupport.EVAL_BODY_TAG} value.
- *
- * @throws JspException
- */
- public int doStartTag() throws JspException {
- //checkEnclosedInIncludeTag(); // Moved to TagLibValidator
-
- // Get request
- HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
-
- // Get filename
- subpage = createFileNameFromRequest(request);
-
- // Get include tag, and add to parameters
- IncludeTag includeTag = (IncludeTag) getParent();
- includeTag.addParameter(parameterName, new Oparam(subpage.getName()));
-
- // if ! subpage.exist || jsp newer than subpage, write new
- File jsp = new File(pageContext.getServletContext().getRealPath(request.getServletPath()));
-
- if (!subpage.exists() || jsp.lastModified() > subpage.lastModified()) {
- return EVAL_BODY_BUFFERED;
- }
-
- // No need to evaluate body again!
- return SKIP_BODY;
- }
-
- /**
- * This is the method responsible for actually testing that the tag
- * implemented by this class is enclosed within an {@code IncludeTag}.
- *
- * @exception JspException
- */
- /*
- protected void checkEnclosedInIncludeTag() throws JspException {
- Tag parentTag = getParent();
-
- if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
- return;
- }
-
- String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
- "is not enclosed within an IncludeTag.";
- log(msg);
- throw new JspException(msg);
- }
- */
-
- /**
- * This method cleans up the member variables for this tag in preparation
- * for being used again. This method is called when the tag finishes it's
- * current call with in the page but could be called upon again within this
- * same page. This method is also called in the release stage of the tag
- * life cycle just in case a JspException was thrown during the tag
- * execution.
- */
- protected void clearServiceState() {
- parameterName = null;
- }
-
- /**
- * This is the method responsible for taking the result of the JSP code
- * that forms the body of this tag and inserts it as a parameter into the
- * request scope session. If any problems occur while loading the body
- * into the session scope then a {@code JspException} will be thrown.
- *
- * @param pContent The body of the tag as a String.
- * @throws JspException
- */
- protected void processBody(String pContent) throws JspException {
- // Okay, we have the content, we need to write it to disk somewhere
- String content = pContent;
-
- if (!StringUtil.isEmpty(language)) {
- content = "<%@page language=\"" + language + "\" %>" + content;
- }
-
- if (!StringUtil.isEmpty(prefix)) {
- content = "<%@taglib uri=\"/twelvemonkeys-common\" prefix=\"" + prefix + "\" %>" + content;
- }
-
- // Write the content of the oparam to disk
- try {
- log("Processing subpage " + subpage.getPath());
- FileUtil.write(subpage, content.getBytes());
- }
- catch (IOException ioe) {
- throw new JspException(ioe);
- }
- }
-
- /** Creates a unique filename for each (nested) oparam */
- private File createFileNameFromRequest(HttpServletRequest pRequest) {
- //System.out.println("ServletPath" + pRequest.getServletPath());
- String path = pRequest.getServletPath();
-
- // Find last '/'
- int splitIndex = path.lastIndexOf("/");
-
- // Split -> path + name
- String name = path.substring(splitIndex + 1);
- path = path.substring(0, splitIndex);
-
- // Replace special chars in name with '_'
- name = name.replace('.', '_');
- String param = parameterName.replace('.', '_');
- param = param.replace('/', '_');
- param = param.replace('\\', '_');
- param = param.replace(':', '_');
-
- // tempfile = realPath(path) + name + "_oparam_" + number + ".jsp"
- int count = getOparamCountFromRequest(pRequest);
-
- // Hmm.. Would be great, but seems like I can't serve pages from within the temp dir
- //File temp = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
- //return new File(new File(temp, path), name + "_oparam_" + count + "_" + param + ".jsp");
-
- return new File(new File(pageContext.getServletContext().getRealPath(path)), name + "_oparam_" + count + "_" + param + ".jsp");
- }
-
- /** Gets the current oparam count for this request */
- private int getOparamCountFromRequest(HttpServletRequest pRequest) {
- // Use request.attribute for incrementing oparam counter
- Integer count = (Integer) pRequest.getAttribute(COUNTER);
- if (count == null) {
- count = new Integer(0);
- }
- else {
- count = new Integer(count.intValue() + 1);
- }
-
- // ... and set it back
- pRequest.setAttribute(COUNTER, count);
-
- return count.intValue();
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: OparamTag.java,v $
+ * Revision 1.4 2003/10/06 14:25:53 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.3 2002/11/18 14:12:43 WMHAKUR
+ * *** empty log message ***
+ *
+ * Revision 1.2 2002/11/07 12:20:14 WMHAKUR
+ * Updated to reflect changes in com.twelvemonkeys.util.*Util
+ *
+ * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.jsp.droplet.Oparam;
+import com.twelvemonkeys.servlet.jsp.taglib.BodyReaderTag;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspException;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Open parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: jsp/droplet/taglib/OparamTag.java#1 $
+ */
+public class OparamTag extends BodyReaderTag {
+
+ protected final static String COUNTER = "com.twelvemonkeys.servlet.jsp.taglib.OparamTag.counter";
+
+ private File subpage = null;
+
+ /**
+ * This is the name of the parameter to be inserted into the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ private String parameterName = null;
+
+ private String language = null;
+
+ private String prefix = null;
+
+ /**
+ * This method allows the JSP page to set the name for the parameter by
+ * using the {@code name} tag attribute.
+ *
+ * @param pName The name for the parameter to insert into the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ public void setName(String pName) {
+ parameterName = pName;
+ }
+
+ public void setLanguage(String pLanguage) {
+ //System.out.println("setLanguage:"+pLanguage);
+ language = pLanguage;
+ }
+
+ public void setPrefix(String pPrefix) {
+ //System.out.println("setPrefix:"+pPrefix);
+ prefix = pPrefix;
+ }
+
+ /**
+ * Ensure that the tag implemented by this class is enclosed by an {@code IncludeTag}.
+ * If the tag is not enclosed by an {@code IncludeTag} then a {@code JspException} is thrown.
+ *
+ * @return If this tag is enclosed within an {@code IncludeTag}, then the default return value
+ * from this method is the {@code TagSupport.EVAL_BODY_TAG} value.
+ *
+ * @throws JspException
+ */
+ public int doStartTag() throws JspException {
+ //checkEnclosedInIncludeTag(); // Moved to TagLibValidator
+
+ // Get request
+ HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
+
+ // Get filename
+ subpage = createFileNameFromRequest(request);
+
+ // Get include tag, and add to parameters
+ IncludeTag includeTag = (IncludeTag) getParent();
+ includeTag.addParameter(parameterName, new Oparam(subpage.getName()));
+
+ // if ! subpage.exist || jsp newer than subpage, write new
+ File jsp = new File(pageContext.getServletContext().getRealPath(request.getServletPath()));
+
+ if (!subpage.exists() || jsp.lastModified() > subpage.lastModified()) {
+ return EVAL_BODY_BUFFERED;
+ }
+
+ // No need to evaluate body again!
+ return SKIP_BODY;
+ }
+
+ /**
+ * This is the method responsible for actually testing that the tag
+ * implemented by this class is enclosed within an {@code IncludeTag}.
+ *
+ * @exception JspException
+ */
+ /*
+ protected void checkEnclosedInIncludeTag() throws JspException {
+ Tag parentTag = getParent();
+
+ if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
+ return;
+ }
+
+ String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
+ "is not enclosed within an IncludeTag.";
+ log(msg);
+ throw new JspException(msg);
+ }
+ */
+
+ /**
+ * This method cleans up the member variables for this tag in preparation
+ * for being used again. This method is called when the tag finishes it's
+ * current call with in the page but could be called upon again within this
+ * same page. This method is also called in the release stage of the tag
+ * life cycle just in case a JspException was thrown during the tag
+ * execution.
+ */
+ protected void clearServiceState() {
+ parameterName = null;
+ }
+
+ /**
+ * This is the method responsible for taking the result of the JSP code
+ * that forms the body of this tag and inserts it as a parameter into the
+ * request scope session. If any problems occur while loading the body
+ * into the session scope then a {@code JspException} will be thrown.
+ *
+ * @param pContent The body of the tag as a String.
+ * @throws JspException
+ */
+ protected void processBody(String pContent) throws JspException {
+ // Okay, we have the content, we need to write it to disk somewhere
+ String content = pContent;
+
+ if (!StringUtil.isEmpty(language)) {
+ content = "<%@page language=\"" + language + "\" %>" + content;
+ }
+
+ if (!StringUtil.isEmpty(prefix)) {
+ content = "<%@taglib uri=\"/twelvemonkeys-common\" prefix=\"" + prefix + "\" %>" + content;
+ }
+
+ // Write the content of the oparam to disk
+ try {
+ log("Processing subpage " + subpage.getPath());
+ FileUtil.write(subpage, content.getBytes());
+ }
+ catch (IOException ioe) {
+ throw new JspException(ioe);
+ }
+ }
+
+ /** Creates a unique filename for each (nested) oparam */
+ private File createFileNameFromRequest(HttpServletRequest pRequest) {
+ //System.out.println("ServletPath" + pRequest.getServletPath());
+ String path = pRequest.getServletPath();
+
+ // Find last '/'
+ int splitIndex = path.lastIndexOf("/");
+
+ // Split -> path + name
+ String name = path.substring(splitIndex + 1);
+ path = path.substring(0, splitIndex);
+
+ // Replace special chars in name with '_'
+ name = name.replace('.', '_');
+ String param = parameterName.replace('.', '_');
+ param = param.replace('/', '_');
+ param = param.replace('\\', '_');
+ param = param.replace(':', '_');
+
+ // tempfile = realPath(path) + name + "_oparam_" + number + ".jsp"
+ int count = getOparamCountFromRequest(pRequest);
+
+ // Hmm.. Would be great, but seems like I can't serve pages from within the temp dir
+ //File temp = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
+ //return new File(new File(temp, path), name + "_oparam_" + count + "_" + param + ".jsp");
+
+ return new File(new File(pageContext.getServletContext().getRealPath(path)), name + "_oparam_" + count + "_" + param + ".jsp");
+ }
+
+ /** Gets the current oparam count for this request */
+ private int getOparamCountFromRequest(HttpServletRequest pRequest) {
+ // Use request.attribute for incrementing oparam counter
+ Integer count = (Integer) pRequest.getAttribute(COUNTER);
+ if (count == null) {
+ count = new Integer(0);
+ }
+ else {
+ count = new Integer(count.intValue() + 1);
+ }
+
+ // ... and set it back
+ pRequest.setAttribute(COUNTER, count);
+
+ return count.intValue();
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java
index a08e73e5..fbe11574 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java
@@ -1,129 +1,129 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ParamTag.java,v $
- * Revision 1.2 2003/10/06 14:26:00 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import com.twelvemonkeys.servlet.jsp.droplet.Param;
-import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
-
-import javax.servlet.jsp.JspException;
-
-/**
- * Parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- *
- */
-public class ParamTag extends ExTagSupport {
-
- /**
- * This is the name of the parameter to be inserted into the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- private String parameterName;
-
- /**
- * This is the value for the parameter to be inserted into the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- private Object parameterValue;
-
- /**
- * This method allows the JSP page to set the name for the parameter by
- * using the {@code name} tag attribute.
- *
- * @param pName The name for the parameter to insert into the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- public void setName(String pName) {
- parameterName = pName;
- }
-
- /**
- * This method allows the JSP page to set the value for hte parameter by
- * using the {@code value} tag attribute.
- *
- * @param pValue The value for the parameter to insert into the
- * PageContext.REQUEST_SCOPE scope.
- */
- public void setValue(String pValue) {
- parameterValue = new Param(pValue);
- }
-
- /**
- * Ensure that the tag implemented by this class is enclosed by an {@code
- * IncludeTag}. If the tag is not enclosed by an
- * {@code IncludeTag} then a {@code JspException} is thrown.
- *
- * @return If this tag is enclosed within an {@code IncludeTag}, then
- * the default return value from this method is the {@code
- * TagSupport.SKIP_BODY} value.
- * @exception JspException
- */
- public int doStartTag() throws JspException {
- //checkEnclosedInIncludeTag();
-
- addParameter();
-
- return SKIP_BODY;
- }
-
- /**
- * This is the method responsible for actually testing that the tag
- * implemented by this class is enclosed within an {@code IncludeTag}.
- *
- * @exception JspException
- */
- /*
- protected void checkEnclosedInIncludeTag() throws JspException {
- Tag parentTag = getParent();
-
- if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
- return;
- }
-
- String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
- "is not enclosed within an IncludeTag.";
- log(msg);
- throw new JspException(msg);
- }
- */
-
- /**
- * This method adds the parameter whose name and value were passed to this
- * object via the tag attributes to the parent {@code Include} tag.
- */
- private void addParameter() {
- IncludeTag includeTag = (IncludeTag) getParent();
-
- includeTag.addParameter(parameterName, parameterValue);
- }
-
- /**
- * This method cleans up the member variables for this tag in preparation
- * for being used again. This method is called when the tag finishes it's
- * current call with in the page but could be called upon again within this
- * same page. This method is also called in the release stage of the tag
- * life cycle just in case a JspException was thrown during the tag
- * execution.
- */
- protected void clearServiceState() {
- parameterName = null;
- parameterValue = null;
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ParamTag.java,v $
+ * Revision 1.2 2003/10/06 14:26:00 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/10/18 14:03:09 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import com.twelvemonkeys.servlet.jsp.droplet.Param;
+import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
+
+import javax.servlet.jsp.JspException;
+
+/**
+ * Parameter tag that emulates ATG Dynamo JHTML behaviour for JSP.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ *
+ */
+public class ParamTag extends ExTagSupport {
+
+ /**
+ * This is the name of the parameter to be inserted into the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ private String parameterName;
+
+ /**
+ * This is the value for the parameter to be inserted into the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ private Object parameterValue;
+
+ /**
+ * This method allows the JSP page to set the name for the parameter by
+ * using the {@code name} tag attribute.
+ *
+ * @param pName The name for the parameter to insert into the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ public void setName(String pName) {
+ parameterName = pName;
+ }
+
+ /**
+ * This method allows the JSP page to set the value for hte parameter by
+ * using the {@code value} tag attribute.
+ *
+ * @param pValue The value for the parameter to insert into the
+ * PageContext.REQUEST_SCOPE scope.
+ */
+ public void setValue(String pValue) {
+ parameterValue = new Param(pValue);
+ }
+
+ /**
+ * Ensure that the tag implemented by this class is enclosed by an {@code
+ * IncludeTag}. If the tag is not enclosed by an
+ * {@code IncludeTag} then a {@code JspException} is thrown.
+ *
+ * @return If this tag is enclosed within an {@code IncludeTag}, then
+ * the default return value from this method is the {@code
+ * TagSupport.SKIP_BODY} value.
+ * @exception JspException
+ */
+ public int doStartTag() throws JspException {
+ //checkEnclosedInIncludeTag();
+
+ addParameter();
+
+ return SKIP_BODY;
+ }
+
+ /**
+ * This is the method responsible for actually testing that the tag
+ * implemented by this class is enclosed within an {@code IncludeTag}.
+ *
+ * @exception JspException
+ */
+ /*
+ protected void checkEnclosedInIncludeTag() throws JspException {
+ Tag parentTag = getParent();
+
+ if ((parentTag != null) && (parentTag instanceof IncludeTag)) {
+ return;
+ }
+
+ String msg = "A class that extends EnclosedIncludeBodyReaderTag " +
+ "is not enclosed within an IncludeTag.";
+ log(msg);
+ throw new JspException(msg);
+ }
+ */
+
+ /**
+ * This method adds the parameter whose name and value were passed to this
+ * object via the tag attributes to the parent {@code Include} tag.
+ */
+ private void addParameter() {
+ IncludeTag includeTag = (IncludeTag) getParent();
+
+ includeTag.addParameter(parameterName, parameterValue);
+ }
+
+ /**
+ * This method cleans up the member variables for this tag in preparation
+ * for being used again. This method is called when the tag finishes it's
+ * current call with in the page but could be called upon again within this
+ * same page. This method is also called in the release stage of the tag
+ * life cycle just in case a JspException was thrown during the tag
+ * execution.
+ */
+ protected void clearServiceState() {
+ parameterName = null;
+ parameterValue = null;
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java
index d7a658fe..692001bc 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java
@@ -1,47 +1,47 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ValueOfTEI.java,v $
- * Revision 1.3 2003/10/06 14:26:07 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
- * Fixed package error.
- *
- * Revision 1.1 2002/10/18 14:03:52 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import javax.servlet.jsp.tagext.TagData;
-import javax.servlet.jsp.tagext.TagExtraInfo;
-
-/**
- * TagExtraInfo for ValueOf.
- * @todo More meaningful response to the user.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- *
- */
-
-public class ValueOfTEI extends TagExtraInfo {
-
- public boolean isValid(TagData pTagData) {
- Object nameAttr = pTagData.getAttribute("name");
- Object paramAttr = pTagData.getAttribute("param");
-
- if ((nameAttr != null && paramAttr == null) || (nameAttr == null && paramAttr != null)) {
- return true; // Exactly one of name or param set
- }
-
- // Either both or none,
- return false;
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ValueOfTEI.java,v $
+ * Revision 1.3 2003/10/06 14:26:07 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.2 2002/10/18 14:28:07 WMHAKUR
+ * Fixed package error.
+ *
+ * Revision 1.1 2002/10/18 14:03:52 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import javax.servlet.jsp.tagext.TagData;
+import javax.servlet.jsp.tagext.TagExtraInfo;
+
+/**
+ * TagExtraInfo for ValueOf.
+ * @todo More meaningful response to the user.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ *
+ */
+
+public class ValueOfTEI extends TagExtraInfo {
+
+ public boolean isValid(TagData pTagData) {
+ Object nameAttr = pTagData.getAttribute("name");
+ Object paramAttr = pTagData.getAttribute("param");
+
+ if ((nameAttr != null && paramAttr == null) || (nameAttr == null && paramAttr != null)) {
+ return true; // Exactly one of name or param set
+ }
+
+ // Either both or none,
+ return false;
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java
index 7989b082..a7e2a866 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java
@@ -1,148 +1,148 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ValueOfTag.java,v $
- * Revision 1.2 2003/10/06 14:26:14 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/10/18 14:03:52 WMHAKUR
- * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.droplet.taglib;
-
-import com.twelvemonkeys.servlet.jsp.droplet.JspFragment;
-import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
-
-import javax.servlet.ServletException;
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.PageContext;
-import java.io.IOException;
-
-/**
- * ValueOf tag that emulates ATG Dynamo JHTML behaviour for JSP.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Revision: #1 $, ($Date: 2008/05/05 $)
- */
-public class ValueOfTag extends ExTagSupport {
-
- /**
- * This is the name of the parameter whose value is to be inserted into
- * the current JSP page. This value will be set via the {@code name}
- * attribute.
- */
- private String parameterName;
-
- /**
- * This is the value of the parameter read from the {@code
- * PageContext.REQUEST_SCOPE} scope. If the parameter doesn't exist,
- * then this will be null.
- */
- private Object parameterValue;
-
- /**
- * This method is called as part of the initialisation phase of the tag
- * life cycle. It sets the parameter name to be read from the {@code
- * PageContext.REQUEST_SCOPE} scope.
- *
- * @param pName The name of the parameter to be read from the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- public void setName(String pName) {
- parameterName = pName;
- }
-
- /**
- * This method is called as part of the initialisation phase of the tag
- * life cycle. It sets the parameter name to be read from the {@code
- * PageContext.REQUEST_SCOPE} scope. This is just a synonym for
- * setName, to be more like ATG Dynamo.
- *
- * @param pName The name of the parameter to be read from the {@code
- * PageContext.REQUEST_SCOPE} scope.
- */
- public void setParam(String pName) {
- parameterName = pName;
- }
-
- /**
- * This method looks in the session scope for the session-scoped attribute
- * whose name matches the {@code name} tag attribute for this tag.
- * If it finds it, then it replaces this tag with the value for the
- * session-scoped attribute. If it fails to find the session-scoped
- * attribute, it displays the body for this tag.
- *
- * @return If the session-scoped attribute is found, then this method will
- * return {@code TagSupport.SKIP_BODY}, otherwise it will return
- * {@code TagSupport.EVAL_BODY_INCLUDE}.
- * @exception JspException
- *
- */
- public int doStartTag() throws JspException {
- try {
- if (parameterExists()) {
- if (parameterValue instanceof JspFragment) {
- // OPARAM or PARAM
- ((JspFragment) parameterValue).service(pageContext);
- /*
- log("Service subpage " + pageContext.getServletContext().getRealPath(((Oparam) parameterValue).getName()));
-
- pageContext.include(((Oparam) parameterValue).getName());
- */
- }
- else {
- // Normal JSP parameter value
- JspWriter writer = pageContext.getOut();
- writer.print(parameterValue);
- }
-
- return SKIP_BODY;
- }
- else {
- return EVAL_BODY_INCLUDE;
- }
- }
- catch (ServletException se) {
- log(se.getMessage(), se);
- throw new JspException(se);
- }
- catch (IOException ioe) {
- String msg = "Caught an IOException in ValueOfTag.doStartTag()\n"
- + ioe.toString();
- log(msg, ioe);
- throw new JspException(msg);
- }
- }
-
- /**
- * This method is used to determine whether the parameter whose name is
- * stored in {@code mParameterName} exists within the {@code
- * PageContext.REQUEST_SCOPE} scope. If the parameter does exist,
- * then this method will return {@code true}, otherwise it returns
- * {@code false}. This method has the side affect of loading the
- * parameter value into {@code mParameterValue} if the parameter
- * does exist.
- *
- * @return {@code true} if the parameter whose name is in {@code
- * mParameterName} exists in the {@code PageContext.REQUEST_SCOPE
- * } scope, {@code false} otherwise.
- */
- private boolean parameterExists() {
- parameterValue = pageContext.getAttribute(parameterName, PageContext.REQUEST_SCOPE);
-
- // -- Harald K 20020726
- if (parameterValue == null) {
- parameterValue = pageContext.getRequest().getParameter(parameterName);
- }
-
- return (parameterValue != null);
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ValueOfTag.java,v $
+ * Revision 1.2 2003/10/06 14:26:14 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/10/18 14:03:52 WMHAKUR
+ * Moved to com.twelvemonkeys.servlet.jsp.droplet.taglib
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.droplet.taglib;
+
+import com.twelvemonkeys.servlet.jsp.droplet.JspFragment;
+import com.twelvemonkeys.servlet.jsp.taglib.ExTagSupport;
+
+import javax.servlet.ServletException;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import java.io.IOException;
+
+/**
+ * ValueOf tag that emulates ATG Dynamo JHTML behaviour for JSP.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Revision: #1 $, ($Date: 2008/05/05 $)
+ */
+public class ValueOfTag extends ExTagSupport {
+
+ /**
+ * This is the name of the parameter whose value is to be inserted into
+ * the current JSP page. This value will be set via the {@code name}
+ * attribute.
+ */
+ private String parameterName;
+
+ /**
+ * This is the value of the parameter read from the {@code
+ * PageContext.REQUEST_SCOPE} scope. If the parameter doesn't exist,
+ * then this will be null.
+ */
+ private Object parameterValue;
+
+ /**
+ * This method is called as part of the initialisation phase of the tag
+ * life cycle. It sets the parameter name to be read from the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ *
+ * @param pName The name of the parameter to be read from the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ public void setName(String pName) {
+ parameterName = pName;
+ }
+
+ /**
+ * This method is called as part of the initialisation phase of the tag
+ * life cycle. It sets the parameter name to be read from the {@code
+ * PageContext.REQUEST_SCOPE} scope. This is just a synonym for
+ * setName, to be more like ATG Dynamo.
+ *
+ * @param pName The name of the parameter to be read from the {@code
+ * PageContext.REQUEST_SCOPE} scope.
+ */
+ public void setParam(String pName) {
+ parameterName = pName;
+ }
+
+ /**
+ * This method looks in the session scope for the session-scoped attribute
+ * whose name matches the {@code name} tag attribute for this tag.
+ * If it finds it, then it replaces this tag with the value for the
+ * session-scoped attribute. If it fails to find the session-scoped
+ * attribute, it displays the body for this tag.
+ *
+ * @return If the session-scoped attribute is found, then this method will
+ * return {@code TagSupport.SKIP_BODY}, otherwise it will return
+ * {@code TagSupport.EVAL_BODY_INCLUDE}.
+ * @exception JspException
+ *
+ */
+ public int doStartTag() throws JspException {
+ try {
+ if (parameterExists()) {
+ if (parameterValue instanceof JspFragment) {
+ // OPARAM or PARAM
+ ((JspFragment) parameterValue).service(pageContext);
+ /*
+ log("Service subpage " + pageContext.getServletContext().getRealPath(((Oparam) parameterValue).getName()));
+
+ pageContext.include(((Oparam) parameterValue).getName());
+ */
+ }
+ else {
+ // Normal JSP parameter value
+ JspWriter writer = pageContext.getOut();
+ writer.print(parameterValue);
+ }
+
+ return SKIP_BODY;
+ }
+ else {
+ return EVAL_BODY_INCLUDE;
+ }
+ }
+ catch (ServletException se) {
+ log(se.getMessage(), se);
+ throw new JspException(se);
+ }
+ catch (IOException ioe) {
+ String msg = "Caught an IOException in ValueOfTag.doStartTag()\n"
+ + ioe.toString();
+ log(msg, ioe);
+ throw new JspException(msg);
+ }
+ }
+
+ /**
+ * This method is used to determine whether the parameter whose name is
+ * stored in {@code mParameterName} exists within the {@code
+ * PageContext.REQUEST_SCOPE} scope. If the parameter does exist,
+ * then this method will return {@code true}, otherwise it returns
+ * {@code false}. This method has the side affect of loading the
+ * parameter value into {@code mParameterValue} if the parameter
+ * does exist.
+ *
+ * @return {@code true} if the parameter whose name is in {@code
+ * mParameterName} exists in the {@code PageContext.REQUEST_SCOPE
+ * } scope, {@code false} otherwise.
+ */
+ private boolean parameterExists() {
+ parameterValue = pageContext.getAttribute(parameterName, PageContext.REQUEST_SCOPE);
+
+ // -- Harald K 20020726
+ if (parameterValue == null) {
+ parameterValue = pageContext.getRequest().getParameter(parameterName);
+ }
+
+ return (parameterValue != null);
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java
index 1d260c8d..60ea0a91 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java
@@ -1,39 +1,39 @@
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import javax.servlet.jsp.JspException;
-
-/**
- *
- *
- * @author Thomas Purcell (CSC Australia)
- *
- * @version 1.0
- */
-public abstract class BodyReaderTag extends ExBodyTagSupport {
- /**
- * This is the method called by the JSP engine when the body for a tag
- * has been parsed and is ready for inclusion in this current tag. This
- * method takes the content as a string and passes it to the {@code
- * processBody} method.
- *
- * @return This method returns the {@code BodyTagSupport.SKIP_BODY}
- * constant. This means that the body of the tag will only be
- * processed the one time.
- * @exception JspException
- */
- public int doAfterBody() throws JspException {
- processBody(bodyContent.getString());
- return SKIP_BODY;
- }
-
- /**
- * This is the method that child classes must implement. It takes the
- * body of the tag converted to a String as it's parameter. The body of
- * the tag will have been interpreted to a String by the JSP engine before
- * this method is called.
- *
- * @param pContent The body for the custom tag converted to a String.
- * @exception JspException
- */
- protected abstract void processBody(String pContent) throws JspException;
-}
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import javax.servlet.jsp.JspException;
+
+/**
+ *
+ *
+ * @author Thomas Purcell (CSC Australia)
+ *
+ * @version 1.0
+ */
+public abstract class BodyReaderTag extends ExBodyTagSupport {
+ /**
+ * This is the method called by the JSP engine when the body for a tag
+ * has been parsed and is ready for inclusion in this current tag. This
+ * method takes the content as a string and passes it to the {@code
+ * processBody} method.
+ *
+ * @return This method returns the {@code BodyTagSupport.SKIP_BODY}
+ * constant. This means that the body of the tag will only be
+ * processed the one time.
+ * @exception JspException
+ */
+ public int doAfterBody() throws JspException {
+ processBody(bodyContent.getString());
+ return SKIP_BODY;
+ }
+
+ /**
+ * This is the method that child classes must implement. It takes the
+ * body of the tag converted to a String as it's parameter. The body of
+ * the tag will have been interpreted to a String by the JSP engine before
+ * this method is called.
+ *
+ * @param pContent The body for the custom tag converted to a String.
+ * @exception JspException
+ */
+ protected abstract void processBody(String pContent) throws JspException;
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java
index e698f2ad..9db7b6be 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java
@@ -1,235 +1,235 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: CSVToTableTag.java,v $
- * Revision 1.3 2003/10/06 14:24:50 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.2 2002/11/26 17:33:49 WMHAKUR
- * Added documentation & removed System.out.println()s.
- *
- * Revision 1.1 2002/11/19 10:50:10 WMHAKUR
- * Renamed from CSVToTable, to follow naming conventions.
- *
- * Revision 1.1 2002/11/18 22:11:16 WMHAKUR
- * Tag to convert CSV to HTML table.
- * Can be further transformed, using XSLT.
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.tagext.BodyContent;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.Reader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.StringTokenizer;
-
-/**
- * Creates a table from a string of "comma-separated values" (CSV).
- * The delimiter character can be any character (or combination of characters).
- * The default delimiter is TAB ({@code \t}).
- *
- *
- *
- *
- *
- * The input may look like this:
- *
- * <c:totable firstRowIsHeader="true" delimiter=";">
- * header A;header B
- * data 1A; data 1B
- * data 2A; data 2B
- * </c:totable>
- *
- *
- * The output (source) will look like this:
- *
- * <TABLE>
- * <TR>
- * <TH>header A</TH><TH>header B</TH>
- * </TR>
- * <TR>
- * <TD>data 1A</TD><TD>data 1B</TD>
- * </TR>
- * <TR>
- * <TD>data 2A</TD><TD>data 2B</TD>
- * </TR>
- * </TABLE>
- *
- * You wil probably want to use XSLT to make the final output look nicer. :-)
- *
- * @see StringTokenizer
- * @see XSLT spec
- *
- * @author Harald Kuhr
- *
- * @version $Id: jsp/taglib/CSVToTableTag.java#1 $
- */
-public class CSVToTableTag extends ExBodyTagSupport {
- public final static String TAB = "\t";
-
- protected String delimiter = null;
- protected boolean firstRowIsHeader = false;
- protected boolean firstColIsHeader = false;
-
- public void setDelimiter(String pDelimiter) {
- delimiter = pDelimiter;
- }
-
- public String getDelimiter() {
- return delimiter != null ? delimiter : TAB;
- }
-
- public void setFirstRowIsHeader(String pBoolean) {
- firstRowIsHeader = Boolean.valueOf(pBoolean);
- }
-
- public void setFirstColIsHeader(String pBoolean) {
- firstColIsHeader = Boolean.valueOf(pBoolean);
- }
-
-
- public int doEndTag() throws JspException {
- BodyContent content = getBodyContent();
-
- try {
- Table table =
- Table.parseContent(content.getReader(), getDelimiter());
-
- JspWriter out = pageContext.getOut();
-
- //System.out.println("CSVToTable: " + table.getRows() + " rows, "
- // + table.getCols() + " cols.");
-
- if (table.getRows() > 0) {
- out.println("");
- // Loop over rows
- for (int row = 0; row < table.getRows(); row++) {
- out.println("");
-
- // Loop over cells in each row
- for (int col = 0; col < table.getCols(); col++) {
- // Test if we are using headers, else normal cell
- if (firstRowIsHeader && row == 0 || firstColIsHeader && col == 0) {
- out.println("" + table.get(row, col) + " | ");
- }
- else {
- out.println("" + table.get(row, col) + " | ");
- }
- }
-
- out.println("
");
-
- }
- out.println("
");
- }
- }
- catch (IOException ioe) {
- throw new JspException(ioe);
- }
-
- return super.doEndTag();
- }
-
- static class Table {
- List rows = null;
- int cols = 0;
-
- private Table(List pRows, int pCols) {
- rows = pRows;
- cols = pCols;
- }
-
- int getRows() {
- return rows != null ? rows.size() : 0;
- }
-
- int getCols() {
- return cols;
- }
-
- List getTableRows() {
- return rows;
- }
-
- List getTableRow(int pRow) {
- return rows != null
- ? (List) rows.get(pRow)
- : Collections.EMPTY_LIST;
- }
-
- String get(int pRow, int pCol) {
- List row = getTableRow(pRow);
- // Rows may contain unequal number of cols
- return (row.size() > pCol) ? (String) row.get(pCol) : "";
- }
-
- /**
- * Parses a BodyContent to a table.
- *
- */
- static Table parseContent(Reader pContent, String pDelim) throws IOException {
- List> tableRows = new ArrayList>();
- int tdsPerTR = 0;
-
- // Loop through TRs
- BufferedReader reader = new BufferedReader(pContent);
- String tr;
- while ((tr = reader.readLine()) != null) {
- // Discard blank lines
- if (tr.trim().length() <= 0 && tr.indexOf(pDelim) < 0) {
- continue;
- }
-
- //System.out.println("CSVToTable: read LINE=\"" + tr + "\"");
-
- List tableDatas = new ArrayList();
- StringTokenizer tableRow = new StringTokenizer(tr, pDelim,
- true);
-
- boolean lastWasDelim = false;
- while (tableRow.hasMoreTokens()) {
- String td = tableRow.nextToken();
-
- //System.out.println("CSVToTable: read data=\"" + td + "\"");
-
- // Test if we have empty TD
- if (td.equals(pDelim)) {
- if (lastWasDelim) {
- // Add empty TD
- tableDatas.add("");
- }
-
- // We just read a delimitter
- lastWasDelim = true;
- }
- else {
- // No tab, normal data
- lastWasDelim = false;
-
- // Add normal TD
- tableDatas.add(td);
- }
- } // end while (tableRow.hasNext())
-
- // Store max TD count
- if (tableDatas.size() > tdsPerTR) {
- tdsPerTR = tableDatas.size();
- }
-
- // Add a table row
- tableRows.add(tableDatas);
- }
-
- // Return TABLE
- return new Table(tableRows, tdsPerTR);
- }
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: CSVToTableTag.java,v $
+ * Revision 1.3 2003/10/06 14:24:50 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.2 2002/11/26 17:33:49 WMHAKUR
+ * Added documentation & removed System.out.println()s.
+ *
+ * Revision 1.1 2002/11/19 10:50:10 WMHAKUR
+ * Renamed from CSVToTable, to follow naming conventions.
+ *
+ * Revision 1.1 2002/11/18 22:11:16 WMHAKUR
+ * Tag to convert CSV to HTML table.
+ * Can be further transformed, using XSLT.
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * Creates a table from a string of "comma-separated values" (CSV).
+ * The delimiter character can be any character (or combination of characters).
+ * The default delimiter is TAB ({@code \t}).
+ *
+ *
+ *
+ *
+ *
+ * The input may look like this:
+ *
+ * <c:totable firstRowIsHeader="true" delimiter=";">
+ * header A;header B
+ * data 1A; data 1B
+ * data 2A; data 2B
+ * </c:totable>
+ *
+ *
+ * The output (source) will look like this:
+ *
+ * <TABLE>
+ * <TR>
+ * <TH>header A</TH><TH>header B</TH>
+ * </TR>
+ * <TR>
+ * <TD>data 1A</TD><TD>data 1B</TD>
+ * </TR>
+ * <TR>
+ * <TD>data 2A</TD><TD>data 2B</TD>
+ * </TR>
+ * </TABLE>
+ *
+ * You wil probably want to use XSLT to make the final output look nicer. :-)
+ *
+ * @see StringTokenizer
+ * @see XSLT spec
+ *
+ * @author Harald Kuhr
+ *
+ * @version $Id: jsp/taglib/CSVToTableTag.java#1 $
+ */
+public class CSVToTableTag extends ExBodyTagSupport {
+ public final static String TAB = "\t";
+
+ protected String delimiter = null;
+ protected boolean firstRowIsHeader = false;
+ protected boolean firstColIsHeader = false;
+
+ public void setDelimiter(String pDelimiter) {
+ delimiter = pDelimiter;
+ }
+
+ public String getDelimiter() {
+ return delimiter != null ? delimiter : TAB;
+ }
+
+ public void setFirstRowIsHeader(String pBoolean) {
+ firstRowIsHeader = Boolean.valueOf(pBoolean);
+ }
+
+ public void setFirstColIsHeader(String pBoolean) {
+ firstColIsHeader = Boolean.valueOf(pBoolean);
+ }
+
+
+ public int doEndTag() throws JspException {
+ BodyContent content = getBodyContent();
+
+ try {
+ Table table =
+ Table.parseContent(content.getReader(), getDelimiter());
+
+ JspWriter out = pageContext.getOut();
+
+ //System.out.println("CSVToTable: " + table.getRows() + " rows, "
+ // + table.getCols() + " cols.");
+
+ if (table.getRows() > 0) {
+ out.println("");
+ // Loop over rows
+ for (int row = 0; row < table.getRows(); row++) {
+ out.println("");
+
+ // Loop over cells in each row
+ for (int col = 0; col < table.getCols(); col++) {
+ // Test if we are using headers, else normal cell
+ if (firstRowIsHeader && row == 0 || firstColIsHeader && col == 0) {
+ out.println("" + table.get(row, col) + " | ");
+ }
+ else {
+ out.println("" + table.get(row, col) + " | ");
+ }
+ }
+
+ out.println("
");
+
+ }
+ out.println("
");
+ }
+ }
+ catch (IOException ioe) {
+ throw new JspException(ioe);
+ }
+
+ return super.doEndTag();
+ }
+
+ static class Table {
+ List rows = null;
+ int cols = 0;
+
+ private Table(List pRows, int pCols) {
+ rows = pRows;
+ cols = pCols;
+ }
+
+ int getRows() {
+ return rows != null ? rows.size() : 0;
+ }
+
+ int getCols() {
+ return cols;
+ }
+
+ List getTableRows() {
+ return rows;
+ }
+
+ List getTableRow(int pRow) {
+ return rows != null
+ ? (List) rows.get(pRow)
+ : Collections.EMPTY_LIST;
+ }
+
+ String get(int pRow, int pCol) {
+ List row = getTableRow(pRow);
+ // Rows may contain unequal number of cols
+ return (row.size() > pCol) ? (String) row.get(pCol) : "";
+ }
+
+ /**
+ * Parses a BodyContent to a table.
+ *
+ */
+ static Table parseContent(Reader pContent, String pDelim) throws IOException {
+ List> tableRows = new ArrayList>();
+ int tdsPerTR = 0;
+
+ // Loop through TRs
+ BufferedReader reader = new BufferedReader(pContent);
+ String tr;
+ while ((tr = reader.readLine()) != null) {
+ // Discard blank lines
+ if (tr.trim().length() <= 0 && tr.indexOf(pDelim) < 0) {
+ continue;
+ }
+
+ //System.out.println("CSVToTable: read LINE=\"" + tr + "\"");
+
+ List tableDatas = new ArrayList();
+ StringTokenizer tableRow = new StringTokenizer(tr, pDelim,
+ true);
+
+ boolean lastWasDelim = false;
+ while (tableRow.hasMoreTokens()) {
+ String td = tableRow.nextToken();
+
+ //System.out.println("CSVToTable: read data=\"" + td + "\"");
+
+ // Test if we have empty TD
+ if (td.equals(pDelim)) {
+ if (lastWasDelim) {
+ // Add empty TD
+ tableDatas.add("");
+ }
+
+ // We just read a delimitter
+ lastWasDelim = true;
+ }
+ else {
+ // No tab, normal data
+ lastWasDelim = false;
+
+ // Add normal TD
+ tableDatas.add(td);
+ }
+ } // end while (tableRow.hasNext())
+
+ // Store max TD count
+ if (tableDatas.size() > tdsPerTR) {
+ tdsPerTR = tableDatas.size();
+ }
+
+ // Add a table row
+ tableRows.add(tableDatas);
+ }
+
+ // Return TABLE
+ return new Table(tableRows, tdsPerTR);
+ }
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java
index 78902c0a..10381ded 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java
@@ -1,290 +1,290 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ExBodyTagSupport.java,v $
- * Revision 1.3 2003/10/06 14:24:57 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.2 2002/11/18 22:10:27 WMHAKUR
- * *** empty log message ***
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.tagext.BodyTagSupport;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.StringTokenizer;
-
-/**
- * This is the class that should be extended by all jsp pages that do use their
- * body. It contains a lot of helper methods for simplifying common tasks.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- *
- * @version $Id: jsp/taglib/ExBodyTagSupport.java#1 $
- */
-
-public class ExBodyTagSupport extends BodyTagSupport implements ExTag {
- /**
- * writeHtml ensures that the text being outputted appears as it was
- * entered. This prevents users from hacking the system by entering
- * html or jsp code into an entry form where that value will be displayed
- * later in the site.
- *
- * @param pOut The JspWriter to write the output to.
- * @param pHtml The original html to filter and output to the user.
- * @throws IOException If the user clicks Stop in the browser, or their
- * browser crashes, then the JspWriter will throw an IOException when
- * the jsp tries to write to it.
- */
-
- public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
- StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
-
- while (parser.hasMoreTokens()) {
- String token = parser.nextToken();
-
- if (token.equals("<")) {
- pOut.print("<");
- }
- else if (token.equals(">")) {
- pOut.print(">");
- }
- else if (token.equals("&")) {
- pOut.print("&");
- }
- else {
- pOut.print(token);
- }
- }
- }
-
- /**
- * Log a message to the servlet context.
- *
- * @param pMsg The error message to log.
- */
-
- public void log(String pMsg) {
- getServletContext().log(pMsg);
- }
-
- /**
- * Log a message to the servlet context and include the exception that is
- * passed in as the second parameter.
- *
- * @param pMsg The error message to log.
- * @param pException The exception that caused this error message to be
- * logged.
- */
-
- public void log(String pMsg, Throwable pException) {
- getServletContext().log(pMsg, pException);
- }
-
- /**
- * Retrieves the ServletContext object associated with the current
- * PageContext object.
- *
- * @return The ServletContext object associated with the current
- * PageContext object.
- */
-
- public ServletContext getServletContext() {
- return pageContext.getServletContext();
- }
-
- /**
- * Called when the tag has finished running. Any clean up that needs
- * to be done between calls to this tag but within the same JSP page is
- * called in the {@code clearServiceState()} method call.
- *
- * @exception JspException
- */
-
- public int doEndTag() throws JspException {
- clearServiceState();
- return super.doEndTag();
- }
-
- /**
- * Called when a tag's role in the current JSP page is finished. After
- * the {@code clearProperties()} method is called, the custom tag
- * should be in an identical state as when it was first created. The
- * {@code clearServiceState()} method is called here just in case an
- * exception was thrown in the custom tag. If an exception was thrown,
- * then the {@code doEndTag()} method will not have been called and
- * the tag might not have been cleaned up properly.
- */
-
- public void release() {
- clearServiceState();
-
- clearProperties();
- super.release();
- }
-
- /**
- * The default implementation for the {@code clearProperties()}. Not
- * all tags will need to overload this method call. By implementing it
- * here, all classes that extend this object are able to call {@code
- * super.clearProperties()}. So, if the class extends a different
- * tag, or this one, the parent method should always be called. This
- * method will be called when the tag is to be released. That is, the
- * tag has finished for the current page and should be returned to it's
- * initial state.
- */
-
- protected void clearProperties() {
- }
-
- /**
- * The default implementation for the {@code clearServiceState()}.
- * Not all tags will need to overload this method call. By implementing it
- * here, all classes that extend this object are able to call {@code
- * super.clearServiceState()}. So, if the class extends a different
- * tag, or this one, the parent method should always be called. This
- * method will be called when the tag has finished it's current tag
- * within the page, but may be called upon again in this same JSP page.
- */
-
- protected void clearServiceState() {
- }
-
- /**
- * Returns the initialisation parameter from the {@code
- * PageContext.APPLICATION_SCOPE} scope. These initialisation
- * parameters are defined in the {@code web.xml} configuration file.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @return The value for the parameter whose name was passed in as a
- * parameter. If the parameter does not exist, then {@code null}
- * will be returned.
- */
-
- public String getInitParameter(String pName) {
- return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
- }
-
- /**
- * Returns an Enumeration containing all the names for all the
- * initialisation parametes defined in the {@code
- * PageContext.APPLICATION_SCOPE} scope.
- *
- * @return An {@code Enumeration} containing all the names for all the
- * initialisation parameters.
- */
-
- public Enumeration getInitParameterNames() {
- return getInitParameterNames(PageContext.APPLICATION_SCOPE);
- }
-
- /**
- * Returns the initialisation parameter from the scope specified with the
- * name specified.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @param pScope The scope to search for the initialisation parameter
- * within.
- * @return The value of the parameter found. If no parameter with the
- * name specified is found in the scope specified, then {@code null
- * } is returned.
- */
-
- public String getInitParameter(String pName, int pScope) {
- switch (pScope) {
- case PageContext.PAGE_SCOPE:
- return getServletConfig().getInitParameter(pName);
- case PageContext.APPLICATION_SCOPE:
- return getServletContext().getInitParameter(pName);
- default:
- throw new IllegalArgumentException("Illegal scope.");
- }
- }
-
- /**
- * Returns an enumeration containing all the parameters defined in the
- * scope specified by the parameter.
- *
- * @param pScope The scope to return the names of all the parameters
- * defined within.
- * @return An {@code Enumeration} containing all the names for all the
- * parameters defined in the scope passed in as a parameter.
- */
-
- public Enumeration getInitParameterNames(int pScope) {
- switch (pScope) {
- case PageContext.PAGE_SCOPE:
- return getServletConfig().getInitParameterNames();
- case PageContext.APPLICATION_SCOPE:
- return getServletContext().getInitParameterNames();
- default:
- throw new IllegalArgumentException("Illegal scope");
- }
- }
-
- /**
- * Returns the servlet config associated with the current JSP page request.
- *
- * @return The {@code ServletConfig} associated with the current
- * request.
- */
-
- public ServletConfig getServletConfig() {
- return pageContext.getServletConfig();
- }
-
- /**
- * Gets the context path associated with the current JSP page request.
- * If the request is not a HttpServletRequest, this method will
- * return "/".
- *
- * @return a path relative to the current context's root, or
- * {@code "/"} if this is not a HTTP request.
- */
-
- public String getContextPath() {
- ServletRequest request = pageContext.getRequest();
- if (request instanceof HttpServletRequest) {
- return ((HttpServletRequest) request).getContextPath();
- }
- return "/";
- }
-
- /**
- * Gets the resource associated with the given relative path for the
- * current JSP page request.
- * The path may be absolute, or relative to the current context root.
- *
- * @param pPath the path
- *
- * @return a path relative to the current context root
- */
-
- public InputStream getResourceAsStream(String pPath) {
- // throws MalformedURLException {
- String path = pPath;
-
- if (pPath != null && !pPath.startsWith("/")) {
- path = getContextPath() + pPath;
- }
-
- return pageContext.getServletContext().getResourceAsStream(path);
- }
-
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ExBodyTagSupport.java,v $
+ * Revision 1.3 2003/10/06 14:24:57 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.2 2002/11/18 22:10:27 WMHAKUR
+ * *** empty log message ***
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.BodyTagSupport;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+
+/**
+ * This is the class that should be extended by all jsp pages that do use their
+ * body. It contains a lot of helper methods for simplifying common tasks.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ *
+ * @version $Id: jsp/taglib/ExBodyTagSupport.java#1 $
+ */
+
+public class ExBodyTagSupport extends BodyTagSupport implements ExTag {
+ /**
+ * writeHtml ensures that the text being outputted appears as it was
+ * entered. This prevents users from hacking the system by entering
+ * html or jsp code into an entry form where that value will be displayed
+ * later in the site.
+ *
+ * @param pOut The JspWriter to write the output to.
+ * @param pHtml The original html to filter and output to the user.
+ * @throws IOException If the user clicks Stop in the browser, or their
+ * browser crashes, then the JspWriter will throw an IOException when
+ * the jsp tries to write to it.
+ */
+
+ public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
+ StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
+
+ while (parser.hasMoreTokens()) {
+ String token = parser.nextToken();
+
+ if (token.equals("<")) {
+ pOut.print("<");
+ }
+ else if (token.equals(">")) {
+ pOut.print(">");
+ }
+ else if (token.equals("&")) {
+ pOut.print("&");
+ }
+ else {
+ pOut.print(token);
+ }
+ }
+ }
+
+ /**
+ * Log a message to the servlet context.
+ *
+ * @param pMsg The error message to log.
+ */
+
+ public void log(String pMsg) {
+ getServletContext().log(pMsg);
+ }
+
+ /**
+ * Log a message to the servlet context and include the exception that is
+ * passed in as the second parameter.
+ *
+ * @param pMsg The error message to log.
+ * @param pException The exception that caused this error message to be
+ * logged.
+ */
+
+ public void log(String pMsg, Throwable pException) {
+ getServletContext().log(pMsg, pException);
+ }
+
+ /**
+ * Retrieves the ServletContext object associated with the current
+ * PageContext object.
+ *
+ * @return The ServletContext object associated with the current
+ * PageContext object.
+ */
+
+ public ServletContext getServletContext() {
+ return pageContext.getServletContext();
+ }
+
+ /**
+ * Called when the tag has finished running. Any clean up that needs
+ * to be done between calls to this tag but within the same JSP page is
+ * called in the {@code clearServiceState()} method call.
+ *
+ * @exception JspException
+ */
+
+ public int doEndTag() throws JspException {
+ clearServiceState();
+ return super.doEndTag();
+ }
+
+ /**
+ * Called when a tag's role in the current JSP page is finished. After
+ * the {@code clearProperties()} method is called, the custom tag
+ * should be in an identical state as when it was first created. The
+ * {@code clearServiceState()} method is called here just in case an
+ * exception was thrown in the custom tag. If an exception was thrown,
+ * then the {@code doEndTag()} method will not have been called and
+ * the tag might not have been cleaned up properly.
+ */
+
+ public void release() {
+ clearServiceState();
+
+ clearProperties();
+ super.release();
+ }
+
+ /**
+ * The default implementation for the {@code clearProperties()}. Not
+ * all tags will need to overload this method call. By implementing it
+ * here, all classes that extend this object are able to call {@code
+ * super.clearProperties()}. So, if the class extends a different
+ * tag, or this one, the parent method should always be called. This
+ * method will be called when the tag is to be released. That is, the
+ * tag has finished for the current page and should be returned to it's
+ * initial state.
+ */
+
+ protected void clearProperties() {
+ }
+
+ /**
+ * The default implementation for the {@code clearServiceState()}.
+ * Not all tags will need to overload this method call. By implementing it
+ * here, all classes that extend this object are able to call {@code
+ * super.clearServiceState()}. So, if the class extends a different
+ * tag, or this one, the parent method should always be called. This
+ * method will be called when the tag has finished it's current tag
+ * within the page, but may be called upon again in this same JSP page.
+ */
+
+ protected void clearServiceState() {
+ }
+
+ /**
+ * Returns the initialisation parameter from the {@code
+ * PageContext.APPLICATION_SCOPE} scope. These initialisation
+ * parameters are defined in the {@code web.xml} configuration file.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @return The value for the parameter whose name was passed in as a
+ * parameter. If the parameter does not exist, then {@code null}
+ * will be returned.
+ */
+
+ public String getInitParameter(String pName) {
+ return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
+ }
+
+ /**
+ * Returns an Enumeration containing all the names for all the
+ * initialisation parametes defined in the {@code
+ * PageContext.APPLICATION_SCOPE} scope.
+ *
+ * @return An {@code Enumeration} containing all the names for all the
+ * initialisation parameters.
+ */
+
+ public Enumeration getInitParameterNames() {
+ return getInitParameterNames(PageContext.APPLICATION_SCOPE);
+ }
+
+ /**
+ * Returns the initialisation parameter from the scope specified with the
+ * name specified.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @param pScope The scope to search for the initialisation parameter
+ * within.
+ * @return The value of the parameter found. If no parameter with the
+ * name specified is found in the scope specified, then {@code null
+ * } is returned.
+ */
+
+ public String getInitParameter(String pName, int pScope) {
+ switch (pScope) {
+ case PageContext.PAGE_SCOPE:
+ return getServletConfig().getInitParameter(pName);
+ case PageContext.APPLICATION_SCOPE:
+ return getServletContext().getInitParameter(pName);
+ default:
+ throw new IllegalArgumentException("Illegal scope.");
+ }
+ }
+
+ /**
+ * Returns an enumeration containing all the parameters defined in the
+ * scope specified by the parameter.
+ *
+ * @param pScope The scope to return the names of all the parameters
+ * defined within.
+ * @return An {@code Enumeration} containing all the names for all the
+ * parameters defined in the scope passed in as a parameter.
+ */
+
+ public Enumeration getInitParameterNames(int pScope) {
+ switch (pScope) {
+ case PageContext.PAGE_SCOPE:
+ return getServletConfig().getInitParameterNames();
+ case PageContext.APPLICATION_SCOPE:
+ return getServletContext().getInitParameterNames();
+ default:
+ throw new IllegalArgumentException("Illegal scope");
+ }
+ }
+
+ /**
+ * Returns the servlet config associated with the current JSP page request.
+ *
+ * @return The {@code ServletConfig} associated with the current
+ * request.
+ */
+
+ public ServletConfig getServletConfig() {
+ return pageContext.getServletConfig();
+ }
+
+ /**
+ * Gets the context path associated with the current JSP page request.
+ * If the request is not a HttpServletRequest, this method will
+ * return "/".
+ *
+ * @return a path relative to the current context's root, or
+ * {@code "/"} if this is not a HTTP request.
+ */
+
+ public String getContextPath() {
+ ServletRequest request = pageContext.getRequest();
+ if (request instanceof HttpServletRequest) {
+ return ((HttpServletRequest) request).getContextPath();
+ }
+ return "/";
+ }
+
+ /**
+ * Gets the resource associated with the given relative path for the
+ * current JSP page request.
+ * The path may be absolute, or relative to the current context root.
+ *
+ * @param pPath the path
+ *
+ * @return a path relative to the current context root
+ */
+
+ public InputStream getResourceAsStream(String pPath) {
+ // throws MalformedURLException {
+ String path = pPath;
+
+ if (pPath != null && !pPath.startsWith("/")) {
+ path = getContextPath() + pPath;
+ }
+
+ return pageContext.getServletContext().getResourceAsStream(path);
+ }
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java
index 7dee5cfd..e0817362 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java
@@ -1,163 +1,163 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ExTag.java,v $
- * Revision 1.2 2003/10/06 14:25:05 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/11/18 22:10:27 WMHAKUR
- * *** empty log message ***
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.tagext.Tag;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-
-/**
- * This interface contains a lot of helper methods for simplifying common
- * taglib related tasks.
- *
- * @author Harald Kuhr
- *
- * @version $Id: jsp/taglib/ExTag.java#1 $
- */
-
-public interface ExTag extends Tag {
-
- /**
- * writeHtml ensures that the text being outputted appears as it was
- * entered. This prevents users from hacking the system by entering
- * html or jsp code into an entry form where that value will be displayed
- * later in the site.
- *
- * @param pOut The JspWriter to write the output to.
- * @param pHtml The original html to filter and output to the user.
- * @throws IOException If the user clicks Stop in the browser, or their
- * browser crashes, then the JspWriter will throw an IOException when
- * the jsp tries to write to it.
- */
-
- public void writeHtml(JspWriter pOut, String pHtml) throws IOException;
-
- /**
- * Log a message to the servlet context.
- *
- * @param pMsg The error message to log.
- */
-
- public void log(String pMsg);
-
- /**
- * Logs a message to the servlet context and include the exception that is
- * passed in as the second parameter.
- *
- * @param pMsg The error message to log.
- * @param pException The exception that caused this error message to be
- * logged.
- */
-
- public void log(String pMsg, Throwable pException);
-
- /**
- * Retrieves the ServletContext object associated with the current
- * PageContext object.
- *
- * @return The ServletContext object associated with the current
- * PageContext object.
- */
-
- public ServletContext getServletContext();
-
- /**
- * Returns the initialisation parameter from the {@code
- * PageContext.APPLICATION_SCOPE} scope. These initialisation
- * parameters are defined in the {@code web.xml} configuration file.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @return The value for the parameter whose name was passed in as a
- * parameter. If the parameter does not exist, then {@code null}
- * will be returned.
- */
-
- public String getInitParameter(String pName);
-
- /**
- * Returns an Enumeration containing all the names for all the
- * initialisation parametes defined in the {@code
- * PageContext.APPLICATION_SCOPE} scope.
- *
- * @return An {@code Enumeration} containing all the names for all the
- * initialisation parameters.
- */
-
- public Enumeration getInitParameterNames();
-
- /**
- * Returns the initialisation parameter from the scope specified with the
- * name specified.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @param pScope The scope to search for the initialisation parameter
- * within.
- * @return The value of the parameter found. If no parameter with the
- * name specified is found in the scope specified, then {@code null
- * } is returned.
- */
-
- public String getInitParameter(String pName, int pScope);
-
- /**
- * Returns an enumeration containing all the parameters defined in the
- * scope specified by the parameter.
- *
- * @param pScope The scope to return the names of all the parameters
- * defined within.
- * @return An {@code Enumeration} containing all the names for all the
- * parameters defined in the scope passed in as a parameter.
- */
-
- public Enumeration getInitParameterNames(int pScope);
-
- /**
- * Returns the servlet config associated with the current JSP page request.
- *
- * @return The {@code ServletConfig} associated with the current
- * request.
- */
-
- public ServletConfig getServletConfig();
-
- /**
- * Gets the context path associated with the current JSP page request.
- *
- * @return a path relative to the current context's root.
- */
-
- public String getContextPath();
-
-
- /**
- * Gets the resource associated with the given relative path for the
- * current JSP page request.
- * The path may be absolute, or relative to the current context root.
- *
- * @param pPath the path
- *
- * @return a path relative to the current context root
- */
-
- public InputStream getResourceAsStream(String pPath);
-
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ExTag.java,v $
+ * Revision 1.2 2003/10/06 14:25:05 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/11/18 22:10:27 WMHAKUR
+ * *** empty log message ***
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.Tag;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+
+/**
+ * This interface contains a lot of helper methods for simplifying common
+ * taglib related tasks.
+ *
+ * @author Harald Kuhr
+ *
+ * @version $Id: jsp/taglib/ExTag.java#1 $
+ */
+
+public interface ExTag extends Tag {
+
+ /**
+ * writeHtml ensures that the text being outputted appears as it was
+ * entered. This prevents users from hacking the system by entering
+ * html or jsp code into an entry form where that value will be displayed
+ * later in the site.
+ *
+ * @param pOut The JspWriter to write the output to.
+ * @param pHtml The original html to filter and output to the user.
+ * @throws IOException If the user clicks Stop in the browser, or their
+ * browser crashes, then the JspWriter will throw an IOException when
+ * the jsp tries to write to it.
+ */
+
+ public void writeHtml(JspWriter pOut, String pHtml) throws IOException;
+
+ /**
+ * Log a message to the servlet context.
+ *
+ * @param pMsg The error message to log.
+ */
+
+ public void log(String pMsg);
+
+ /**
+ * Logs a message to the servlet context and include the exception that is
+ * passed in as the second parameter.
+ *
+ * @param pMsg The error message to log.
+ * @param pException The exception that caused this error message to be
+ * logged.
+ */
+
+ public void log(String pMsg, Throwable pException);
+
+ /**
+ * Retrieves the ServletContext object associated with the current
+ * PageContext object.
+ *
+ * @return The ServletContext object associated with the current
+ * PageContext object.
+ */
+
+ public ServletContext getServletContext();
+
+ /**
+ * Returns the initialisation parameter from the {@code
+ * PageContext.APPLICATION_SCOPE} scope. These initialisation
+ * parameters are defined in the {@code web.xml} configuration file.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @return The value for the parameter whose name was passed in as a
+ * parameter. If the parameter does not exist, then {@code null}
+ * will be returned.
+ */
+
+ public String getInitParameter(String pName);
+
+ /**
+ * Returns an Enumeration containing all the names for all the
+ * initialisation parametes defined in the {@code
+ * PageContext.APPLICATION_SCOPE} scope.
+ *
+ * @return An {@code Enumeration} containing all the names for all the
+ * initialisation parameters.
+ */
+
+ public Enumeration getInitParameterNames();
+
+ /**
+ * Returns the initialisation parameter from the scope specified with the
+ * name specified.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @param pScope The scope to search for the initialisation parameter
+ * within.
+ * @return The value of the parameter found. If no parameter with the
+ * name specified is found in the scope specified, then {@code null
+ * } is returned.
+ */
+
+ public String getInitParameter(String pName, int pScope);
+
+ /**
+ * Returns an enumeration containing all the parameters defined in the
+ * scope specified by the parameter.
+ *
+ * @param pScope The scope to return the names of all the parameters
+ * defined within.
+ * @return An {@code Enumeration} containing all the names for all the
+ * parameters defined in the scope passed in as a parameter.
+ */
+
+ public Enumeration getInitParameterNames(int pScope);
+
+ /**
+ * Returns the servlet config associated with the current JSP page request.
+ *
+ * @return The {@code ServletConfig} associated with the current
+ * request.
+ */
+
+ public ServletConfig getServletConfig();
+
+ /**
+ * Gets the context path associated with the current JSP page request.
+ *
+ * @return a path relative to the current context's root.
+ */
+
+ public String getContextPath();
+
+
+ /**
+ * Gets the resource associated with the given relative path for the
+ * current JSP page request.
+ * The path may be absolute, or relative to the current context root.
+ *
+ * @param pPath the path
+ *
+ * @return a path relative to the current context root
+ */
+
+ public InputStream getResourceAsStream(String pPath);
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java
index c356d237..3391064e 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java
@@ -1,293 +1,293 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: ExTagSupport.java,v $
- * Revision 1.3 2003/10/06 14:25:11 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.2 2002/11/18 22:10:27 WMHAKUR
- * *** empty log message ***
- *
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.PageContext;
-import javax.servlet.jsp.tagext.TagSupport;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Enumeration;
-import java.util.StringTokenizer;
-
-/**
- * This is the class that should be extended by all jsp pages that don't use
- * their body. It contains a lot of helper methods for simplifying common
- * tasks.
- *
- * @author Thomas Purcell (CSC Australia)
- * @author Harald Kuhr
- *
- * @version $Id: jsp/taglib/ExTagSupport.java#1 $
- */
-
-public class ExTagSupport extends TagSupport implements ExTag {
- /**
- * writeHtml ensures that the text being outputted appears as it was
- * entered. This prevents users from hacking the system by entering
- * html or jsp code into an entry form where that value will be displayed
- * later in the site.
- *
- * @param pOut The JspWriter to write the output to.
- * @param pHtml The original html to filter and output to the user.
- * @throws IOException If the user clicks Stop in the browser, or their
- * browser crashes, then the JspWriter will throw an IOException when
- * the jsp tries to write to it.
- */
-
- public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
- StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
-
- while (parser.hasMoreTokens()) {
- String token = parser.nextToken();
-
- if (token.equals("<")) {
- pOut.print("<");
- }
- else if (token.equals(">")) {
- pOut.print(">");
- }
- else if (token.equals("&")) {
- pOut.print("&");
- }
- else {
- pOut.print(token);
- }
- }
- }
-
- /**
- * Log a message to the servlet context.
- *
- * @param pMsg The error message to log.
- */
-
- public void log(String pMsg) {
- getServletContext().log(pMsg);
- }
-
- /**
- * Log a message to the servlet context and include the exception that is
- * passed in as the second parameter.
- *
- * @param pMsg The error message to log.
- * @param pException The exception that caused this error message to be
- * logged.
- */
-
- public void log(String pMsg, Throwable pException) {
- getServletContext().log(pMsg, pException);
- }
-
- /**
- * Retrieves the ServletContext object associated with the current
- * PageContext object.
- *
- * @return The ServletContext object associated with the current
- * PageContext object.
- */
-
- public ServletContext getServletContext() {
- return pageContext.getServletContext();
- }
-
- /**
- * Called when the tag has finished running. Any clean up that needs
- * to be done between calls to this tag but within the same JSP page is
- * called in the {@code clearServiceState()} method call.
- *
- * @exception JspException
- */
-
- public int doEndTag() throws JspException {
- clearServiceState();
- return super.doEndTag();
- }
-
- /**
- * Called when a tag's role in the current JSP page is finished. After
- * the {@code clearProperties()} method is called, the custom tag
- * should be in an identical state as when it was first created. The
- * {@code clearServiceState()} method is called here just in case an
- * exception was thrown in the custom tag. If an exception was thrown,
- * then the {@code doEndTag()} method will not have been called and
- * the tag might not have been cleaned up properly.
- */
-
- public void release() {
- clearServiceState();
-
- clearProperties();
- super.release();
- }
-
- /**
- * The default implementation for the {@code clearProperties()}. Not
- * all tags will need to overload this method call. By implementing it
- * here, all classes that extend this object are able to call {@code
- * super.clearProperties()}. So, if the class extends a different
- * tag, or this one, the parent method should always be called. This
- * method will be called when the tag is to be released. That is, the
- * tag has finished for the current page and should be returned to it's
- * initial state.
- */
-
- protected void clearProperties() {
- }
-
- /**
- * The default implementation for the {@code clearServiceState()}.
- * Not all tags will need to overload this method call. By implementing it
- * here, all classes that extend this object are able to call {@code
- * super.clearServiceState()}. So, if the class extends a different
- * tag, or this one, the parent method should always be called. This
- * method will be called when the tag has finished it's current tag
- * within the page, but may be called upon again in this same JSP page.
- */
-
- protected void clearServiceState() {
- }
-
- /**
- * Returns the initialisation parameter from the {@code
- * PageContext.APPLICATION_SCOPE} scope. These initialisation
- * parameters are defined in the {@code web.xml} configuration file.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @return The value for the parameter whose name was passed in as a
- * parameter. If the parameter does not exist, then {@code null}
- * will be returned.
- */
-
- public String getInitParameter(String pName) {
- return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
- }
-
- /**
- * Returns an Enumeration containing all the names for all the
- * initialisation parametes defined in the {@code
- * PageContext.APPLICATION_SCOPE} scope.
- *
- * @return An {@code Enumeration} containing all the names for all the
- * initialisation parameters.
- */
-
- public Enumeration getInitParameterNames() {
- return getInitParameterNames(PageContext.APPLICATION_SCOPE);
- }
-
- /**
- * Returns the initialisation parameter from the scope specified with the
- * name specified.
- *
- * @param pName The name of the initialisation parameter to return the
- * value for.
- * @param pScope The scope to search for the initialisation parameter
- * within.
- * @return The value of the parameter found. If no parameter with the
- * name specified is found in the scope specified, then {@code null
- * } is returned.
- */
-
- public String getInitParameter(String pName, int pScope) {
- switch (pScope) {
- case PageContext.PAGE_SCOPE:
- return getServletConfig().getInitParameter(pName);
- case PageContext.APPLICATION_SCOPE:
- return getServletContext().getInitParameter(pName);
- default:
- throw new IllegalArgumentException("Illegal scope.");
- }
- }
-
- /**
- * Returns an enumeration containing all the parameters defined in the
- * scope specified by the parameter.
- *
- * @param pScope The scope to return the names of all the parameters
- * defined within.
- * @return An {@code Enumeration} containing all the names for all the
- * parameters defined in the scope passed in as a parameter.
- */
-
- public Enumeration getInitParameterNames(int pScope) {
- switch (pScope) {
- case PageContext.PAGE_SCOPE:
- return getServletConfig().getInitParameterNames();
- case PageContext.APPLICATION_SCOPE:
- return getServletContext().getInitParameterNames();
- default:
- throw new IllegalArgumentException("Illegal scope");
- }
- }
-
- /**
- * Returns the servlet config associated with the current JSP page request.
- *
- * @return The {@code ServletConfig} associated with the current
- * request.
- */
-
- public ServletConfig getServletConfig() {
- return pageContext.getServletConfig();
- }
-
- /**
- * Gets the context path associated with the current JSP page request.
- * If the request is not a HttpServletRequest, this method will
- * return "/".
- *
- * @return a path relative to the current context's root, or
- * {@code "/"} if this is not a HTTP request.
- */
-
- public String getContextPath() {
- ServletRequest request = pageContext.getRequest();
- if (request instanceof HttpServletRequest) {
- return ((HttpServletRequest) request).getContextPath();
- }
- return "/";
- }
-
- /**
- * Gets the resource associated with the given relative path for the
- * current JSP page request.
- * The path may be absolute, or relative to the current context root.
- *
- * @param pPath the path
- *
- * @return a path relative to the current context root
- */
-
- public InputStream getResourceAsStream(String pPath) {
- //throws MalformedURLException {
- String path = pPath;
-
- if (pPath != null && !pPath.startsWith("/")) {
- path = getContextPath() + pPath;
- }
-
- return pageContext.getServletContext().getResourceAsStream(path);
- }
-
-
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: ExTagSupport.java,v $
+ * Revision 1.3 2003/10/06 14:25:11 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.2 2002/11/18 22:10:27 WMHAKUR
+ * *** empty log message ***
+ *
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.TagSupport;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+
+/**
+ * This is the class that should be extended by all jsp pages that don't use
+ * their body. It contains a lot of helper methods for simplifying common
+ * tasks.
+ *
+ * @author Thomas Purcell (CSC Australia)
+ * @author Harald Kuhr
+ *
+ * @version $Id: jsp/taglib/ExTagSupport.java#1 $
+ */
+
+public class ExTagSupport extends TagSupport implements ExTag {
+ /**
+ * writeHtml ensures that the text being outputted appears as it was
+ * entered. This prevents users from hacking the system by entering
+ * html or jsp code into an entry form where that value will be displayed
+ * later in the site.
+ *
+ * @param pOut The JspWriter to write the output to.
+ * @param pHtml The original html to filter and output to the user.
+ * @throws IOException If the user clicks Stop in the browser, or their
+ * browser crashes, then the JspWriter will throw an IOException when
+ * the jsp tries to write to it.
+ */
+
+ public void writeHtml(JspWriter pOut, String pHtml) throws IOException {
+ StringTokenizer parser = new StringTokenizer(pHtml, "<>&", true);
+
+ while (parser.hasMoreTokens()) {
+ String token = parser.nextToken();
+
+ if (token.equals("<")) {
+ pOut.print("<");
+ }
+ else if (token.equals(">")) {
+ pOut.print(">");
+ }
+ else if (token.equals("&")) {
+ pOut.print("&");
+ }
+ else {
+ pOut.print(token);
+ }
+ }
+ }
+
+ /**
+ * Log a message to the servlet context.
+ *
+ * @param pMsg The error message to log.
+ */
+
+ public void log(String pMsg) {
+ getServletContext().log(pMsg);
+ }
+
+ /**
+ * Log a message to the servlet context and include the exception that is
+ * passed in as the second parameter.
+ *
+ * @param pMsg The error message to log.
+ * @param pException The exception that caused this error message to be
+ * logged.
+ */
+
+ public void log(String pMsg, Throwable pException) {
+ getServletContext().log(pMsg, pException);
+ }
+
+ /**
+ * Retrieves the ServletContext object associated with the current
+ * PageContext object.
+ *
+ * @return The ServletContext object associated with the current
+ * PageContext object.
+ */
+
+ public ServletContext getServletContext() {
+ return pageContext.getServletContext();
+ }
+
+ /**
+ * Called when the tag has finished running. Any clean up that needs
+ * to be done between calls to this tag but within the same JSP page is
+ * called in the {@code clearServiceState()} method call.
+ *
+ * @exception JspException
+ */
+
+ public int doEndTag() throws JspException {
+ clearServiceState();
+ return super.doEndTag();
+ }
+
+ /**
+ * Called when a tag's role in the current JSP page is finished. After
+ * the {@code clearProperties()} method is called, the custom tag
+ * should be in an identical state as when it was first created. The
+ * {@code clearServiceState()} method is called here just in case an
+ * exception was thrown in the custom tag. If an exception was thrown,
+ * then the {@code doEndTag()} method will not have been called and
+ * the tag might not have been cleaned up properly.
+ */
+
+ public void release() {
+ clearServiceState();
+
+ clearProperties();
+ super.release();
+ }
+
+ /**
+ * The default implementation for the {@code clearProperties()}. Not
+ * all tags will need to overload this method call. By implementing it
+ * here, all classes that extend this object are able to call {@code
+ * super.clearProperties()}. So, if the class extends a different
+ * tag, or this one, the parent method should always be called. This
+ * method will be called when the tag is to be released. That is, the
+ * tag has finished for the current page and should be returned to it's
+ * initial state.
+ */
+
+ protected void clearProperties() {
+ }
+
+ /**
+ * The default implementation for the {@code clearServiceState()}.
+ * Not all tags will need to overload this method call. By implementing it
+ * here, all classes that extend this object are able to call {@code
+ * super.clearServiceState()}. So, if the class extends a different
+ * tag, or this one, the parent method should always be called. This
+ * method will be called when the tag has finished it's current tag
+ * within the page, but may be called upon again in this same JSP page.
+ */
+
+ protected void clearServiceState() {
+ }
+
+ /**
+ * Returns the initialisation parameter from the {@code
+ * PageContext.APPLICATION_SCOPE} scope. These initialisation
+ * parameters are defined in the {@code web.xml} configuration file.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @return The value for the parameter whose name was passed in as a
+ * parameter. If the parameter does not exist, then {@code null}
+ * will be returned.
+ */
+
+ public String getInitParameter(String pName) {
+ return getInitParameter(pName, PageContext.APPLICATION_SCOPE);
+ }
+
+ /**
+ * Returns an Enumeration containing all the names for all the
+ * initialisation parametes defined in the {@code
+ * PageContext.APPLICATION_SCOPE} scope.
+ *
+ * @return An {@code Enumeration} containing all the names for all the
+ * initialisation parameters.
+ */
+
+ public Enumeration getInitParameterNames() {
+ return getInitParameterNames(PageContext.APPLICATION_SCOPE);
+ }
+
+ /**
+ * Returns the initialisation parameter from the scope specified with the
+ * name specified.
+ *
+ * @param pName The name of the initialisation parameter to return the
+ * value for.
+ * @param pScope The scope to search for the initialisation parameter
+ * within.
+ * @return The value of the parameter found. If no parameter with the
+ * name specified is found in the scope specified, then {@code null
+ * } is returned.
+ */
+
+ public String getInitParameter(String pName, int pScope) {
+ switch (pScope) {
+ case PageContext.PAGE_SCOPE:
+ return getServletConfig().getInitParameter(pName);
+ case PageContext.APPLICATION_SCOPE:
+ return getServletContext().getInitParameter(pName);
+ default:
+ throw new IllegalArgumentException("Illegal scope.");
+ }
+ }
+
+ /**
+ * Returns an enumeration containing all the parameters defined in the
+ * scope specified by the parameter.
+ *
+ * @param pScope The scope to return the names of all the parameters
+ * defined within.
+ * @return An {@code Enumeration} containing all the names for all the
+ * parameters defined in the scope passed in as a parameter.
+ */
+
+ public Enumeration getInitParameterNames(int pScope) {
+ switch (pScope) {
+ case PageContext.PAGE_SCOPE:
+ return getServletConfig().getInitParameterNames();
+ case PageContext.APPLICATION_SCOPE:
+ return getServletContext().getInitParameterNames();
+ default:
+ throw new IllegalArgumentException("Illegal scope");
+ }
+ }
+
+ /**
+ * Returns the servlet config associated with the current JSP page request.
+ *
+ * @return The {@code ServletConfig} associated with the current
+ * request.
+ */
+
+ public ServletConfig getServletConfig() {
+ return pageContext.getServletConfig();
+ }
+
+ /**
+ * Gets the context path associated with the current JSP page request.
+ * If the request is not a HttpServletRequest, this method will
+ * return "/".
+ *
+ * @return a path relative to the current context's root, or
+ * {@code "/"} if this is not a HTTP request.
+ */
+
+ public String getContextPath() {
+ ServletRequest request = pageContext.getRequest();
+ if (request instanceof HttpServletRequest) {
+ return ((HttpServletRequest) request).getContextPath();
+ }
+ return "/";
+ }
+
+ /**
+ * Gets the resource associated with the given relative path for the
+ * current JSP page request.
+ * The path may be absolute, or relative to the current context root.
+ *
+ * @param pPath the path
+ *
+ * @return a path relative to the current context root
+ */
+
+ public InputStream getResourceAsStream(String pPath) {
+ //throws MalformedURLException {
+ String path = pPath;
+
+ if (pPath != null && !pPath.startsWith("/")) {
+ path = getContextPath() + pPath;
+ }
+
+ return pageContext.getServletContext().getResourceAsStream(path);
+ }
+
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java
index c61c4078..99c3e5fc 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java
@@ -1,20 +1,20 @@
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import javax.servlet.jsp.*;
-import javax.servlet.jsp.tagext.*;
-
-/**
- * TagExtraInfo for LastModifiedTag
- *
- * @author Harald Kuhr
- *
- * @version 1.1
- */
-
-public class LastModifiedTEI extends TagExtraInfo {
- public VariableInfo[] getVariableInfo(TagData pData) {
- return new VariableInfo[]{
- new VariableInfo("lastModified", "java.lang.String", true, VariableInfo.NESTED),
- };
- }
-}
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * TagExtraInfo for LastModifiedTag
+ *
+ * @author Harald Kuhr
+ *
+ * @version 1.1
+ */
+
+public class LastModifiedTEI extends TagExtraInfo {
+ public VariableInfo[] getVariableInfo(TagData pData) {
+ return new VariableInfo[]{
+ new VariableInfo("lastModified", "java.lang.String", true, VariableInfo.NESTED),
+ };
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java
index ecdbdba2..38d47836 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java
@@ -1,87 +1,87 @@
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import java.io.IOException;
-
-import javax.servlet.jsp.JspException;
-
-/**
- * This tag truncates all consecutive whitespace in sequence inside its body,
- * to one whitespace character. The first whitespace character in the sequence
- * will be left untouched (except for CR/LF, which will always leave a LF).
- *
- * @author Harald Kuhr
- *
- * @version 1.0
- */
-
-public class TrimWhiteSpaceTag extends ExBodyTagSupport {
-
- /**
- * doStartTag implementation, simply returns
- * {@code BodyTag.EVAL_BODY_BUFFERED}.
- *
- * @return {@code BodyTag.EVAL_BODY_BUFFERED}
- */
-
- public int doStartTag() throws JspException {
- return EVAL_BODY_BUFFERED;
- }
-
- /**
- * doEndTag implementation, truncates all whitespace.
- *
- * @return {@code super.doEndTag()}
- */
-
- public int doEndTag() throws JspException {
- // Trim
- String trimmed = truncateWS(bodyContent.getString());
- try {
- // Print trimmed content
- //pageContext.getOut().print("\n");
- pageContext.getOut().print(trimmed);
- //pageContext.getOut().print("\n");
- }
- catch (IOException ioe) {
- throw new JspException(ioe);
- }
-
- return super.doEndTag();
- }
-
- /**
- * Truncates whitespace from the given string.
- *
- * @todo Candidate for StringUtil?
- */
-
- private static String truncateWS(String pStr) {
- char[] chars = pStr.toCharArray();
-
- int count = 0;
- boolean lastWasWS = true; // Avoids leading WS
- for (int i = 0; i < chars.length; i++) {
- if (!Character.isWhitespace(chars[i])) {
- // if char is not WS, just store
- chars[count++] = chars[i];
- lastWasWS = false;
- }
- else {
- // else, if char is WS, store first, skip the rest
- if (!lastWasWS) {
- if (chars[i] == 0x0d) {
- chars[count++] = 0x0a; //Always new line
- }
- else {
- chars[count++] = chars[i];
- }
- }
- lastWasWS = true;
- }
- }
-
- // Return the trucated string
- return new String(chars, 0, count);
- }
-
-}
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import java.io.IOException;
+
+import javax.servlet.jsp.JspException;
+
+/**
+ * This tag truncates all consecutive whitespace in sequence inside its body,
+ * to one whitespace character. The first whitespace character in the sequence
+ * will be left untouched (except for CR/LF, which will always leave a LF).
+ *
+ * @author Harald Kuhr
+ *
+ * @version 1.0
+ */
+
+public class TrimWhiteSpaceTag extends ExBodyTagSupport {
+
+ /**
+ * doStartTag implementation, simply returns
+ * {@code BodyTag.EVAL_BODY_BUFFERED}.
+ *
+ * @return {@code BodyTag.EVAL_BODY_BUFFERED}
+ */
+
+ public int doStartTag() throws JspException {
+ return EVAL_BODY_BUFFERED;
+ }
+
+ /**
+ * doEndTag implementation, truncates all whitespace.
+ *
+ * @return {@code super.doEndTag()}
+ */
+
+ public int doEndTag() throws JspException {
+ // Trim
+ String trimmed = truncateWS(bodyContent.getString());
+ try {
+ // Print trimmed content
+ //pageContext.getOut().print("\n");
+ pageContext.getOut().print(trimmed);
+ //pageContext.getOut().print("\n");
+ }
+ catch (IOException ioe) {
+ throw new JspException(ioe);
+ }
+
+ return super.doEndTag();
+ }
+
+ /**
+ * Truncates whitespace from the given string.
+ *
+ * @todo Candidate for StringUtil?
+ */
+
+ private static String truncateWS(String pStr) {
+ char[] chars = pStr.toCharArray();
+
+ int count = 0;
+ boolean lastWasWS = true; // Avoids leading WS
+ for (int i = 0; i < chars.length; i++) {
+ if (!Character.isWhitespace(chars[i])) {
+ // if char is not WS, just store
+ chars[count++] = chars[i];
+ lastWasWS = false;
+ }
+ else {
+ // else, if char is WS, store first, skip the rest
+ if (!lastWasWS) {
+ if (chars[i] == 0x0d) {
+ chars[count++] = 0x0a; //Always new line
+ }
+ else {
+ chars[count++] = chars[i];
+ }
+ }
+ lastWasWS = true;
+ }
+ }
+
+ // Return the trucated string
+ return new String(chars, 0, count);
+ }
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java
index 59b0c571..6e712544 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java
@@ -1,158 +1,158 @@
-/*
- * Copyright (c) 2002 TwelveMonkeys.
- * All rights reserved.
- *
- * $Log: XMLTransformTag.java,v $
- * Revision 1.2 2003/10/06 14:25:43 WMHAKUR
- * Code clean-up only.
- *
- * Revision 1.1 2002/11/19 10:50:41 WMHAKUR
- * *** empty log message ***
- *
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib;
-
-import java.io.*;
-import java.net.*;
-
-import javax.servlet.jsp.*;
-import javax.xml.transform.*;
-import javax.xml.transform.stream.*;
-
-/**
- * This tag performs XSL Transformations (XSLT) on a given XML document or its
- * body content.
- *
- * @author Harald Kuhr
- *
- * @version $Id: jsp/taglib/XMLTransformTag.java#1 $
- */
-
-public class XMLTransformTag extends ExBodyTagSupport {
- private String mDocumentURI = null;
- private String mStylesheetURI = null;
-
- /**
- * Sets the document attribute for this tag.
- */
-
- public void setDocumentURI(String pDocumentURI) {
- mDocumentURI = pDocumentURI;
- }
-
- /**
- * Sets the stylesheet attribute for this tag.
- */
-
- public void setStylesheetURI(String pStylesheetURI) {
- mStylesheetURI = pStylesheetURI;
- }
-
-
- /**
- * doStartTag implementation, that performs XML Transformation on the
- * given document, if any.
- * If the documentURI attribute is set, then the transformation is
- * performed on the document at that location, and
- * {@code Tag.SKIP_BODY} is returned.
- * Otherwise, this method simply returns
- * {@code BodyTag.EVAL_BODY_BUFFERED} and leaves the transformation to
- * the doEndTag.
- *
- * @return {@code Tag.SKIP_BODY} if {@code documentURI} is not
- * {@code null}, otherwise
- * {@code BodyTag.EVAL_BODY_BUFFERED}.
- *
- * @todo Is it really a good idea to allow "inline" XML in a JSP?
- */
-
- public int doStartTag() throws JspException {
- //log("XML: " + mDocumentURI + " XSL: " + mStylesheetURI);
-
- if (mDocumentURI != null) {
- // If document given, transform and skip body...
- try {
- transform(getSource(mDocumentURI));
- }
- catch (MalformedURLException murle) {
- throw new JspException(murle.getMessage(), murle);
- }
- catch (IOException ioe) {
- throw new JspException(ioe.getMessage(), ioe);
- }
-
- return SKIP_BODY;
- }
-
- // ...else process the body
- return EVAL_BODY_BUFFERED;
- }
-
- /**
- * doEndTag implementation, that will perform XML Transformation on the
- * body content.
- *
- * @return super.doEndTag()
- */
-
- public int doEndTag() throws JspException {
- // Get body content (trim is CRUCIAL, as some XML parsers are picky...)
- String body = bodyContent.getString().trim();
-
- // Do transformation
- transform(new StreamSource(new ByteArrayInputStream(body.getBytes())));
-
- return super.doEndTag();
- }
-
- /**
- * Performs the transformation and writes the result to the JSP writer.
- *
- * @param in the source document to transform.
- */
-
- public void transform(Source pIn) throws JspException {
- try {
- // Create transformer
- Transformer transformer = TransformerFactory.newInstance()
- .newTransformer(getSource(mStylesheetURI));
-
- // Store temporary output in a bytearray, as the transformer will
- // usually try to flush the stream (illegal operation from a custom
- // tag).
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- StreamResult out = new StreamResult(os);
-
- // Perform the transformation
- transformer.transform(pIn, out);
-
- // Write the result back to the JSP writer
- pageContext.getOut().print(os.toString());
- }
- catch (MalformedURLException murle) {
- throw new JspException(murle.getMessage(), murle);
- }
- catch (IOException ioe) {
- throw new JspException(ioe.getMessage(), ioe);
- }
- catch (TransformerException te) {
- throw new JspException("XSLT Trandformation failed: " + te.getMessage(), te);
- }
- }
-
- /**
- * Returns a StreamSource object, for the given URI
- */
-
- private StreamSource getSource(String pURI)
- throws IOException, MalformedURLException {
- if (pURI != null && pURI.indexOf("://") < 0) {
- // If local, get as stream
- return new StreamSource(getResourceAsStream(pURI));
- }
-
- // ...else, create from URI string
- return new StreamSource(pURI);
- }
-}
+/*
+ * Copyright (c) 2002 TwelveMonkeys.
+ * All rights reserved.
+ *
+ * $Log: XMLTransformTag.java,v $
+ * Revision 1.2 2003/10/06 14:25:43 WMHAKUR
+ * Code clean-up only.
+ *
+ * Revision 1.1 2002/11/19 10:50:41 WMHAKUR
+ * *** empty log message ***
+ *
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib;
+
+import java.io.*;
+import java.net.*;
+
+import javax.servlet.jsp.*;
+import javax.xml.transform.*;
+import javax.xml.transform.stream.*;
+
+/**
+ * This tag performs XSL Transformations (XSLT) on a given XML document or its
+ * body content.
+ *
+ * @author Harald Kuhr
+ *
+ * @version $Id: jsp/taglib/XMLTransformTag.java#1 $
+ */
+
+public class XMLTransformTag extends ExBodyTagSupport {
+ private String mDocumentURI = null;
+ private String mStylesheetURI = null;
+
+ /**
+ * Sets the document attribute for this tag.
+ */
+
+ public void setDocumentURI(String pDocumentURI) {
+ mDocumentURI = pDocumentURI;
+ }
+
+ /**
+ * Sets the stylesheet attribute for this tag.
+ */
+
+ public void setStylesheetURI(String pStylesheetURI) {
+ mStylesheetURI = pStylesheetURI;
+ }
+
+
+ /**
+ * doStartTag implementation, that performs XML Transformation on the
+ * given document, if any.
+ * If the documentURI attribute is set, then the transformation is
+ * performed on the document at that location, and
+ * {@code Tag.SKIP_BODY} is returned.
+ * Otherwise, this method simply returns
+ * {@code BodyTag.EVAL_BODY_BUFFERED} and leaves the transformation to
+ * the doEndTag.
+ *
+ * @return {@code Tag.SKIP_BODY} if {@code documentURI} is not
+ * {@code null}, otherwise
+ * {@code BodyTag.EVAL_BODY_BUFFERED}.
+ *
+ * @todo Is it really a good idea to allow "inline" XML in a JSP?
+ */
+
+ public int doStartTag() throws JspException {
+ //log("XML: " + mDocumentURI + " XSL: " + mStylesheetURI);
+
+ if (mDocumentURI != null) {
+ // If document given, transform and skip body...
+ try {
+ transform(getSource(mDocumentURI));
+ }
+ catch (MalformedURLException murle) {
+ throw new JspException(murle.getMessage(), murle);
+ }
+ catch (IOException ioe) {
+ throw new JspException(ioe.getMessage(), ioe);
+ }
+
+ return SKIP_BODY;
+ }
+
+ // ...else process the body
+ return EVAL_BODY_BUFFERED;
+ }
+
+ /**
+ * doEndTag implementation, that will perform XML Transformation on the
+ * body content.
+ *
+ * @return super.doEndTag()
+ */
+
+ public int doEndTag() throws JspException {
+ // Get body content (trim is CRUCIAL, as some XML parsers are picky...)
+ String body = bodyContent.getString().trim();
+
+ // Do transformation
+ transform(new StreamSource(new ByteArrayInputStream(body.getBytes())));
+
+ return super.doEndTag();
+ }
+
+ /**
+ * Performs the transformation and writes the result to the JSP writer.
+ *
+ * @param in the source document to transform.
+ */
+
+ public void transform(Source pIn) throws JspException {
+ try {
+ // Create transformer
+ Transformer transformer = TransformerFactory.newInstance()
+ .newTransformer(getSource(mStylesheetURI));
+
+ // Store temporary output in a bytearray, as the transformer will
+ // usually try to flush the stream (illegal operation from a custom
+ // tag).
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ StreamResult out = new StreamResult(os);
+
+ // Perform the transformation
+ transformer.transform(pIn, out);
+
+ // Write the result back to the JSP writer
+ pageContext.getOut().print(os.toString());
+ }
+ catch (MalformedURLException murle) {
+ throw new JspException(murle.getMessage(), murle);
+ }
+ catch (IOException ioe) {
+ throw new JspException(ioe.getMessage(), ioe);
+ }
+ catch (TransformerException te) {
+ throw new JspException("XSLT Trandformation failed: " + te.getMessage(), te);
+ }
+ }
+
+ /**
+ * Returns a StreamSource object, for the given URI
+ */
+
+ private StreamSource getSource(String pURI)
+ throws IOException, MalformedURLException {
+ if (pURI != null && pURI.indexOf("://") < 0) {
+ // If local, get as stream
+ return new StreamSource(getResourceAsStream(pURI));
+ }
+
+ // ...else, create from URI string
+ return new StreamSource(pURI);
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java
index 4cb4136d..9a2bb605 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java
@@ -1,138 +1,138 @@
-/****************************************************
- * *
- * (c) 2000-2003 TwelveMonkeys *
- * All rights reserved *
- * http://www.twelvemonkeys.no *
- * *
- * $RCSfile: ConditionalTagBase.java,v $
- * @version $Revision: #1 $
- * $Date: 2008/05/05 $
- * *
- * @author Last modified by: $Author: haku $
- * *
- ****************************************************/
-
-
-
-/*
- * Produced (p) 2002 TwelveMonkeys
- * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
- * Phone : +47 22 57 70 00
- * Fax : +47 22 57 70 70
- */
-package com.twelvemonkeys.servlet.jsp.taglib.logic;
-
-
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.TagSupport;
-
-
-/**
- * An abstract base class for tags with some kind of conditional presentation of the tag body.
- *
- * @version 1.0
- * @author Eirik Torske
- */
-public abstract class ConditionalTagBase extends TagSupport {
-
- // Members
- protected String objectName;
- protected String objectValue;
-
- // Properties
-
- /**
- * Method getName
- *
- *
- * @return
- *
- */
- public String getName() {
- return objectName;
- }
-
- /**
- * Method setName
- *
- *
- * @param pObjectName
- *
- */
- public void setName(String pObjectName) {
- this.objectName = pObjectName;
- }
-
- /**
- * Method getValue
- *
- *
- * @return
- *
- */
- public String getValue() {
- return objectValue;
- }
-
- /**
- * Method setValue
- *
- *
- * @param pObjectValue
- *
- */
- public void setValue(String pObjectValue) {
- this.objectValue = pObjectValue;
- }
-
- /**
- * Perform the test required for this particular tag, and either evaluate or skip the body of this tag.
- *
- *
- * @return
- * @exception JspException if a JSP exception occurs.
- */
- public int doStartTag() throws JspException {
-
- if (condition()) {
- return (EVAL_BODY_INCLUDE);
- } else {
- return (SKIP_BODY);
- }
- }
-
- /**
- * Evaluate the remainder of the current page as normal.
- *
- *
- * @return
- * @exception JspException if a JSP exception occurs.
- */
- public int doEndTag() throws JspException {
- return (EVAL_PAGE);
- }
-
- /**
- * Release all allocated resources.
- */
- public void release() {
-
- super.release();
- objectName = null;
- objectValue = null;
- }
-
- /**
- * The condition that must be met in order to display the body of this tag.
- *
- * @exception JspException if a JSP exception occurs.
- * @return {@code true} if and only if all conditions are met.
- */
- protected abstract boolean condition() throws JspException;
-}
-
-
-/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
-
-
-/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/
+/****************************************************
+ * *
+ * (c) 2000-2003 TwelveMonkeys *
+ * All rights reserved *
+ * http://www.twelvemonkeys.no *
+ * *
+ * $RCSfile: ConditionalTagBase.java,v $
+ * @version $Revision: #1 $
+ * $Date: 2008/05/05 $
+ * *
+ * @author Last modified by: $Author: haku $
+ * *
+ ****************************************************/
+
+
+
+/*
+ * Produced (p) 2002 TwelveMonkeys
+ * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
+ * Phone : +47 22 57 70 00
+ * Fax : +47 22 57 70 70
+ */
+package com.twelvemonkeys.servlet.jsp.taglib.logic;
+
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.TagSupport;
+
+
+/**
+ * An abstract base class for tags with some kind of conditional presentation of the tag body.
+ *
+ * @version 1.0
+ * @author Eirik Torske
+ */
+public abstract class ConditionalTagBase extends TagSupport {
+
+ // Members
+ protected String objectName;
+ protected String objectValue;
+
+ // Properties
+
+ /**
+ * Method getName
+ *
+ *
+ * @return
+ *
+ */
+ public String getName() {
+ return objectName;
+ }
+
+ /**
+ * Method setName
+ *
+ *
+ * @param pObjectName
+ *
+ */
+ public void setName(String pObjectName) {
+ this.objectName = pObjectName;
+ }
+
+ /**
+ * Method getValue
+ *
+ *
+ * @return
+ *
+ */
+ public String getValue() {
+ return objectValue;
+ }
+
+ /**
+ * Method setValue
+ *
+ *
+ * @param pObjectValue
+ *
+ */
+ public void setValue(String pObjectValue) {
+ this.objectValue = pObjectValue;
+ }
+
+ /**
+ * Perform the test required for this particular tag, and either evaluate or skip the body of this tag.
+ *
+ *
+ * @return
+ * @exception JspException if a JSP exception occurs.
+ */
+ public int doStartTag() throws JspException {
+
+ if (condition()) {
+ return (EVAL_BODY_INCLUDE);
+ } else {
+ return (SKIP_BODY);
+ }
+ }
+
+ /**
+ * Evaluate the remainder of the current page as normal.
+ *
+ *
+ * @return
+ * @exception JspException if a JSP exception occurs.
+ */
+ public int doEndTag() throws JspException {
+ return (EVAL_PAGE);
+ }
+
+ /**
+ * Release all allocated resources.
+ */
+ public void release() {
+
+ super.release();
+ objectName = null;
+ objectValue = null;
+ }
+
+ /**
+ * The condition that must be met in order to display the body of this tag.
+ *
+ * @exception JspException if a JSP exception occurs.
+ * @return {@code true} if and only if all conditions are met.
+ */
+ protected abstract boolean condition() throws JspException;
+}
+
+
+/*--- Formatted in Sun Java Convention Style on ma, des 1, '03 ---*/
+
+
+/*------ Formatted by Jindent 3.23 Basic 1.0 --- http://www.jindent.de ------*/
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java
index a3a856c5..8726bb8b 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java
@@ -1,168 +1,168 @@
-/*
- * Produced (p) 2002 TwelveMonkeys
- * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
- * Phone : +47 22 57 70 00
- * Fax : +47 22 57 70 70
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib.logic;
-
-
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.jsp.JspException;
-
-
-/**
- *
- * Custom tag for testing equality of an attribute against a given value.
- * The attribute types supported so far is:
- *
- * - {@code java.lang.String} (ver. 1.0)
- *
- {@code javax.servlet.http.Cookie} (ver. 1.0)
- *
- *
- * See the implemented {@code condition} method for details regarding the equality conditions.
- *
- *
- *
- * Tag Reference
- *
- *
- * equal |
- * Availability: 1.0 |
- *
- *
- * Tag for testing if an attribute is equal to a given value. |
- *
- *
- * Tag Body |
- * JSP |
- * |
- * |
- * |
- * |
- *
- *
- * Restrictions |
- * None |
- *
- *
- *
- * Attributes |
- * Name |
- * Required |
- * Runtime Expression Evaluation |
- * Availability |
- *
- *
- *
- * |
- * name |
- * Yes |
- * Yes |
- * 1.0 |
- *
- *
- * |
- * The attribute name |
- *
- *
- *
- * |
- * value |
- * No |
- * Yes |
- * 1.0 |
- *
- *
- * |
- * The value for equality testing |
- *
- *
- *
- * Variables |
- * None |
- *
- *
- *
- * Examples |
- *
- *
- *<%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %>
- *<bean:cookie id="logonUsernameCookie"
- * name="<%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %>"
- * value="no_username_set" />
- *<twelvemonkeys:equal name="logonUsernameCookie" value="no_username_set">
- * <html:text property="username" />
- *</twelvemonkeys:equal>
- *
- * |
- *
- *
- *
- *
- *
- * @version 1.0
- * @author Eirik Torske
- * @see notEqual
- */
-public class EqualTag extends ConditionalTagBase {
-
- /**
- *
- *
- * The conditions that must be met in order to display the body of this tag:
- *
- * - The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
- *
- The attribute must exist.
- *
- The attribute must be an instance of one of the supported classes:
- *
- * - {@code java.lang.String}
- *
- {@code javax.servlet.http.Cookie}
- *
- * - The value of the attribute must be equal to the object value property ({@code value} -> {@code mObjectValue}).
- *
- *
- * NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
- *
- *
- * @return {@code true} if and only if all conditions are met.
- */
- protected boolean condition() throws JspException {
-
- if (StringUtil.isEmpty(objectName)) {
- return false;
- }
-
- if (StringUtil.isEmpty(objectValue)) {
- return true;
- }
-
- Object pageScopedAttribute = pageContext.getAttribute(objectName);
- if (pageScopedAttribute == null) {
- return false;
- }
-
- String pageScopedStringAttribute;
-
- // String
- if (pageScopedAttribute instanceof String) {
- pageScopedStringAttribute = (String) pageScopedAttribute;
-
- // Cookie
- }
- else if (pageScopedAttribute instanceof Cookie) {
- pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
-
- // Type not yet supported...
- }
- else {
- return false;
- }
-
- return (pageScopedStringAttribute.equals(objectValue));
- }
-
-}
+/*
+ * Produced (p) 2002 TwelveMonkeys
+ * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
+ * Phone : +47 22 57 70 00
+ * Fax : +47 22 57 70 70
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib.logic;
+
+
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.jsp.JspException;
+
+
+/**
+ *
+ * Custom tag for testing equality of an attribute against a given value.
+ * The attribute types supported so far is:
+ *
+ * - {@code java.lang.String} (ver. 1.0)
+ *
- {@code javax.servlet.http.Cookie} (ver. 1.0)
+ *
+ *
+ * See the implemented {@code condition} method for details regarding the equality conditions.
+ *
+ *
+ *
+ * Tag Reference
+ *
+ *
+ * equal |
+ * Availability: 1.0 |
+ *
+ *
+ * Tag for testing if an attribute is equal to a given value. |
+ *
+ *
+ * Tag Body |
+ * JSP |
+ * |
+ * |
+ * |
+ * |
+ *
+ *
+ * Restrictions |
+ * None |
+ *
+ *
+ *
+ * Attributes |
+ * Name |
+ * Required |
+ * Runtime Expression Evaluation |
+ * Availability |
+ *
+ *
+ *
+ * |
+ * name |
+ * Yes |
+ * Yes |
+ * 1.0 |
+ *
+ *
+ * |
+ * The attribute name |
+ *
+ *
+ *
+ * |
+ * value |
+ * No |
+ * Yes |
+ * 1.0 |
+ *
+ *
+ * |
+ * The value for equality testing |
+ *
+ *
+ *
+ * Variables |
+ * None |
+ *
+ *
+ *
+ * Examples |
+ *
+ *
+ *<%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %>
+ *<bean:cookie id="logonUsernameCookie"
+ * name="<%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %>"
+ * value="no_username_set" />
+ *<twelvemonkeys:equal name="logonUsernameCookie" value="no_username_set">
+ * <html:text property="username" />
+ *</twelvemonkeys:equal>
+ *
+ * |
+ *
+ *
+ *
+ *
+ *
+ * @version 1.0
+ * @author Eirik Torske
+ * @see notEqual
+ */
+public class EqualTag extends ConditionalTagBase {
+
+ /**
+ *
+ *
+ * The conditions that must be met in order to display the body of this tag:
+ *
+ * - The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
+ *
- The attribute must exist.
+ *
- The attribute must be an instance of one of the supported classes:
+ *
+ * - {@code java.lang.String}
+ *
- {@code javax.servlet.http.Cookie}
+ *
+ * - The value of the attribute must be equal to the object value property ({@code value} -> {@code mObjectValue}).
+ *
+ *
+ * NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
+ *
+ *
+ * @return {@code true} if and only if all conditions are met.
+ */
+ protected boolean condition() throws JspException {
+
+ if (StringUtil.isEmpty(objectName)) {
+ return false;
+ }
+
+ if (StringUtil.isEmpty(objectValue)) {
+ return true;
+ }
+
+ Object pageScopedAttribute = pageContext.getAttribute(objectName);
+ if (pageScopedAttribute == null) {
+ return false;
+ }
+
+ String pageScopedStringAttribute;
+
+ // String
+ if (pageScopedAttribute instanceof String) {
+ pageScopedStringAttribute = (String) pageScopedAttribute;
+
+ // Cookie
+ }
+ else if (pageScopedAttribute instanceof Cookie) {
+ pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
+
+ // Type not yet supported...
+ }
+ else {
+ return false;
+ }
+
+ return (pageScopedStringAttribute.equals(objectValue));
+ }
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java
index 013c11b1..1dcb412a 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java
@@ -1,40 +1,40 @@
-package com.twelvemonkeys.servlet.jsp.taglib.logic;
-
-import javax.servlet.jsp.tagext.*;
-
-/**
- * TagExtraInfo class for IteratorProvider tags.
- *
- * @author Harald Kuhr
- * @version $id: $
- */
-public class IteratorProviderTEI extends TagExtraInfo {
- /**
- * Gets the variable info for IteratorProvider tags. The attribute with the
- * name defined by the "id" attribute and type defined by the "type"
- * attribute is declared with scope {@code VariableInfo.AT_END}.
- *
- * @param pData TagData instance provided by container
- * @return an VariableInfo array of lenght 1, containing the attribute
- * defined by the id parameter, declared, and with scope
- * {@code VariableInfo.AT_END}.
- */
- public VariableInfo[] getVariableInfo(TagData pData) {
- // Get attribute name
- String attributeName = pData.getId();
- if (attributeName == null) {
- attributeName = IteratorProviderTag.getDefaultIteratorName();
- }
-
- // Get type
- String type = pData.getAttributeString(IteratorProviderTag.ATTRIBUTE_TYPE);
- if (type == null) {
- type = IteratorProviderTag.getDefaultIteratorType();
- }
-
- // Return the variable info
- return new VariableInfo[]{
- new VariableInfo(attributeName, type, true, VariableInfo.AT_END),
- };
- }
-}
+package com.twelvemonkeys.servlet.jsp.taglib.logic;
+
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * TagExtraInfo class for IteratorProvider tags.
+ *
+ * @author Harald Kuhr
+ * @version $id: $
+ */
+public class IteratorProviderTEI extends TagExtraInfo {
+ /**
+ * Gets the variable info for IteratorProvider tags. The attribute with the
+ * name defined by the "id" attribute and type defined by the "type"
+ * attribute is declared with scope {@code VariableInfo.AT_END}.
+ *
+ * @param pData TagData instance provided by container
+ * @return an VariableInfo array of lenght 1, containing the attribute
+ * defined by the id parameter, declared, and with scope
+ * {@code VariableInfo.AT_END}.
+ */
+ public VariableInfo[] getVariableInfo(TagData pData) {
+ // Get attribute name
+ String attributeName = pData.getId();
+ if (attributeName == null) {
+ attributeName = IteratorProviderTag.getDefaultIteratorName();
+ }
+
+ // Get type
+ String type = pData.getAttributeString(IteratorProviderTag.ATTRIBUTE_TYPE);
+ if (type == null) {
+ type = IteratorProviderTag.getDefaultIteratorType();
+ }
+
+ // Return the variable info
+ return new VariableInfo[]{
+ new VariableInfo(attributeName, type, true, VariableInfo.AT_END),
+ };
+ }
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java
index 6409664d..1fd61a1a 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java
@@ -1,86 +1,86 @@
-package com.twelvemonkeys.servlet.jsp.taglib.logic;
-
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.tagext.Tag;
-import javax.servlet.jsp.tagext.TagSupport;
-import java.util.Iterator;
-
-/**
- * Abstract base class for adding iterators to a page.
- *
- * @todo Possible to use same strategy for all types of objects? Rename class
- * to ObjectProviderTag? Hmmm... Might work.
- *
- * @author Harald Kuhr
- * @version $id: $
- */
-public abstract class IteratorProviderTag extends TagSupport {
- /** {@code iterator} */
- protected final static String DEFAULT_ITERATOR_NAME = "iterator";
- /** {@code java.util.iterator} */
- protected final static String DEFAULT_ITERATOR_TYPE = "java.util.Iterator";
- /** {@code type} */
- public final static String ATTRIBUTE_TYPE = "type";
-
- /** */
- private String type = null;
-
- /**
- * Gets the type.
- *
- * @return the type (class name)
- */
- public String getType() {
- return type;
- }
-
- /**
- * Sets the type.
- *
- * @param pType
- */
-
- public void setType(String pType) {
- type = pType;
- }
-
- /**
- * doEndTag implementation.
- *
- * @return {@code Tag.EVAL_PAGE}
- * @throws JspException
- */
-
- public int doEndTag() throws JspException {
- // Set the iterator
- pageContext.setAttribute(getId(), getIterator());
-
- return Tag.EVAL_PAGE;
- }
-
- /**
- * Gets the iterator for this tag.
- *
- * @return an {@link java.util.Iterator}
- */
- protected abstract Iterator getIterator();
-
- /**
- * Gets the default iterator name.
- *
- * @return {@link #DEFAULT_ITERATOR_NAME}
- */
- protected static String getDefaultIteratorName() {
- return DEFAULT_ITERATOR_NAME;
- }
-
- /**
- * Gets the default iterator type.
- *
- * @return {@link #DEFAULT_ITERATOR_TYPE}
- */
- protected static String getDefaultIteratorType() {
- return DEFAULT_ITERATOR_TYPE;
- }
-
-}
+package com.twelvemonkeys.servlet.jsp.taglib.logic;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.tagext.Tag;
+import javax.servlet.jsp.tagext.TagSupport;
+import java.util.Iterator;
+
+/**
+ * Abstract base class for adding iterators to a page.
+ *
+ * @todo Possible to use same strategy for all types of objects? Rename class
+ * to ObjectProviderTag? Hmmm... Might work.
+ *
+ * @author Harald Kuhr
+ * @version $id: $
+ */
+public abstract class IteratorProviderTag extends TagSupport {
+ /** {@code iterator} */
+ protected final static String DEFAULT_ITERATOR_NAME = "iterator";
+ /** {@code java.util.iterator} */
+ protected final static String DEFAULT_ITERATOR_TYPE = "java.util.Iterator";
+ /** {@code type} */
+ public final static String ATTRIBUTE_TYPE = "type";
+
+ /** */
+ private String type = null;
+
+ /**
+ * Gets the type.
+ *
+ * @return the type (class name)
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type.
+ *
+ * @param pType
+ */
+
+ public void setType(String pType) {
+ type = pType;
+ }
+
+ /**
+ * doEndTag implementation.
+ *
+ * @return {@code Tag.EVAL_PAGE}
+ * @throws JspException
+ */
+
+ public int doEndTag() throws JspException {
+ // Set the iterator
+ pageContext.setAttribute(getId(), getIterator());
+
+ return Tag.EVAL_PAGE;
+ }
+
+ /**
+ * Gets the iterator for this tag.
+ *
+ * @return an {@link java.util.Iterator}
+ */
+ protected abstract Iterator getIterator();
+
+ /**
+ * Gets the default iterator name.
+ *
+ * @return {@link #DEFAULT_ITERATOR_NAME}
+ */
+ protected static String getDefaultIteratorName() {
+ return DEFAULT_ITERATOR_NAME;
+ }
+
+ /**
+ * Gets the default iterator type.
+ *
+ * @return {@link #DEFAULT_ITERATOR_TYPE}
+ */
+ protected static String getDefaultIteratorType() {
+ return DEFAULT_ITERATOR_TYPE;
+ }
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java
index 3c489957..72082b08 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java
@@ -1,168 +1,168 @@
-/*
- * Produced (p) 2002 TwelveMonkeys
- * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
- * Phone : +47 22 57 70 00
- * Fax : +47 22 57 70 70
- */
-
-package com.twelvemonkeys.servlet.jsp.taglib.logic;
-
-
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.http.Cookie;
-import javax.servlet.jsp.JspException;
-
-
-/**
- *
- * Custom tag for testing non-equality of an attribute against a given value.
- * The attribute types supported so far is:
- *
- * - {@code java.lang.String} (ver. 1.0)
- *
- {@code javax.servlet.http.Cookie} (ver. 1.0)
- *
- *
- * See the implemented {@code condition} method for details regarding the non-equality conditions.
- *
- *
- *
- * Tag Reference
- *
- *
- * notEqual |
- * Availability: 1.0 |
- *
- *
- * Tag for testing if an attribute is NOT equal to a given value. |
- *
- *
- * Tag Body |
- * JSP |
- * |
- * |
- * |
- * |
- *
- *
- * Restrictions |
- * None |
- *
- *
- *
- * Attributes |
- * Name |
- * Required |
- * Runtime Expression Evaluation |
- * Availability |
- *
- *
- *
- * |
- * name |
- * Yes |
- * Yes |
- * 1.0 |
- *
- *
- * |
- * The attribute name |
- *
- *
- *
- * |
- * value |
- * No |
- * Yes |
- * 1.0 |
- *
- *
- * |
- * The value for equality testing |
- *
- *
- *
- * Variables |
- * None |
- *
- *
- *
- * Examples |
- *
- *
- *<%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %>
- *<bean:cookie id="logonUsernameCookie"
- * name="<%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %>"
- * value="no_username_set" />
- *<twelvemonkeys:notEqual name="logonUsernameCookie" value="no_username_set">
- * <html:text property="username" value="<%= logonUsernameCookie.getValue() %>" />
- *</twelvemonkeys:notEqual>
- *
- * |
- *
- *
- *
- *
- *
- * @version 1.0
- * @author Eirik Torske
- * @see equal
- */
-public class NotEqualTag extends ConditionalTagBase {
-
- /**
- *
- *
- * The condition that must be met in order to display the body of this tag:
- *
- * - The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
- *
- The attribute must exist.
- *
- The attribute must be an instance of one of the supported classes:
- *
- * - {@code java.lang.String}
- *
- {@code javax.servlet.http.Cookie}
- *
- * - The value of the attribute must NOT be equal to the object value property ({@code value} -> {@code mObjectValue}).
- *
- *
- * NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
- *
- *
- * @return {@code true} if and only if all conditions are met.
- */
- protected boolean condition() throws JspException {
-
- if (StringUtil.isEmpty(objectName)) {
- return false;
- }
-
- if (StringUtil.isEmpty(objectValue)) {
- return true;
- }
-
- Object pageScopedAttribute = pageContext.getAttribute(objectName);
- if (pageScopedAttribute == null) {
- return false;
- }
-
- String pageScopedStringAttribute;
-
- // String
- if (pageScopedAttribute instanceof String) {
- pageScopedStringAttribute = (String) pageScopedAttribute;
-
- // Cookie
- }
- else if (pageScopedAttribute instanceof Cookie) {
- pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
-
- // Type not yet supported...
- }
- else {
- return false;
- }
-
- return (!(pageScopedStringAttribute.equals(objectValue)));
- }
-
-}
+/*
+ * Produced (p) 2002 TwelveMonkeys
+ * Address : Svovelstikka 1, Box 6432 Etterstad, 0605 Oslo, Norway.
+ * Phone : +47 22 57 70 00
+ * Fax : +47 22 57 70 70
+ */
+
+package com.twelvemonkeys.servlet.jsp.taglib.logic;
+
+
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.jsp.JspException;
+
+
+/**
+ *
+ * Custom tag for testing non-equality of an attribute against a given value.
+ * The attribute types supported so far is:
+ *
+ * - {@code java.lang.String} (ver. 1.0)
+ *
- {@code javax.servlet.http.Cookie} (ver. 1.0)
+ *
+ *
+ * See the implemented {@code condition} method for details regarding the non-equality conditions.
+ *
+ *
+ *
+ * Tag Reference
+ *
+ *
+ * notEqual |
+ * Availability: 1.0 |
+ *
+ *
+ * Tag for testing if an attribute is NOT equal to a given value. |
+ *
+ *
+ * Tag Body |
+ * JSP |
+ * |
+ * |
+ * |
+ * |
+ *
+ *
+ * Restrictions |
+ * None |
+ *
+ *
+ *
+ * Attributes |
+ * Name |
+ * Required |
+ * Runtime Expression Evaluation |
+ * Availability |
+ *
+ *
+ *
+ * |
+ * name |
+ * Yes |
+ * Yes |
+ * 1.0 |
+ *
+ *
+ * |
+ * The attribute name |
+ *
+ *
+ *
+ * |
+ * value |
+ * No |
+ * Yes |
+ * 1.0 |
+ *
+ *
+ * |
+ * The value for equality testing |
+ *
+ *
+ *
+ * Variables |
+ * None |
+ *
+ *
+ *
+ * Examples |
+ *
+ *
+ *<%@ taglib prefix="twelvemonkeys" uri="twelvemonkeys-logic" %>
+ *<bean:cookie id="logonUsernameCookie"
+ * name="<%= com.strutscommand.Constants.LOGON_USERNAME_COOKIE_NAME %>"
+ * value="no_username_set" />
+ *<twelvemonkeys:notEqual name="logonUsernameCookie" value="no_username_set">
+ * <html:text property="username" value="<%= logonUsernameCookie.getValue() %>" />
+ *</twelvemonkeys:notEqual>
+ *
+ * |
+ *
+ *
+ *
+ *
+ *
+ * @version 1.0
+ * @author Eirik Torske
+ * @see equal
+ */
+public class NotEqualTag extends ConditionalTagBase {
+
+ /**
+ *
+ *
+ * The condition that must be met in order to display the body of this tag:
+ *
+ * - The attribute name property ({@code name} -> {@code mObjectName}) must not be empty.
+ *
- The attribute must exist.
+ *
- The attribute must be an instance of one of the supported classes:
+ *
+ * - {@code java.lang.String}
+ *
- {@code javax.servlet.http.Cookie}
+ *
+ * - The value of the attribute must NOT be equal to the object value property ({@code value} -> {@code mObjectValue}).
+ *
+ *
+ * NB! If the object value property ({@code value} -> {@code mObjectValue}) is empty than {@code true} will be returned.
+ *
+ *
+ * @return {@code true} if and only if all conditions are met.
+ */
+ protected boolean condition() throws JspException {
+
+ if (StringUtil.isEmpty(objectName)) {
+ return false;
+ }
+
+ if (StringUtil.isEmpty(objectValue)) {
+ return true;
+ }
+
+ Object pageScopedAttribute = pageContext.getAttribute(objectName);
+ if (pageScopedAttribute == null) {
+ return false;
+ }
+
+ String pageScopedStringAttribute;
+
+ // String
+ if (pageScopedAttribute instanceof String) {
+ pageScopedStringAttribute = (String) pageScopedAttribute;
+
+ // Cookie
+ }
+ else if (pageScopedAttribute instanceof Cookie) {
+ pageScopedStringAttribute = ((Cookie) pageScopedAttribute).getValue();
+
+ // Type not yet supported...
+ }
+ else {
+ return false;
+ }
+
+ return (!(pageScopedStringAttribute.equals(objectValue)));
+ }
+
+}
diff --git a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java
index 19194b5c..0599a468 100755
--- a/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java
+++ b/sandbox/sandbox-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java
@@ -1,183 +1,183 @@
-package com.twelvemonkeys.servlet.log4j;
-
-import org.apache.log4j.Logger;
-
-import javax.servlet.RequestDispatcher;
-import javax.servlet.Servlet;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import java.io.InputStream;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.Set;
-
-/**
- * Log4JContextWrapper
- *
- *
- * @author Harald Kuhr
- * @version $Id: log4j/Log4JContextWrapper.java#1 $
- */
-final class Log4JContextWrapper implements ServletContext {
- // TODO: Move to sandbox
-
- // TODO: This solution sucks...
- // How about starting to create some kind of pluggable decorator system,
- // something along the lines of AOP mixins/interceptor pattern..
- // Probably using a dynamic Proxy, delegating to the mixins and or the
- // wrapped object based on configuration.
- // This way we could simply call ServletUtil.decorate(ServletContext):ServletContext
- // And the context would be decorated with all configured mixins at once,
- // requiring less boilerplate delegation code, and less layers of wrapping
- // (alternatively we could decorate the Servlet/FilterConfig objects).
- // See the ServletUtil.createWrapper methods for some hints..
-
-
- // Something like this:
- public static ServletContext wrap(final ServletContext pContext, final Object[] pDelegates, final ClassLoader pLoader) {
- ClassLoader cl = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
-
- // TODO: Create a "static" mapping between methods in the ServletContext
- // and the corresponding delegate
-
- // TODO: Resolve super-invokations, to delegate to next delegate in
- // chain, and finally invoke pContext
-
- return (ServletContext) Proxy.newProxyInstance(cl, new Class[] {ServletContext.class}, new InvocationHandler() {
- public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable {
- // TODO: Test if any of the delegates should receive, if so invoke
-
- // Else, invoke on original object
- return pMethod.invoke(pContext, pArgs);
- }
- });
- }
-
- private final ServletContext context;
-
- private final Logger logger;
-
- Log4JContextWrapper(ServletContext pContext) {
- context = pContext;
-
- // TODO: We want a logger per servlet, not per servlet context, right?
- logger = Logger.getLogger(pContext.getServletContextName());
-
- // TODO: Automatic init/config of Log4J using context parameter for log4j.xml?
- // See Log4JInit.java
-
- // TODO: Automatic config of properties in the context wrapper?
- }
-
- public final void log(final Exception pException, final String pMessage) {
- log(pMessage, pException);
- }
-
- // TODO: Add more logging methods to interface info/warn/error?
- // TODO: Implement these mehtods in GenericFilter/GenericServlet?
-
- public void log(String pMessage) {
- // TODO: Get logger for caller..
- // Should be possible using some stack peek hack, but that's slow...
- // Find a good way...
- // Maybe just pass it into the constuctor, and have one wrapper per servlet
- logger.info(pMessage);
- }
-
- public void log(String pMessage, Throwable pCause) {
- // TODO: Get logger for caller..
-
- logger.error(pMessage, pCause);
- }
-
- public Object getAttribute(String pMessage) {
- return context.getAttribute(pMessage);
- }
-
- public Enumeration getAttributeNames() {
- return context.getAttributeNames();
- }
-
- public ServletContext getContext(String pMessage) {
- return context.getContext(pMessage);
- }
-
- public String getInitParameter(String pMessage) {
- return context.getInitParameter(pMessage);
- }
-
- public Enumeration getInitParameterNames() {
- return context.getInitParameterNames();
- }
-
- public int getMajorVersion() {
- return context.getMajorVersion();
- }
-
- public String getMimeType(String pMessage) {
- return context.getMimeType(pMessage);
- }
-
- public int getMinorVersion() {
- return context.getMinorVersion();
- }
-
- public RequestDispatcher getNamedDispatcher(String pMessage) {
- return context.getNamedDispatcher(pMessage);
- }
-
- public String getRealPath(String pMessage) {
- return context.getRealPath(pMessage);
- }
-
- public RequestDispatcher getRequestDispatcher(String pMessage) {
- return context.getRequestDispatcher(pMessage);
- }
-
- public URL getResource(String pMessage) throws MalformedURLException {
- return context.getResource(pMessage);
- }
-
- public InputStream getResourceAsStream(String pMessage) {
- return context.getResourceAsStream(pMessage);
- }
-
- public Set getResourcePaths(String pMessage) {
- return context.getResourcePaths(pMessage);
- }
-
- public String getServerInfo() {
- return context.getServerInfo();
- }
-
- public Servlet getServlet(String pMessage) throws ServletException {
- //noinspection deprecation
- return context.getServlet(pMessage);
- }
-
- public String getServletContextName() {
- return context.getServletContextName();
- }
-
- public Enumeration getServletNames() {
- //noinspection deprecation
- return context.getServletNames();
- }
-
- public Enumeration getServlets() {
- //noinspection deprecation
- return context.getServlets();
- }
-
- public void removeAttribute(String pMessage) {
- context.removeAttribute(pMessage);
- }
-
- public void setAttribute(String pMessage, Object pExtension) {
- context.setAttribute(pMessage, pExtension);
- }
-}
+package com.twelvemonkeys.servlet.log4j;
+
+import org.apache.log4j.Logger;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.Servlet;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Set;
+
+/**
+ * Log4JContextWrapper
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: log4j/Log4JContextWrapper.java#1 $
+ */
+final class Log4JContextWrapper implements ServletContext {
+ // TODO: Move to sandbox
+
+ // TODO: This solution sucks...
+ // How about starting to create some kind of pluggable decorator system,
+ // something along the lines of AOP mixins/interceptor pattern..
+ // Probably using a dynamic Proxy, delegating to the mixins and or the
+ // wrapped object based on configuration.
+ // This way we could simply call ServletUtil.decorate(ServletContext):ServletContext
+ // And the context would be decorated with all configured mixins at once,
+ // requiring less boilerplate delegation code, and less layers of wrapping
+ // (alternatively we could decorate the Servlet/FilterConfig objects).
+ // See the ServletUtil.createWrapper methods for some hints..
+
+
+ // Something like this:
+ public static ServletContext wrap(final ServletContext pContext, final Object[] pDelegates, final ClassLoader pLoader) {
+ ClassLoader cl = pLoader != null ? pLoader : Thread.currentThread().getContextClassLoader();
+
+ // TODO: Create a "static" mapping between methods in the ServletContext
+ // and the corresponding delegate
+
+ // TODO: Resolve super-invokations, to delegate to next delegate in
+ // chain, and finally invoke pContext
+
+ return (ServletContext) Proxy.newProxyInstance(cl, new Class[] {ServletContext.class}, new InvocationHandler() {
+ public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable {
+ // TODO: Test if any of the delegates should receive, if so invoke
+
+ // Else, invoke on original object
+ return pMethod.invoke(pContext, pArgs);
+ }
+ });
+ }
+
+ private final ServletContext context;
+
+ private final Logger logger;
+
+ Log4JContextWrapper(ServletContext pContext) {
+ context = pContext;
+
+ // TODO: We want a logger per servlet, not per servlet context, right?
+ logger = Logger.getLogger(pContext.getServletContextName());
+
+ // TODO: Automatic init/config of Log4J using context parameter for log4j.xml?
+ // See Log4JInit.java
+
+ // TODO: Automatic config of properties in the context wrapper?
+ }
+
+ public final void log(final Exception pException, final String pMessage) {
+ log(pMessage, pException);
+ }
+
+ // TODO: Add more logging methods to interface info/warn/error?
+ // TODO: Implement these mehtods in GenericFilter/GenericServlet?
+
+ public void log(String pMessage) {
+ // TODO: Get logger for caller..
+ // Should be possible using some stack peek hack, but that's slow...
+ // Find a good way...
+ // Maybe just pass it into the constuctor, and have one wrapper per servlet
+ logger.info(pMessage);
+ }
+
+ public void log(String pMessage, Throwable pCause) {
+ // TODO: Get logger for caller..
+
+ logger.error(pMessage, pCause);
+ }
+
+ public Object getAttribute(String pMessage) {
+ return context.getAttribute(pMessage);
+ }
+
+ public Enumeration getAttributeNames() {
+ return context.getAttributeNames();
+ }
+
+ public ServletContext getContext(String pMessage) {
+ return context.getContext(pMessage);
+ }
+
+ public String getInitParameter(String pMessage) {
+ return context.getInitParameter(pMessage);
+ }
+
+ public Enumeration getInitParameterNames() {
+ return context.getInitParameterNames();
+ }
+
+ public int getMajorVersion() {
+ return context.getMajorVersion();
+ }
+
+ public String getMimeType(String pMessage) {
+ return context.getMimeType(pMessage);
+ }
+
+ public int getMinorVersion() {
+ return context.getMinorVersion();
+ }
+
+ public RequestDispatcher getNamedDispatcher(String pMessage) {
+ return context.getNamedDispatcher(pMessage);
+ }
+
+ public String getRealPath(String pMessage) {
+ return context.getRealPath(pMessage);
+ }
+
+ public RequestDispatcher getRequestDispatcher(String pMessage) {
+ return context.getRequestDispatcher(pMessage);
+ }
+
+ public URL getResource(String pMessage) throws MalformedURLException {
+ return context.getResource(pMessage);
+ }
+
+ public InputStream getResourceAsStream(String pMessage) {
+ return context.getResourceAsStream(pMessage);
+ }
+
+ public Set getResourcePaths(String pMessage) {
+ return context.getResourcePaths(pMessage);
+ }
+
+ public String getServerInfo() {
+ return context.getServerInfo();
+ }
+
+ public Servlet getServlet(String pMessage) throws ServletException {
+ //noinspection deprecation
+ return context.getServlet(pMessage);
+ }
+
+ public String getServletContextName() {
+ return context.getServletContextName();
+ }
+
+ public Enumeration getServletNames() {
+ //noinspection deprecation
+ return context.getServletNames();
+ }
+
+ public Enumeration getServlets() {
+ //noinspection deprecation
+ return context.getServlets();
+ }
+
+ public void removeAttribute(String pMessage) {
+ context.removeAttribute(pMessage);
+ }
+
+ public void setAttribute(String pMessage, Object pExtension) {
+ context.setAttribute(pMessage, pExtension);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java b/servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java
index 004b1274..894dbf6c 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java
@@ -1,118 +1,118 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.Enumeration;
-
-/**
- * DebugServlet class description.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: DebugServlet.java#1 $
- */
-public class DebugServlet extends GenericServlet {
- private long dateModified;
-
- public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
- service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
- }
-
- public void init() throws ServletException {
- super.init();
- dateModified = System.currentTimeMillis();
- }
-
- public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
- pResponse.setContentType("text/plain");
- // Include these to allow browser caching
- pResponse.setDateHeader("Last-Modified", dateModified);
- pResponse.setHeader("ETag", getServletName());
-
- ServletOutputStream out = pResponse.getOutputStream();
-
- out.println("Remote address: " + pRequest.getRemoteAddr());
- out.println("Remote host name: " + pRequest.getRemoteHost());
- out.println("Remote user: " + pRequest.getRemoteUser());
- out.println();
-
- out.println("Request Method: " + pRequest.getMethod());
- out.println("Request Scheme: " + pRequest.getScheme());
- out.println("Request URI: " + pRequest.getRequestURI());
- out.println("Request URL: " + pRequest.getRequestURL().toString());
- out.println("Request PathInfo: " + pRequest.getPathInfo());
- out.println("Request ContentLength: " + pRequest.getContentLength());
- out.println();
-
- out.println("Request Headers:");
- Enumeration headerNames = pRequest.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String headerName = (String) headerNames.nextElement();
- Enumeration headerValues = pRequest.getHeaders(headerName);
-
- if (headerName != null) {
- while (headerValues.hasMoreElements()) {
- String value = (String) headerValues.nextElement();
- out.println(" " + headerName + ": " + value);
- }
- }
- }
- out.println();
-
- out.println("Request parameters:");
- Enumeration paramNames = pRequest.getParameterNames();
- while (paramNames.hasMoreElements()) {
- String name = (String) paramNames.nextElement();
- String[] values = pRequest.getParameterValues(name);
-
- for (String value : values) {
- out.println(" " + name + ": " + value);
- }
- }
- out.println();
-
- out.println("Request attributes:");
- Enumeration attribNames = pRequest.getAttributeNames();
- while (attribNames.hasMoreElements()) {
- String name = (String) attribNames.nextElement();
- Object value = pRequest.getAttribute(name);
- out.println(" " + name + ": " + value);
- }
-
-
- out.flush();
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.Enumeration;
+
+/**
+ * DebugServlet class description.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: DebugServlet.java#1 $
+ */
+public class DebugServlet extends GenericServlet {
+ private long dateModified;
+
+ public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
+ service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
+ }
+
+ public void init() throws ServletException {
+ super.init();
+ dateModified = System.currentTimeMillis();
+ }
+
+ public void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
+ pResponse.setContentType("text/plain");
+ // Include these to allow browser caching
+ pResponse.setDateHeader("Last-Modified", dateModified);
+ pResponse.setHeader("ETag", getServletName());
+
+ ServletOutputStream out = pResponse.getOutputStream();
+
+ out.println("Remote address: " + pRequest.getRemoteAddr());
+ out.println("Remote host name: " + pRequest.getRemoteHost());
+ out.println("Remote user: " + pRequest.getRemoteUser());
+ out.println();
+
+ out.println("Request Method: " + pRequest.getMethod());
+ out.println("Request Scheme: " + pRequest.getScheme());
+ out.println("Request URI: " + pRequest.getRequestURI());
+ out.println("Request URL: " + pRequest.getRequestURL().toString());
+ out.println("Request PathInfo: " + pRequest.getPathInfo());
+ out.println("Request ContentLength: " + pRequest.getContentLength());
+ out.println();
+
+ out.println("Request Headers:");
+ Enumeration headerNames = pRequest.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = (String) headerNames.nextElement();
+ Enumeration headerValues = pRequest.getHeaders(headerName);
+
+ if (headerName != null) {
+ while (headerValues.hasMoreElements()) {
+ String value = (String) headerValues.nextElement();
+ out.println(" " + headerName + ": " + value);
+ }
+ }
+ }
+ out.println();
+
+ out.println("Request parameters:");
+ Enumeration paramNames = pRequest.getParameterNames();
+ while (paramNames.hasMoreElements()) {
+ String name = (String) paramNames.nextElement();
+ String[] values = pRequest.getParameterValues(name);
+
+ for (String value : values) {
+ out.println(" " + name + ": " + value);
+ }
+ }
+ out.println();
+
+ out.println("Request attributes:");
+ Enumeration attribNames = pRequest.getAttributeNames();
+ while (attribNames.hasMoreElements()) {
+ String name = (String) attribNames.nextElement();
+ Object value = pRequest.getAttribute(name);
+ out.println(" " + name + ": " + value);
+ }
+
+
+ out.flush();
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java
index 29541b16..6397e182 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java
@@ -1,382 +1,382 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.BeanUtil;
-
-import javax.servlet.*;
-import java.io.IOException;
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.util.Enumeration;
-
-/**
- * Defines a generic, protocol-independent filter.
- *
- * {@code GenericFilter} is inspired by {@link GenericServlet}, and
- * implements the {@code Filter} and {@code FilterConfig} interfaces.
- *
- * {@code GenericFilter} makes writing filters easier. It provides simple
- * versions of the lifecycle methods {@code init} and {@code destroy}
- * and of the methods in the {@code FilterConfig} interface.
- * {@code GenericFilter} also implements the {@code log} methods,
- * declared in the {@code ServletContext} interface.
- *
- * To write a generic filter, you need only override the abstract
- * {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: GenericFilter.java#1 $
- *
- * @see Filter
- * @see FilterConfig
- */
-public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
- // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
-
- /**
- * The filter config.
- */
- private transient FilterConfig filterConfig = null;
-
- /**
- * Makes sure the filter runs once per request
- *
- * @see #isRunOnce
- * @see #ATTRIB_RUN_ONCE_VALUE
- * @see #oncePerRequest
- */
- private final static String ATTRIB_RUN_ONCE_EXT = ".REQUEST_HANDLED";
-
- /**
- * Makes sure the filter runs once per request.
- * Must be configured through init method, as the filter name is not
- * available before we have a {@code FilterConfig} object.
- *
- * @see #isRunOnce
- * @see #ATTRIB_RUN_ONCE_VALUE
- * @see #oncePerRequest
- */
- private String attribRunOnce = null;
-
- /**
- * Makes sure the filter runs once per request
- *
- * @see #isRunOnce
- * @see #ATTRIB_RUN_ONCE_EXT
- * @see #oncePerRequest
- */
- private static final Object ATTRIB_RUN_ONCE_VALUE = new Object();
-
- /**
- * Indicates if this filter should run once per request ({@code true}),
- * or for each forward/include resource ({@code false}).
- *
- * Set this variable to true, to make sure the filter runs once per request.
- *
- * NOTE: As of Servlet 2.4, this field
- * should always be left to it's default value ({@code false}).
- *
- * To run the filter once per request, the {@code filter-mapping} element
- * of the web-descriptor should include a {@code dispatcher} element:
- * <dispatcher>REQUEST</dispatcher>
- *
- */
- protected boolean oncePerRequest = false;
-
- /**
- * Does nothing.
- */
- public GenericFilter() {}
-
- /**
- * Called by the web container to indicate to a filter that it is being
- * placed into service.
- *
- * This implementation stores the {@code FilterConfig} object it
- * receives from the servlet container for later use.
- * Generally, there's no reason to override this method, override the
- * no-argument {@code init} instead. However, if you are
- * overriding this form of the method,
- * always call {@code super.init(config)}.
- *
- * This implementation will also set all configured key/value pairs, that
- * have a matching setter method annotated with {@link InitParam}.
- *
- * @param pConfig the filter config
- * @throws ServletException if an error occurs during init
- *
- * @see Filter#init(javax.servlet.FilterConfig)
- * @see #init() init
- * @see BeanUtil#configure(Object, java.util.Map, boolean)
- */
- public void init(final FilterConfig pConfig) throws ServletException {
- if (pConfig == null) {
- throw new ServletConfigException("filter config == null");
- }
-
- // Store filter config
- filterConfig = pConfig;
-
- // Configure this
- try {
- BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
- }
- catch (InvocationTargetException e) {
- throw new ServletConfigException("Could not configure " + getFilterName(), e.getCause());
- }
-
- // Create run-once attribute name
- attribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT;
- log("init (oncePerRequest=" + oncePerRequest + ", attribRunOnce=" + attribRunOnce + ")");
- init();
- }
-
- /**
- * A convenience method which can be overridden so that there's no need to
- * call {@code super.init(config)}.
- *
- * @see #init(FilterConfig)
- *
- * @throws ServletException if an error occurs during init
- */
- public void init() throws ServletException {}
-
- /**
- * The {@code doFilter} method of the Filter is called by the container
- * each time a request/response pair is passed through the chain due to a
- * client request for a resource at the end of the chain.
- *
- * Subclasses should not override this method, but rather the
- * abstract {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
- *
- * @param pRequest the servlet request
- * @param pResponse the servlet response
- * @param pFilterChain the filter chain
- *
- * @throws IOException
- * @throws ServletException
- *
- * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
- * @see #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilterImpl
- */
- public final void doFilter(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pFilterChain) throws IOException, ServletException {
- // If request filter and already run, continue chain and return fast
- if (oncePerRequest && isRunOnce(pRequest)) {
- pFilterChain.doFilter(pRequest, pResponse);
- return;
- }
-
- // Do real filter
- doFilterImpl(pRequest, pResponse, pFilterChain);
- }
-
- /**
- * If request is filtered, returns true, otherwise marks request as filtered
- * and returns false.
- * A return value of false, indicates that the filter has not yet run.
- * A return value of true, indicates that the filter has run for this
- * request, and processing should not continue.
- *
- * Note that the method will mark the request as filtered on first
- * invocation.
- *
- * @see #ATTRIB_RUN_ONCE_EXT
- * @see #ATTRIB_RUN_ONCE_VALUE
- *
- * @param pRequest the servlet request
- * @return {@code true} if the request is already filtered, otherwise
- * {@code false}.
- */
- private boolean isRunOnce(final ServletRequest pRequest) {
- // If request already filtered, return true (skip)
- if (pRequest.getAttribute(attribRunOnce) == ATTRIB_RUN_ONCE_VALUE) {
- return true;
- }
-
- // Set attribute and return false (continue)
- pRequest.setAttribute(attribRunOnce, ATTRIB_RUN_ONCE_VALUE);
-
- return false;
- }
-
- /**
- * Invoked once, or each time a request/response pair is passed through the
- * chain, depending on the {@link #oncePerRequest} member variable.
- *
- * @param pRequest the servlet request
- * @param pResponse the servlet response
- * @param pChain the filter chain
- *
- * @throws IOException if an I/O error occurs
- * @throws ServletException if an exception occurs during the filter process
- *
- * @see #oncePerRequest
- * @see #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilter
- * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
- */
- protected abstract void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
- throws IOException, ServletException;
-
- /**
- * Called by the web container to indicate to a filter that it is being
- * taken out of service.
- *
- * @see Filter#destroy
- */
- public void destroy() {
- log("destroy");
- filterConfig = null;
- }
-
- /**
- * Returns the filter-name of this filter as defined in the deployment
- * descriptor.
- *
- * @return the filter-name
- * @see FilterConfig#getFilterName
- */
- public String getFilterName() {
- return filterConfig.getFilterName();
- }
-
- /**
- * Returns a reference to the {@link ServletContext} in which the caller is
- * executing.
- *
- * @return the {@code ServletContext} object, used by the caller to
- * interact with its servlet container
- * @see FilterConfig#getServletContext
- * @see ServletContext
- */
- public ServletContext getServletContext() {
- return filterConfig.getServletContext();
- }
-
- /**
- * Returns a {@code String} containing the value of the named
- * initialization parameter, or null if the parameter does not exist.
- *
- * @param pKey a {@code String} specifying the name of the
- * initialization parameter
- * @return a {@code String} containing the value of the initialization
- * parameter
- */
- public String getInitParameter(final String pKey) {
- return filterConfig.getInitParameter(pKey);
- }
-
- /**
- * Returns the names of the servlet's initialization parameters as an
- * {@code Enumeration} of {@code String} objects, or an empty
- * {@code Enumeration} if the servlet has no initialization parameters.
- *
- * @return an {@code Enumeration} of {@code String} objects
- * containing the mNames of the servlet's initialization parameters
- */
- public Enumeration getInitParameterNames() {
- return filterConfig.getInitParameterNames();
- }
-
- /**
- * Writes the specified message to a servlet log file, prepended by the
- * filter's name.
- *
- * @param pMessage the log message
- * @see ServletContext#log(String)
- */
- protected void log(final String pMessage) {
- getServletContext().log(getFilterName() + ": " + pMessage);
- }
-
- /**
- * Writes an explanatory message and a stack trace for a given
- * {@code Throwable} to the servlet log file, prepended by the
- * filter's name.
- *
- * @param pMessage the log message
- * @param pThrowable the exception
- * @see ServletContext#log(String,Throwable)
- */
- protected void log(final String pMessage, final Throwable pThrowable) {
- getServletContext().log(getFilterName() + ": " + pMessage, pThrowable);
- }
-
- /**
- * Initializes the filter.
- *
- * @param pFilterConfig the filter config
- * @see #init init
- *
- * @deprecated For compatibility only, use {@link #init init} instead.
- */
- @SuppressWarnings("UnusedDeclaration")
- public void setFilterConfig(final FilterConfig pFilterConfig) {
- try {
- init(pFilterConfig);
- }
- catch (ServletException e) {
- log("Error in init(), see stack trace for details.", e);
- }
- }
-
- /**
- * Gets the {@code FilterConfig} for this filter.
- *
- * @return the {@code FilterConfig} for this filter
- * @see FilterConfig
- */
- public FilterConfig getFilterConfig() {
- return filterConfig;
- }
-
- /**
- * Specifies if this filter should run once per request ({@code true}),
- * or for each forward/include resource ({@code false}).
- * Called automatically from the {@code init}-method, with settings
- * from web.xml.
- *
- * @param pOncePerRequest {@code true} if the filter should run only
- * once per request
- * @see #oncePerRequest
- */
- @InitParam(name = "once-per-request")
- public void setOncePerRequest(final boolean pOncePerRequest) {
- oncePerRequest = pOncePerRequest;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.BeanUtil;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Enumeration;
+
+/**
+ * Defines a generic, protocol-independent filter.
+ *
+ * {@code GenericFilter} is inspired by {@link GenericServlet}, and
+ * implements the {@code Filter} and {@code FilterConfig} interfaces.
+ *
+ * {@code GenericFilter} makes writing filters easier. It provides simple
+ * versions of the lifecycle methods {@code init} and {@code destroy}
+ * and of the methods in the {@code FilterConfig} interface.
+ * {@code GenericFilter} also implements the {@code log} methods,
+ * declared in the {@code ServletContext} interface.
+ *
+ * To write a generic filter, you need only override the abstract
+ * {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: GenericFilter.java#1 $
+ *
+ * @see Filter
+ * @see FilterConfig
+ */
+public abstract class GenericFilter implements Filter, FilterConfig, Serializable {
+ // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
+
+ /**
+ * The filter config.
+ */
+ private transient FilterConfig filterConfig = null;
+
+ /**
+ * Makes sure the filter runs once per request
+ *
+ * @see #isRunOnce
+ * @see #ATTRIB_RUN_ONCE_VALUE
+ * @see #oncePerRequest
+ */
+ private final static String ATTRIB_RUN_ONCE_EXT = ".REQUEST_HANDLED";
+
+ /**
+ * Makes sure the filter runs once per request.
+ * Must be configured through init method, as the filter name is not
+ * available before we have a {@code FilterConfig} object.
+ *
+ * @see #isRunOnce
+ * @see #ATTRIB_RUN_ONCE_VALUE
+ * @see #oncePerRequest
+ */
+ private String attribRunOnce = null;
+
+ /**
+ * Makes sure the filter runs once per request
+ *
+ * @see #isRunOnce
+ * @see #ATTRIB_RUN_ONCE_EXT
+ * @see #oncePerRequest
+ */
+ private static final Object ATTRIB_RUN_ONCE_VALUE = new Object();
+
+ /**
+ * Indicates if this filter should run once per request ({@code true}),
+ * or for each forward/include resource ({@code false}).
+ *
+ * Set this variable to true, to make sure the filter runs once per request.
+ *
+ * NOTE: As of Servlet 2.4, this field
+ * should always be left to it's default value ({@code false}).
+ *
+ * To run the filter once per request, the {@code filter-mapping} element
+ * of the web-descriptor should include a {@code dispatcher} element:
+ * <dispatcher>REQUEST</dispatcher>
+ *
+ */
+ protected boolean oncePerRequest = false;
+
+ /**
+ * Does nothing.
+ */
+ public GenericFilter() {}
+
+ /**
+ * Called by the web container to indicate to a filter that it is being
+ * placed into service.
+ *
+ * This implementation stores the {@code FilterConfig} object it
+ * receives from the servlet container for later use.
+ * Generally, there's no reason to override this method, override the
+ * no-argument {@code init} instead. However, if you are
+ * overriding this form of the method,
+ * always call {@code super.init(config)}.
+ *
+ * This implementation will also set all configured key/value pairs, that
+ * have a matching setter method annotated with {@link InitParam}.
+ *
+ * @param pConfig the filter config
+ * @throws ServletException if an error occurs during init
+ *
+ * @see Filter#init(javax.servlet.FilterConfig)
+ * @see #init() init
+ * @see BeanUtil#configure(Object, java.util.Map, boolean)
+ */
+ public void init(final FilterConfig pConfig) throws ServletException {
+ if (pConfig == null) {
+ throw new ServletConfigException("filter config == null");
+ }
+
+ // Store filter config
+ filterConfig = pConfig;
+
+ // Configure this
+ try {
+ BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
+ }
+ catch (InvocationTargetException e) {
+ throw new ServletConfigException("Could not configure " + getFilterName(), e.getCause());
+ }
+
+ // Create run-once attribute name
+ attribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT;
+ log("init (oncePerRequest=" + oncePerRequest + ", attribRunOnce=" + attribRunOnce + ")");
+ init();
+ }
+
+ /**
+ * A convenience method which can be overridden so that there's no need to
+ * call {@code super.init(config)}.
+ *
+ * @see #init(FilterConfig)
+ *
+ * @throws ServletException if an error occurs during init
+ */
+ public void init() throws ServletException {}
+
+ /**
+ * The {@code doFilter} method of the Filter is called by the container
+ * each time a request/response pair is passed through the chain due to a
+ * client request for a resource at the end of the chain.
+ *
+ * Subclasses should not override this method, but rather the
+ * abstract {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
+ *
+ * @param pRequest the servlet request
+ * @param pResponse the servlet response
+ * @param pFilterChain the filter chain
+ *
+ * @throws IOException
+ * @throws ServletException
+ *
+ * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
+ * @see #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilterImpl
+ */
+ public final void doFilter(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pFilterChain) throws IOException, ServletException {
+ // If request filter and already run, continue chain and return fast
+ if (oncePerRequest && isRunOnce(pRequest)) {
+ pFilterChain.doFilter(pRequest, pResponse);
+ return;
+ }
+
+ // Do real filter
+ doFilterImpl(pRequest, pResponse, pFilterChain);
+ }
+
+ /**
+ * If request is filtered, returns true, otherwise marks request as filtered
+ * and returns false.
+ * A return value of false, indicates that the filter has not yet run.
+ * A return value of true, indicates that the filter has run for this
+ * request, and processing should not continue.
+ *
+ * Note that the method will mark the request as filtered on first
+ * invocation.
+ *
+ * @see #ATTRIB_RUN_ONCE_EXT
+ * @see #ATTRIB_RUN_ONCE_VALUE
+ *
+ * @param pRequest the servlet request
+ * @return {@code true} if the request is already filtered, otherwise
+ * {@code false}.
+ */
+ private boolean isRunOnce(final ServletRequest pRequest) {
+ // If request already filtered, return true (skip)
+ if (pRequest.getAttribute(attribRunOnce) == ATTRIB_RUN_ONCE_VALUE) {
+ return true;
+ }
+
+ // Set attribute and return false (continue)
+ pRequest.setAttribute(attribRunOnce, ATTRIB_RUN_ONCE_VALUE);
+
+ return false;
+ }
+
+ /**
+ * Invoked once, or each time a request/response pair is passed through the
+ * chain, depending on the {@link #oncePerRequest} member variable.
+ *
+ * @param pRequest the servlet request
+ * @param pResponse the servlet response
+ * @param pChain the filter chain
+ *
+ * @throws IOException if an I/O error occurs
+ * @throws ServletException if an exception occurs during the filter process
+ *
+ * @see #oncePerRequest
+ * @see #doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilter
+ * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
+ */
+ protected abstract void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
+ throws IOException, ServletException;
+
+ /**
+ * Called by the web container to indicate to a filter that it is being
+ * taken out of service.
+ *
+ * @see Filter#destroy
+ */
+ public void destroy() {
+ log("destroy");
+ filterConfig = null;
+ }
+
+ /**
+ * Returns the filter-name of this filter as defined in the deployment
+ * descriptor.
+ *
+ * @return the filter-name
+ * @see FilterConfig#getFilterName
+ */
+ public String getFilterName() {
+ return filterConfig.getFilterName();
+ }
+
+ /**
+ * Returns a reference to the {@link ServletContext} in which the caller is
+ * executing.
+ *
+ * @return the {@code ServletContext} object, used by the caller to
+ * interact with its servlet container
+ * @see FilterConfig#getServletContext
+ * @see ServletContext
+ */
+ public ServletContext getServletContext() {
+ return filterConfig.getServletContext();
+ }
+
+ /**
+ * Returns a {@code String} containing the value of the named
+ * initialization parameter, or null if the parameter does not exist.
+ *
+ * @param pKey a {@code String} specifying the name of the
+ * initialization parameter
+ * @return a {@code String} containing the value of the initialization
+ * parameter
+ */
+ public String getInitParameter(final String pKey) {
+ return filterConfig.getInitParameter(pKey);
+ }
+
+ /**
+ * Returns the names of the servlet's initialization parameters as an
+ * {@code Enumeration} of {@code String} objects, or an empty
+ * {@code Enumeration} if the servlet has no initialization parameters.
+ *
+ * @return an {@code Enumeration} of {@code String} objects
+ * containing the mNames of the servlet's initialization parameters
+ */
+ public Enumeration getInitParameterNames() {
+ return filterConfig.getInitParameterNames();
+ }
+
+ /**
+ * Writes the specified message to a servlet log file, prepended by the
+ * filter's name.
+ *
+ * @param pMessage the log message
+ * @see ServletContext#log(String)
+ */
+ protected void log(final String pMessage) {
+ getServletContext().log(getFilterName() + ": " + pMessage);
+ }
+
+ /**
+ * Writes an explanatory message and a stack trace for a given
+ * {@code Throwable} to the servlet log file, prepended by the
+ * filter's name.
+ *
+ * @param pMessage the log message
+ * @param pThrowable the exception
+ * @see ServletContext#log(String,Throwable)
+ */
+ protected void log(final String pMessage, final Throwable pThrowable) {
+ getServletContext().log(getFilterName() + ": " + pMessage, pThrowable);
+ }
+
+ /**
+ * Initializes the filter.
+ *
+ * @param pFilterConfig the filter config
+ * @see #init init
+ *
+ * @deprecated For compatibility only, use {@link #init init} instead.
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public void setFilterConfig(final FilterConfig pFilterConfig) {
+ try {
+ init(pFilterConfig);
+ }
+ catch (ServletException e) {
+ log("Error in init(), see stack trace for details.", e);
+ }
+ }
+
+ /**
+ * Gets the {@code FilterConfig} for this filter.
+ *
+ * @return the {@code FilterConfig} for this filter
+ * @see FilterConfig
+ */
+ public FilterConfig getFilterConfig() {
+ return filterConfig;
+ }
+
+ /**
+ * Specifies if this filter should run once per request ({@code true}),
+ * or for each forward/include resource ({@code false}).
+ * Called automatically from the {@code init}-method, with settings
+ * from web.xml.
+ *
+ * @param pOncePerRequest {@code true} if the filter should run only
+ * once per request
+ * @see #oncePerRequest
+ */
+ @InitParam(name = "once-per-request")
+ public void setOncePerRequest(final boolean pOncePerRequest) {
+ oncePerRequest = pOncePerRequest;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java b/servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java
index 56641a05..7f2198ca 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java
@@ -1,88 +1,88 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.BeanUtil;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * Defines a generic, protocol-independent servlet.
- *
- * {@code GenericServlet} has an auto-init system, that automatically invokes
- * the method matching the signature {@code void setX(<Type>)},
- * for every init-parameter {@code x}. Both camelCase and lisp-style parameter
- * naming is supported, lisp-style names will be converted to camelCase.
- * Parameter values are automatically converted from string representation to
- * most basic types, if necessary.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: GenericServlet.java#1 $
- */
-public abstract class GenericServlet extends javax.servlet.GenericServlet {
- // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
-
- /**
- * Called by the web container to indicate to a servlet that it is being
- * placed into service.
- *
- * This implementation stores the {@code ServletConfig} object it
- * receives from the servlet container for later use. When overriding this
- * form of the method, call {@code super.init(config)}.
- *
- * This implementation will also set all configured key/value pairs, that
- * have a matching setter method annotated with {@link InitParam}.
- *
- * @param pConfig the servlet config
- * @throws ServletException
- *
- * @see javax.servlet.GenericServlet#init
- * @see #init() init
- * @see BeanUtil#configure(Object, java.util.Map, boolean)
- */
- @Override
- public void init(final ServletConfig pConfig) throws ServletException {
- if (pConfig == null) {
- throw new ServletConfigException("servlet config == null");
- }
-
- try {
- BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
- }
- catch (InvocationTargetException e) {
- throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
- }
-
- super.init(pConfig);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.BeanUtil;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Defines a generic, protocol-independent servlet.
+ *
+ * {@code GenericServlet} has an auto-init system, that automatically invokes
+ * the method matching the signature {@code void setX(<Type>)},
+ * for every init-parameter {@code x}. Both camelCase and lisp-style parameter
+ * naming is supported, lisp-style names will be converted to camelCase.
+ * Parameter values are automatically converted from string representation to
+ * most basic types, if necessary.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: GenericServlet.java#1 $
+ */
+public abstract class GenericServlet extends javax.servlet.GenericServlet {
+ // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
+
+ /**
+ * Called by the web container to indicate to a servlet that it is being
+ * placed into service.
+ *
+ * This implementation stores the {@code ServletConfig} object it
+ * receives from the servlet container for later use. When overriding this
+ * form of the method, call {@code super.init(config)}.
+ *
+ * This implementation will also set all configured key/value pairs, that
+ * have a matching setter method annotated with {@link InitParam}.
+ *
+ * @param pConfig the servlet config
+ * @throws ServletException
+ *
+ * @see javax.servlet.GenericServlet#init
+ * @see #init() init
+ * @see BeanUtil#configure(Object, java.util.Map, boolean)
+ */
+ @Override
+ public void init(final ServletConfig pConfig) throws ServletException {
+ if (pConfig == null) {
+ throw new ServletConfigException("servlet config == null");
+ }
+
+ try {
+ BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
+ }
+ catch (InvocationTargetException e) {
+ throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
+ }
+
+ super.init(pConfig);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java b/servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java
index f1ab6060..cff681bf 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java
@@ -1,88 +1,88 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.BeanUtil;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import java.lang.reflect.InvocationTargetException;
-
-/**
- * Defines a generic, HTTP specific servlet.
- *
- * {@code HttpServlet} has an auto-init system, that automatically invokes
- * the method matching the signature {@code void setX(<Type>)},
- * for every init-parameter {@code x}. Both camelCase and lisp-style parameter
- * naming is supported, lisp-style names will be converted to camelCase.
- * Parameter values are automatically converted from string representation to
- * most basic types, if necessary.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: HttpServlet.java#1 $
- */
-public abstract class HttpServlet extends javax.servlet.http.HttpServlet {
- // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
-
- /**
- * Called by the web container to indicate to a servlet that it is being
- * placed into service.
- *
- * This implementation stores the {@code ServletConfig} object it
- * receives from the servlet container for later use. When overriding this
- * form of the method, call {@code super.init(config)}.
- *
- * This implementation will also set all configured key/value pairs, that
- * have a matching setter method annotated with {@link InitParam}.
- *
- * @param pConfig the servlet config
- * @throws ServletException if an error occurred during init
- *
- * @see javax.servlet.GenericServlet#init
- * @see #init() init
- * @see BeanUtil#configure(Object, java.util.Map, boolean)
- */
- @Override
- public void init(ServletConfig pConfig) throws ServletException {
- if (pConfig == null) {
- throw new ServletConfigException("servlet config == null");
- }
-
- try {
- BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
- }
- catch (InvocationTargetException e) {
- throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
- }
-
- super.init(pConfig);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.BeanUtil;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Defines a generic, HTTP specific servlet.
+ *
+ * {@code HttpServlet} has an auto-init system, that automatically invokes
+ * the method matching the signature {@code void setX(<Type>)},
+ * for every init-parameter {@code x}. Both camelCase and lisp-style parameter
+ * naming is supported, lisp-style names will be converted to camelCase.
+ * Parameter values are automatically converted from string representation to
+ * most basic types, if necessary.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: HttpServlet.java#1 $
+ */
+public abstract class HttpServlet extends javax.servlet.http.HttpServlet {
+ // TODO: Rewrite to use ServletConfigurator instead of BeanUtil
+
+ /**
+ * Called by the web container to indicate to a servlet that it is being
+ * placed into service.
+ *
+ * This implementation stores the {@code ServletConfig} object it
+ * receives from the servlet container for later use. When overriding this
+ * form of the method, call {@code super.init(config)}.
+ *
+ * This implementation will also set all configured key/value pairs, that
+ * have a matching setter method annotated with {@link InitParam}.
+ *
+ * @param pConfig the servlet config
+ * @throws ServletException if an error occurred during init
+ *
+ * @see javax.servlet.GenericServlet#init
+ * @see #init() init
+ * @see BeanUtil#configure(Object, java.util.Map, boolean)
+ */
+ @Override
+ public void init(ServletConfig pConfig) throws ServletException {
+ if (pConfig == null) {
+ throw new ServletConfigException("servlet config == null");
+ }
+
+ try {
+ BeanUtil.configure(this, ServletUtil.asMap(pConfig), true);
+ }
+ catch (InvocationTargetException e) {
+ throw new ServletConfigException("Could not configure " + getServletName(), e.getCause());
+ }
+
+ super.init(pConfig);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java
index 35c9ca48..d3316f08 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java
@@ -1,120 +1,120 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.Validate;
-
-import javax.servlet.ServletOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * A {@code ServletOutputStream} implementation backed by a
- * {@link java.io.OutputStream}. For filters that need to buffer the
- * response and do post filtering, it may be used like this:
- * ByteArrayOutputStream buffer = new ByteArraOutputStream();
- * ServletOutputStream adapter = new OutputStreamAdapter(buffer);
- *
- *
- * As a {@code ServletOutputStream} is itself an {@code OutputStream}, this
- * class may also be used as a superclass for wrappers of other
- * {@code ServletOutputStream}s, like this:
- * class FilterServletOutputStream extends OutputStreamAdapter {
- * public FilterServletOutputStream(ServletOutputStream out) {
- * super(out);
- * }
- *
- * public void write(int abyte) {
- * // do filtering...
- * super.write(...);
- * }
- * }
- *
- * ...
- *
- * ServletOutputStream original = response.getOutputStream();
- * ServletOutputStream wrapper = new FilterServletOutputStream(original);
- *
- * @author Harald Kuhr
- * @author $Author: haku $
- * @version $Id: OutputStreamAdapter.java#1 $
- *
- */
-public class OutputStreamAdapter extends ServletOutputStream {
-
- /** The wrapped {@code OutputStream}. */
- protected final OutputStream out;
-
- /**
- * Creates an {@code OutputStreamAdapter}.
- *
- * @param pOut the wrapped {@code OutputStream}
- *
- * @throws IllegalArgumentException if {@code pOut} is {@code null}.
- */
- public OutputStreamAdapter(final OutputStream pOut) {
- Validate.notNull(pOut, "out");
- out = pOut;
- }
-
- /**
- * Returns the wrapped {@code OutputStream}.
- *
- * @return the wrapped {@code OutputStream}.
- */
- public OutputStream getOutputStream() {
- return out;
- }
-
- @Override
- public String toString() {
- return "ServletOutputStream adapted from " + out.toString();
- }
-
- /**
- * Writes a byte to the underlying stream.
- *
- * @param pByte the byte to write.
- *
- * @throws IOException if an error occurs during writing
- */
- public void write(final int pByte) throws IOException {
- out.write(pByte);
- }
-
- // Overide for efficiency
- public void write(final byte pBytes[]) throws IOException {
- out.write(pBytes);
- }
-
- // Overide for efficiency
- public void write(final byte pBytes[], final int pOff, final int pLen) throws IOException {
- out.write(pBytes, pOff, pLen);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.Validate;
+
+import javax.servlet.ServletOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A {@code ServletOutputStream} implementation backed by a
+ * {@link java.io.OutputStream}. For filters that need to buffer the
+ * response and do post filtering, it may be used like this:
+ * ByteArrayOutputStream buffer = new ByteArraOutputStream();
+ * ServletOutputStream adapter = new OutputStreamAdapter(buffer);
+ *
+ *
+ * As a {@code ServletOutputStream} is itself an {@code OutputStream}, this
+ * class may also be used as a superclass for wrappers of other
+ * {@code ServletOutputStream}s, like this:
+ * class FilterServletOutputStream extends OutputStreamAdapter {
+ * public FilterServletOutputStream(ServletOutputStream out) {
+ * super(out);
+ * }
+ *
+ * public void write(int abyte) {
+ * // do filtering...
+ * super.write(...);
+ * }
+ * }
+ *
+ * ...
+ *
+ * ServletOutputStream original = response.getOutputStream();
+ * ServletOutputStream wrapper = new FilterServletOutputStream(original);
+ *
+ * @author Harald Kuhr
+ * @author $Author: haku $
+ * @version $Id: OutputStreamAdapter.java#1 $
+ *
+ */
+public class OutputStreamAdapter extends ServletOutputStream {
+
+ /** The wrapped {@code OutputStream}. */
+ protected final OutputStream out;
+
+ /**
+ * Creates an {@code OutputStreamAdapter}.
+ *
+ * @param pOut the wrapped {@code OutputStream}
+ *
+ * @throws IllegalArgumentException if {@code pOut} is {@code null}.
+ */
+ public OutputStreamAdapter(final OutputStream pOut) {
+ Validate.notNull(pOut, "out");
+ out = pOut;
+ }
+
+ /**
+ * Returns the wrapped {@code OutputStream}.
+ *
+ * @return the wrapped {@code OutputStream}.
+ */
+ public OutputStream getOutputStream() {
+ return out;
+ }
+
+ @Override
+ public String toString() {
+ return "ServletOutputStream adapted from " + out.toString();
+ }
+
+ /**
+ * Writes a byte to the underlying stream.
+ *
+ * @param pByte the byte to write.
+ *
+ * @throws IOException if an error occurs during writing
+ */
+ public void write(final int pByte) throws IOException {
+ out.write(pByte);
+ }
+
+ // Overide for efficiency
+ public void write(final byte pBytes[]) throws IOException {
+ out.write(pBytes);
+ }
+
+ // Overide for efficiency
+ public void write(final byte pBytes[], final int pOff, final int pLen) throws IOException {
+ out.write(pBytes, pOff, pLen);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java
index f66df68d..ae16403f 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java
@@ -1,435 +1,435 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.ConnectException;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.Enumeration;
-
-/**
- * A simple proxy servlet implementation. Supports HTTP and HTTPS.
- *
- * Note: The servlet is not a true HTTP proxy as described in
- * RFC 2616,
- * instead it passes on all incoming HTTP requests to the configured remote
- * server.
- * Useful for bypassing firewalls or to avoid exposing internal network
- * infrastructure to external clients.
- *
- * At the moment, no caching of content is implemented.
- *
- * If the {@code remoteServer} init parameter is not set, the servlet will
- * respond by sending a {@code 500 Internal Server Error} response to the client.
- * If the configured remote server is down, or unreachable, the servlet will
- * respond by sending a {@code 502 Bad Gateway} response to the client.
- * Otherwise, the response from the remote server will be tunneled unmodified
- * to the client.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: ProxyServlet.java#1 $
- */
-public class ProxyServlet extends GenericServlet {
-
- /** Remote server host name or IP address */
- protected String remoteServer = null;
- /** Remote server port */
- protected int remotePort = 80;
- /** Remote server "mount" path */
- protected String remotePath = "";
-
- private static final String HTTP_REQUEST_HEADER_HOST = "host";
- private static final String HTTP_RESPONSE_HEADER_SERVER = "server";
- private static final String MESSAGE_REMOTE_SERVER_NOT_CONFIGURED = "Remote server not configured.";
-
- /**
- * Called by {@code init} to set the remote server. Must be a valid host
- * name or IP address. No default.
- *
- * @param pRemoteServer
- */
- public void setRemoteServer(String pRemoteServer) {
- remoteServer = pRemoteServer;
- }
-
- /**
- * Called by {@code init} to set the remote port. Must be a number.
- * Default is {@code 80}.
- *
- * @param pRemotePort
- */
- public void setRemotePort(String pRemotePort) {
- try {
- remotePort = Integer.parseInt(pRemotePort);
- }
- catch (NumberFormatException e) {
- log("RemotePort must be a number!", e);
- }
- }
-
- /**
- * Called by {@code init} to set the remote path. May be an empty string
- * for the root path, or any other valid path on the remote server.
- * Default is {@code ""}.
- *
- * @param pRemotePath
- */
- public void setRemotePath(String pRemotePath) {
- if (StringUtil.isEmpty(pRemotePath)) {
- pRemotePath = "";
- }
- else if (pRemotePath.charAt(0) != '/') {
- pRemotePath = "/" + pRemotePath;
- }
-
- remotePath = pRemotePath;
- }
-
- /**
- * Override {@code service} to use HTTP specifics.
- *
- * @param pRequest
- * @param pResponse
- *
- * @throws ServletException
- * @throws IOException
- *
- * @see #service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
- */
- public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
- service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
- }
-
- /**
- * Services a single request.
- * Supports HTTP and HTTPS.
- *
- * @param pRequest
- * @param pResponse
- *
- * @throws ServletException
- * @throws IOException
- *
- * @see ProxyServlet Class descrition
- */
- protected void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
- // Sanity check configuration
- if (remoteServer == null) {
- log(MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
- pResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
- MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
- return;
- }
-
- HttpURLConnection remoteConnection = null;
- try {
- // Recreate request URI for remote request
- String requestURI = createRemoteRequestURI(pRequest);
- URL remoteURL = new URL(pRequest.getScheme(), remoteServer, remotePort, requestURI);
-
- // Get connection, with method from original request
- // NOTE: The actual connection is not done before we ask for streams...
- // NOTE: The HttpURLConnection is supposed to handle multiple
- // requests to the same server internally
- String method = pRequest.getMethod();
- remoteConnection = (HttpURLConnection) remoteURL.openConnection();
- remoteConnection.setRequestMethod(method);
-
- // Copy header fields
- copyHeadersFromClient(pRequest, remoteConnection);
-
- // Do proxy specifc stuff?
- // TODO: Read up the specs from RFC 2616 (HTTP) on proxy behaviour
- // TODO: RFC 2616 says "[a] proxy server MUST NOT establish an HTTP/1.1
- // persistent connection with an HTTP/1.0 client"
-
- // Copy message body from client to remote server
- copyBodyFromClient(pRequest, remoteConnection);
-
- // Set response status code from remote server to client
- int responseCode = remoteConnection.getResponseCode();
- pResponse.setStatus(responseCode);
- //System.out.println("Response is: " + responseCode + " " + remoteConnection.getResponseMessage());
-
- // Copy header fields back
- copyHeadersToClient(remoteConnection, pResponse);
-
- // More proxy specific stuff?
-
- // Copy message body from remote server to client
- copyBodyToClient(remoteConnection, pResponse);
- }
- catch (ConnectException e) {
- // In case we could not connecto to the remote server
- log("Could not connect to remote server.", e);
- pResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, e.getMessage());
- }
- finally {
- // Disconnect from server
- // TODO: Should we actually do this?
- if (remoteConnection != null) {
- remoteConnection.disconnect();
- }
- }
- }
-
- /**
- * Copies the message body from the remote server to the client (outgoing
- * {@code HttpServletResponse}).
- *
- * @param pRemoteConnection
- * @param pResponse
- *
- * @throws IOException
- */
- private void copyBodyToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) throws IOException {
- InputStream fromRemote = null;
- OutputStream toClient = null;
-
- try {
- // Get either input or error stream
- try {
- fromRemote = pRemoteConnection.getInputStream();
- }
- catch (IOException e) {
- // If exception, use errorStream instead
- fromRemote = pRemoteConnection.getErrorStream();
- }
-
- // I guess the stream might be null if there is no response other
- // than headers (Continue, No Content, etc).
- if (fromRemote != null) {
- toClient = pResponse.getOutputStream();
- FileUtil.copy(fromRemote, toClient);
- }
- }
- finally {
- if (fromRemote != null) {
- try {
- fromRemote.close();
- }
- catch (IOException e) {
- log("Stream from remote could not be closed.", e);
- }
- }
- if (toClient != null) {
- try {
- toClient.close();
- }
- catch (IOException e) {
- log("Stream to client could not be closed.", e);
- }
- }
- }
- }
-
- /**
- * Copies the message body from the client (incomming
- * {@code HttpServletRequest}) to the remote server if the request method
- * is {@code POST} or PUT.
- * Otherwise this method does nothing.
- *
- * @param pRequest
- * @param pRemoteConnection
- *
- * @throws java.io.IOException
- */
- private void copyBodyFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) throws IOException {
- // If this is a POST or PUT, copy message body from client remote server
- if (!("POST".equals(pRequest.getMethod()) || "PUT".equals(pRequest.getMethod()))) {
- return;
- }
-
- // NOTE: Setting doOutput to true, will make it a POST request (why?)...
- pRemoteConnection.setDoOutput(true);
-
- // Get streams and do the copying
- InputStream fromClient = null;
- OutputStream toRemote = null;
- try {
- fromClient = pRequest.getInputStream();
- toRemote = pRemoteConnection.getOutputStream();
- FileUtil.copy(fromClient, toRemote);
- }
- finally {
- if (fromClient != null) {
- try {
- fromClient.close();
- }
- catch (IOException e) {
- log("Stream from client could not be closed.", e);
- }
- }
- if (toRemote != null) {
- try {
- toRemote.close();
- }
- catch (IOException e) {
- log("Stream to remote could not be closed.", e);
- }
- }
- }
- }
-
- /**
- * Creates the remote request URI based on the incoming request.
- * The URI will include any query strings etc.
- *
- * @param pRequest
- *
- * @return a {@code String} representing the remote request URI
- */
- private String createRemoteRequestURI(HttpServletRequest pRequest) {
- StringBuilder requestURI = new StringBuilder(remotePath);
- requestURI.append(pRequest.getPathInfo());
-
- if (!StringUtil.isEmpty(pRequest.getQueryString())) {
- requestURI.append("?");
- requestURI.append(pRequest.getQueryString());
- }
-
- return requestURI.toString();
- }
-
- /**
- * Copies headers from the remote connection back to the client
- * (the outgoing HttpServletResponse). All headers except the "Server"
- * header are copied.
- *
- * @param pRemoteConnection
- * @param pResponse
- */
- private void copyHeadersToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) {
- // NOTE: There is no getHeaderFieldCount method or similar...
- // Also, the getHeaderFields() method was introduced in J2SE 1.4, and
- // we want to be 1.2 compatible.
- // So, just try to loop until there are no more headers.
- int i = 0;
- while (true) {
- String key = pRemoteConnection.getHeaderFieldKey(i);
- // NOTE: getHeaderField(String) returns only the last value
- String value = pRemoteConnection.getHeaderField(i);
-
- // If the key is not null, life is simple, and Sun is shining
- // However, the default implementations includes the HTTP response
- // code ("HTTP/1.1 200 Ok" or similar) as a header field with
- // key "null" (why..?)...
- // In addition, we want to skip the original "Server" header
- if (key != null && !HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
- //System.out.println("client <<<-- remote: " + key + ": " + value);
- pResponse.setHeader(key, value);
- }
- else if (value == null) {
- // If BOTH key and value is null, there are no more header fields
- break;
- }
-
- i++;
- }
-
- /* 1.4+ version below....
- Map headers = pRemoteConnection.getHeaderFields();
- for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();) {
- Map.Entry header = (Map.Entry) iterator.next();
-
- List values = (List) header.getValue();
-
- for (Iterator valueIter = values.iterator(); valueIter.hasNext();) {
- String value = (String) valueIter.next();
- String key = (String) header.getKey();
-
- // Skip the server header
- if (HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
- key = null;
- }
-
- // The default implementations includes the HTTP response code
- // ("HTTP/1.1 200 Ok" or similar) as a header field with
- // key "null" (why..?)...
- if (key != null) {
- //System.out.println("client <<<-- remote: " + key + ": " + value);
- pResponse.setHeader(key, value);
- }
- }
- }
- */
- }
-
- /**
- * Copies headers from the client (the incoming {@code HttpServletRequest})
- * to the outgoing connection.
- * All headers except the "Host" header are copied.
- *
- * @param pRequest
- * @param pRemoteConnection
- */
- private void copyHeadersFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) {
- Enumeration headerNames = pRequest.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String headerName = (String) headerNames.nextElement();
- Enumeration headerValues = pRequest.getHeaders(headerName);
-
- // Skip the "host" header, as we want something else
- if (HTTP_REQUEST_HEADER_HOST.equalsIgnoreCase(headerName)) {
- // Skip this header
- headerName = null;
- }
-
- // Set the the header to the remoteConnection
- if (headerName != null) {
- // Convert from multiple line to single line, comma separated, as
- // there seems to be a shortcoming in the URLConneciton API...
- StringBuilder headerValue = new StringBuilder();
- while (headerValues.hasMoreElements()) {
- String value = (String) headerValues.nextElement();
- headerValue.append(value);
- if (headerValues.hasMoreElements()) {
- headerValue.append(", ");
- }
- }
-
- //System.out.println("client -->>> remote: " + headerName + ": " + headerValue);
- pRemoteConnection.setRequestProperty(headerName, headerValue.toString());
- }
- }
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.Enumeration;
+
+/**
+ * A simple proxy servlet implementation. Supports HTTP and HTTPS.
+ *
+ * Note: The servlet is not a true HTTP proxy as described in
+ * RFC 2616,
+ * instead it passes on all incoming HTTP requests to the configured remote
+ * server.
+ * Useful for bypassing firewalls or to avoid exposing internal network
+ * infrastructure to external clients.
+ *
+ * At the moment, no caching of content is implemented.
+ *
+ * If the {@code remoteServer} init parameter is not set, the servlet will
+ * respond by sending a {@code 500 Internal Server Error} response to the client.
+ * If the configured remote server is down, or unreachable, the servlet will
+ * respond by sending a {@code 502 Bad Gateway} response to the client.
+ * Otherwise, the response from the remote server will be tunneled unmodified
+ * to the client.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: ProxyServlet.java#1 $
+ */
+public class ProxyServlet extends GenericServlet {
+
+ /** Remote server host name or IP address */
+ protected String remoteServer = null;
+ /** Remote server port */
+ protected int remotePort = 80;
+ /** Remote server "mount" path */
+ protected String remotePath = "";
+
+ private static final String HTTP_REQUEST_HEADER_HOST = "host";
+ private static final String HTTP_RESPONSE_HEADER_SERVER = "server";
+ private static final String MESSAGE_REMOTE_SERVER_NOT_CONFIGURED = "Remote server not configured.";
+
+ /**
+ * Called by {@code init} to set the remote server. Must be a valid host
+ * name or IP address. No default.
+ *
+ * @param pRemoteServer
+ */
+ public void setRemoteServer(String pRemoteServer) {
+ remoteServer = pRemoteServer;
+ }
+
+ /**
+ * Called by {@code init} to set the remote port. Must be a number.
+ * Default is {@code 80}.
+ *
+ * @param pRemotePort
+ */
+ public void setRemotePort(String pRemotePort) {
+ try {
+ remotePort = Integer.parseInt(pRemotePort);
+ }
+ catch (NumberFormatException e) {
+ log("RemotePort must be a number!", e);
+ }
+ }
+
+ /**
+ * Called by {@code init} to set the remote path. May be an empty string
+ * for the root path, or any other valid path on the remote server.
+ * Default is {@code ""}.
+ *
+ * @param pRemotePath
+ */
+ public void setRemotePath(String pRemotePath) {
+ if (StringUtil.isEmpty(pRemotePath)) {
+ pRemotePath = "";
+ }
+ else if (pRemotePath.charAt(0) != '/') {
+ pRemotePath = "/" + pRemotePath;
+ }
+
+ remotePath = pRemotePath;
+ }
+
+ /**
+ * Override {@code service} to use HTTP specifics.
+ *
+ * @param pRequest
+ * @param pResponse
+ *
+ * @throws ServletException
+ * @throws IOException
+ *
+ * @see #service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+ */
+ public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException {
+ service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse);
+ }
+
+ /**
+ * Services a single request.
+ * Supports HTTP and HTTPS.
+ *
+ * @param pRequest
+ * @param pResponse
+ *
+ * @throws ServletException
+ * @throws IOException
+ *
+ * @see ProxyServlet Class descrition
+ */
+ protected void service(HttpServletRequest pRequest, HttpServletResponse pResponse) throws ServletException, IOException {
+ // Sanity check configuration
+ if (remoteServer == null) {
+ log(MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
+ pResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+ MESSAGE_REMOTE_SERVER_NOT_CONFIGURED);
+ return;
+ }
+
+ HttpURLConnection remoteConnection = null;
+ try {
+ // Recreate request URI for remote request
+ String requestURI = createRemoteRequestURI(pRequest);
+ URL remoteURL = new URL(pRequest.getScheme(), remoteServer, remotePort, requestURI);
+
+ // Get connection, with method from original request
+ // NOTE: The actual connection is not done before we ask for streams...
+ // NOTE: The HttpURLConnection is supposed to handle multiple
+ // requests to the same server internally
+ String method = pRequest.getMethod();
+ remoteConnection = (HttpURLConnection) remoteURL.openConnection();
+ remoteConnection.setRequestMethod(method);
+
+ // Copy header fields
+ copyHeadersFromClient(pRequest, remoteConnection);
+
+ // Do proxy specifc stuff?
+ // TODO: Read up the specs from RFC 2616 (HTTP) on proxy behaviour
+ // TODO: RFC 2616 says "[a] proxy server MUST NOT establish an HTTP/1.1
+ // persistent connection with an HTTP/1.0 client"
+
+ // Copy message body from client to remote server
+ copyBodyFromClient(pRequest, remoteConnection);
+
+ // Set response status code from remote server to client
+ int responseCode = remoteConnection.getResponseCode();
+ pResponse.setStatus(responseCode);
+ //System.out.println("Response is: " + responseCode + " " + remoteConnection.getResponseMessage());
+
+ // Copy header fields back
+ copyHeadersToClient(remoteConnection, pResponse);
+
+ // More proxy specific stuff?
+
+ // Copy message body from remote server to client
+ copyBodyToClient(remoteConnection, pResponse);
+ }
+ catch (ConnectException e) {
+ // In case we could not connecto to the remote server
+ log("Could not connect to remote server.", e);
+ pResponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, e.getMessage());
+ }
+ finally {
+ // Disconnect from server
+ // TODO: Should we actually do this?
+ if (remoteConnection != null) {
+ remoteConnection.disconnect();
+ }
+ }
+ }
+
+ /**
+ * Copies the message body from the remote server to the client (outgoing
+ * {@code HttpServletResponse}).
+ *
+ * @param pRemoteConnection
+ * @param pResponse
+ *
+ * @throws IOException
+ */
+ private void copyBodyToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) throws IOException {
+ InputStream fromRemote = null;
+ OutputStream toClient = null;
+
+ try {
+ // Get either input or error stream
+ try {
+ fromRemote = pRemoteConnection.getInputStream();
+ }
+ catch (IOException e) {
+ // If exception, use errorStream instead
+ fromRemote = pRemoteConnection.getErrorStream();
+ }
+
+ // I guess the stream might be null if there is no response other
+ // than headers (Continue, No Content, etc).
+ if (fromRemote != null) {
+ toClient = pResponse.getOutputStream();
+ FileUtil.copy(fromRemote, toClient);
+ }
+ }
+ finally {
+ if (fromRemote != null) {
+ try {
+ fromRemote.close();
+ }
+ catch (IOException e) {
+ log("Stream from remote could not be closed.", e);
+ }
+ }
+ if (toClient != null) {
+ try {
+ toClient.close();
+ }
+ catch (IOException e) {
+ log("Stream to client could not be closed.", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Copies the message body from the client (incomming
+ * {@code HttpServletRequest}) to the remote server if the request method
+ * is {@code POST} or PUT.
+ * Otherwise this method does nothing.
+ *
+ * @param pRequest
+ * @param pRemoteConnection
+ *
+ * @throws java.io.IOException
+ */
+ private void copyBodyFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) throws IOException {
+ // If this is a POST or PUT, copy message body from client remote server
+ if (!("POST".equals(pRequest.getMethod()) || "PUT".equals(pRequest.getMethod()))) {
+ return;
+ }
+
+ // NOTE: Setting doOutput to true, will make it a POST request (why?)...
+ pRemoteConnection.setDoOutput(true);
+
+ // Get streams and do the copying
+ InputStream fromClient = null;
+ OutputStream toRemote = null;
+ try {
+ fromClient = pRequest.getInputStream();
+ toRemote = pRemoteConnection.getOutputStream();
+ FileUtil.copy(fromClient, toRemote);
+ }
+ finally {
+ if (fromClient != null) {
+ try {
+ fromClient.close();
+ }
+ catch (IOException e) {
+ log("Stream from client could not be closed.", e);
+ }
+ }
+ if (toRemote != null) {
+ try {
+ toRemote.close();
+ }
+ catch (IOException e) {
+ log("Stream to remote could not be closed.", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates the remote request URI based on the incoming request.
+ * The URI will include any query strings etc.
+ *
+ * @param pRequest
+ *
+ * @return a {@code String} representing the remote request URI
+ */
+ private String createRemoteRequestURI(HttpServletRequest pRequest) {
+ StringBuilder requestURI = new StringBuilder(remotePath);
+ requestURI.append(pRequest.getPathInfo());
+
+ if (!StringUtil.isEmpty(pRequest.getQueryString())) {
+ requestURI.append("?");
+ requestURI.append(pRequest.getQueryString());
+ }
+
+ return requestURI.toString();
+ }
+
+ /**
+ * Copies headers from the remote connection back to the client
+ * (the outgoing HttpServletResponse). All headers except the "Server"
+ * header are copied.
+ *
+ * @param pRemoteConnection
+ * @param pResponse
+ */
+ private void copyHeadersToClient(HttpURLConnection pRemoteConnection, HttpServletResponse pResponse) {
+ // NOTE: There is no getHeaderFieldCount method or similar...
+ // Also, the getHeaderFields() method was introduced in J2SE 1.4, and
+ // we want to be 1.2 compatible.
+ // So, just try to loop until there are no more headers.
+ int i = 0;
+ while (true) {
+ String key = pRemoteConnection.getHeaderFieldKey(i);
+ // NOTE: getHeaderField(String) returns only the last value
+ String value = pRemoteConnection.getHeaderField(i);
+
+ // If the key is not null, life is simple, and Sun is shining
+ // However, the default implementations includes the HTTP response
+ // code ("HTTP/1.1 200 Ok" or similar) as a header field with
+ // key "null" (why..?)...
+ // In addition, we want to skip the original "Server" header
+ if (key != null && !HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
+ //System.out.println("client <<<-- remote: " + key + ": " + value);
+ pResponse.setHeader(key, value);
+ }
+ else if (value == null) {
+ // If BOTH key and value is null, there are no more header fields
+ break;
+ }
+
+ i++;
+ }
+
+ /* 1.4+ version below....
+ Map headers = pRemoteConnection.getHeaderFields();
+ for (Iterator iterator = headers.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry header = (Map.Entry) iterator.next();
+
+ List values = (List) header.getValue();
+
+ for (Iterator valueIter = values.iterator(); valueIter.hasNext();) {
+ String value = (String) valueIter.next();
+ String key = (String) header.getKey();
+
+ // Skip the server header
+ if (HTTP_RESPONSE_HEADER_SERVER.equalsIgnoreCase(key)) {
+ key = null;
+ }
+
+ // The default implementations includes the HTTP response code
+ // ("HTTP/1.1 200 Ok" or similar) as a header field with
+ // key "null" (why..?)...
+ if (key != null) {
+ //System.out.println("client <<<-- remote: " + key + ": " + value);
+ pResponse.setHeader(key, value);
+ }
+ }
+ }
+ */
+ }
+
+ /**
+ * Copies headers from the client (the incoming {@code HttpServletRequest})
+ * to the outgoing connection.
+ * All headers except the "Host" header are copied.
+ *
+ * @param pRequest
+ * @param pRemoteConnection
+ */
+ private void copyHeadersFromClient(HttpServletRequest pRequest, HttpURLConnection pRemoteConnection) {
+ Enumeration headerNames = pRequest.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = (String) headerNames.nextElement();
+ Enumeration headerValues = pRequest.getHeaders(headerName);
+
+ // Skip the "host" header, as we want something else
+ if (HTTP_REQUEST_HEADER_HOST.equalsIgnoreCase(headerName)) {
+ // Skip this header
+ headerName = null;
+ }
+
+ // Set the the header to the remoteConnection
+ if (headerName != null) {
+ // Convert from multiple line to single line, comma separated, as
+ // there seems to be a shortcoming in the URLConneciton API...
+ StringBuilder headerValue = new StringBuilder();
+ while (headerValues.hasMoreElements()) {
+ String value = (String) headerValues.nextElement();
+ headerValue.append(value);
+ if (headerValues.hasMoreElements()) {
+ headerValue.append(", ");
+ }
+ }
+
+ //System.out.println("client -->>> remote: " + headerName + ": " + headerValue);
+ pRemoteConnection.setRequestProperty(headerName, headerValue.toString());
+ }
+ }
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java
index 9fbcbaf8..4d0b6389 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java
@@ -1,81 +1,81 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import javax.servlet.ServletException;
-
-/**
- * ServletConfigException.
- *
- * @author Harald Kuhr
- * @version $Id: ServletConfigException.java#2 $
- */
-public class ServletConfigException extends ServletException {
-
- // TODO: Parameters for init-param at fault, and possibly servlet name?
-
- /**
- * Creates a {@code ServletConfigException} with the given message.
- *
- * @param pMessage the exception message
- */
- public ServletConfigException(String pMessage) {
- super(pMessage);
- }
-
- /**
- * Creates a {@code ServletConfigException} with the given message and cause.
- *
- * @param pMessage the exception message
- * @param pCause the exception cause
- */
- public ServletConfigException(final String pMessage, final Throwable pCause) {
- super(pMessage, pCause);
-
- maybeInitCause(pCause);
- }
-
- /**
- * Creates a {@code ServletConfigException} with the cause.
- *
- * @param pCause the exception cause
- */
- public ServletConfigException(final Throwable pCause) {
- super(String.format("Error in Servlet configuration: %s", pCause.getMessage()), pCause);
-
- maybeInitCause(pCause);
- }
-
- private void maybeInitCause(Throwable pCause) {
- // Workaround for ServletExceptions that does not do proper exception chaining
- if (getCause() == null) {
- initCause(pCause);
- }
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import javax.servlet.ServletException;
+
+/**
+ * ServletConfigException.
+ *
+ * @author Harald Kuhr
+ * @version $Id: ServletConfigException.java#2 $
+ */
+public class ServletConfigException extends ServletException {
+
+ // TODO: Parameters for init-param at fault, and possibly servlet name?
+
+ /**
+ * Creates a {@code ServletConfigException} with the given message.
+ *
+ * @param pMessage the exception message
+ */
+ public ServletConfigException(String pMessage) {
+ super(pMessage);
+ }
+
+ /**
+ * Creates a {@code ServletConfigException} with the given message and cause.
+ *
+ * @param pMessage the exception message
+ * @param pCause the exception cause
+ */
+ public ServletConfigException(final String pMessage, final Throwable pCause) {
+ super(pMessage, pCause);
+
+ maybeInitCause(pCause);
+ }
+
+ /**
+ * Creates a {@code ServletConfigException} with the cause.
+ *
+ * @param pCause the exception cause
+ */
+ public ServletConfigException(final Throwable pCause) {
+ super(String.format("Error in Servlet configuration: %s", pCause.getMessage()), pCause);
+
+ maybeInitCause(pCause);
+ }
+
+ private void maybeInitCause(Throwable pCause) {
+ // Workaround for ServletExceptions that does not do proper exception chaining
+ if (getCause() == null) {
+ initCause(pCause);
+ }
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java
index 6806f5ea..1d23226a 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java
@@ -1,282 +1,282 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.lang.Validate;
-
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletContext;
-import java.io.Serializable;
-import java.util.*;
-
-/**
- * {@code ServletConfig} or {@code FilterConfig} adapter, that implements
- * the {@code Map} interface for interoperability with collection-based API's.
- *
- * This {@code Map} is not synchronized.
- *
- *
- * @author Harald Kuhr
- * @version $Id: ServletConfigMapAdapter.java#2 $
- */
-class ServletConfigMapAdapter extends AbstractMap implements Map, Serializable, Cloneable {
-
- enum ConfigType {
- ServletConfig, FilterConfig, ServletContext
- }
-
- private final ConfigType type;
-
- private final ServletConfig servletConfig;
- private final FilterConfig filterConfig;
- private final ServletContext servletContext;
-
- // Cache the entry set
- private transient Set> entrySet;
-
- public ServletConfigMapAdapter(final ServletConfig pConfig) {
- this(pConfig, ConfigType.ServletConfig);
- }
-
- public ServletConfigMapAdapter(final FilterConfig pConfig) {
- this(pConfig, ConfigType.FilterConfig);
- }
-
- public ServletConfigMapAdapter(final ServletContext pContext) {
- this(pContext, ConfigType.ServletContext);
- }
-
- private ServletConfigMapAdapter(final Object pConfig, final ConfigType pType) {
- // Could happen if client code invokes with null reference
- Validate.notNull(pConfig, "config");
-
- type = pType;
-
- switch (type) {
- case ServletConfig:
- servletConfig = (ServletConfig) pConfig;
- filterConfig = null;
- servletContext = null;
- break;
- case FilterConfig:
- servletConfig = null;
- filterConfig = (FilterConfig) pConfig;
- servletContext = null;
- break;
- case ServletContext:
- servletConfig = null;
- filterConfig = null;
- servletContext = (ServletContext) pConfig;
- break;
- default:
- throw new IllegalArgumentException("Wrong type: " + pType);
- }
- }
-
- /**
- * Gets the servlet or filter name from the config.
- *
- * @return the servlet or filter name
- */
- public final String getName() {
- switch (type) {
- case ServletConfig:
- return servletConfig.getServletName();
- case FilterConfig:
- return filterConfig.getFilterName();
- case ServletContext:
- return servletContext.getServletContextName();
- default:
- throw new IllegalStateException();
- }
- }
-
- /**
- * Gets the servlet context from the config.
- *
- * @return the servlet context
- */
- public final ServletContext getServletContext() {
- switch (type) {
- case ServletConfig:
- return servletConfig.getServletContext();
- case FilterConfig:
- return filterConfig.getServletContext();
- case ServletContext:
- return servletContext;
- default:
- throw new IllegalStateException();
- }
- }
-
- public final Enumeration getInitParameterNames() {
- switch (type) {
- case ServletConfig:
- return servletConfig.getInitParameterNames();
- case FilterConfig:
- return filterConfig.getInitParameterNames();
- case ServletContext:
- return servletContext.getInitParameterNames();
- default:
- throw new IllegalStateException();
- }
- }
-
- public final String getInitParameter(final String pName) {
- switch (type) {
- case ServletConfig:
- return servletConfig.getInitParameter(pName);
- case FilterConfig:
- return filterConfig.getInitParameter(pName);
- case ServletContext:
- return servletContext.getInitParameter(pName);
- default:
- throw new IllegalStateException();
- }
- }
-
- public Set> entrySet() {
- if (entrySet == null) {
- entrySet = createEntrySet();
- }
- return entrySet;
- }
-
- private Set> createEntrySet() {
- return new AbstractSet>() {
- // Cache size, if requested, -1 means not calculated
- private int size = -1;
-
- public Iterator> iterator() {
- return new Iterator>() {
- // Iterator is backed by initParameterNames enumeration
- final Enumeration names = getInitParameterNames();
-
- public boolean hasNext() {
- return names.hasMoreElements();
- }
-
- public Entry next() {
- final String key = (String) names.nextElement();
- return new Entry() {
- public String getKey() {
- return key;
- }
-
- public String getValue() {
- return get(key);
- }
-
- public String setValue(String pValue) {
- throw new UnsupportedOperationException();
- }
-
- // NOTE: Override equals
- public boolean equals(Object pOther) {
- if (!(pOther instanceof Map.Entry)) {
- return false;
- }
-
- Map.Entry e = (Map.Entry) pOther;
- Object value = get(key);
- Object rKey = e.getKey();
- Object rValue = e.getValue();
- return (key == null ? rKey == null : key.equals(rKey))
- && (value == null ? rValue == null : value.equals(rValue));
- }
-
- // NOTE: Override hashCode to keep the map's
- // hashCode constant and compatible
- public int hashCode() {
- Object value = get(key);
- return ((key == null) ? 0 : key.hashCode()) ^
- ((value == null) ? 0 : value.hashCode());
- }
-
- public String toString() {
- return key + "=" + get(key);
- }
- };
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- public int size() {
- if (size < 0) {
- size = calculateSize();
- }
-
- return size;
- }
-
- private int calculateSize() {
- final Enumeration names = getInitParameterNames();
-
- int size = 0;
- while (names.hasMoreElements()) {
- size++;
- names.nextElement();
- }
-
- return size;
- }
- };
- }
-
- public String get(Object pKey) {
- return getInitParameter(StringUtil.valueOf(pKey));
- }
-
- /// Unsupported Map methods
- @Override
- public String put(String pKey, String pValue) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String remove(Object pKey) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void putAll(Map pMap) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void clear() {
- throw new UnsupportedOperationException();
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.lang.Validate;
+
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * {@code ServletConfig} or {@code FilterConfig} adapter, that implements
+ * the {@code Map} interface for interoperability with collection-based API's.
+ *
+ * This {@code Map} is not synchronized.
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: ServletConfigMapAdapter.java#2 $
+ */
+class ServletConfigMapAdapter extends AbstractMap implements Map, Serializable, Cloneable {
+
+ enum ConfigType {
+ ServletConfig, FilterConfig, ServletContext
+ }
+
+ private final ConfigType type;
+
+ private final ServletConfig servletConfig;
+ private final FilterConfig filterConfig;
+ private final ServletContext servletContext;
+
+ // Cache the entry set
+ private transient Set> entrySet;
+
+ public ServletConfigMapAdapter(final ServletConfig pConfig) {
+ this(pConfig, ConfigType.ServletConfig);
+ }
+
+ public ServletConfigMapAdapter(final FilterConfig pConfig) {
+ this(pConfig, ConfigType.FilterConfig);
+ }
+
+ public ServletConfigMapAdapter(final ServletContext pContext) {
+ this(pContext, ConfigType.ServletContext);
+ }
+
+ private ServletConfigMapAdapter(final Object pConfig, final ConfigType pType) {
+ // Could happen if client code invokes with null reference
+ Validate.notNull(pConfig, "config");
+
+ type = pType;
+
+ switch (type) {
+ case ServletConfig:
+ servletConfig = (ServletConfig) pConfig;
+ filterConfig = null;
+ servletContext = null;
+ break;
+ case FilterConfig:
+ servletConfig = null;
+ filterConfig = (FilterConfig) pConfig;
+ servletContext = null;
+ break;
+ case ServletContext:
+ servletConfig = null;
+ filterConfig = null;
+ servletContext = (ServletContext) pConfig;
+ break;
+ default:
+ throw new IllegalArgumentException("Wrong type: " + pType);
+ }
+ }
+
+ /**
+ * Gets the servlet or filter name from the config.
+ *
+ * @return the servlet or filter name
+ */
+ public final String getName() {
+ switch (type) {
+ case ServletConfig:
+ return servletConfig.getServletName();
+ case FilterConfig:
+ return filterConfig.getFilterName();
+ case ServletContext:
+ return servletContext.getServletContextName();
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ /**
+ * Gets the servlet context from the config.
+ *
+ * @return the servlet context
+ */
+ public final ServletContext getServletContext() {
+ switch (type) {
+ case ServletConfig:
+ return servletConfig.getServletContext();
+ case FilterConfig:
+ return filterConfig.getServletContext();
+ case ServletContext:
+ return servletContext;
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ public final Enumeration getInitParameterNames() {
+ switch (type) {
+ case ServletConfig:
+ return servletConfig.getInitParameterNames();
+ case FilterConfig:
+ return filterConfig.getInitParameterNames();
+ case ServletContext:
+ return servletContext.getInitParameterNames();
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ public final String getInitParameter(final String pName) {
+ switch (type) {
+ case ServletConfig:
+ return servletConfig.getInitParameter(pName);
+ case FilterConfig:
+ return filterConfig.getInitParameter(pName);
+ case ServletContext:
+ return servletContext.getInitParameter(pName);
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ public Set> entrySet() {
+ if (entrySet == null) {
+ entrySet = createEntrySet();
+ }
+ return entrySet;
+ }
+
+ private Set> createEntrySet() {
+ return new AbstractSet>() {
+ // Cache size, if requested, -1 means not calculated
+ private int size = -1;
+
+ public Iterator> iterator() {
+ return new Iterator>() {
+ // Iterator is backed by initParameterNames enumeration
+ final Enumeration names = getInitParameterNames();
+
+ public boolean hasNext() {
+ return names.hasMoreElements();
+ }
+
+ public Entry next() {
+ final String key = (String) names.nextElement();
+ return new Entry() {
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return get(key);
+ }
+
+ public String setValue(String pValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ // NOTE: Override equals
+ public boolean equals(Object pOther) {
+ if (!(pOther instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry e = (Map.Entry) pOther;
+ Object value = get(key);
+ Object rKey = e.getKey();
+ Object rValue = e.getValue();
+ return (key == null ? rKey == null : key.equals(rKey))
+ && (value == null ? rValue == null : value.equals(rValue));
+ }
+
+ // NOTE: Override hashCode to keep the map's
+ // hashCode constant and compatible
+ public int hashCode() {
+ Object value = get(key);
+ return ((key == null) ? 0 : key.hashCode()) ^
+ ((value == null) ? 0 : value.hashCode());
+ }
+
+ public String toString() {
+ return key + "=" + get(key);
+ }
+ };
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ public int size() {
+ if (size < 0) {
+ size = calculateSize();
+ }
+
+ return size;
+ }
+
+ private int calculateSize() {
+ final Enumeration names = getInitParameterNames();
+
+ int size = 0;
+ while (names.hasMoreElements()) {
+ size++;
+ names.nextElement();
+ }
+
+ return size;
+ }
+ };
+ }
+
+ public String get(Object pKey) {
+ return getInitParameter(StringUtil.valueOf(pKey));
+ }
+
+ /// Unsupported Map methods
+ @Override
+ public String put(String pKey, String pValue) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String remove(Object pKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void putAll(Map pMap) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java
index 36493c83..bf645e53 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java
@@ -1,114 +1,114 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletResponse;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-
-import static com.twelvemonkeys.lang.Validate.notNull;
-
-/**
- * A delegate for handling stream support in wrapped servlet responses.
- *
- * Client code should delegate {@code getOutputStream}, {@code getWriter},
- * {@code flushBuffer} and {@code resetBuffer} methods from the servlet response.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: ServletResponseStreamDelegate.java#2 $
- */
-public class ServletResponseStreamDelegate {
- private Object out = null;
- protected final ServletResponse response;
-
- public ServletResponseStreamDelegate(final ServletResponse pResponse) {
- response = notNull(pResponse, "response");
- }
-
- // NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
- public final ServletOutputStream getOutputStream() throws IOException {
- if (out == null) {
- OutputStream out = createOutputStream();
- this.out = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out);
- }
- else if (out instanceof PrintWriter) {
- throw new IllegalStateException("getWriter() already called.");
- }
-
- return (ServletOutputStream) out;
- }
-
- // NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
- public final PrintWriter getWriter() throws IOException {
- if (out == null) {
- // NOTE: getCharacterEncoding may/should not return null
- OutputStream out = createOutputStream();
- String charEncoding = response.getCharacterEncoding();
- this.out = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out));
- }
- else if (out instanceof ServletOutputStream) {
- throw new IllegalStateException("getOutputStream() already called.");
- }
-
- return (PrintWriter) out;
- }
-
- /**
- * Returns the {@code OutputStream}.
- * Subclasses should override this method to provide a decorated output stream.
- * This method is guaranteed to be invoked only once for a request/response
- * (unless {@code resetBuffer} is invoked).
- *
- * This implementation simply returns the output stream from the wrapped
- * response.
- *
- * @return the {@code OutputStream} to use for the response
- * @throws IOException if an I/O exception occurs
- */
- protected OutputStream createOutputStream() throws IOException {
- return response.getOutputStream();
- }
-
- public void flushBuffer() throws IOException {
- if (out instanceof ServletOutputStream) {
- ((ServletOutputStream) out).flush();
- }
- else if (out != null) {
- ((PrintWriter) out).flush();
- }
- }
-
- public void resetBuffer() {
- out = null;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+import static com.twelvemonkeys.lang.Validate.notNull;
+
+/**
+ * A delegate for handling stream support in wrapped servlet responses.
+ *
+ * Client code should delegate {@code getOutputStream}, {@code getWriter},
+ * {@code flushBuffer} and {@code resetBuffer} methods from the servlet response.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: ServletResponseStreamDelegate.java#2 $
+ */
+public class ServletResponseStreamDelegate {
+ private Object out = null;
+ protected final ServletResponse response;
+
+ public ServletResponseStreamDelegate(final ServletResponse pResponse) {
+ response = notNull(pResponse, "response");
+ }
+
+ // NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
+ public final ServletOutputStream getOutputStream() throws IOException {
+ if (out == null) {
+ OutputStream out = createOutputStream();
+ this.out = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out);
+ }
+ else if (out instanceof PrintWriter) {
+ throw new IllegalStateException("getWriter() already called.");
+ }
+
+ return (ServletOutputStream) out;
+ }
+
+ // NOTE: Intentionally NOT thread safe, as one request/response should be handled by one thread ONLY.
+ public final PrintWriter getWriter() throws IOException {
+ if (out == null) {
+ // NOTE: getCharacterEncoding may/should not return null
+ OutputStream out = createOutputStream();
+ String charEncoding = response.getCharacterEncoding();
+ this.out = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out));
+ }
+ else if (out instanceof ServletOutputStream) {
+ throw new IllegalStateException("getOutputStream() already called.");
+ }
+
+ return (PrintWriter) out;
+ }
+
+ /**
+ * Returns the {@code OutputStream}.
+ * Subclasses should override this method to provide a decorated output stream.
+ * This method is guaranteed to be invoked only once for a request/response
+ * (unless {@code resetBuffer} is invoked).
+ *
+ * This implementation simply returns the output stream from the wrapped
+ * response.
+ *
+ * @return the {@code OutputStream} to use for the response
+ * @throws IOException if an I/O exception occurs
+ */
+ protected OutputStream createOutputStream() throws IOException {
+ return response.getOutputStream();
+ }
+
+ public void flushBuffer() throws IOException {
+ if (out instanceof ServletOutputStream) {
+ ((ServletOutputStream) out).flush();
+ }
+ else if (out != null) {
+ ((PrintWriter) out).flush();
+ }
+ }
+
+ public void resetBuffer() {
+ out = null;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java
index b2019707..41072fa8 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java
@@ -1,773 +1,773 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.util.convert.ConversionException;
-import com.twelvemonkeys.util.convert.Converter;
-
-import javax.servlet.*;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import java.io.File;
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.List;
-import java.util.Map;
-
-
-/**
- * Various servlet related helper methods.
- *
- * @author Harald Kuhr
- * @author Eirik Torske
- * @author last modified by $Author: haku $
- * @version $Id: ServletUtil.java#3 $
- */
-public final class ServletUtil {
-
- /**
- * {@code "javax.servlet.include.request_uri"}
- */
- private final static String ATTRIB_INC_REQUEST_URI = "javax.servlet.include.request_uri";
-
- /**
- * {@code "javax.servlet.include.context_path"}
- */
- private final static String ATTRIB_INC_CONTEXT_PATH = "javax.servlet.include.context_path";
-
- /**
- * {@code "javax.servlet.include.servlet_path"}
- */
- private final static String ATTRIB_INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
-
- /**
- * {@code "javax.servlet.include.path_info"}
- */
- private final static String ATTRIB_INC_PATH_INFO = "javax.servlet.include.path_info";
-
- /**
- * {@code "javax.servlet.include.query_string"}
- */
- private final static String ATTRIB_INC_QUERY_STRING = "javax.servlet.include.query_string";
-
- /**
- * {@code "javax.servlet.forward.request_uri"}
- */
- private final static String ATTRIB_FWD_REQUEST_URI = "javax.servlet.forward.request_uri";
-
- /**
- * {@code "javax.servlet.forward.context_path"}
- */
- private final static String ATTRIB_FWD_CONTEXT_PATH = "javax.servlet.forward.context_path";
-
- /**
- * {@code "javax.servlet.forward.servlet_path"}
- */
- private final static String ATTRIB_FWD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
-
- /**
- * {@code "javax.servlet.forward.path_info"}
- */
- private final static String ATTRIB_FWD_PATH_INFO = "javax.servlet.forward.path_info";
-
- /**
- * {@code "javax.servlet.forward.query_string"}
- */
- private final static String ATTRIB_FWD_QUERY_STRING = "javax.servlet.forward.query_string";
-
- /**
- * Don't create, static methods only
- */
- private ServletUtil() {
- }
-
- /**
- * Gets the value of the given parameter from the request, or if the
- * parameter is not set, the default value.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter, or the default value, if the
- * parameter is not set.
- */
- public static String getParameter(final ServletRequest pReq, final String pName, final String pDefault) {
- String str = pReq.getParameter(pName);
-
- return str != null ? str : pDefault;
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * an Object. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pType the type of object (class) to return
- * @param pFormat the format to use (might be {@code null} in many cases)
- * @param pDefault the default value
- * @return the value of the parameter converted to a boolean, or the
- * default value, if the parameter is not set.
- * @throws IllegalArgumentException if {@code pDefault} is
- * non-{@code null} and not an instance of {@code pType}
- * @throws NullPointerException if {@code pReq}, {@code pName} or
- * {@code pType} is {@code null}.
- * @todo Well, it's done. Need some thinking... We probably don't want default if conversion fails...
- * @see Converter#toObject
- */
- static T getParameter(final ServletRequest pReq, final String pName, final Class pType, final String pFormat, final T pDefault) {
- // Test if pDefault is either null or instance of pType
- if (pDefault != null && !pType.isInstance(pDefault)) {
- throw new IllegalArgumentException("default value not instance of " + pType + ": " + pDefault.getClass());
- }
-
- String str = pReq.getParameter(pName);
-
- if (str == null) {
- return pDefault;
- }
-
- try {
- return pType.cast(Converter.getInstance().toObject(str, pType, pFormat));
- }
- catch (ConversionException ce) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * a {@code boolean}. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to a {@code boolean}, or the
- * default value, if the parameter is not set.
- */
- public static boolean getBooleanParameter(final ServletRequest pReq, final String pName, final boolean pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return str != null ? Boolean.valueOf(str) : pDefault;
- }
- catch (NumberFormatException nfe) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * an {@code int}. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to an {@code int}, or the default
- * value, if the parameter is not set.
- */
- public static int getIntParameter(final ServletRequest pReq, final String pName, final int pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return str != null ? Integer.parseInt(str) : pDefault;
- }
- catch (NumberFormatException nfe) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * an {@code long}. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to an {@code long}, or the default
- * value, if the parameter is not set.
- */
- public static long getLongParameter(final ServletRequest pReq, final String pName, final long pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return str != null ? Long.parseLong(str) : pDefault;
- }
- catch (NumberFormatException nfe) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * a {@code float}. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to a {@code float}, or the default
- * value, if the parameter is not set.
- */
- public static float getFloatParameter(final ServletRequest pReq, final String pName, final float pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return str != null ? Float.parseFloat(str) : pDefault;
- }
- catch (NumberFormatException nfe) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * a {@code double}. If the parameter is not set or not parseable, the default
- * value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to n {@code double}, or the default
- * value, if the parameter is not set.
- */
- public static double getDoubleParameter(final ServletRequest pReq, final String pName, final double pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return str != null ? Double.parseDouble(str) : pDefault;
- }
- catch (NumberFormatException nfe) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * a {@code Date}. If the parameter is not set or not parseable, the
- * default value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pDefault the default value
- * @return the value of the parameter converted to a {@code Date}, or the
- * default value, if the parameter is not set.
- * @see com.twelvemonkeys.lang.StringUtil#toDate(String)
- */
- public static long getDateParameter(final ServletRequest pReq, final String pName, final long pDefault) {
- String str = pReq.getParameter(pName);
- try {
- return str != null ? StringUtil.toDate(str).getTime() : pDefault;
- }
- catch (IllegalArgumentException iae) {
- return pDefault;
- }
- }
-
- /**
- * Gets the value of the given parameter from the request converted to
- * a Date. If the parameter is not set or not parseable, the
- * default value is returned.
- *
- * @param pReq the servlet request
- * @param pName the parameter name
- * @param pFormat the date format to use
- * @param pDefault the default value
- * @return the value of the parameter converted to a Date, or the
- * default value, if the parameter is not set.
- * @see com.twelvemonkeys.lang.StringUtil#toDate(String,String)
- */
- /*
- public static long getDateParameter(ServletRequest pReq, String pName, String pFormat, long pDefault) {
- String str = pReq.getParameter(pName);
-
- try {
- return ((str != null) ? StringUtil.toDate(str, pFormat).getTime() : pDefault);
- }
- catch (IllegalArgumentException iae) {
- return pDefault;
- }
- }
- */
-
- /**
- * Builds a full-blown HTTP/HTTPS URL from a
- * {@code javax.servlet.http.HttpServletRequest} object.
- *
- *
- * @param pRequest The HTTP servlet request object.
- * @return the reproduced URL
- * @deprecated Use {@link javax.servlet.http.HttpServletRequest#getRequestURL()}
- * instead.
- */
- static StringBuffer buildHTTPURL(final HttpServletRequest pRequest) {
- StringBuffer resultURL = new StringBuffer();
-
- // Scheme, as in http, https, ftp etc
- String scheme = pRequest.getScheme();
- resultURL.append(scheme);
- resultURL.append("://");
- resultURL.append(pRequest.getServerName());
-
- // Append port only if not default port
- int port = pRequest.getServerPort();
- if (port > 0 &&
- !(("http".equals(scheme) && port == 80) ||
- ("https".equals(scheme) && port == 443))) {
- resultURL.append(":");
- resultURL.append(port);
- }
-
- // Append URI
- resultURL.append(pRequest.getRequestURI());
-
- // If present, append extra path info
- String pathInfo = pRequest.getPathInfo();
- if (pathInfo != null) {
- resultURL.append(pathInfo);
- }
-
- return resultURL;
- }
-
- /**
- * Gets the URI of the resource currently included.
- * The value is read from the request attribute
- * {@code "javax.servlet.include.request_uri"}
- *
- * @param pRequest the servlet request
- * @return the URI of the included resource, or {@code null} if no include
- * @see HttpServletRequest#getRequestURI
- * @since Servlet 2.2
- */
- public static String getIncludeRequestURI(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_INC_REQUEST_URI);
- }
-
- /**
- * Gets the context path of the resource currently included.
- * The value is read from the request attribute
- * {@code "javax.servlet.include.context_path"}
- *
- * @param pRequest the servlet request
- * @return the context path of the included resource, or {@code null} if no include
- * @see HttpServletRequest#getContextPath
- * @since Servlet 2.2
- */
- public static String getIncludeContextPath(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_INC_CONTEXT_PATH);
- }
-
- /**
- * Gets the servlet path of the resource currently included.
- * The value is read from the request attribute
- * {@code "javax.servlet.include.servlet_path"}
- *
- * @param pRequest the servlet request
- * @return the servlet path of the included resource, or {@code null} if no include
- * @see HttpServletRequest#getServletPath
- * @since Servlet 2.2
- */
- public static String getIncludeServletPath(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_INC_SERVLET_PATH);
- }
-
- /**
- * Gets the path info of the resource currently included.
- * The value is read from the request attribute
- * {@code "javax.servlet.include.path_info"}
- *
- * @param pRequest the servlet request
- * @return the path info of the included resource, or {@code null} if no include
- * @see HttpServletRequest#getPathInfo
- * @since Servlet 2.2
- */
- public static String getIncludePathInfo(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_INC_PATH_INFO);
- }
-
- /**
- * Gets the query string of the resource currently included.
- * The value is read from the request attribute
- * {@code "javax.servlet.include.query_string"}
- *
- * @param pRequest the servlet request
- * @return the query string of the included resource, or {@code null} if no include
- * @see HttpServletRequest#getQueryString
- * @since Servlet 2.2
- */
- public static String getIncludeQueryString(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_INC_QUERY_STRING);
- }
-
- /**
- * Gets the URI of the resource this request was forwarded from.
- * The value is read from the request attribute
- * {@code "javax.servlet.forward.request_uri"}
- *
- * @param pRequest the servlet request
- * @return the URI of the resource, or {@code null} if not forwarded
- * @see HttpServletRequest#getRequestURI
- * @since Servlet 2.4
- */
- public static String getForwardRequestURI(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_FWD_REQUEST_URI);
- }
-
- /**
- * Gets the context path of the resource this request was forwarded from.
- * The value is read from the request attribute
- * {@code "javax.servlet.forward.context_path"}
- *
- * @param pRequest the servlet request
- * @return the context path of the resource, or {@code null} if not forwarded
- * @see HttpServletRequest#getContextPath
- * @since Servlet 2.4
- */
- public static String getForwardContextPath(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_FWD_CONTEXT_PATH);
- }
-
- /**
- * Gets the servlet path of the resource this request was forwarded from.
- * The value is read from the request attribute
- * {@code "javax.servlet.forward.servlet_path"}
- *
- * @param pRequest the servlet request
- * @return the servlet path of the resource, or {@code null} if not forwarded
- * @see HttpServletRequest#getServletPath
- * @since Servlet 2.4
- */
- public static String getForwardServletPath(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_FWD_SERVLET_PATH);
- }
-
- /**
- * Gets the path info of the resource this request was forwarded from.
- * The value is read from the request attribute
- * {@code "javax.servlet.forward.path_info"}
- *
- * @param pRequest the servlet request
- * @return the path info of the resource, or {@code null} if not forwarded
- * @see HttpServletRequest#getPathInfo
- * @since Servlet 2.4
- */
- public static String getForwardPathInfo(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_FWD_PATH_INFO);
- }
-
- /**
- * Gets the query string of the resource this request was forwarded from.
- * The value is read from the request attribute
- * {@code "javax.servlet.forward.query_string"}
- *
- * @param pRequest the servlet request
- * @return the query string of the resource, or {@code null} if not forwarded
- * @see HttpServletRequest#getQueryString
- * @since Servlet 2.4
- */
- public static String getForwardQueryString(final ServletRequest pRequest) {
- return (String) pRequest.getAttribute(ATTRIB_FWD_QUERY_STRING);
- }
-
- /**
- * Gets the name of the servlet or the script that generated the servlet.
- *
- * @param pRequest The HTTP servlet request object.
- * @return the script name.
- * @todo Read the spec, seems to be a mismatch with the Servlet API...
- * @see javax.servlet.http.HttpServletRequest#getServletPath()
- */
- static String getScriptName(final HttpServletRequest pRequest) {
- String requestURI = pRequest.getRequestURI();
- return StringUtil.getLastElement(requestURI, "/");
- }
-
- /**
- * Gets the request URI relative to the current context path.
- *
- * As an example:
- * requestURI = "/webapp/index.jsp"
- * contextPath = "/webapp"
- *
- * The method will return {@code "/index.jsp"}.
- *
- * @param pRequest the current HTTP request
- * @return the request URI relative to the current context path.
- */
- public static String getContextRelativeURI(final HttpServletRequest pRequest) {
- String context = pRequest.getContextPath();
-
- if (!StringUtil.isEmpty(context)) { // "" for root context
- return pRequest.getRequestURI().substring(context.length());
- }
-
- return pRequest.getRequestURI();
- }
-
- /**
- * Returns a {@code URL} containing the real path for a given virtual
- * path, on URL form.
- * Note that this method will return {@code null} for all the same reasons
- * as {@code ServletContext.getRealPath(java.lang.String)} does.
- *
- * @param pContext the servlet context
- * @param pPath the virtual path
- * @return a {@code URL} object containing the path, or {@code null}.
- * @throws MalformedURLException if the path refers to a malformed URL
- * @see ServletContext#getRealPath(java.lang.String)
- * @see ServletContext#getResource(java.lang.String)
- */
- public static URL getRealURL(final ServletContext pContext, final String pPath) throws MalformedURLException {
- String realPath = pContext.getRealPath(pPath);
-
- if (realPath != null) {
- // NOTE: First convert to URI, as of Java 6 File.toURL is deprecated
- return new File(realPath).toURI().toURL();
- }
-
- return null;
- }
-
- /**
- * Gets the temp directory for the given {@code ServletContext} (web app).
- *
- * @param pContext the servlet context
- * @return the temp directory
- */
- public static File getTempDir(final ServletContext pContext) {
- return (File) pContext.getAttribute("javax.servlet.context.tempdir");
- }
-
- /**
- * Gets the unique identifier assigned to this session.
- * The identifier is assigned by the servlet container and is implementation
- * dependent.
- *
- * @param pRequest The HTTP servlet request object.
- * @return the session Id
- */
- public static String getSessionId(final HttpServletRequest pRequest) {
- HttpSession session = pRequest.getSession();
-
- return (session != null) ? session.getId() : null;
- }
-
- /**
- * Creates an unmodifiable {@code Map} view of the given
- * {@code ServletConfig}s init-parameters.
- * Note: The returned {@code Map} is optimized for {@code get}
- * operations and iterating over it's {@code keySet}.
- * For other operations it may not perform well.
- *
- * @param pConfig the servlet configuration
- * @return a {@code Map} view of the config
- * @throws IllegalArgumentException if {@code pConfig} is {@code null}
- */
- public static Map asMap(final ServletConfig pConfig) {
- return new ServletConfigMapAdapter(pConfig);
- }
-
- /**
- * Creates an unmodifiable {@code Map} view of the given
- * {@code FilterConfig}s init-parameters.
- * Note: The returned {@code Map} is optimized for {@code get}
- * operations and iterating over it's {@code keySet}.
- * For other operations it may not perform well.
- *
- * @param pConfig the servlet filter configuration
- * @return a {@code Map} view of the config
- * @throws IllegalArgumentException if {@code pConfig} is {@code null}
- */
- public static Map asMap(final FilterConfig pConfig) {
- return new ServletConfigMapAdapter(pConfig);
- }
-
- /**
- * Creates an unmodifiable {@code Map} view of the given
- * {@code ServletContext}s init-parameters.
- * Note: The returned {@code Map} is optimized for {@code get}
- * operations and iterating over it's {@code keySet}.
- * For other operations it may not perform well.
- *
- * @param pContext the servlet context
- * @return a {@code Map} view of the init parameters
- * @throws IllegalArgumentException if {@code pContext} is {@code null}
- */
- public static Map initParamsAsMap(final ServletContext pContext) {
- return new ServletConfigMapAdapter(pContext);
- }
-
- /**
- * Creates an modifiable {@code Map} view of the given
- * {@code ServletContext}s attributes.
- *
- * @param pContext the servlet context
- * @return a {@code Map} view of the attributes
- * @throws IllegalArgumentException if {@code pContext} is {@code null}
- */
- public static Map attributesAsMap(final ServletContext pContext) {
- return new ServletAttributesMapAdapter(pContext);
- }
-
- /**
- * Creates an modifiable {@code Map} view of the given
- * {@code ServletRequest}s attributes.
- *
- * @param pRequest the servlet request
- * @return a {@code Map} view of the attributes
- * @throws IllegalArgumentException if {@code pContext} is {@code null}
- */
- public static Map attributesAsMap(final ServletRequest pRequest) {
- return new ServletAttributesMapAdapter(pRequest);
- }
-
- /**
- * Creates an unmodifiable {@code Map} view of the given
- * {@code HttpServletRequest}s request parameters.
- *
- * @param pRequest the request
- * @return a {@code Map} view of the request parameters
- * @throws IllegalArgumentException if {@code pRequest} is {@code null}
- */
- public static Map> parametersAsMap(final ServletRequest pRequest) {
- return new ServletParametersMapAdapter(pRequest);
- }
-
- /**
- * Creates an unmodifiable {@code Map} view of the given
- * {@code HttpServletRequest}s request headers.
- *
- * @param pRequest the request
- * @return a {@code Map} view of the request headers
- * @throws IllegalArgumentException if {@code pRequest} is {@code null}
- */
- public static Map> headersAsMap(final HttpServletRequest pRequest) {
- return new ServletHeadersMapAdapter(pRequest);
- }
-
- /**
- * Creates a wrapper that implements either {@code ServletResponse} or
- * {@code HttpServletResponse}, depending on the type of
- * {@code pImplementation.getResponse()}.
- *
- * @param pImplementation the servlet response to create a wrapper for
- * @return a {@code ServletResponse} or
- * {@code HttpServletResponse}, depending on the type of
- * {@code pImplementation.getResponse()}
- */
- public static ServletResponse createWrapper(final ServletResponseWrapper pImplementation) {
- // TODO: Get all interfaces from implementation
- if (pImplementation.getResponse() instanceof HttpServletResponse) {
- return (HttpServletResponse) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
- new Class[]{HttpServletResponse.class, ServletResponse.class},
- new HttpServletResponseHandler(pImplementation));
- }
- return pImplementation;
- }
-
- /**
- * Creates a wrapper that implements either {@code ServletRequest} or
- * {@code HttpServletRequest}, depending on the type of
- * {@code pImplementation.getRequest()}.
- *
- * @param pImplementation the servlet request to create a wrapper for
- * @return a {@code ServletResponse} or
- * {@code HttpServletResponse}, depending on the type of
- * {@code pImplementation.getResponse()}
- */
- public static ServletRequest createWrapper(final ServletRequestWrapper pImplementation) {
- // TODO: Get all interfaces from implementation
- if (pImplementation.getRequest() instanceof HttpServletRequest) {
- return (HttpServletRequest) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
- new Class[]{HttpServletRequest.class, ServletRequest.class},
- new HttpServletRequestHandler(pImplementation));
- }
- return pImplementation;
- }
-
- private static class HttpServletResponseHandler implements InvocationHandler {
- private final ServletResponseWrapper response;
-
- HttpServletResponseHandler(final ServletResponseWrapper pResponse) {
- response = pResponse;
- }
-
- public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
- try {
- // TODO: Allow partial implementing?
- if (pMethod.getDeclaringClass().isInstance(response)) {
- return pMethod.invoke(response, pArgs);
- }
-
- // Method is not implemented in wrapper
- return pMethod.invoke(response.getResponse(), pArgs);
- }
- catch (InvocationTargetException e) {
- // Unwrap, to avoid UndeclaredThrowableException...
- throw e.getTargetException();
- }
- }
- }
-
- private static class HttpServletRequestHandler implements InvocationHandler {
- private final ServletRequestWrapper request;
-
- HttpServletRequestHandler(final ServletRequestWrapper pRequest) {
- request = pRequest;
- }
-
- public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
- try {
- // TODO: Allow partial implementing?
- if (pMethod.getDeclaringClass().isInstance(request)) {
- return pMethod.invoke(request, pArgs);
- }
-
- // Method is not implemented in wrapper
- return pMethod.invoke(request.getRequest(), pArgs);
- }
- catch (InvocationTargetException e) {
- // Unwrap, to avoid UndeclaredThrowableException...
- throw e.getTargetException();
- }
- }
- }
-}
-
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.util.convert.ConversionException;
+import com.twelvemonkeys.util.convert.Converter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Various servlet related helper methods.
+ *
+ * @author Harald Kuhr
+ * @author Eirik Torske
+ * @author last modified by $Author: haku $
+ * @version $Id: ServletUtil.java#3 $
+ */
+public final class ServletUtil {
+
+ /**
+ * {@code "javax.servlet.include.request_uri"}
+ */
+ private final static String ATTRIB_INC_REQUEST_URI = "javax.servlet.include.request_uri";
+
+ /**
+ * {@code "javax.servlet.include.context_path"}
+ */
+ private final static String ATTRIB_INC_CONTEXT_PATH = "javax.servlet.include.context_path";
+
+ /**
+ * {@code "javax.servlet.include.servlet_path"}
+ */
+ private final static String ATTRIB_INC_SERVLET_PATH = "javax.servlet.include.servlet_path";
+
+ /**
+ * {@code "javax.servlet.include.path_info"}
+ */
+ private final static String ATTRIB_INC_PATH_INFO = "javax.servlet.include.path_info";
+
+ /**
+ * {@code "javax.servlet.include.query_string"}
+ */
+ private final static String ATTRIB_INC_QUERY_STRING = "javax.servlet.include.query_string";
+
+ /**
+ * {@code "javax.servlet.forward.request_uri"}
+ */
+ private final static String ATTRIB_FWD_REQUEST_URI = "javax.servlet.forward.request_uri";
+
+ /**
+ * {@code "javax.servlet.forward.context_path"}
+ */
+ private final static String ATTRIB_FWD_CONTEXT_PATH = "javax.servlet.forward.context_path";
+
+ /**
+ * {@code "javax.servlet.forward.servlet_path"}
+ */
+ private final static String ATTRIB_FWD_SERVLET_PATH = "javax.servlet.forward.servlet_path";
+
+ /**
+ * {@code "javax.servlet.forward.path_info"}
+ */
+ private final static String ATTRIB_FWD_PATH_INFO = "javax.servlet.forward.path_info";
+
+ /**
+ * {@code "javax.servlet.forward.query_string"}
+ */
+ private final static String ATTRIB_FWD_QUERY_STRING = "javax.servlet.forward.query_string";
+
+ /**
+ * Don't create, static methods only
+ */
+ private ServletUtil() {
+ }
+
+ /**
+ * Gets the value of the given parameter from the request, or if the
+ * parameter is not set, the default value.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter, or the default value, if the
+ * parameter is not set.
+ */
+ public static String getParameter(final ServletRequest pReq, final String pName, final String pDefault) {
+ String str = pReq.getParameter(pName);
+
+ return str != null ? str : pDefault;
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * an Object. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pType the type of object (class) to return
+ * @param pFormat the format to use (might be {@code null} in many cases)
+ * @param pDefault the default value
+ * @return the value of the parameter converted to a boolean, or the
+ * default value, if the parameter is not set.
+ * @throws IllegalArgumentException if {@code pDefault} is
+ * non-{@code null} and not an instance of {@code pType}
+ * @throws NullPointerException if {@code pReq}, {@code pName} or
+ * {@code pType} is {@code null}.
+ * @todo Well, it's done. Need some thinking... We probably don't want default if conversion fails...
+ * @see Converter#toObject
+ */
+ static T getParameter(final ServletRequest pReq, final String pName, final Class pType, final String pFormat, final T pDefault) {
+ // Test if pDefault is either null or instance of pType
+ if (pDefault != null && !pType.isInstance(pDefault)) {
+ throw new IllegalArgumentException("default value not instance of " + pType + ": " + pDefault.getClass());
+ }
+
+ String str = pReq.getParameter(pName);
+
+ if (str == null) {
+ return pDefault;
+ }
+
+ try {
+ return pType.cast(Converter.getInstance().toObject(str, pType, pFormat));
+ }
+ catch (ConversionException ce) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * a {@code boolean}. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to a {@code boolean}, or the
+ * default value, if the parameter is not set.
+ */
+ public static boolean getBooleanParameter(final ServletRequest pReq, final String pName, final boolean pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return str != null ? Boolean.valueOf(str) : pDefault;
+ }
+ catch (NumberFormatException nfe) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * an {@code int}. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to an {@code int}, or the default
+ * value, if the parameter is not set.
+ */
+ public static int getIntParameter(final ServletRequest pReq, final String pName, final int pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return str != null ? Integer.parseInt(str) : pDefault;
+ }
+ catch (NumberFormatException nfe) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * an {@code long}. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to an {@code long}, or the default
+ * value, if the parameter is not set.
+ */
+ public static long getLongParameter(final ServletRequest pReq, final String pName, final long pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return str != null ? Long.parseLong(str) : pDefault;
+ }
+ catch (NumberFormatException nfe) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * a {@code float}. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to a {@code float}, or the default
+ * value, if the parameter is not set.
+ */
+ public static float getFloatParameter(final ServletRequest pReq, final String pName, final float pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return str != null ? Float.parseFloat(str) : pDefault;
+ }
+ catch (NumberFormatException nfe) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * a {@code double}. If the parameter is not set or not parseable, the default
+ * value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to n {@code double}, or the default
+ * value, if the parameter is not set.
+ */
+ public static double getDoubleParameter(final ServletRequest pReq, final String pName, final double pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return str != null ? Double.parseDouble(str) : pDefault;
+ }
+ catch (NumberFormatException nfe) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * a {@code Date}. If the parameter is not set or not parseable, the
+ * default value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pDefault the default value
+ * @return the value of the parameter converted to a {@code Date}, or the
+ * default value, if the parameter is not set.
+ * @see com.twelvemonkeys.lang.StringUtil#toDate(String)
+ */
+ public static long getDateParameter(final ServletRequest pReq, final String pName, final long pDefault) {
+ String str = pReq.getParameter(pName);
+ try {
+ return str != null ? StringUtil.toDate(str).getTime() : pDefault;
+ }
+ catch (IllegalArgumentException iae) {
+ return pDefault;
+ }
+ }
+
+ /**
+ * Gets the value of the given parameter from the request converted to
+ * a Date. If the parameter is not set or not parseable, the
+ * default value is returned.
+ *
+ * @param pReq the servlet request
+ * @param pName the parameter name
+ * @param pFormat the date format to use
+ * @param pDefault the default value
+ * @return the value of the parameter converted to a Date, or the
+ * default value, if the parameter is not set.
+ * @see com.twelvemonkeys.lang.StringUtil#toDate(String,String)
+ */
+ /*
+ public static long getDateParameter(ServletRequest pReq, String pName, String pFormat, long pDefault) {
+ String str = pReq.getParameter(pName);
+
+ try {
+ return ((str != null) ? StringUtil.toDate(str, pFormat).getTime() : pDefault);
+ }
+ catch (IllegalArgumentException iae) {
+ return pDefault;
+ }
+ }
+ */
+
+ /**
+ * Builds a full-blown HTTP/HTTPS URL from a
+ * {@code javax.servlet.http.HttpServletRequest} object.
+ *
+ *
+ * @param pRequest The HTTP servlet request object.
+ * @return the reproduced URL
+ * @deprecated Use {@link javax.servlet.http.HttpServletRequest#getRequestURL()}
+ * instead.
+ */
+ static StringBuffer buildHTTPURL(final HttpServletRequest pRequest) {
+ StringBuffer resultURL = new StringBuffer();
+
+ // Scheme, as in http, https, ftp etc
+ String scheme = pRequest.getScheme();
+ resultURL.append(scheme);
+ resultURL.append("://");
+ resultURL.append(pRequest.getServerName());
+
+ // Append port only if not default port
+ int port = pRequest.getServerPort();
+ if (port > 0 &&
+ !(("http".equals(scheme) && port == 80) ||
+ ("https".equals(scheme) && port == 443))) {
+ resultURL.append(":");
+ resultURL.append(port);
+ }
+
+ // Append URI
+ resultURL.append(pRequest.getRequestURI());
+
+ // If present, append extra path info
+ String pathInfo = pRequest.getPathInfo();
+ if (pathInfo != null) {
+ resultURL.append(pathInfo);
+ }
+
+ return resultURL;
+ }
+
+ /**
+ * Gets the URI of the resource currently included.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.include.request_uri"}
+ *
+ * @param pRequest the servlet request
+ * @return the URI of the included resource, or {@code null} if no include
+ * @see HttpServletRequest#getRequestURI
+ * @since Servlet 2.2
+ */
+ public static String getIncludeRequestURI(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_INC_REQUEST_URI);
+ }
+
+ /**
+ * Gets the context path of the resource currently included.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.include.context_path"}
+ *
+ * @param pRequest the servlet request
+ * @return the context path of the included resource, or {@code null} if no include
+ * @see HttpServletRequest#getContextPath
+ * @since Servlet 2.2
+ */
+ public static String getIncludeContextPath(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_INC_CONTEXT_PATH);
+ }
+
+ /**
+ * Gets the servlet path of the resource currently included.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.include.servlet_path"}
+ *
+ * @param pRequest the servlet request
+ * @return the servlet path of the included resource, or {@code null} if no include
+ * @see HttpServletRequest#getServletPath
+ * @since Servlet 2.2
+ */
+ public static String getIncludeServletPath(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_INC_SERVLET_PATH);
+ }
+
+ /**
+ * Gets the path info of the resource currently included.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.include.path_info"}
+ *
+ * @param pRequest the servlet request
+ * @return the path info of the included resource, or {@code null} if no include
+ * @see HttpServletRequest#getPathInfo
+ * @since Servlet 2.2
+ */
+ public static String getIncludePathInfo(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_INC_PATH_INFO);
+ }
+
+ /**
+ * Gets the query string of the resource currently included.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.include.query_string"}
+ *
+ * @param pRequest the servlet request
+ * @return the query string of the included resource, or {@code null} if no include
+ * @see HttpServletRequest#getQueryString
+ * @since Servlet 2.2
+ */
+ public static String getIncludeQueryString(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_INC_QUERY_STRING);
+ }
+
+ /**
+ * Gets the URI of the resource this request was forwarded from.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.forward.request_uri"}
+ *
+ * @param pRequest the servlet request
+ * @return the URI of the resource, or {@code null} if not forwarded
+ * @see HttpServletRequest#getRequestURI
+ * @since Servlet 2.4
+ */
+ public static String getForwardRequestURI(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_FWD_REQUEST_URI);
+ }
+
+ /**
+ * Gets the context path of the resource this request was forwarded from.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.forward.context_path"}
+ *
+ * @param pRequest the servlet request
+ * @return the context path of the resource, or {@code null} if not forwarded
+ * @see HttpServletRequest#getContextPath
+ * @since Servlet 2.4
+ */
+ public static String getForwardContextPath(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_FWD_CONTEXT_PATH);
+ }
+
+ /**
+ * Gets the servlet path of the resource this request was forwarded from.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.forward.servlet_path"}
+ *
+ * @param pRequest the servlet request
+ * @return the servlet path of the resource, or {@code null} if not forwarded
+ * @see HttpServletRequest#getServletPath
+ * @since Servlet 2.4
+ */
+ public static String getForwardServletPath(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_FWD_SERVLET_PATH);
+ }
+
+ /**
+ * Gets the path info of the resource this request was forwarded from.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.forward.path_info"}
+ *
+ * @param pRequest the servlet request
+ * @return the path info of the resource, or {@code null} if not forwarded
+ * @see HttpServletRequest#getPathInfo
+ * @since Servlet 2.4
+ */
+ public static String getForwardPathInfo(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_FWD_PATH_INFO);
+ }
+
+ /**
+ * Gets the query string of the resource this request was forwarded from.
+ * The value is read from the request attribute
+ * {@code "javax.servlet.forward.query_string"}
+ *
+ * @param pRequest the servlet request
+ * @return the query string of the resource, or {@code null} if not forwarded
+ * @see HttpServletRequest#getQueryString
+ * @since Servlet 2.4
+ */
+ public static String getForwardQueryString(final ServletRequest pRequest) {
+ return (String) pRequest.getAttribute(ATTRIB_FWD_QUERY_STRING);
+ }
+
+ /**
+ * Gets the name of the servlet or the script that generated the servlet.
+ *
+ * @param pRequest The HTTP servlet request object.
+ * @return the script name.
+ * @todo Read the spec, seems to be a mismatch with the Servlet API...
+ * @see javax.servlet.http.HttpServletRequest#getServletPath()
+ */
+ static String getScriptName(final HttpServletRequest pRequest) {
+ String requestURI = pRequest.getRequestURI();
+ return StringUtil.getLastElement(requestURI, "/");
+ }
+
+ /**
+ * Gets the request URI relative to the current context path.
+ *
+ * As an example:
+ * requestURI = "/webapp/index.jsp"
+ * contextPath = "/webapp"
+ *
+ * The method will return {@code "/index.jsp"}.
+ *
+ * @param pRequest the current HTTP request
+ * @return the request URI relative to the current context path.
+ */
+ public static String getContextRelativeURI(final HttpServletRequest pRequest) {
+ String context = pRequest.getContextPath();
+
+ if (!StringUtil.isEmpty(context)) { // "" for root context
+ return pRequest.getRequestURI().substring(context.length());
+ }
+
+ return pRequest.getRequestURI();
+ }
+
+ /**
+ * Returns a {@code URL} containing the real path for a given virtual
+ * path, on URL form.
+ * Note that this method will return {@code null} for all the same reasons
+ * as {@code ServletContext.getRealPath(java.lang.String)} does.
+ *
+ * @param pContext the servlet context
+ * @param pPath the virtual path
+ * @return a {@code URL} object containing the path, or {@code null}.
+ * @throws MalformedURLException if the path refers to a malformed URL
+ * @see ServletContext#getRealPath(java.lang.String)
+ * @see ServletContext#getResource(java.lang.String)
+ */
+ public static URL getRealURL(final ServletContext pContext, final String pPath) throws MalformedURLException {
+ String realPath = pContext.getRealPath(pPath);
+
+ if (realPath != null) {
+ // NOTE: First convert to URI, as of Java 6 File.toURL is deprecated
+ return new File(realPath).toURI().toURL();
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the temp directory for the given {@code ServletContext} (web app).
+ *
+ * @param pContext the servlet context
+ * @return the temp directory
+ */
+ public static File getTempDir(final ServletContext pContext) {
+ return (File) pContext.getAttribute("javax.servlet.context.tempdir");
+ }
+
+ /**
+ * Gets the unique identifier assigned to this session.
+ * The identifier is assigned by the servlet container and is implementation
+ * dependent.
+ *
+ * @param pRequest The HTTP servlet request object.
+ * @return the session Id
+ */
+ public static String getSessionId(final HttpServletRequest pRequest) {
+ HttpSession session = pRequest.getSession();
+
+ return (session != null) ? session.getId() : null;
+ }
+
+ /**
+ * Creates an unmodifiable {@code Map} view of the given
+ * {@code ServletConfig}s init-parameters.
+ * Note: The returned {@code Map} is optimized for {@code get}
+ * operations and iterating over it's {@code keySet}.
+ * For other operations it may not perform well.
+ *
+ * @param pConfig the servlet configuration
+ * @return a {@code Map} view of the config
+ * @throws IllegalArgumentException if {@code pConfig} is {@code null}
+ */
+ public static Map asMap(final ServletConfig pConfig) {
+ return new ServletConfigMapAdapter(pConfig);
+ }
+
+ /**
+ * Creates an unmodifiable {@code Map} view of the given
+ * {@code FilterConfig}s init-parameters.
+ * Note: The returned {@code Map} is optimized for {@code get}
+ * operations and iterating over it's {@code keySet}.
+ * For other operations it may not perform well.
+ *
+ * @param pConfig the servlet filter configuration
+ * @return a {@code Map} view of the config
+ * @throws IllegalArgumentException if {@code pConfig} is {@code null}
+ */
+ public static Map asMap(final FilterConfig pConfig) {
+ return new ServletConfigMapAdapter(pConfig);
+ }
+
+ /**
+ * Creates an unmodifiable {@code Map} view of the given
+ * {@code ServletContext}s init-parameters.
+ * Note: The returned {@code Map} is optimized for {@code get}
+ * operations and iterating over it's {@code keySet}.
+ * For other operations it may not perform well.
+ *
+ * @param pContext the servlet context
+ * @return a {@code Map} view of the init parameters
+ * @throws IllegalArgumentException if {@code pContext} is {@code null}
+ */
+ public static Map initParamsAsMap(final ServletContext pContext) {
+ return new ServletConfigMapAdapter(pContext);
+ }
+
+ /**
+ * Creates an modifiable {@code Map} view of the given
+ * {@code ServletContext}s attributes.
+ *
+ * @param pContext the servlet context
+ * @return a {@code Map} view of the attributes
+ * @throws IllegalArgumentException if {@code pContext} is {@code null}
+ */
+ public static Map attributesAsMap(final ServletContext pContext) {
+ return new ServletAttributesMapAdapter(pContext);
+ }
+
+ /**
+ * Creates an modifiable {@code Map} view of the given
+ * {@code ServletRequest}s attributes.
+ *
+ * @param pRequest the servlet request
+ * @return a {@code Map} view of the attributes
+ * @throws IllegalArgumentException if {@code pContext} is {@code null}
+ */
+ public static Map attributesAsMap(final ServletRequest pRequest) {
+ return new ServletAttributesMapAdapter(pRequest);
+ }
+
+ /**
+ * Creates an unmodifiable {@code Map} view of the given
+ * {@code HttpServletRequest}s request parameters.
+ *
+ * @param pRequest the request
+ * @return a {@code Map} view of the request parameters
+ * @throws IllegalArgumentException if {@code pRequest} is {@code null}
+ */
+ public static Map> parametersAsMap(final ServletRequest pRequest) {
+ return new ServletParametersMapAdapter(pRequest);
+ }
+
+ /**
+ * Creates an unmodifiable {@code Map} view of the given
+ * {@code HttpServletRequest}s request headers.
+ *
+ * @param pRequest the request
+ * @return a {@code Map} view of the request headers
+ * @throws IllegalArgumentException if {@code pRequest} is {@code null}
+ */
+ public static Map> headersAsMap(final HttpServletRequest pRequest) {
+ return new ServletHeadersMapAdapter(pRequest);
+ }
+
+ /**
+ * Creates a wrapper that implements either {@code ServletResponse} or
+ * {@code HttpServletResponse}, depending on the type of
+ * {@code pImplementation.getResponse()}.
+ *
+ * @param pImplementation the servlet response to create a wrapper for
+ * @return a {@code ServletResponse} or
+ * {@code HttpServletResponse}, depending on the type of
+ * {@code pImplementation.getResponse()}
+ */
+ public static ServletResponse createWrapper(final ServletResponseWrapper pImplementation) {
+ // TODO: Get all interfaces from implementation
+ if (pImplementation.getResponse() instanceof HttpServletResponse) {
+ return (HttpServletResponse) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
+ new Class[]{HttpServletResponse.class, ServletResponse.class},
+ new HttpServletResponseHandler(pImplementation));
+ }
+ return pImplementation;
+ }
+
+ /**
+ * Creates a wrapper that implements either {@code ServletRequest} or
+ * {@code HttpServletRequest}, depending on the type of
+ * {@code pImplementation.getRequest()}.
+ *
+ * @param pImplementation the servlet request to create a wrapper for
+ * @return a {@code ServletResponse} or
+ * {@code HttpServletResponse}, depending on the type of
+ * {@code pImplementation.getResponse()}
+ */
+ public static ServletRequest createWrapper(final ServletRequestWrapper pImplementation) {
+ // TODO: Get all interfaces from implementation
+ if (pImplementation.getRequest() instanceof HttpServletRequest) {
+ return (HttpServletRequest) Proxy.newProxyInstance(pImplementation.getClass().getClassLoader(),
+ new Class[]{HttpServletRequest.class, ServletRequest.class},
+ new HttpServletRequestHandler(pImplementation));
+ }
+ return pImplementation;
+ }
+
+ private static class HttpServletResponseHandler implements InvocationHandler {
+ private final ServletResponseWrapper response;
+
+ HttpServletResponseHandler(final ServletResponseWrapper pResponse) {
+ response = pResponse;
+ }
+
+ public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
+ try {
+ // TODO: Allow partial implementing?
+ if (pMethod.getDeclaringClass().isInstance(response)) {
+ return pMethod.invoke(response, pArgs);
+ }
+
+ // Method is not implemented in wrapper
+ return pMethod.invoke(response.getResponse(), pArgs);
+ }
+ catch (InvocationTargetException e) {
+ // Unwrap, to avoid UndeclaredThrowableException...
+ throw e.getTargetException();
+ }
+ }
+ }
+
+ private static class HttpServletRequestHandler implements InvocationHandler {
+ private final ServletRequestWrapper request;
+
+ HttpServletRequestHandler(final ServletRequestWrapper pRequest) {
+ request = pRequest;
+ }
+
+ public Object invoke(final Object pProxy, final Method pMethod, final Object[] pArgs) throws Throwable {
+ try {
+ // TODO: Allow partial implementing?
+ if (pMethod.getDeclaringClass().isInstance(request)) {
+ return pMethod.invoke(request, pArgs);
+ }
+
+ // Method is not implemented in wrapper
+ return pMethod.invoke(request.getRequest(), pArgs);
+ }
+ catch (InvocationTargetException e) {
+ // Unwrap, to avoid UndeclaredThrowableException...
+ throw e.getTargetException();
+ }
+ }
+ }
+}
+
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java
index 71778898..99d073b1 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java
@@ -1,306 +1,306 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * ThrottleFilter, a filter for easing server during heavy load.
- *
- * Intercepts requests, and returns HTTP response code {@code 503 (Service Unavailable)},
- * if there are more than a given number of concurrent
- * requests, to avoid large backlogs. The number of concurrent requests and the
- * response messages sent to the user agent, is configurable from the web
- * descriptor.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: ThrottleFilter.java#1 $
- * @see #setMaxConcurrentThreadCount
- * @see #setResponseMessages
- */
-public class ThrottleFilter extends GenericFilter {
-
- /**
- * Minimum free thread count, defaults to {@code 10}
- */
- protected int maxConcurrentThreadCount = 10;
-
- /**
- * The number of running request threads
- */
- private int runningThreads = 0;
- private final Object runningThreadsLock = new Object();
-
- /**
- * Default response message sent to user agents, if the request is rejected
- */
- protected final static String DEFUALT_RESPONSE_MESSAGE =
- "Service temporarily unavailable, please try again later.";
-
- /**
- * Default response content type
- */
- protected static final String DEFAULT_TYPE = "text/html";
-
- /**
- * The reposne message sent to user agenta, if the request is rejected
- */
- private Map responseMessageNames = new HashMap(10);
-
- /**
- * The reposne message sent to user agents, if the request is rejected
- */
- private String[] responseMessageTypes = null;
-
- /**
- * Cache for response messages
- */
- private Map responseCache = new HashMap(10);
-
-
- /**
- * Sets the minimum free thread count.
- *
- * @param pMaxConcurrentThreadCount
- */
- public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) {
- if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) {
- try {
- maxConcurrentThreadCount = Integer.parseInt(pMaxConcurrentThreadCount);
- }
- catch (NumberFormatException nfe) {
- // Use default
- }
- }
- }
-
- /**
- * Sets the response message sent to the user agent, if the request is
- * rejected.
- *
- * The format is {@code <mime-type>=<filename>,
- * <mime-type>=<filename>}.
- *
- * Example: {@code <text/vnd.wap.wmlgt;=</errors/503.wml>,
- * <text/html>=</errors/503.html>}
- *
- * @param pResponseMessages
- */
- public void setResponseMessages(String pResponseMessages) {
- // Split string in type=filename pairs
- String[] mappings = StringUtil.toStringArray(pResponseMessages, ", \r\n\t");
- List types = new ArrayList();
-
- for (String pair : mappings) {
- // Split pairs on '='
- String[] mapping = StringUtil.toStringArray(pair, "= ");
-
- // Test for wrong mapping
- if ((mapping == null) || (mapping.length < 2)) {
- log("Error in init param \"responseMessages\": " + pResponseMessages);
- continue;
- }
-
- types.add(mapping[0]);
- responseMessageNames.put(mapping[0], mapping[1]);
- }
-
- // Create arrays
- responseMessageTypes = types.toArray(new String[types.size()]);
- }
-
- /**
- * @param pRequest
- * @param pResponse
- * @param pChain
- * @throws IOException
- * @throws ServletException
- */
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- try {
- if (beginRequest()) {
- // Continue request
- pChain.doFilter(pRequest, pResponse);
- }
- else {
- // Send error and end request
- // Get HTTP specific versions
- HttpServletRequest request = (HttpServletRequest) pRequest;
- HttpServletResponse response = (HttpServletResponse) pResponse;
-
- // Get content type
- String contentType = getContentType(request);
-
- // Note: This is not the way the spec says you should do it.
- // However, we handle error response this way for preformace reasons.
- // The "correct" way would be to use sendError() and register a servlet
- // that does the content negotiation as errorpage in the web descriptor.
- response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
- response.setContentType(contentType);
- response.getWriter().println(getMessage(contentType));
-
- // Log warning, as this shouldn't happen too often
- log("Request denied, no more available threads for requestURI=" + request.getRequestURI());
- }
- }
- finally {
- doneRequest();
- }
- }
-
- /**
- * Marks the beginning of a request
- *
- * @return {@code true} if the request should be handled.
- */
- private boolean beginRequest() {
- synchronized (runningThreadsLock) {
- runningThreads++;
- }
-
- return (runningThreads <= maxConcurrentThreadCount);
- }
-
- /**
- * Marks the end of the request
- */
- private void doneRequest() {
- synchronized (runningThreadsLock) {
- runningThreads--;
- }
- }
-
- /**
- * Gets the content type for the response, suitable for the requesting user agent.
- *
- * @param pRequest
- * @return the content type
- */
- private String getContentType(HttpServletRequest pRequest) {
- if (responseMessageTypes != null) {
- String accept = pRequest.getHeader("Accept");
-
- for (String type : responseMessageTypes) {
- // Note: This is not 100% correct way of doing content negotiation
- // But we just want a compatible result, quick, so this is okay
- if (StringUtil.contains(accept, type)) {
- return type;
- }
- }
- }
-
- // If none found, return default
- return DEFAULT_TYPE;
- }
-
- /**
- * Gets the response message for the given content type.
- *
- * @param pContentType
- * @return the message
- */
- private String getMessage(String pContentType) {
- String fileName = responseMessageNames.get(pContentType);
-
- // Get cached value
- CacheEntry entry = responseCache.get(fileName);
-
- if ((entry == null) || entry.isExpired()) {
-
- // Create and add or replace cached value
- entry = new CacheEntry(readMessage(fileName));
- responseCache.put(fileName, entry);
- }
-
- // Return value
- return (entry.getValue() != null)
- ? (String) entry.getValue()
- : DEFUALT_RESPONSE_MESSAGE;
- }
-
- /**
- * Reads the response message from a file in the current web app.
- *
- * @param pFileName
- * @return the message
- */
- private String readMessage(String pFileName) {
- try {
- // Read resource from web app
- InputStream is = getServletContext().getResourceAsStream(pFileName);
-
- if (is != null) {
- return new String(FileUtil.read(is));
- }
- else {
- log("File not found: " + pFileName);
- }
- }
- catch (IOException ioe) {
- log("Error reading file: " + pFileName + " (" + ioe.getMessage() + ")");
- }
- return null;
- }
-
- /**
- * Keeps track of Cached objects
- */
- private static class CacheEntry {
- private Object value;
- private long timestamp = -1;
-
- CacheEntry(Object pValue) {
- value = pValue;
- timestamp = System.currentTimeMillis();
- }
-
- Object getValue() {
- return value;
- }
-
- boolean isExpired() {
- return (System.currentTimeMillis() - timestamp) > 60000; // Cache 1 minute
- }
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * ThrottleFilter, a filter for easing server during heavy load.
+ *
+ * Intercepts requests, and returns HTTP response code {@code 503 (Service Unavailable)},
+ * if there are more than a given number of concurrent
+ * requests, to avoid large backlogs. The number of concurrent requests and the
+ * response messages sent to the user agent, is configurable from the web
+ * descriptor.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: ThrottleFilter.java#1 $
+ * @see #setMaxConcurrentThreadCount
+ * @see #setResponseMessages
+ */
+public class ThrottleFilter extends GenericFilter {
+
+ /**
+ * Minimum free thread count, defaults to {@code 10}
+ */
+ protected int maxConcurrentThreadCount = 10;
+
+ /**
+ * The number of running request threads
+ */
+ private int runningThreads = 0;
+ private final Object runningThreadsLock = new Object();
+
+ /**
+ * Default response message sent to user agents, if the request is rejected
+ */
+ protected final static String DEFUALT_RESPONSE_MESSAGE =
+ "Service temporarily unavailable, please try again later.";
+
+ /**
+ * Default response content type
+ */
+ protected static final String DEFAULT_TYPE = "text/html";
+
+ /**
+ * The reposne message sent to user agenta, if the request is rejected
+ */
+ private Map responseMessageNames = new HashMap(10);
+
+ /**
+ * The reposne message sent to user agents, if the request is rejected
+ */
+ private String[] responseMessageTypes = null;
+
+ /**
+ * Cache for response messages
+ */
+ private Map responseCache = new HashMap(10);
+
+
+ /**
+ * Sets the minimum free thread count.
+ *
+ * @param pMaxConcurrentThreadCount
+ */
+ public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) {
+ if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) {
+ try {
+ maxConcurrentThreadCount = Integer.parseInt(pMaxConcurrentThreadCount);
+ }
+ catch (NumberFormatException nfe) {
+ // Use default
+ }
+ }
+ }
+
+ /**
+ * Sets the response message sent to the user agent, if the request is
+ * rejected.
+ *
+ * The format is {@code <mime-type>=<filename>,
+ * <mime-type>=<filename>}.
+ *
+ * Example: {@code <text/vnd.wap.wmlgt;=</errors/503.wml>,
+ * <text/html>=</errors/503.html>}
+ *
+ * @param pResponseMessages
+ */
+ public void setResponseMessages(String pResponseMessages) {
+ // Split string in type=filename pairs
+ String[] mappings = StringUtil.toStringArray(pResponseMessages, ", \r\n\t");
+ List types = new ArrayList();
+
+ for (String pair : mappings) {
+ // Split pairs on '='
+ String[] mapping = StringUtil.toStringArray(pair, "= ");
+
+ // Test for wrong mapping
+ if ((mapping == null) || (mapping.length < 2)) {
+ log("Error in init param \"responseMessages\": " + pResponseMessages);
+ continue;
+ }
+
+ types.add(mapping[0]);
+ responseMessageNames.put(mapping[0], mapping[1]);
+ }
+
+ // Create arrays
+ responseMessageTypes = types.toArray(new String[types.size()]);
+ }
+
+ /**
+ * @param pRequest
+ * @param pResponse
+ * @param pChain
+ * @throws IOException
+ * @throws ServletException
+ */
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ try {
+ if (beginRequest()) {
+ // Continue request
+ pChain.doFilter(pRequest, pResponse);
+ }
+ else {
+ // Send error and end request
+ // Get HTTP specific versions
+ HttpServletRequest request = (HttpServletRequest) pRequest;
+ HttpServletResponse response = (HttpServletResponse) pResponse;
+
+ // Get content type
+ String contentType = getContentType(request);
+
+ // Note: This is not the way the spec says you should do it.
+ // However, we handle error response this way for preformace reasons.
+ // The "correct" way would be to use sendError() and register a servlet
+ // that does the content negotiation as errorpage in the web descriptor.
+ response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+ response.setContentType(contentType);
+ response.getWriter().println(getMessage(contentType));
+
+ // Log warning, as this shouldn't happen too often
+ log("Request denied, no more available threads for requestURI=" + request.getRequestURI());
+ }
+ }
+ finally {
+ doneRequest();
+ }
+ }
+
+ /**
+ * Marks the beginning of a request
+ *
+ * @return {@code true} if the request should be handled.
+ */
+ private boolean beginRequest() {
+ synchronized (runningThreadsLock) {
+ runningThreads++;
+ }
+
+ return (runningThreads <= maxConcurrentThreadCount);
+ }
+
+ /**
+ * Marks the end of the request
+ */
+ private void doneRequest() {
+ synchronized (runningThreadsLock) {
+ runningThreads--;
+ }
+ }
+
+ /**
+ * Gets the content type for the response, suitable for the requesting user agent.
+ *
+ * @param pRequest
+ * @return the content type
+ */
+ private String getContentType(HttpServletRequest pRequest) {
+ if (responseMessageTypes != null) {
+ String accept = pRequest.getHeader("Accept");
+
+ for (String type : responseMessageTypes) {
+ // Note: This is not 100% correct way of doing content negotiation
+ // But we just want a compatible result, quick, so this is okay
+ if (StringUtil.contains(accept, type)) {
+ return type;
+ }
+ }
+ }
+
+ // If none found, return default
+ return DEFAULT_TYPE;
+ }
+
+ /**
+ * Gets the response message for the given content type.
+ *
+ * @param pContentType
+ * @return the message
+ */
+ private String getMessage(String pContentType) {
+ String fileName = responseMessageNames.get(pContentType);
+
+ // Get cached value
+ CacheEntry entry = responseCache.get(fileName);
+
+ if ((entry == null) || entry.isExpired()) {
+
+ // Create and add or replace cached value
+ entry = new CacheEntry(readMessage(fileName));
+ responseCache.put(fileName, entry);
+ }
+
+ // Return value
+ return (entry.getValue() != null)
+ ? (String) entry.getValue()
+ : DEFUALT_RESPONSE_MESSAGE;
+ }
+
+ /**
+ * Reads the response message from a file in the current web app.
+ *
+ * @param pFileName
+ * @return the message
+ */
+ private String readMessage(String pFileName) {
+ try {
+ // Read resource from web app
+ InputStream is = getServletContext().getResourceAsStream(pFileName);
+
+ if (is != null) {
+ return new String(FileUtil.read(is));
+ }
+ else {
+ log("File not found: " + pFileName);
+ }
+ }
+ catch (IOException ioe) {
+ log("Error reading file: " + pFileName + " (" + ioe.getMessage() + ")");
+ }
+ return null;
+ }
+
+ /**
+ * Keeps track of Cached objects
+ */
+ private static class CacheEntry {
+ private Object value;
+ private long timestamp = -1;
+
+ CacheEntry(Object pValue) {
+ value = pValue;
+ timestamp = System.currentTimeMillis();
+ }
+
+ Object getValue() {
+ return value;
+ }
+
+ boolean isExpired() {
+ return (System.currentTimeMillis() - timestamp) > 60000; // Cache 1 minute
+ }
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java
index 38664018..23a79a5e 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java
@@ -1,112 +1,112 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-
-/**
- * TimingFilter class description.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: TimingFilter.java#1 $
- */
-public class TimingFilter extends GenericFilter {
-
- private String attribUsage = null;
-
- /**
- * Method init
- *
- * @throws ServletException
- */
- public void init() throws ServletException {
- attribUsage = getFilterName() + ".timerDelta";
- }
-
- /**
- *
- * @param pRequest
- * @param pResponse
- * @param pChain
- * @throws IOException
- * @throws ServletException
- */
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
- throws IOException, ServletException {
- // Get total usage of earlier filters on same level
- Object usageAttrib = pRequest.getAttribute(attribUsage);
- long total = 0;
-
- if (usageAttrib instanceof Long) {
- // If set, get value, and remove attribute for nested resources
- total = (Long) usageAttrib;
- pRequest.removeAttribute(attribUsage);
- }
-
- // Start timing
- long start = System.currentTimeMillis();
-
- try {
- // Continue chain
- pChain.doFilter(pRequest, pResponse);
- }
- finally {
- // Stop timing
- long end = System.currentTimeMillis();
-
- // Get time usage of included resources, add to total usage
- usageAttrib = pRequest.getAttribute(attribUsage);
- long usage = 0;
- if (usageAttrib instanceof Long) {
- usage = (Long) usageAttrib;
- }
-
- // Get the name of the included resource
- String resourceURI = ServletUtil.getIncludeRequestURI(pRequest);
-
- // If none, this is probably the parent page itself
- if (resourceURI == null) {
- resourceURI = ((HttpServletRequest) pRequest).getRequestURI();
- }
- long delta = end - start;
-
- log(String.format("Request processing time for resource \"%s\": %d ms (accumulated: %d ms).", resourceURI, (delta - usage), delta));
-
- // Store total usage
- total += delta;
- pRequest.setAttribute(attribUsage, total);
- }
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+
+/**
+ * TimingFilter class description.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: TimingFilter.java#1 $
+ */
+public class TimingFilter extends GenericFilter {
+
+ private String attribUsage = null;
+
+ /**
+ * Method init
+ *
+ * @throws ServletException
+ */
+ public void init() throws ServletException {
+ attribUsage = getFilterName() + ".timerDelta";
+ }
+
+ /**
+ *
+ * @param pRequest
+ * @param pResponse
+ * @param pChain
+ * @throws IOException
+ * @throws ServletException
+ */
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain)
+ throws IOException, ServletException {
+ // Get total usage of earlier filters on same level
+ Object usageAttrib = pRequest.getAttribute(attribUsage);
+ long total = 0;
+
+ if (usageAttrib instanceof Long) {
+ // If set, get value, and remove attribute for nested resources
+ total = (Long) usageAttrib;
+ pRequest.removeAttribute(attribUsage);
+ }
+
+ // Start timing
+ long start = System.currentTimeMillis();
+
+ try {
+ // Continue chain
+ pChain.doFilter(pRequest, pResponse);
+ }
+ finally {
+ // Stop timing
+ long end = System.currentTimeMillis();
+
+ // Get time usage of included resources, add to total usage
+ usageAttrib = pRequest.getAttribute(attribUsage);
+ long usage = 0;
+ if (usageAttrib instanceof Long) {
+ usage = (Long) usageAttrib;
+ }
+
+ // Get the name of the included resource
+ String resourceURI = ServletUtil.getIncludeRequestURI(pRequest);
+
+ // If none, this is probably the parent page itself
+ if (resourceURI == null) {
+ resourceURI = ((HttpServletRequest) pRequest).getRequestURI();
+ }
+ long delta = end - start;
+
+ log(String.format("Request processing time for resource \"%s\": %d ms (accumulated: %d ms).", resourceURI, (delta - usage), delta));
+
+ // Store total usage
+ total += delta;
+ pRequest.setAttribute(attribUsage, total);
+ }
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java
index 5dbb30fb..9005136e 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java
@@ -1,238 +1,238 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.ServletResponseWrapper;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.OutputStream;
-import java.io.FilterOutputStream;
-
-/**
- * Removes extra unneccessary white space from a servlet response.
- * White space is defined as per {@link Character#isWhitespace(char)}.
- *
- * This filter has no understanding of the content in the reponse, and will
- * remove repeated white space anywhere in the stream. It is intended for
- * removing white space from HTML or XML streams, but this limitation makes it
- * less suited for filtering HTML/XHTML with embedded CSS or JavaScript,
- * in case white space should be significant here. It is strongly reccommended
- * you keep CSS and JavaScript in separate files (this will have the added
- * benefit of further reducing the ammount of data communicated between
- * server and client).
- *
- * At the moment this filter has no concept of encoding.
- * This means, that if some multi-byte escape sequence contains one or more
- * bytes that individually is treated as a white space, these bytes
- * may be skipped.
- * As UTF-8
- * guarantees that no bytes are repeated in this way, this filter can safely
- * filter UTF-8.
- * Simple 8 bit character encodings, like the
- * ISO/IEC 8859 standard, or
- *
- * are always safe.
- *
- * Configuration
- * To use {@code TrimWhiteSpaceFilter} in your web-application, you simply need
- * to add it to your web descriptor ({@code web.xml}).
- * If using a servlet container that supports the Servlet 2.4 spec, the new
- * {@code dispatcher} element should be used, and set to
- * {@code REQUEST/FORWARD}, to make sure the filter is invoked only once for
- * requests.
- * If using an older web descriptor, set the {@code init-param}
- * {@code "once-per-request"} to {@code "true"} (this will have the same effect,
- * but might perform slightly worse than the 2.4 version).
- * Please see the examples below.
- *
- * Servlet 2.4 version, filter section:
- *
- * <!-- TrimWS Filter Configuration -->
- * <filter>
- * <filter-name>trimws</filter-name>
- * <filter-class>com.twelvemonkeys.servlet.TrimWhiteSpaceFilter</filter-class>
- * <!-- auto-flush=true is the default, may be omitted -->
- * <init-param>
- * <param-name>auto-flush</param-name>
- * <param-value>true</param-value>
- * </init-param>
- * </filter>
- *
- * Filter-mapping section:
- *
- * <!-- TimWS Filter Mapping -->
- * <filter-mapping>
- * <filter-name>trimws</filter-name>
- * <url-pattern>*.html</url-pattern>
- * <dispatcher>REQUEST</dispatcher>
- * <dispatcher>FORWARD</dispatcher>
- * </filter-mapping>
- * <filter-mapping>
- * <filter-name>trimws</filter-name>
- * <url-pattern>*.jsp</url-pattern>
- * <dispatcher>REQUEST</dispatcher>
- * <dispatcher>FORWARD</dispatcher>
- * </filter-mapping>
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: TrimWhiteSpaceFilter.java#2 $
- */
-public class TrimWhiteSpaceFilter extends GenericFilter {
-
- private boolean autoFlush = true;
-
- @InitParam
- public void setAutoFlush(final boolean pAutoFlush) {
- autoFlush = pAutoFlush;
- }
-
- public void init() throws ServletException {
- super.init();
- log("Automatic flushing is " + (autoFlush ? "enabled" : "disabled"));
- }
-
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- ServletResponseWrapper wrapped = new TrimWSServletResponseWrapper(pResponse);
- pChain.doFilter(pRequest, ServletUtil.createWrapper(wrapped));
- if (autoFlush) {
- wrapped.flushBuffer();
- }
- }
-
- static final class TrimWSFilterOutputStream extends FilterOutputStream {
- boolean lastWasWS = true; // Avoids leading WS by init to true
-
- public TrimWSFilterOutputStream(OutputStream pOut) {
- super(pOut);
- }
-
- // Override this, in case the wrapped outputstream overrides...
- public final void write(byte pBytes[]) throws IOException {
- write(pBytes, 0, pBytes.length);
- }
-
- // Override this, in case the wrapped outputstream overrides...
- public final void write(byte pBytes[], int pOff, int pLen) throws IOException {
- if (pBytes == null) {
- throw new NullPointerException("bytes == null");
- }
- else if (pOff < 0 || pLen < 0 || (pOff + pLen > pBytes.length)) {
- throw new IndexOutOfBoundsException("Bytes: " + pBytes.length + " Offset: " + pOff + " Length: " + pLen);
- }
-
- for (int i = 0; i < pLen ; i++) {
- write(pBytes[pOff + i]);
- }
- }
-
- public void write(int pByte) throws IOException {
- // TODO: Is this good enough for multi-byte encodings like UTF-16?
- // Consider writing through a Writer that does that for us, and
- // also buffer whitespace, so we write a linefeed every time there's
- // one in the original...
-
- // According to http://en.wikipedia.org/wiki/UTF-8:
- // "[...] US-ASCII octet values do not appear otherwise in a UTF-8
- // encoded character stream. This provides compatibility with file
- // systems or other software (e.g., the printf() function in
- // C libraries) that parse based on US-ASCII values but are
- // transparent to other values."
-
- if (!Character.isWhitespace((char) pByte)) {
- // If char is not WS, just store
- super.write(pByte);
- lastWasWS = false;
- }
- else {
- // TODO: Consider writing only 0x0a (LF) and 0x20 (space)
- // Else, if char is WS, store first, skip the rest
- if (!lastWasWS) {
- if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a
- super.write(0x0a);
- }
- else {
- super.write(pByte);
- }
- }
- lastWasWS = true;
- }
- }
- }
-
- private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate {
- public TrimWSStreamDelegate(ServletResponse pResponse) {
- super(pResponse);
- }
-
- protected OutputStream createOutputStream() throws IOException {
- return new TrimWSFilterOutputStream(response.getOutputStream());
- }
- }
-
- static class TrimWSServletResponseWrapper extends ServletResponseWrapper {
- private final ServletResponseStreamDelegate streamDelegate = new TrimWSStreamDelegate(getResponse());
-
- public TrimWSServletResponseWrapper(ServletResponse pResponse) {
- super(pResponse);
- }
-
- public ServletOutputStream getOutputStream() throws IOException {
- return streamDelegate.getOutputStream();
- }
-
- public PrintWriter getWriter() throws IOException {
- return streamDelegate.getWriter();
- }
-
- public void setContentLength(int pLength) {
- // Will be changed by filter, so don't set.
- }
-
- @Override
- public void flushBuffer() throws IOException {
- streamDelegate.flushBuffer();
- }
-
- @Override
- public void resetBuffer() {
- streamDelegate.resetBuffer();
- }
-
- // TODO: Consider picking up content-type/encoding, as we can only
- // filter US-ASCII, UTF-8 and other compatible encodings?
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.ServletResponseWrapper;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.OutputStream;
+import java.io.FilterOutputStream;
+
+/**
+ * Removes extra unneccessary white space from a servlet response.
+ * White space is defined as per {@link Character#isWhitespace(char)}.
+ *
+ * This filter has no understanding of the content in the reponse, and will
+ * remove repeated white space anywhere in the stream. It is intended for
+ * removing white space from HTML or XML streams, but this limitation makes it
+ * less suited for filtering HTML/XHTML with embedded CSS or JavaScript,
+ * in case white space should be significant here. It is strongly reccommended
+ * you keep CSS and JavaScript in separate files (this will have the added
+ * benefit of further reducing the ammount of data communicated between
+ * server and client).
+ *
+ * At the moment this filter has no concept of encoding.
+ * This means, that if some multi-byte escape sequence contains one or more
+ * bytes that individually is treated as a white space, these bytes
+ * may be skipped.
+ * As UTF-8
+ * guarantees that no bytes are repeated in this way, this filter can safely
+ * filter UTF-8.
+ * Simple 8 bit character encodings, like the
+ * ISO/IEC 8859 standard, or
+ *
+ * are always safe.
+ *
+ * Configuration
+ * To use {@code TrimWhiteSpaceFilter} in your web-application, you simply need
+ * to add it to your web descriptor ({@code web.xml}).
+ * If using a servlet container that supports the Servlet 2.4 spec, the new
+ * {@code dispatcher} element should be used, and set to
+ * {@code REQUEST/FORWARD}, to make sure the filter is invoked only once for
+ * requests.
+ * If using an older web descriptor, set the {@code init-param}
+ * {@code "once-per-request"} to {@code "true"} (this will have the same effect,
+ * but might perform slightly worse than the 2.4 version).
+ * Please see the examples below.
+ *
+ * Servlet 2.4 version, filter section:
+ *
+ * <!-- TrimWS Filter Configuration -->
+ * <filter>
+ * <filter-name>trimws</filter-name>
+ * <filter-class>com.twelvemonkeys.servlet.TrimWhiteSpaceFilter</filter-class>
+ * <!-- auto-flush=true is the default, may be omitted -->
+ * <init-param>
+ * <param-name>auto-flush</param-name>
+ * <param-value>true</param-value>
+ * </init-param>
+ * </filter>
+ *
+ * Filter-mapping section:
+ *
+ * <!-- TimWS Filter Mapping -->
+ * <filter-mapping>
+ * <filter-name>trimws</filter-name>
+ * <url-pattern>*.html</url-pattern>
+ * <dispatcher>REQUEST</dispatcher>
+ * <dispatcher>FORWARD</dispatcher>
+ * </filter-mapping>
+ * <filter-mapping>
+ * <filter-name>trimws</filter-name>
+ * <url-pattern>*.jsp</url-pattern>
+ * <dispatcher>REQUEST</dispatcher>
+ * <dispatcher>FORWARD</dispatcher>
+ * </filter-mapping>
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: TrimWhiteSpaceFilter.java#2 $
+ */
+public class TrimWhiteSpaceFilter extends GenericFilter {
+
+ private boolean autoFlush = true;
+
+ @InitParam
+ public void setAutoFlush(final boolean pAutoFlush) {
+ autoFlush = pAutoFlush;
+ }
+
+ public void init() throws ServletException {
+ super.init();
+ log("Automatic flushing is " + (autoFlush ? "enabled" : "disabled"));
+ }
+
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ ServletResponseWrapper wrapped = new TrimWSServletResponseWrapper(pResponse);
+ pChain.doFilter(pRequest, ServletUtil.createWrapper(wrapped));
+ if (autoFlush) {
+ wrapped.flushBuffer();
+ }
+ }
+
+ static final class TrimWSFilterOutputStream extends FilterOutputStream {
+ boolean lastWasWS = true; // Avoids leading WS by init to true
+
+ public TrimWSFilterOutputStream(OutputStream pOut) {
+ super(pOut);
+ }
+
+ // Override this, in case the wrapped outputstream overrides...
+ public final void write(byte pBytes[]) throws IOException {
+ write(pBytes, 0, pBytes.length);
+ }
+
+ // Override this, in case the wrapped outputstream overrides...
+ public final void write(byte pBytes[], int pOff, int pLen) throws IOException {
+ if (pBytes == null) {
+ throw new NullPointerException("bytes == null");
+ }
+ else if (pOff < 0 || pLen < 0 || (pOff + pLen > pBytes.length)) {
+ throw new IndexOutOfBoundsException("Bytes: " + pBytes.length + " Offset: " + pOff + " Length: " + pLen);
+ }
+
+ for (int i = 0; i < pLen ; i++) {
+ write(pBytes[pOff + i]);
+ }
+ }
+
+ public void write(int pByte) throws IOException {
+ // TODO: Is this good enough for multi-byte encodings like UTF-16?
+ // Consider writing through a Writer that does that for us, and
+ // also buffer whitespace, so we write a linefeed every time there's
+ // one in the original...
+
+ // According to http://en.wikipedia.org/wiki/UTF-8:
+ // "[...] US-ASCII octet values do not appear otherwise in a UTF-8
+ // encoded character stream. This provides compatibility with file
+ // systems or other software (e.g., the printf() function in
+ // C libraries) that parse based on US-ASCII values but are
+ // transparent to other values."
+
+ if (!Character.isWhitespace((char) pByte)) {
+ // If char is not WS, just store
+ super.write(pByte);
+ lastWasWS = false;
+ }
+ else {
+ // TODO: Consider writing only 0x0a (LF) and 0x20 (space)
+ // Else, if char is WS, store first, skip the rest
+ if (!lastWasWS) {
+ if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a
+ super.write(0x0a);
+ }
+ else {
+ super.write(pByte);
+ }
+ }
+ lastWasWS = true;
+ }
+ }
+ }
+
+ private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate {
+ public TrimWSStreamDelegate(ServletResponse pResponse) {
+ super(pResponse);
+ }
+
+ protected OutputStream createOutputStream() throws IOException {
+ return new TrimWSFilterOutputStream(response.getOutputStream());
+ }
+ }
+
+ static class TrimWSServletResponseWrapper extends ServletResponseWrapper {
+ private final ServletResponseStreamDelegate streamDelegate = new TrimWSStreamDelegate(getResponse());
+
+ public TrimWSServletResponseWrapper(ServletResponse pResponse) {
+ super(pResponse);
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ return streamDelegate.getOutputStream();
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return streamDelegate.getWriter();
+ }
+
+ public void setContentLength(int pLength) {
+ // Will be changed by filter, so don't set.
+ }
+
+ @Override
+ public void flushBuffer() throws IOException {
+ streamDelegate.flushBuffer();
+ }
+
+ @Override
+ public void resetBuffer() {
+ streamDelegate.resetBuffer();
+ }
+
+ // TODO: Consider picking up content-type/encoding, as we can only
+ // filter US-ASCII, UTF-8 and other compatible encodings?
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java
index ec917a37..1996060f 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java
@@ -1,200 +1,200 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.GenericFilter;
-import com.twelvemonkeys.servlet.ServletConfigException;
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.*;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.File;
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A Filter that provides response caching, for HTTP {@code GET} requests.
- *
- * Originally based on ideas and code found in the ONJava article
- * Two
- * Servlet Filters Every Web Application Should Have
- * by Jayson Falkner.
- *
- * @author Jayson Falkner
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: CacheFilter.java#4 $
- *
- */
-public class CacheFilter extends GenericFilter {
-
- HTTPCache cache;
-
- /**
- * Initializes the filter
- *
- * @throws javax.servlet.ServletException
- */
- public void init() throws ServletException {
- FilterConfig config = getFilterConfig();
-
- // Default don't delete cache files on exit (persistent cache)
- boolean deleteCacheOnExit = "TRUE".equalsIgnoreCase(config.getInitParameter("deleteCacheOnExit"));
-
- // Default expiry time 10 minutes
- int expiryTime = 10 * 60 * 1000;
-
- String expiryTimeStr = config.getInitParameter("expiryTime");
- if (!StringUtil.isEmpty(expiryTimeStr)) {
- try {
- // TODO: This is insane.. :-P Let the expiry time be in minutes or seconds..
- expiryTime = Integer.parseInt(expiryTimeStr);
- }
- catch (NumberFormatException e) {
- throw new ServletConfigException("Could not parse expiryTime: " + e.toString(), e);
- }
- }
-
- // Default max mem cache size 10 MB
- int memCacheSize = 10;
-
- String memCacheSizeStr = config.getInitParameter("memCacheSize");
- if (!StringUtil.isEmpty(memCacheSizeStr)) {
- try {
- memCacheSize = Integer.parseInt(memCacheSizeStr);
- }
- catch (NumberFormatException e) {
- throw new ServletConfigException("Could not parse memCacheSize: " + e.toString(), e);
- }
- }
-
- int maxCachedEntites = 10000;
-
- try {
- cache = new HTTPCache(
- getTempFolder(),
- expiryTime,
- memCacheSize * 1024 * 1024,
- maxCachedEntites,
- deleteCacheOnExit,
- new ServletContextLoggerAdapter(getFilterName(), getServletContext())
- ) {
- @Override
- protected File getRealFile(CacheRequest pRequest) {
- String contextRelativeURI = ServletUtil.getContextRelativeURI(((ServletCacheRequest) pRequest).getRequest());
-
- String path = getServletContext().getRealPath(contextRelativeURI);
-
- if (path != null) {
- return new File(path);
- }
-
- return null;
- }
- };
- log("Created cache: " + cache);
- }
- catch (IllegalArgumentException e) {
- throw new ServletConfigException("Could not create cache: " + e.toString(), e);
- }
- }
-
- private File getTempFolder() {
- File tempRoot = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
- if (tempRoot == null) {
- throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
- }
- return new File(tempRoot, getFilterName());
- }
-
- public void destroy() {
- log("Destroying cache: " + cache);
- cache = null;
- super.destroy();
- }
-
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- // We can only cache HTTP GET/HEAD requests
- if (!(pRequest instanceof HttpServletRequest
- && pResponse instanceof HttpServletResponse
- && isCachable((HttpServletRequest) pRequest))) {
- pChain.doFilter(pRequest, pResponse); // Continue chain
- }
- else {
- ServletCacheRequest cacheRequest = new ServletCacheRequest((HttpServletRequest) pRequest);
- ServletCacheResponse cacheResponse = new ServletCacheResponse((HttpServletResponse) pResponse);
- ServletResponseResolver resolver = new ServletResponseResolver(cacheRequest, cacheResponse, pChain);
-
- // Render fast
- try {
- cache.doCached(cacheRequest, cacheResponse, resolver);
- }
- catch (CacheException e) {
- if (e.getCause() instanceof ServletException) {
- throw (ServletException) e.getCause();
- }
- else {
- throw new ServletException(e);
- }
- }
- finally {
- pResponse.flushBuffer();
- }
- }
- }
-
- private boolean isCachable(HttpServletRequest pRequest) {
- // TODO: Get Cache-Control: no-cache/max-age=0 and Pragma: no-cache from REQUEST too?
- return "GET".equals(pRequest.getMethod()) || "HEAD".equals(pRequest.getMethod());
- }
-
- // TODO: Extract, complete and document this class, might be useful in other cases
- // Maybe add it to the ServletUtil class
- static class ServletContextLoggerAdapter extends Logger {
- private final ServletContext context;
-
- public ServletContextLoggerAdapter(String pName, ServletContext pContext) {
- super(pName, null);
- context = pContext;
- }
-
- @Override
- public void log(Level pLevel, String pMessage) {
- context.log(pMessage);
- }
-
- @Override
- public void log(Level pLevel, String pMessage, Throwable pThrowable) {
- context.log(pMessage, pThrowable);
- }
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.GenericFilter;
+import com.twelvemonkeys.servlet.ServletConfigException;
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A Filter that provides response caching, for HTTP {@code GET} requests.
+ *
+ * Originally based on ideas and code found in the ONJava article
+ * Two
+ * Servlet Filters Every Web Application Should Have
+ * by Jayson Falkner.
+ *
+ * @author Jayson Falkner
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: CacheFilter.java#4 $
+ *
+ */
+public class CacheFilter extends GenericFilter {
+
+ HTTPCache cache;
+
+ /**
+ * Initializes the filter
+ *
+ * @throws javax.servlet.ServletException
+ */
+ public void init() throws ServletException {
+ FilterConfig config = getFilterConfig();
+
+ // Default don't delete cache files on exit (persistent cache)
+ boolean deleteCacheOnExit = "TRUE".equalsIgnoreCase(config.getInitParameter("deleteCacheOnExit"));
+
+ // Default expiry time 10 minutes
+ int expiryTime = 10 * 60 * 1000;
+
+ String expiryTimeStr = config.getInitParameter("expiryTime");
+ if (!StringUtil.isEmpty(expiryTimeStr)) {
+ try {
+ // TODO: This is insane.. :-P Let the expiry time be in minutes or seconds..
+ expiryTime = Integer.parseInt(expiryTimeStr);
+ }
+ catch (NumberFormatException e) {
+ throw new ServletConfigException("Could not parse expiryTime: " + e.toString(), e);
+ }
+ }
+
+ // Default max mem cache size 10 MB
+ int memCacheSize = 10;
+
+ String memCacheSizeStr = config.getInitParameter("memCacheSize");
+ if (!StringUtil.isEmpty(memCacheSizeStr)) {
+ try {
+ memCacheSize = Integer.parseInt(memCacheSizeStr);
+ }
+ catch (NumberFormatException e) {
+ throw new ServletConfigException("Could not parse memCacheSize: " + e.toString(), e);
+ }
+ }
+
+ int maxCachedEntites = 10000;
+
+ try {
+ cache = new HTTPCache(
+ getTempFolder(),
+ expiryTime,
+ memCacheSize * 1024 * 1024,
+ maxCachedEntites,
+ deleteCacheOnExit,
+ new ServletContextLoggerAdapter(getFilterName(), getServletContext())
+ ) {
+ @Override
+ protected File getRealFile(CacheRequest pRequest) {
+ String contextRelativeURI = ServletUtil.getContextRelativeURI(((ServletCacheRequest) pRequest).getRequest());
+
+ String path = getServletContext().getRealPath(contextRelativeURI);
+
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+ }
+ };
+ log("Created cache: " + cache);
+ }
+ catch (IllegalArgumentException e) {
+ throw new ServletConfigException("Could not create cache: " + e.toString(), e);
+ }
+ }
+
+ private File getTempFolder() {
+ File tempRoot = (File) getServletContext().getAttribute("javax.servlet.context.tempdir");
+ if (tempRoot == null) {
+ throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
+ }
+ return new File(tempRoot, getFilterName());
+ }
+
+ public void destroy() {
+ log("Destroying cache: " + cache);
+ cache = null;
+ super.destroy();
+ }
+
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ // We can only cache HTTP GET/HEAD requests
+ if (!(pRequest instanceof HttpServletRequest
+ && pResponse instanceof HttpServletResponse
+ && isCachable((HttpServletRequest) pRequest))) {
+ pChain.doFilter(pRequest, pResponse); // Continue chain
+ }
+ else {
+ ServletCacheRequest cacheRequest = new ServletCacheRequest((HttpServletRequest) pRequest);
+ ServletCacheResponse cacheResponse = new ServletCacheResponse((HttpServletResponse) pResponse);
+ ServletResponseResolver resolver = new ServletResponseResolver(cacheRequest, cacheResponse, pChain);
+
+ // Render fast
+ try {
+ cache.doCached(cacheRequest, cacheResponse, resolver);
+ }
+ catch (CacheException e) {
+ if (e.getCause() instanceof ServletException) {
+ throw (ServletException) e.getCause();
+ }
+ else {
+ throw new ServletException(e);
+ }
+ }
+ finally {
+ pResponse.flushBuffer();
+ }
+ }
+ }
+
+ private boolean isCachable(HttpServletRequest pRequest) {
+ // TODO: Get Cache-Control: no-cache/max-age=0 and Pragma: no-cache from REQUEST too?
+ return "GET".equals(pRequest.getMethod()) || "HEAD".equals(pRequest.getMethod());
+ }
+
+ // TODO: Extract, complete and document this class, might be useful in other cases
+ // Maybe add it to the ServletUtil class
+ static class ServletContextLoggerAdapter extends Logger {
+ private final ServletContext context;
+
+ public ServletContextLoggerAdapter(String pName, ServletContext pContext) {
+ super(pName, null);
+ context = pContext;
+ }
+
+ @Override
+ public void log(Level pLevel, String pMessage) {
+ context.log(pMessage);
+ }
+
+ @Override
+ public void log(Level pLevel, String pMessage, Throwable pThrowable) {
+ context.log(pMessage, pThrowable);
+ }
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java
index a666d68e..719bb378 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java
@@ -1,261 +1,261 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.net.HTTPUtil;
-import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletResponseWrapper;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-
-/**
- * CacheResponseWrapper class description.
- *
- * Based on ideas and code found in the ONJava article
- * Two
- * Servlet Filters Every Web Application Should Have
- * by Jayson Falkner.
- *
- * @author Jayson Falkner
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: CacheResponseWrapper.java#3 $
- */
-class CacheResponseWrapper extends HttpServletResponseWrapper {
- private ServletResponseStreamDelegate streamDelegate;
-
- private CacheResponse response;
- private CachedEntity cached;
- private WritableCachedResponse cachedResponse;
-
- private Boolean cacheable;
- private int status;
-
- public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) {
- super(pResponse.getResponse());
- response = pResponse;
- cached = pCached;
- init();
- }
-
- /*
- NOTE: This class defers determining if a response is cacheable until the
- output stream is needed.
- This it the reason for the somewhat complicated logic in the add/setHeader
- methods below.
- */
- private void init() {
- cacheable = null;
- status = SC_OK;
- cachedResponse = cached.createCachedResponse();
- streamDelegate = new ServletResponseStreamDelegate(this) {
- protected OutputStream createOutputStream() throws IOException {
- // Test if this request is really cacheable, otherwise,
- // just write through to underlying response, and don't cache
- if (isCacheable()) {
- return cachedResponse.getOutputStream();
- }
- else {
- cachedResponse.setStatus(status);
- cachedResponse.writeHeadersTo(CacheResponseWrapper.this.response);
- return super.getOutputStream();
- }
- }
- };
- }
-
- CachedResponse getCachedResponse() {
- return cachedResponse.getCachedResponse();
- }
-
- public boolean isCacheable() {
- // NOTE: Intentionally not synchronized
- if (cacheable == null) {
- cacheable = isCacheableImpl();
- }
-
- return cacheable;
- }
-
- private boolean isCacheableImpl() {
- if (status != SC_OK) {
- return false;
- }
-
- // Vary: *
- String[] values = cachedResponse.getHeaderValues(HTTPCache.HEADER_VARY);
- if (values != null) {
- for (String value : values) {
- if ("*".equals(value)) {
- return false;
- }
- }
- }
-
- // Cache-Control: no-cache, no-store, must-revalidate
- values = cachedResponse.getHeaderValues(HTTPCache.HEADER_CACHE_CONTROL);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")
- || StringUtil.contains(value, "no-store")
- || StringUtil.contains(value, "must-revalidate")) {
- return false;
- }
- }
- }
-
- // Pragma: no-cache
- values = cachedResponse.getHeaderValues(HTTPCache.HEADER_PRAGMA);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- public void flushBuffer() throws IOException {
- streamDelegate.flushBuffer();
- }
-
- public void resetBuffer() {
- // Servlet 2.3
- streamDelegate.resetBuffer();
- }
-
- public void reset() {
- if (Boolean.FALSE.equals(cacheable)) {
- super.reset();
- }
- // No else, might be cacheable after all..
- init();
- }
-
- public ServletOutputStream getOutputStream() throws IOException {
- return streamDelegate.getOutputStream();
- }
-
- public PrintWriter getWriter() throws IOException {
- return streamDelegate.getWriter();
- }
-
- public boolean containsHeader(String name) {
- return cachedResponse.getHeaderValues(name) != null;
- }
-
- public void sendError(int pStatusCode, String msg) throws IOException {
- // NOT cacheable
- status = pStatusCode;
- super.sendError(pStatusCode, msg);
- }
-
- public void sendError(int pStatusCode) throws IOException {
- // NOT cacheable
- status = pStatusCode;
- super.sendError(pStatusCode);
- }
-
- public void setStatus(int pStatusCode, String sm) {
- // NOTE: This method is deprecated
- setStatus(pStatusCode);
- }
-
- public void setStatus(int pStatusCode) {
- // NOT cacheable unless pStatusCode == 200 (or a FEW others?)
- if (pStatusCode != SC_OK) {
- status = pStatusCode;
- super.setStatus(pStatusCode);
- }
- }
-
- public void sendRedirect(String pLocation) throws IOException {
- // NOT cacheable
- status = SC_MOVED_TEMPORARILY;
- super.sendRedirect(pLocation);
- }
-
- public void setDateHeader(String pName, long pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setDateHeader(pName, pValue);
- }
- cachedResponse.setHeader(pName, HTTPUtil.formatHTTPDate(pValue));
- }
-
- public void addDateHeader(String pName, long pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addDateHeader(pName, pValue);
- }
- cachedResponse.addHeader(pName, HTTPUtil.formatHTTPDate(pValue));
- }
-
- public void setHeader(String pName, String pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setHeader(pName, pValue);
- }
- cachedResponse.setHeader(pName, pValue);
- }
-
- public void addHeader(String pName, String pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addHeader(pName, pValue);
- }
- cachedResponse.addHeader(pName, pValue);
- }
-
- public void setIntHeader(String pName, int pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setIntHeader(pName, pValue);
- }
- cachedResponse.setHeader(pName, String.valueOf(pValue));
- }
-
- public void addIntHeader(String pName, int pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addIntHeader(pName, pValue);
- }
- cachedResponse.addHeader(pName, String.valueOf(pValue));
- }
-
- public final void setContentType(String type) {
- setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.net.HTTPUtil;
+import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+/**
+ * CacheResponseWrapper class description.
+ *
+ * Based on ideas and code found in the ONJava article
+ * Two
+ * Servlet Filters Every Web Application Should Have
+ * by Jayson Falkner.
+ *
+ * @author Jayson Falkner
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: CacheResponseWrapper.java#3 $
+ */
+class CacheResponseWrapper extends HttpServletResponseWrapper {
+ private ServletResponseStreamDelegate streamDelegate;
+
+ private CacheResponse response;
+ private CachedEntity cached;
+ private WritableCachedResponse cachedResponse;
+
+ private Boolean cacheable;
+ private int status;
+
+ public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) {
+ super(pResponse.getResponse());
+ response = pResponse;
+ cached = pCached;
+ init();
+ }
+
+ /*
+ NOTE: This class defers determining if a response is cacheable until the
+ output stream is needed.
+ This it the reason for the somewhat complicated logic in the add/setHeader
+ methods below.
+ */
+ private void init() {
+ cacheable = null;
+ status = SC_OK;
+ cachedResponse = cached.createCachedResponse();
+ streamDelegate = new ServletResponseStreamDelegate(this) {
+ protected OutputStream createOutputStream() throws IOException {
+ // Test if this request is really cacheable, otherwise,
+ // just write through to underlying response, and don't cache
+ if (isCacheable()) {
+ return cachedResponse.getOutputStream();
+ }
+ else {
+ cachedResponse.setStatus(status);
+ cachedResponse.writeHeadersTo(CacheResponseWrapper.this.response);
+ return super.getOutputStream();
+ }
+ }
+ };
+ }
+
+ CachedResponse getCachedResponse() {
+ return cachedResponse.getCachedResponse();
+ }
+
+ public boolean isCacheable() {
+ // NOTE: Intentionally not synchronized
+ if (cacheable == null) {
+ cacheable = isCacheableImpl();
+ }
+
+ return cacheable;
+ }
+
+ private boolean isCacheableImpl() {
+ if (status != SC_OK) {
+ return false;
+ }
+
+ // Vary: *
+ String[] values = cachedResponse.getHeaderValues(HTTPCache.HEADER_VARY);
+ if (values != null) {
+ for (String value : values) {
+ if ("*".equals(value)) {
+ return false;
+ }
+ }
+ }
+
+ // Cache-Control: no-cache, no-store, must-revalidate
+ values = cachedResponse.getHeaderValues(HTTPCache.HEADER_CACHE_CONTROL);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")
+ || StringUtil.contains(value, "no-store")
+ || StringUtil.contains(value, "must-revalidate")) {
+ return false;
+ }
+ }
+ }
+
+ // Pragma: no-cache
+ values = cachedResponse.getHeaderValues(HTTPCache.HEADER_PRAGMA);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void flushBuffer() throws IOException {
+ streamDelegate.flushBuffer();
+ }
+
+ public void resetBuffer() {
+ // Servlet 2.3
+ streamDelegate.resetBuffer();
+ }
+
+ public void reset() {
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.reset();
+ }
+ // No else, might be cacheable after all..
+ init();
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ return streamDelegate.getOutputStream();
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return streamDelegate.getWriter();
+ }
+
+ public boolean containsHeader(String name) {
+ return cachedResponse.getHeaderValues(name) != null;
+ }
+
+ public void sendError(int pStatusCode, String msg) throws IOException {
+ // NOT cacheable
+ status = pStatusCode;
+ super.sendError(pStatusCode, msg);
+ }
+
+ public void sendError(int pStatusCode) throws IOException {
+ // NOT cacheable
+ status = pStatusCode;
+ super.sendError(pStatusCode);
+ }
+
+ public void setStatus(int pStatusCode, String sm) {
+ // NOTE: This method is deprecated
+ setStatus(pStatusCode);
+ }
+
+ public void setStatus(int pStatusCode) {
+ // NOT cacheable unless pStatusCode == 200 (or a FEW others?)
+ if (pStatusCode != SC_OK) {
+ status = pStatusCode;
+ super.setStatus(pStatusCode);
+ }
+ }
+
+ public void sendRedirect(String pLocation) throws IOException {
+ // NOT cacheable
+ status = SC_MOVED_TEMPORARILY;
+ super.sendRedirect(pLocation);
+ }
+
+ public void setDateHeader(String pName, long pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setDateHeader(pName, pValue);
+ }
+ cachedResponse.setHeader(pName, HTTPUtil.formatHTTPDate(pValue));
+ }
+
+ public void addDateHeader(String pName, long pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addDateHeader(pName, pValue);
+ }
+ cachedResponse.addHeader(pName, HTTPUtil.formatHTTPDate(pValue));
+ }
+
+ public void setHeader(String pName, String pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setHeader(pName, pValue);
+ }
+ cachedResponse.setHeader(pName, pValue);
+ }
+
+ public void addHeader(String pName, String pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addHeader(pName, pValue);
+ }
+ cachedResponse.addHeader(pName, pValue);
+ }
+
+ public void setIntHeader(String pName, int pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setIntHeader(pName, pValue);
+ }
+ cachedResponse.setHeader(pName, String.valueOf(pValue));
+ }
+
+ public void addIntHeader(String pName, int pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addIntHeader(pName, pValue);
+ }
+ cachedResponse.addHeader(pName, String.valueOf(pValue));
+ }
+
+ public final void setContentType(String type) {
+ setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java
index d68708a5..e82e5d82 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java
@@ -1,75 +1,75 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import java.io.IOException;
-
-/**
- * CachedEntity
- *
- * @author Harald Kuhr
- * @version $Id: CachedEntity.java#3 $
- */
-interface CachedEntity {
-
- /**
- * Renders the cached entity to the response.
- *
- * @param pRequest the request
- * @param pResponse the response
- * @throws java.io.IOException if an I/O exception occurs
- */
- void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException;
-
- /**
- * Captures (caches) the response for the given request.
- *
- * @param pRequest the request
- * @param pResponse the response
- * @throws java.io.IOException if an I/O exception occurs
- *
- * @see #createCachedResponse()
- */
- void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException;
-
- /**
- * Tests if the content of this entity is stale for the given request.
- *
- * @param pRequest the request
- * @return {@code true} if content is stale
- */
- boolean isStale(CacheRequest pRequest);
-
- /**
- * Creates a {@code WritableCachedResponse} to use to capture the response.
- *
- * @return a {@code WritableCachedResponse}
- */
- WritableCachedResponse createCachedResponse();
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import java.io.IOException;
+
+/**
+ * CachedEntity
+ *
+ * @author Harald Kuhr
+ * @version $Id: CachedEntity.java#3 $
+ */
+interface CachedEntity {
+
+ /**
+ * Renders the cached entity to the response.
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ * @throws java.io.IOException if an I/O exception occurs
+ */
+ void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException;
+
+ /**
+ * Captures (caches) the response for the given request.
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ * @throws java.io.IOException if an I/O exception occurs
+ *
+ * @see #createCachedResponse()
+ */
+ void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException;
+
+ /**
+ * Tests if the content of this entity is stale for the given request.
+ *
+ * @param pRequest the request
+ * @return {@code true} if content is stale
+ */
+ boolean isStale(CacheRequest pRequest);
+
+ /**
+ * Creates a {@code WritableCachedResponse} to use to capture the response.
+ *
+ * @return a {@code WritableCachedResponse}
+ */
+ WritableCachedResponse createCachedResponse();
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java
index 1a502456..f612bca5 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java
@@ -1,170 +1,170 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.lang.Validate;
-
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * CachedEntity
- *
- * @author Harald Kuhr
- * @version $Id: CachedEntityImpl.java#3 $
- */
-class CachedEntityImpl implements CachedEntity {
- private String cacheURI;
- private HTTPCache cache;
-
- CachedEntityImpl(String pCacheURI, HTTPCache pCache) {
- cacheURI = Validate.notNull(pCacheURI, "cacheURI");
- cache = pCache;
- }
-
- public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException {
- // Get cached content
- CachedResponse cached = cache.getContent(cacheURI, pRequest);
-
- // Sanity check
- if (cached == null) {
- throw new IllegalStateException("Tried to render non-cached response (cache == null).");
- }
-
- // If the cached entity is not modified since the date of the browsers
- // version, then simply send a "304 Not Modified" response
- // Otherwise send the full response.
-
- // TODO: WHY DID I COMMENT OUT THIS LINE AND REPLACE IT WITH THE ONE BELOW??
- //long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_LAST_MODIFIED));
- long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_CACHED_TIME));
-
- // TODO: Consider handling time skews between server "now" and client "now"?
- // NOTE: The If-Modified-Since is probably right according to the server
- // even in a time skew situation, as the client should use either the
- // Date or Last-Modifed dates from the response headers (server generated)
- long ifModifiedSince = -1L;
- try {
- List ifmh = pRequest.getHeaders().get(HTTPCache.HEADER_IF_MODIFIED_SINCE);
- ifModifiedSince = ifmh != null ? HTTPCache.getDateHeader(ifmh.get(0)) : -1L;
- if (ifModifiedSince != -1L) {
- /*
- long serverTime = DateUtil.currentTimeMinute();
- long clientTime = DateUtil.roundToMinute(pRequest.getDateHeader(HTTPCache.HEADER_DATE));
-
- // Test if time skew is greater than time skew threshold (currently 1 minute)
- if (Math.abs(serverTime - clientTime) > 1) {
- // TODO: Correct error in ifModifiedSince?
- }
- */
-
- // System.out.println(" << CachedEntity >> If-Modified-Since present: " + ifModifiedSince + " --> " + NetUtil.formatHTTPDate(ifModifiedSince) + "==" + pRequest.getHeader(HTTPCache.HEADER_IF_MODIFIED_SINCE));
- // System.out.println(" << CachedEntity >> Last-Modified for entity: " + lastModified + " --> " + NetUtil.formatHTTPDate(lastModified));
- }
- }
- catch (IllegalArgumentException e) {
- // Seems to be a bug in FireFox 1.0.2..?!
- cache.log("Error in date header from user-agent. User-Agent: " + pRequest.getHeaders().get("User-Agent"), e);
- }
-
- if (lastModified == -1L || (ifModifiedSince < (lastModified / 1000L) * 1000L)) {
- pResponse.setStatus(cached.getStatus());
- cached.writeHeadersTo(pResponse);
- if (isStale(pRequest)) {
- // Add warning header
- // Warning: 110 : Content is stale
- pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
- }
-
- // NOTE: At the moment we only ever try to cache HEAD and GET requests
- if (!"HEAD".equals(pRequest.getMethod())) {
- cached.writeContentsTo(pResponse.getOutputStream());
- }
- }
- else {
- pResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
- // System.out.println(" << CachedEntity >> Not modified: " + toString());
- if (isStale(pRequest)) {
- // Add warning header
- // Warning: 110 : Content is stale
- pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
- }
- }
- }
-
- /* Utility method to get Host header */
- private static String getHost(CacheRequest pRequest) {
- return pRequest.getServerName() + ":" + pRequest.getServerPort();
- }
-
- public void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException {
-// if (!(pResponse instanceof CacheResponseWrapper)) {
-// throw new IllegalArgumentException("Response must be created by CachedEntity.createResponseWrapper()");
-// }
-//
-// CacheResponseWrapper response = (CacheResponseWrapper) pResponse;
-
-// if (response.isCacheable()) {
- cache.registerContent(
- cacheURI,
- pRequest,
- pResponse instanceof WritableCachedResponse ? ((WritableCachedResponse) pResponse).getCachedResponse() : pResponse
- );
-// }
-// else {
- // Else store that the response for this request is not cachable
-// pRequest.setAttribute(ATTRIB_NOT_CACHEABLE, Boolean.TRUE);
-
- // TODO: Store this in HTTPCache, for subsequent requests to same resource?
-// }
- }
-
- public boolean isStale(CacheRequest pRequest) {
- return cache.isContentStale(cacheURI, pRequest);
- }
-
- public WritableCachedResponse createCachedResponse() {
- return new WritableCachedResponseImpl();
- }
-
- public int hashCode() {
- return (cacheURI != null ? cacheURI.hashCode() : 0) + 1397;
- }
-
- public boolean equals(Object pOther) {
- return pOther instanceof CachedEntityImpl &&
- ((cacheURI == null && ((CachedEntityImpl) pOther).cacheURI == null) ||
- cacheURI != null && cacheURI.equals(((CachedEntityImpl) pOther).cacheURI));
- }
-
- public String toString() {
- return "CachedEntity[URI=" + cacheURI + "]";
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.lang.Validate;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * CachedEntity
+ *
+ * @author Harald Kuhr
+ * @version $Id: CachedEntityImpl.java#3 $
+ */
+class CachedEntityImpl implements CachedEntity {
+ private String cacheURI;
+ private HTTPCache cache;
+
+ CachedEntityImpl(String pCacheURI, HTTPCache pCache) {
+ cacheURI = Validate.notNull(pCacheURI, "cacheURI");
+ cache = pCache;
+ }
+
+ public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException {
+ // Get cached content
+ CachedResponse cached = cache.getContent(cacheURI, pRequest);
+
+ // Sanity check
+ if (cached == null) {
+ throw new IllegalStateException("Tried to render non-cached response (cache == null).");
+ }
+
+ // If the cached entity is not modified since the date of the browsers
+ // version, then simply send a "304 Not Modified" response
+ // Otherwise send the full response.
+
+ // TODO: WHY DID I COMMENT OUT THIS LINE AND REPLACE IT WITH THE ONE BELOW??
+ //long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_LAST_MODIFIED));
+ long lastModified = HTTPCache.getDateHeader(cached.getHeaderValue(HTTPCache.HEADER_CACHED_TIME));
+
+ // TODO: Consider handling time skews between server "now" and client "now"?
+ // NOTE: The If-Modified-Since is probably right according to the server
+ // even in a time skew situation, as the client should use either the
+ // Date or Last-Modifed dates from the response headers (server generated)
+ long ifModifiedSince = -1L;
+ try {
+ List ifmh = pRequest.getHeaders().get(HTTPCache.HEADER_IF_MODIFIED_SINCE);
+ ifModifiedSince = ifmh != null ? HTTPCache.getDateHeader(ifmh.get(0)) : -1L;
+ if (ifModifiedSince != -1L) {
+ /*
+ long serverTime = DateUtil.currentTimeMinute();
+ long clientTime = DateUtil.roundToMinute(pRequest.getDateHeader(HTTPCache.HEADER_DATE));
+
+ // Test if time skew is greater than time skew threshold (currently 1 minute)
+ if (Math.abs(serverTime - clientTime) > 1) {
+ // TODO: Correct error in ifModifiedSince?
+ }
+ */
+
+ // System.out.println(" << CachedEntity >> If-Modified-Since present: " + ifModifiedSince + " --> " + NetUtil.formatHTTPDate(ifModifiedSince) + "==" + pRequest.getHeader(HTTPCache.HEADER_IF_MODIFIED_SINCE));
+ // System.out.println(" << CachedEntity >> Last-Modified for entity: " + lastModified + " --> " + NetUtil.formatHTTPDate(lastModified));
+ }
+ }
+ catch (IllegalArgumentException e) {
+ // Seems to be a bug in FireFox 1.0.2..?!
+ cache.log("Error in date header from user-agent. User-Agent: " + pRequest.getHeaders().get("User-Agent"), e);
+ }
+
+ if (lastModified == -1L || (ifModifiedSince < (lastModified / 1000L) * 1000L)) {
+ pResponse.setStatus(cached.getStatus());
+ cached.writeHeadersTo(pResponse);
+ if (isStale(pRequest)) {
+ // Add warning header
+ // Warning: 110 : Content is stale
+ pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
+ }
+
+ // NOTE: At the moment we only ever try to cache HEAD and GET requests
+ if (!"HEAD".equals(pRequest.getMethod())) {
+ cached.writeContentsTo(pResponse.getOutputStream());
+ }
+ }
+ else {
+ pResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ // System.out.println(" << CachedEntity >> Not modified: " + toString());
+ if (isStale(pRequest)) {
+ // Add warning header
+ // Warning: 110 : Content is stale
+ pResponse.addHeader(HTTPCache.HEADER_WARNING, "110 " + getHost(pRequest) + " Content is stale.");
+ }
+ }
+ }
+
+ /* Utility method to get Host header */
+ private static String getHost(CacheRequest pRequest) {
+ return pRequest.getServerName() + ":" + pRequest.getServerPort();
+ }
+
+ public void capture(CacheRequest pRequest, CachedResponse pResponse) throws IOException {
+// if (!(pResponse instanceof CacheResponseWrapper)) {
+// throw new IllegalArgumentException("Response must be created by CachedEntity.createResponseWrapper()");
+// }
+//
+// CacheResponseWrapper response = (CacheResponseWrapper) pResponse;
+
+// if (response.isCacheable()) {
+ cache.registerContent(
+ cacheURI,
+ pRequest,
+ pResponse instanceof WritableCachedResponse ? ((WritableCachedResponse) pResponse).getCachedResponse() : pResponse
+ );
+// }
+// else {
+ // Else store that the response for this request is not cachable
+// pRequest.setAttribute(ATTRIB_NOT_CACHEABLE, Boolean.TRUE);
+
+ // TODO: Store this in HTTPCache, for subsequent requests to same resource?
+// }
+ }
+
+ public boolean isStale(CacheRequest pRequest) {
+ return cache.isContentStale(cacheURI, pRequest);
+ }
+
+ public WritableCachedResponse createCachedResponse() {
+ return new WritableCachedResponseImpl();
+ }
+
+ public int hashCode() {
+ return (cacheURI != null ? cacheURI.hashCode() : 0) + 1397;
+ }
+
+ public boolean equals(Object pOther) {
+ return pOther instanceof CachedEntityImpl &&
+ ((cacheURI == null && ((CachedEntityImpl) pOther).cacheURI == null) ||
+ cacheURI != null && cacheURI.equals(((CachedEntityImpl) pOther).cacheURI));
+ }
+
+ public String toString() {
+ return "CachedEntity[URI=" + cacheURI + "]";
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java
index bc475324..4696f284 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java
@@ -1,95 +1,95 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * CachedResponse
- *
- * @author Harald Kuhr
- * @version $Id: CachedResponse.java#3 $
- */
-interface CachedResponse {
- /**
- * Writes the cached headers to the response
- *
- * @param pResponse the servlet response
- */
- void writeHeadersTo(CacheResponse pResponse);
-
- /**
- * Writes the cahced content to the response
- *
- * @param pStream the response output stream
- * @throws IOException if an I/O exception occurs during write
- */
- void writeContentsTo(OutputStream pStream) throws IOException;
-
- int getStatus();
-
- // TODO: Map> getHeaders()
-
- /**
- * Gets the header names of all headers set in this response.
- *
- * @return an array of {@code String}s
- */
- String[] getHeaderNames();
-
- /**
- * Gets all header values set for the given header in this response. If the
- * header is not set, {@code null} is returned.
- *
- * @param pHeaderName the header name
- * @return an array of {@code String}s, or {@code null} if there is no
- * such header in this response.
- */
- String[] getHeaderValues(String pHeaderName);
-
- /**
- * Gets the first header value set for the given header in this response.
- * If the header is not set, {@code null} is returned.
- * Useful for headers that don't have multiple values, like
- * {@code "Content-Type"} or {@code "Content-Length"}.
- *
- * @param pHeaderName the header name
- * @return a {@code String}, or {@code null} if there is no
- * such header in this response.
- */
- String getHeaderValue(String pHeaderName);
-
- /**
- * Returns the size of this cached response in bytes.
- *
- * @return the size
- */
- int size();
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * CachedResponse
+ *
+ * @author Harald Kuhr
+ * @version $Id: CachedResponse.java#3 $
+ */
+interface CachedResponse {
+ /**
+ * Writes the cached headers to the response
+ *
+ * @param pResponse the servlet response
+ */
+ void writeHeadersTo(CacheResponse pResponse);
+
+ /**
+ * Writes the cahced content to the response
+ *
+ * @param pStream the response output stream
+ * @throws IOException if an I/O exception occurs during write
+ */
+ void writeContentsTo(OutputStream pStream) throws IOException;
+
+ int getStatus();
+
+ // TODO: Map> getHeaders()
+
+ /**
+ * Gets the header names of all headers set in this response.
+ *
+ * @return an array of {@code String}s
+ */
+ String[] getHeaderNames();
+
+ /**
+ * Gets all header values set for the given header in this response. If the
+ * header is not set, {@code null} is returned.
+ *
+ * @param pHeaderName the header name
+ * @return an array of {@code String}s, or {@code null} if there is no
+ * such header in this response.
+ */
+ String[] getHeaderValues(String pHeaderName);
+
+ /**
+ * Gets the first header value set for the given header in this response.
+ * If the header is not set, {@code null} is returned.
+ * Useful for headers that don't have multiple values, like
+ * {@code "Content-Type"} or {@code "Content-Length"}.
+ *
+ * @param pHeaderName the header name
+ * @return a {@code String}, or {@code null} if there is no
+ * such header in this response.
+ */
+ String getHeaderValue(String pHeaderName);
+
+ /**
+ * Returns the size of this cached response in bytes.
+ *
+ * @return the size
+ */
+ int size();
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java
index 8ee3c48c..4fd870aa 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java
@@ -1,213 +1,213 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.io.FastByteArrayOutputStream;
-import com.twelvemonkeys.lang.Validate;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.*;
-
-/**
- * CachedResponseImpl
- *
- * @author Harald Kuhr
- * @version $Id: CachedResponseImpl.java#4 $
- */
-class CachedResponseImpl implements CachedResponse {
- final protected Map> headers;
- protected int headersSize;
- protected ByteArrayOutputStream content = null;
- int status;
-
- protected CachedResponseImpl() {
- headers = new LinkedHashMap>(); // Keep headers in insertion order
- }
-
- // For use by HTTPCache, when recreating CachedResponses from disk cache
- CachedResponseImpl(final int pStatus, final LinkedHashMap> pHeaders, final int pHeaderSize, final byte[] pContent) {
- status = pStatus;
- headers = Validate.notNull(pHeaders, "headers");
- headersSize = pHeaderSize;
- content = new FastByteArrayOutputStream(pContent);
- }
-
- public int getStatus() {
- return status;
- }
-
- /**
- * Writes the cached headers to the response
- *
- * @param pResponse the response
- */
- public void writeHeadersTo(final CacheResponse pResponse) {
- String[] headers = getHeaderNames();
- for (String header : headers) {
- // HACK...
- // Strip away internal headers
- if (HTTPCache.HEADER_CACHED_TIME.equals(header)) {
- continue;
- }
-
- // TODO: Replace Last-Modified with X-Cached-At? See CachedEntityImpl
-
- String[] headerValues = getHeaderValues(header);
-
- for (int i = 0; i < headerValues.length; i++) {
- String headerValue = headerValues[i];
-
- if (i == 0) {
- pResponse.setHeader(header, headerValue);
- }
- else {
- pResponse.addHeader(header, headerValue);
- }
- }
- }
- }
-
- /**
- * Writes the cached content to the response
- *
- * @param pStream the response stream
- * @throws java.io.IOException
- */
- public void writeContentsTo(final OutputStream pStream) throws IOException {
- if (content == null) {
- throw new IOException("Cache is null, no content to write.");
- }
-
- content.writeTo(pStream);
- }
-
- /**
- * Gets the header names of all headers set in this response.
- *
- * @return an array of {@code String}s
- */
- public String[] getHeaderNames() {
- Set headers = this.headers.keySet();
-
- return headers.toArray(new String[headers.size()]);
- }
-
- /**
- * Gets all header values set for the given header in this response. If the
- * header is not set, {@code null} is returned.
- *
- * @param pHeaderName the header name
- * @return an array of {@code String}s, or {@code null} if there is no
- * such header in this response.
- */
- public String[] getHeaderValues(final String pHeaderName) {
- List values = headers.get(pHeaderName);
-
- return values == null ? null : values.toArray(new String[values.size()]);
- }
-
- /**
- * Gets the first header value set for the given header in this response.
- * If the header is not set, {@code null} is returned.
- * Useful for headers that don't have multiple values, like
- * {@code "Content-Type"} or {@code "Content-Length"}.
- *
- * @param pHeaderName the header name
- * @return a {@code String}, or {@code null} if there is no
- * such header in this response.
- */
- public String getHeaderValue(final String pHeaderName) {
- List values = headers.get(pHeaderName);
-
- return (values != null && values.size() > 0) ? values.get(0) : null;
- }
-
- public int size() {
- // content.size() is exact size in bytes, headersSize is an estimate
- return (content != null ? content.size() : 0) + headersSize;
- }
-
- public boolean equals(final Object pOther) {
- if (this == pOther) {
- return true;
- }
-
- if (pOther instanceof CachedResponseImpl) {
- // "Fast"
- return equalsImpl((CachedResponseImpl) pOther);
- }
- else if (pOther instanceof CachedResponse) {
- // Slow
- return equalsGeneric((CachedResponse) pOther);
- }
-
- return false;
- }
-
- private boolean equalsImpl(final CachedResponseImpl pOther) {
- return headersSize == pOther.headersSize &&
- (content == null ? pOther.content == null : content.equals(pOther.content)) &&
- headers.equals(pOther.headers);
- }
-
- private boolean equalsGeneric(final CachedResponse pOther) {
- if (size() != pOther.size()) {
- return false;
- }
-
- String[] headers = getHeaderNames();
- String[] otherHeaders = pOther.getHeaderNames();
- if (!Arrays.equals(headers, otherHeaders)) {
- return false;
- }
-
- if (headers != null) {
- for (String header : headers) {
- String[] values = getHeaderValues(header);
- String[] otherValues = pOther.getHeaderValues(header);
-
- if (!Arrays.equals(values, otherValues)) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- public int hashCode() {
- int result;
- result = headers.hashCode();
- result = 29 * result + headersSize;
- result = 37 * result + (content != null ? content.hashCode() : 0);
- return result;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.lang.Validate;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+
+/**
+ * CachedResponseImpl
+ *
+ * @author Harald Kuhr
+ * @version $Id: CachedResponseImpl.java#4 $
+ */
+class CachedResponseImpl implements CachedResponse {
+ final protected Map> headers;
+ protected int headersSize;
+ protected ByteArrayOutputStream content = null;
+ int status;
+
+ protected CachedResponseImpl() {
+ headers = new LinkedHashMap>(); // Keep headers in insertion order
+ }
+
+ // For use by HTTPCache, when recreating CachedResponses from disk cache
+ CachedResponseImpl(final int pStatus, final LinkedHashMap> pHeaders, final int pHeaderSize, final byte[] pContent) {
+ status = pStatus;
+ headers = Validate.notNull(pHeaders, "headers");
+ headersSize = pHeaderSize;
+ content = new FastByteArrayOutputStream(pContent);
+ }
+
+ public int getStatus() {
+ return status;
+ }
+
+ /**
+ * Writes the cached headers to the response
+ *
+ * @param pResponse the response
+ */
+ public void writeHeadersTo(final CacheResponse pResponse) {
+ String[] headers = getHeaderNames();
+ for (String header : headers) {
+ // HACK...
+ // Strip away internal headers
+ if (HTTPCache.HEADER_CACHED_TIME.equals(header)) {
+ continue;
+ }
+
+ // TODO: Replace Last-Modified with X-Cached-At? See CachedEntityImpl
+
+ String[] headerValues = getHeaderValues(header);
+
+ for (int i = 0; i < headerValues.length; i++) {
+ String headerValue = headerValues[i];
+
+ if (i == 0) {
+ pResponse.setHeader(header, headerValue);
+ }
+ else {
+ pResponse.addHeader(header, headerValue);
+ }
+ }
+ }
+ }
+
+ /**
+ * Writes the cached content to the response
+ *
+ * @param pStream the response stream
+ * @throws java.io.IOException
+ */
+ public void writeContentsTo(final OutputStream pStream) throws IOException {
+ if (content == null) {
+ throw new IOException("Cache is null, no content to write.");
+ }
+
+ content.writeTo(pStream);
+ }
+
+ /**
+ * Gets the header names of all headers set in this response.
+ *
+ * @return an array of {@code String}s
+ */
+ public String[] getHeaderNames() {
+ Set headers = this.headers.keySet();
+
+ return headers.toArray(new String[headers.size()]);
+ }
+
+ /**
+ * Gets all header values set for the given header in this response. If the
+ * header is not set, {@code null} is returned.
+ *
+ * @param pHeaderName the header name
+ * @return an array of {@code String}s, or {@code null} if there is no
+ * such header in this response.
+ */
+ public String[] getHeaderValues(final String pHeaderName) {
+ List values = headers.get(pHeaderName);
+
+ return values == null ? null : values.toArray(new String[values.size()]);
+ }
+
+ /**
+ * Gets the first header value set for the given header in this response.
+ * If the header is not set, {@code null} is returned.
+ * Useful for headers that don't have multiple values, like
+ * {@code "Content-Type"} or {@code "Content-Length"}.
+ *
+ * @param pHeaderName the header name
+ * @return a {@code String}, or {@code null} if there is no
+ * such header in this response.
+ */
+ public String getHeaderValue(final String pHeaderName) {
+ List values = headers.get(pHeaderName);
+
+ return (values != null && values.size() > 0) ? values.get(0) : null;
+ }
+
+ public int size() {
+ // content.size() is exact size in bytes, headersSize is an estimate
+ return (content != null ? content.size() : 0) + headersSize;
+ }
+
+ public boolean equals(final Object pOther) {
+ if (this == pOther) {
+ return true;
+ }
+
+ if (pOther instanceof CachedResponseImpl) {
+ // "Fast"
+ return equalsImpl((CachedResponseImpl) pOther);
+ }
+ else if (pOther instanceof CachedResponse) {
+ // Slow
+ return equalsGeneric((CachedResponse) pOther);
+ }
+
+ return false;
+ }
+
+ private boolean equalsImpl(final CachedResponseImpl pOther) {
+ return headersSize == pOther.headersSize &&
+ (content == null ? pOther.content == null : content.equals(pOther.content)) &&
+ headers.equals(pOther.headers);
+ }
+
+ private boolean equalsGeneric(final CachedResponse pOther) {
+ if (size() != pOther.size()) {
+ return false;
+ }
+
+ String[] headers = getHeaderNames();
+ String[] otherHeaders = pOther.getHeaderNames();
+ if (!Arrays.equals(headers, otherHeaders)) {
+ return false;
+ }
+
+ if (headers != null) {
+ for (String header : headers) {
+ String[] values = getHeaderValues(header);
+ String[] otherValues = pOther.getHeaderValues(header);
+
+ if (!Arrays.equals(values, otherValues)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = headers.hashCode();
+ result = 29 * result + headersSize;
+ result = 37 * result + (content != null ? content.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java
index fc655de0..9d73e8b2 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java
@@ -1,1150 +1,1150 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.lang.Validate;
-import com.twelvemonkeys.net.MIMEUtil;
-import com.twelvemonkeys.net.HTTPUtil;
-import com.twelvemonkeys.util.LRUHashMap;
-import com.twelvemonkeys.util.NullMap;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletResponse;
-import java.io.*;
-import java.util.*;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-/**
- * A "simple" HTTP cache.
- *
- *
- * @author Harald Kuhr
- * @version $Id: HTTPCache.java#4 $
- * @todo OMPTIMIZE: Cache parsed vary-info objects, not the properties-files
- * @todo BUG: Better filename handling, as some filenames become too long..
- * - Use a mix of parameters and hashcode + lenght with fixed (max) lenght?
- * (Hashcodes of Strings are constant).
- * - Store full filenames in .vary, instead of just extension, and use
- * short filenames? (and only one .vary per dir).
- *
- *
- * @todo TEST: Battle-testing using some URL-hammer tool and maybe a profiler
- * @todo ETag/Conditional (If-None-Match) support!
- * @todo Rewrite to use java.util.concurrent Locks (if possible) for performance
- * Maybe use ConcurrentHashMap instead fo synchronized HashMap?
- * @todo Rewrite to use NIO for performance
- * @todo Allow no tempdir for in-memory only cache
- * @todo Specify max size of disk-cache
- */
-public class HTTPCache {
- /**
- * The HTTP header {@code "Cache-Control"}
- */
- protected static final String HEADER_CACHE_CONTROL = "Cache-Control";
- /**
- * The HTTP header {@code "Content-Type"}
- */
- protected static final String HEADER_CONTENT_TYPE = "Content-Type";
- /**
- * The HTTP header {@code "Date"}
- */
- protected static final String HEADER_DATE = "Date";
- /**
- * The HTTP header {@code "ETag"}
- */
- protected static final String HEADER_ETAG = "ETag";
- /**
- * The HTTP header {@code "Expires"}
- */
- protected static final String HEADER_EXPIRES = "Expires";
- /**
- * The HTTP header {@code "If-Modified-Since"}
- */
- protected static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
- /**
- * The HTTP header {@code "If-None-Match"}
- */
- protected static final String HEADER_IF_NONE_MATCH = "If-None-Match";
- /**
- * The HTTP header {@code "Last-Modified"}
- */
- protected static final String HEADER_LAST_MODIFIED = "Last-Modified";
- /**
- * The HTTP header {@code "Pragma"}
- */
- protected static final String HEADER_PRAGMA = "Pragma";
- /**
- * The HTTP header {@code "Vary"}
- */
- protected static final String HEADER_VARY = "Vary";
- /**
- * The HTTP header {@code "Warning"}
- */
- protected static final String HEADER_WARNING = "Warning";
- /**
- * HTTP extension header {@code "X-Cached-At"}
- */
- protected static final String HEADER_CACHED_TIME = "X-Cached-At";
-
- /**
- * The file extension for header files ({@code ".headers"})
- */
- protected static final String FILE_EXT_HEADERS = ".headers";
- /**
- * The file extension for varation-info files ({@code ".vary"})
- */
- protected static final String FILE_EXT_VARY = ".vary";
-
- /**
- * The directory used for the disk-based cache
- */
- private File tempDir;
-
- /**
- * Indicates wether the disk-based cache should be deleted when the
- * container shuts down/VM exits
- */
- private boolean deleteCacheOnExit;
-
- /**
- * In-memory content cache
- */
- private final Map contentCache;
- /**
- * In-memory enity cache
- */
- private final Map entityCache;
- /**
- * In-memory varyiation-info cache
- */
- private final Map varyCache;
-
- private long defaultExpiryTime = -1;
-
- private final Logger logger;
-
- // Internal constructor for sublcasses only
- protected HTTPCache(
- final File pTempFolder,
- final long pDefaultCacheExpiryTime,
- final int pMaxMemCacheSize,
- final int pMaxCachedEntites,
- final boolean pDeleteCacheOnExit,
- final Logger pLogger
- ) {
- Validate.notNull(pTempFolder, "temp folder");
- Validate.isTrue(pTempFolder.exists() || pTempFolder.mkdirs(), pTempFolder.getAbsolutePath(), "Could not create required temp directory: %s");
- Validate.isTrue(pTempFolder.canRead() && pTempFolder.canWrite(), pTempFolder.getAbsolutePath(), "Must have read/write access to temp folder: %s");
-
- Validate.isTrue(pDefaultCacheExpiryTime >= 0, pDefaultCacheExpiryTime, "Negative expiry time: %d");
- Validate.isTrue(pMaxMemCacheSize >= 0, pDefaultCacheExpiryTime, "Negative maximum memory cache size: %d");
- Validate.isTrue(pMaxCachedEntites >= 0, pDefaultCacheExpiryTime, "Negative maximum number of cached entries: %d");
-
- defaultExpiryTime = pDefaultCacheExpiryTime;
-
- if (pMaxMemCacheSize > 0) {
-// Map backing = new SizedLRUMap(pMaxMemCacheSize); // size in bytes
-// contentCache = new TimeoutMap(backing, null, pDefaultCacheExpiryTime);
- contentCache = new SizedLRUMap(pMaxMemCacheSize); // size in bytes
- }
- else {
- contentCache = new NullMap();
- }
-
- entityCache = new LRUHashMap(pMaxCachedEntites);
- varyCache = new LRUHashMap(pMaxCachedEntites);
-
- deleteCacheOnExit = pDeleteCacheOnExit;
-
- tempDir = pTempFolder;
-
- logger = pLogger != null ? pLogger : Logger.getLogger(getClass().getName());
- }
-
- /**
- * Creates an {@code HTTPCache}.
- *
- * @param pTempFolder the temp folder for this cache.
- * @param pDefaultCacheExpiryTime Default expiry time for cached entities,
- * {@code >= 0}
- * @param pMaxMemCacheSize Maximum size of in-memory cache for content
- * in bytes, {@code >= 0} ({@code 0} means no
- * in-memory cache)
- * @param pMaxCachedEntites Maximum number of entities in cache
- * @param pDeleteCacheOnExit specifies wether the file cache should be
- * deleted when the application or VM shuts down
- * @throws IllegalArgumentException if {@code pName} or {@code pContext} is
- * {@code null} or if any of {@code pDefaultCacheExpiryTime},
- * {@code pMaxMemCacheSize} or {@code pMaxCachedEntites} are
- * negative,
- * or if the directory as given in the context attribute
- * {@code "javax.servlet.context.tempdir"} does not exist, and
- * cannot be created.
- */
- public HTTPCache(final File pTempFolder,
- final long pDefaultCacheExpiryTime,
- final int pMaxMemCacheSize, final int pMaxCachedEntites,
- final boolean pDeleteCacheOnExit) {
- this(pTempFolder, pDefaultCacheExpiryTime, pMaxMemCacheSize, pMaxCachedEntites, pDeleteCacheOnExit, null);
- }
-
-
- /**
- * Creates an {@code HTTPCache}.
- *
- * @param pName Name of this cache (should be unique per application).
- * Used for temp folder
- * @param pContext Servlet context for the application.
- * @param pDefaultCacheExpiryTime Default expiry time for cached entities,
- * {@code >= 0}
- * @param pMaxMemCacheSize Maximum size of in-memory cache for content
- * in bytes, {@code >= 0} ({@code 0} means no
- * in-memory cache)
- * @param pMaxCachedEntites Maximum number of entities in cache
- * @param pDeleteCacheOnExit specifies wether the file cache should be
- * deleted when the application or VM shuts down
- * @throws IllegalArgumentException if {@code pName} or {@code pContext} is
- * {@code null} or if any of {@code pDefaultCacheExpiryTime},
- * {@code pMaxMemCacheSize} or {@code pMaxCachedEntites} are
- * negative,
- * or if the directory as given in the context attribute
- * {@code "javax.servlet.context.tempdir"} does not exist, and
- * cannot be created.
- * @deprecated Use {@link #HTTPCache(File, long, int, int, boolean)} instead.
- */
- public HTTPCache(final String pName, final ServletContext pContext,
- final int pDefaultCacheExpiryTime, final int pMaxMemCacheSize,
- final int pMaxCachedEntites, final boolean pDeleteCacheOnExit) {
- this(
- getTempFolder(pName, pContext),
- pDefaultCacheExpiryTime, pMaxMemCacheSize, pMaxCachedEntites, pDeleteCacheOnExit,
- new CacheFilter.ServletContextLoggerAdapter(pName, pContext)
- );
- }
-
- private static File getTempFolder(String pName, ServletContext pContext) {
- Validate.notNull(pName, "name");
- Validate.isTrue(!StringUtil.isEmpty(pName), pName, "empty name: '%s'");
- Validate.notNull(pContext, "context");
-
- File tempRoot = (File) pContext.getAttribute("javax.servlet.context.tempdir");
- if (tempRoot == null) {
- throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
- }
-
- return new File(tempRoot, pName);
- }
-
- public String toString() {
- StringBuilder buf = new StringBuilder(getClass().getSimpleName());
- buf.append("[");
- buf.append("Temp dir: ");
- buf.append(tempDir.getAbsolutePath());
- if (deleteCacheOnExit) {
- buf.append(" (non-persistent)");
- }
- else {
- buf.append(" (persistent)");
- }
- buf.append(", EntityCache: {");
- buf.append(entityCache.size());
- buf.append(" entries in a ");
- buf.append(entityCache.getClass().getName());
- buf.append("}, VaryCache: {");
- buf.append(varyCache.size());
- buf.append(" entries in a ");
- buf.append(varyCache.getClass().getName());
- buf.append("}, ContentCache: {");
- buf.append(contentCache.size());
- buf.append(" entries in a ");
- buf.append(contentCache.getClass().getName());
- buf.append("}]");
-
- return buf.toString();
- }
-
- void log(final String pMessage) {
- logger.log(Level.INFO, pMessage);
- }
-
- void log(final String pMessage, Throwable pException) {
- logger.log(Level.WARNING, pMessage, pException);
- }
-
- /**
- * Looks up the {@code CachedEntity} for the given request.
- *
- * @param pRequest the request
- * @param pResponse the response
- * @param pResolver the resolver
- * @throws java.io.IOException if an I/O error occurs
- * @throws CacheException if the cached entity can't be resolved for some reason
- */
- public void doCached(final CacheRequest pRequest, final CacheResponse pResponse, final ResponseResolver pResolver) throws IOException, CacheException {
- // TODO: Expire cached items on PUT/POST/DELETE/PURGE
- // If not cachable request, resolve directly
- if (!isCacheable(pRequest)) {
- pResolver.resolve(pRequest, pResponse);
- }
- else {
- // Generate cacheURI
- String cacheURI = generateCacheURI(pRequest);
-// System.out.println(" ## HTTPCache ## Request Id (cacheURI): " + cacheURI);
-
- // Get/create cached entity
- CachedEntity cached;
- synchronized (entityCache) {
- cached = entityCache.get(cacheURI);
- if (cached == null) {
- cached = new CachedEntityImpl(cacheURI, this);
- entityCache.put(cacheURI, cached);
- }
- }
-
- // else if (not cached || stale), resolve through wrapped (caching) response
- // else render to response
-
- // TODO: This is a bottleneck for uncachable resources. Should not
- // synchronize, if we know (HOW?) the resource is not cachable.
- synchronized (cached) {
- if (cached.isStale(pRequest) /* TODO: NOT CACHED?! */) {
- // Go fetch...
- WritableCachedResponse cachedResponse = cached.createCachedResponse();
- pResolver.resolve(pRequest, cachedResponse);
-
- if (isCachable(cachedResponse)) {
-// System.out.println("Registering content: " + cachedResponse.getCachedResponse());
- registerContent(cacheURI, pRequest, cachedResponse.getCachedResponse());
- }
- else {
- // TODO: What about non-cachable responses? We need to either remove them from cache, or mark them as stale...
- // Best is probably to mark as non-cacheable for later, and NOT store content (performance)
-// System.out.println("Non-cacheable response: " + cachedResponse);
-
- // TODO: Write, but should really do this unbuffered.... And some resolver might be able to do just that?
- // Might need a resolver.isWriteThroughForUncachableResources() method...
- pResponse.setStatus(cachedResponse.getStatus());
- cachedResponse.writeHeadersTo(pResponse);
- cachedResponse.writeContentsTo(pResponse.getOutputStream());
- return;
- }
- }
- }
-
- cached.render(pRequest, pResponse);
- }
- }
-
- protected void invalidate(CacheRequest pRequest) {
- // Generate cacheURI
- String cacheURI = generateCacheURI(pRequest);
-
- // Get/create cached entity
- CachedEntity cached;
- synchronized (entityCache) {
- cached = entityCache.get(cacheURI);
- if (cached != null) {
- // TODO; Remove all variants
- entityCache.remove(cacheURI);
- }
- }
-
- }
-
- private boolean isCacheable(final CacheRequest pRequest) {
- // TODO: Support public/private cache (a cache probably have to be one of the two, when created)
- // TODO: Only private caches should cache requests with Authorization
-
- // TODO: OptimizeMe!
- // It's probably best to cache the "cacheableness" of a request and a resource separately
- List cacheControlValues = pRequest.getHeaders().get(HEADER_CACHE_CONTROL);
- if (cacheControlValues != null) {
- Map cacheControl = new HashMap();
- for (String cc : cacheControlValues) {
- List directives = Arrays.asList(cc.split(","));
- for (String directive : directives) {
- directive = directive.trim();
- if (directive.length() > 0) {
- String[] directiveParts = directive.split("=", 2);
- cacheControl.put(directiveParts[0], directiveParts.length > 1 ? directiveParts[1] : null);
- }
- }
- }
-
- if (cacheControl.containsKey("no-cache") || cacheControl.containsKey("no-store")) {
- return false;
- }
-
- /*
- "no-cache" ; Section 14.9.1
- | "no-store" ; Section 14.9.2
- | "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4
- | "max-stale" [ "=" delta-seconds ] ; Section 14.9.3
- | "min-fresh" "=" delta-seconds ; Section 14.9.3
- | "no-transform" ; Section 14.9.5
- | "only-if-cached"
- */
- }
-
- return true;
- }
-
- private boolean isCachable(final CacheResponse pResponse) {
- if (pResponse.getStatus() != HttpServletResponse.SC_OK) {
- return false;
- }
-
- // Vary: *
- List values = pResponse.getHeaders().get(HTTPCache.HEADER_VARY);
- if (values != null) {
- for (String value : values) {
- if ("*".equals(value)) {
- return false;
- }
- }
- }
-
- // Cache-Control: no-cache, no-store, must-revalidate
- values = pResponse.getHeaders().get(HTTPCache.HEADER_CACHE_CONTROL);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")
- || StringUtil.contains(value, "no-store")
- || StringUtil.contains(value, "must-revalidate")) {
- return false;
- }
- }
- }
-
- // Pragma: no-cache
- values = pResponse.getHeaders().get(HTTPCache.HEADER_PRAGMA);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")) {
- return false;
- }
- }
- }
-
- return true;
- }
-
-
- /**
- * Allows a server-side cache mechanism to peek at the real file.
- * Default implementation return {@code null}.
- *
- * @param pRequest the request
- * @return {@code null}, always
- */
- protected File getRealFile(final CacheRequest pRequest) {
- // TODO: Create callback for this? Only possible for server-side cache... Maybe we can get away without this?
- // For now: Default implementation that returns null
- return null;
-/*
- String contextRelativeURI = ServletUtil.getContextRelativeURI(pRequest);
- // System.out.println(" ## HTTPCache ## Context relative URI: " + contextRelativeURI);
-
- String path = mContext.getRealPath(contextRelativeURI);
- // System.out.println(" ## HTTPCache ## Real path: " + path);
-
- if (path != null) {
- return new File(path);
- }
-
- return null;
-*/
- }
-
- private File getCachedFile(final String pCacheURI, final CacheRequest pRequest) {
- File file = null;
-
- // Get base dir
- File base = new File(tempDir, "./" + pCacheURI);
- final String basePath = base.getAbsolutePath();
- File directory = base.getParentFile();
-
- // Get list of files that are candidates
- File[] candidates = directory.listFiles(new FileFilter() {
- public boolean accept(File pFile) {
- return pFile.getAbsolutePath().startsWith(basePath)
- && !pFile.getName().endsWith(FILE_EXT_HEADERS)
- && !pFile.getName().endsWith(FILE_EXT_VARY);
- }
- });
-
- // Negotiation
- if (candidates != null) {
- String extension = getVaryExtension(pCacheURI, pRequest);
- //System.out.println("-- Vary ext: " + extension);
- if (extension != null) {
- for (File candidate : candidates) {
- //System.out.println("-- Candidate: " + candidates[i]);
-
- if (extension.equals("ANY") || extension.equals(FileUtil.getExtension(candidate))) {
- //System.out.println("-- Candidate selected");
- file = candidate;
- break;
- }
- }
- }
- }
- else if (base.exists()) {
- //System.out.println("-- File not a directory: " + directory);
- log("File not a directory: " + directory);
- }
-
- return file;
- }
-
- private String getVaryExtension(final String pCacheURI, final CacheRequest pRequest) {
- Properties variations = getVaryProperties(pCacheURI);
-
- String[] varyHeaders = StringUtil.toStringArray(variations.getProperty(HEADER_VARY, ""));
-// System.out.println("-- Vary: \"" + variations.getProperty(HEADER_VARY) + "\"");
-
- String varyKey = createVaryKey(varyHeaders, pRequest);
-// System.out.println("-- Vary key: \"" + varyKey + "\"");
-
- // If no vary, just go with any version...
- return StringUtil.isEmpty(varyKey) ? "ANY" : variations.getProperty(varyKey, null);
- }
-
- private String createVaryKey(final String[] pVaryHeaders, final CacheRequest pRequest) {
- if (pVaryHeaders == null) {
- return null;
- }
-
- StringBuilder headerValues = new StringBuilder();
- for (String varyHeader : pVaryHeaders) {
- List varies = pRequest.getHeaders().get(varyHeader);
- String headerValue = varies != null && varies.size() > 0 ? varies.get(0) : null;
-
- headerValues.append(varyHeader);
- headerValues.append("__V_");
- headerValues.append(createSafeHeader(headerValue));
- }
-
- return headerValues.toString();
- }
-
- private void storeVaryProperties(final String pCacheURI, final Properties pVariations) {
- synchronized (pVariations) {
- try {
- File file = getVaryPropertiesFile(pCacheURI);
- if (!file.exists() && deleteCacheOnExit) {
- file.deleteOnExit();
- }
-
- FileOutputStream out = new FileOutputStream(file);
- try {
- pVariations.store(out, pCacheURI + " Vary info");
- }
- finally {
- out.close();
- }
- }
- catch (IOException ioe) {
- log("Error: Could not store Vary info: " + ioe);
- }
- }
- }
-
- private Properties getVaryProperties(final String pCacheURI) {
- Properties variations;
-
- synchronized (varyCache) {
- variations = varyCache.get(pCacheURI);
- if (variations == null) {
- variations = loadVaryProperties(pCacheURI);
- varyCache.put(pCacheURI, variations);
- }
- }
-
- return variations;
- }
-
- private Properties loadVaryProperties(final String pCacheURI) {
- // Read Vary info, for content negotiation
- Properties variations = new Properties();
- File vary = getVaryPropertiesFile(pCacheURI);
- if (vary.exists()) {
- try {
- FileInputStream in = new FileInputStream(vary);
- try {
- variations.load(in);
- }
- finally {
- in.close();
- }
- }
- catch (IOException ioe) {
- log("Error: Could not load Vary info: " + ioe);
- }
- }
- return variations;
- }
-
- private File getVaryPropertiesFile(final String pCacheURI) {
- return new File(tempDir, "./" + pCacheURI + FILE_EXT_VARY);
- }
-
- private static String generateCacheURI(final CacheRequest pRequest) {
- StringBuilder buffer = new StringBuilder();
-
- // Note: As the '/'s are not replaced, the directory structure will be recreated
- // TODO: Old mehtod relied on context relativization, that must now be handled byt the ServletCacheRequest
-// String contextRelativeURI = ServletUtil.getContextRelativeURI(pRequest);
- String contextRelativeURI = pRequest.getRequestURI().getPath();
- buffer.append(contextRelativeURI);
-
- // Create directory for all resources
- if (contextRelativeURI.charAt(contextRelativeURI.length() - 1) != '/') {
- buffer.append('/');
- }
-
- // Get parameters from request, and recreate query to avoid unneccessary
- // regeneration/caching when parameters are out of order
- // Also makes caching work for POST
- appendSortedRequestParams(pRequest, buffer);
-
- return buffer.toString();
- }
-
- private static void appendSortedRequestParams(final CacheRequest pRequest, final StringBuilder pBuffer) {
- Set names = pRequest.getParameters().keySet();
- if (names.isEmpty()) {
- pBuffer.append("defaultVersion");
- return;
- }
-
- // We now have parameters
- pBuffer.append('_'); // append '_' for '?', to avoid clash with default
-
- // Create a sorted map
- SortedMap> sortedQueryMap = new TreeMap>();
- for (String name : names) {
- List values = pRequest.getParameters().get(name);
-
- sortedQueryMap.put(name, values);
- }
-
- // Iterate over sorted map, and append to stringbuffer
- for (Iterator>> iterator = sortedQueryMap.entrySet().iterator(); iterator.hasNext();) {
- Map.Entry> entry = iterator.next();
- pBuffer.append(createSafe(entry.getKey()));
-
- List values = entry.getValue();
- if (values != null && values.size() > 0) {
- pBuffer.append("_V"); // =
- for (int i = 0; i < values.size(); i++) {
- String value = values.get(i);
- if (i != 0) {
- pBuffer.append(',');
- }
- pBuffer.append(createSafe(value));
- }
- }
-
- if (iterator.hasNext()) {
- pBuffer.append("_P"); // &
- }
- }
- }
-
- private static String createSafe(final String pKey) {
- return pKey.replace('/', '-')
- .replace('&', '-') // In case they are encoded
- .replace('#', '-')
- .replace(';', '-');
- }
-
- private static String createSafeHeader(final String pHeaderValue) {
- if (pHeaderValue == null) {
- return "NULL";
- }
-
- return pHeaderValue.replace(' ', '_')
- .replace(':', '_')
- .replace('=', '_');
- }
-
- /**
- * Registers content for the given URI in the cache.
- *
- * @param pCacheURI the cache URI
- * @param pRequest the request
- * @param pCachedResponse the cached response
- * @throws IOException if the content could not be cached
- */
- void registerContent(
- final String pCacheURI,
- final CacheRequest pRequest,
- final CachedResponse pCachedResponse
- ) throws IOException {
- // System.out.println(" ## HTTPCache ## Registering content for " + pCacheURI);
-
-// pRequest.removeAttribute(ATTRIB_IS_STALE);
-// pRequest.setAttribute(ATTRIB_CACHED_RESPONSE, pCachedResponse);
-
- if ("HEAD".equals(pRequest.getMethod())) {
- // System.out.println(" ## HTTPCache ## Was HEAD request, will NOT store content.");
- return;
- }
-
- // TODO: Several resources may have same extension...
- String extension = MIMEUtil.getExtension(pCachedResponse.getHeaderValue(HEADER_CONTENT_TYPE));
- if (extension == null) {
- extension = "[NULL]";
- }
-
- synchronized (contentCache) {
- contentCache.put(pCacheURI + '.' + extension, pCachedResponse);
-
- // This will be the default version
- if (!contentCache.containsKey(pCacheURI)) {
- contentCache.put(pCacheURI, pCachedResponse);
- }
- }
-
- // Write the cached content to disk
- File content = new File(tempDir, "./" + pCacheURI + '.' + extension);
- if (deleteCacheOnExit && !content.exists()) {
- content.deleteOnExit();
- }
-
- File parent = content.getParentFile();
- if (!(parent.exists() || parent.mkdirs())) {
- log("Could not create directory " + parent.getAbsolutePath());
-
- // TODO: Make sure vary-info is still created in memory
-
- return;
- }
-
- OutputStream mContentStream = new BufferedOutputStream(new FileOutputStream(content));
-
- try {
- pCachedResponse.writeContentsTo(mContentStream);
- }
- finally {
- try {
- mContentStream.close();
- }
- catch (IOException e) {
- log("Error closing content stream: " + e.getMessage(), e);
- }
- }
-
- // Write the cached headers to disk (in pseudo-properties-format)
- File headers = new File(content.getAbsolutePath() + FILE_EXT_HEADERS);
- if (deleteCacheOnExit && !headers.exists()) {
- headers.deleteOnExit();
- }
-
- FileWriter writer = new FileWriter(headers);
- PrintWriter headerWriter = new PrintWriter(writer);
- try {
- String[] names = pCachedResponse.getHeaderNames();
-
- for (String name : names) {
- String[] values = pCachedResponse.getHeaderValues(name);
-
- headerWriter.print(name);
- headerWriter.print(": ");
- headerWriter.println(StringUtil.toCSVString(values, "\\"));
- }
- }
- finally {
- headerWriter.flush();
- try {
- writer.close();
- }
- catch (IOException e) {
- log("Error closing header stream: " + e.getMessage(), e);
- }
- }
-
- // TODO: Make this more robust, if some weird entity is not
- // consistent in it's vary-headers..
- // (sometimes Vary, sometimes not, or somtimes different Vary headers).
-
- // Write extra Vary info to disk
- String[] varyHeaders = pCachedResponse.getHeaderValues(HEADER_VARY);
-
- // If no variations, then don't store vary info
- if (varyHeaders != null && varyHeaders.length > 0) {
- Properties variations = getVaryProperties(pCacheURI);
-
- String vary = StringUtil.toCSVString(varyHeaders);
- variations.setProperty(HEADER_VARY, vary);
-
- // Create Vary-key and map to file extension...
- String varyKey = createVaryKey(varyHeaders, pRequest);
-// System.out.println("varyKey: " + varyKey);
-// System.out.println("extension: " + extension);
- variations.setProperty(varyKey, extension);
-
- storeVaryProperties(pCacheURI, variations);
- }
- }
-
- /**
- * @param pCacheURI the cache URI
- * @param pRequest the request
- * @return a {@code CachedResponse} object
- */
- CachedResponse getContent(final String pCacheURI, final CacheRequest pRequest) {
-// System.err.println(" ## HTTPCache ## Looking up content for " + pCacheURI);
-// Thread.dumpStack();
-
- String extension = getVaryExtension(pCacheURI, pRequest);
-
- CachedResponse response;
- synchronized (contentCache) {
-// System.out.println(" ## HTTPCache ## Looking up content with ext: \"" + extension + "\" from memory cache (" + contentCache /*.size()*/ + " entries)...");
- if ("ANY".equals(extension)) {
- response = contentCache.get(pCacheURI);
- }
- else {
- response = contentCache.get(pCacheURI + '.' + extension);
- }
-
- if (response == null) {
-// System.out.println(" ## HTTPCache ## Content not found in memory cache.");
-//
-// System.out.println(" ## HTTPCache ## Looking up content from disk cache...");
- // Read from disk-cache
- response = readFromDiskCache(pCacheURI, pRequest);
- }
-
-// if (response == null) {
-// System.out.println(" ## HTTPCache ## Content not found in disk cache.");
-// }
-// else {
-// System.out.println(" ## HTTPCache ## Content for " + pCacheURI + " found: " + response);
-// }
- }
-
- return response;
- }
-
- private CachedResponse readFromDiskCache(String pCacheURI, CacheRequest pRequest) {
- CachedResponse response = null;
- try {
- File content = getCachedFile(pCacheURI, pRequest);
- if (content != null && content.exists()) {
- // Read contents
- byte[] contents = FileUtil.read(content);
-
- // Read headers
- File headers = new File(content.getAbsolutePath() + FILE_EXT_HEADERS);
- int headerSize = (int) headers.length();
-
- BufferedReader reader = new BufferedReader(new FileReader(headers));
- LinkedHashMap> headerMap = new LinkedHashMap>();
- String line;
- while ((line = reader.readLine()) != null) {
- int colIdx = line.indexOf(':');
- String name;
- String value;
- if (colIdx >= 0) {
- name = line.substring(0, colIdx);
- value = line.substring(colIdx + 2); // ": "
- }
- else {
- name = line;
- value = "";
- }
-
- headerMap.put(name, Arrays.asList(StringUtil.toStringArray(value, "\\")));
- }
-
- response = new CachedResponseImpl(HttpServletResponse.SC_OK, headerMap, headerSize, contents);
- contentCache.put(pCacheURI + '.' + FileUtil.getExtension(content), response);
- }
- }
- catch (IOException e) {
- log("Error reading from cache: " + e.getMessage(), e);
- }
- return response;
- }
-
- boolean isContentStale(final String pCacheURI, final CacheRequest pRequest) {
- // NOTE: Content is either stale or not, for the duration of one request, unless re-fetched
- // Means that we must retry after a registerContent(), if caching as request-attribute
- Boolean stale;
-// stale = (Boolean) pRequest.getAttribute(ATTRIB_IS_STALE);
-// if (stale != null) {
-// return stale;
-// }
-
- stale = isContentStaleImpl(pCacheURI, pRequest);
-// pRequest.setAttribute(ATTRIB_IS_STALE, stale);
-
- return stale;
- }
-
- private boolean isContentStaleImpl(final String pCacheURI, final CacheRequest pRequest) {
- CachedResponse response = getContent(pCacheURI, pRequest);
-
- if (response == null) {
- // System.out.println(" ## HTTPCache ## Content is stale (no content).");
- return true;
- }
-
- // TODO: Get max-age=... from REQUEST too!
-
- // TODO: What about time skew? Now should be (roughly) same as:
- // long now = pRequest.getDateHeader("Date");
- // TODO: If the time differs (server "now" vs client "now"), should we
- // take that into consideration when testing for stale content?
- // Probably, yes.
- // TODO: Define rules for how to handle time skews
-
- // Set timestamp check
- // NOTE: HTTP Dates are always in GMT time zone
- long now = (System.currentTimeMillis() / 1000L) * 1000L;
- long expires = getDateHeader(response.getHeaderValue(HEADER_EXPIRES));
- //long lastModified = getDateHeader(response, HEADER_LAST_MODIFIED);
- long lastModified = getDateHeader(response.getHeaderValue(HEADER_CACHED_TIME));
-
- // If expires header is not set, compute it
- if (expires == -1L) {
- /*
- // Note: Not all content has Last-Modified header. We should then
- // use lastModified() of the cached file, to compute expires time.
- if (lastModified == -1L) {
- File cached = getCachedFile(pCacheURI, pRequest);
- if (cached != null && cached.exists()) {
- lastModified = cached.lastModified();
- //// System.out.println(" ## HTTPCache ## Last-Modified is " + HTTPUtil.formatHTTPDate(lastModified) + ", using cachedFile.lastModified()");
- }
- }
- */
-
- // If Cache-Control: max-age is present, use it, otherwise default
- int maxAge = getIntHeader(response, HEADER_CACHE_CONTROL, "max-age");
- if (maxAge == -1) {
- expires = lastModified + defaultExpiryTime;
- //// System.out.println(" ## HTTPCache ## Expires is " + HTTPUtil.formatHTTPDate(expires) + ", using lastModified + defaultExpiry");
- }
- else {
- expires = lastModified + (maxAge * 1000L); // max-age is seconds
- //// System.out.println(" ## HTTPCache ## Expires is " + HTTPUtil.formatHTTPDate(expires) + ", using lastModified + maxAge");
- }
- }
- /*
- else {
- // System.out.println(" ## HTTPCache ## Expires header is " + response.getHeaderValue(HEADER_EXPIRES));
- }
- */
-
- // Expired?
- if (expires < now) {
- // System.out.println(" ## HTTPCache ## Content is stale (content expired: "
- // + HTTPUtil.formatHTTPDate(expires) + " before " + HTTPUtil.formatHTTPDate(now) + ").");
- return true;
- }
-
- /*
- if (lastModified == -1L) {
- // Note: Not all content has Last-Modified header. We should then
- // use lastModified() of the cached file, to compute expires time.
- File cached = getCachedFile(pCacheURI, pRequest);
- if (cached != null && cached.exists()) {
- lastModified = cached.lastModified();
- //// System.out.println(" ## HTTPCache ## Last-Modified is " + HTTPUtil.formatHTTPDate(lastModified) + ", using cachedFile.lastModified()");
- }
- }
- */
-
- // Get the real file for this request, if any
- File real = getRealFile(pRequest);
- //noinspection RedundantIfStatement
- if (real != null && real.exists() && real.lastModified() > lastModified) {
- // System.out.println(" ## HTTPCache ## Content is stale (new content"
- // + HTTPUtil.formatHTTPDate(lastModified) + " before " + HTTPUtil.formatHTTPDate(real.lastModified()) + ").");
- return true;
- }
-
- return false;
- }
-
- /**
- * Parses a cached header with directive to an int.
- * E.g: Cache-Control: max-age=60, returns 60
- *
- * @param pCached the cached response
- * @param pHeaderName the header name (e.g: {@code CacheControl})
- * @param pDirective the directive (e.g: {@code max-age}
- * @return the int value, or {@code -1} if not found
- */
- private int getIntHeader(final CachedResponse pCached, final String pHeaderName, final String pDirective) {
- String[] headerValues = pCached.getHeaderValues(pHeaderName);
- int value = -1;
-
- if (headerValues != null) {
- for (String headerValue : headerValues) {
- if (pDirective == null) {
- if (!StringUtil.isEmpty(headerValue)) {
- value = Integer.parseInt(headerValue);
- }
- break;
- }
- else {
- int start = headerValue.indexOf(pDirective);
-
- // Directive found
- if (start >= 0) {
-
- int end = headerValue.lastIndexOf(',');
- if (end < start) {
- end = headerValue.length();
- }
-
- headerValue = headerValue.substring(start, end);
-
- if (!StringUtil.isEmpty(headerValue)) {
- value = Integer.parseInt(headerValue);
- }
-
- break;
- }
- }
- }
- }
-
- return value;
- }
-
- /**
- * Utility to read a date header from a cached response.
- *
- * @param pHeaderValue the header value
- * @return the parsed date as a long, or {@code -1L} if not found
- * @see javax.servlet.http.HttpServletRequest#getDateHeader(String)
- */
- static long getDateHeader(final String pHeaderValue) {
- long date = -1L;
- if (pHeaderValue != null) {
- date = HTTPUtil.parseHTTPDate(pHeaderValue);
- }
- return date;
- }
-
- // TODO: Extract and make public?
- final static class SizedLRUMap extends LRUHashMap {
- int currentSize;
- int maxSize;
-
- public SizedLRUMap(int pMaxSize) {
- //super(true);
- super(); // Note: super.maxSize doesn't count...
- maxSize = pMaxSize;
- }
-
-
- // In super (LRUMap?) this could just return 1...
- protected int sizeOf(Object pValue) {
- // HACK: As this is used as a backing for a TimeoutMap, the values
- // will themselves be Entries...
- while (pValue instanceof Map.Entry) {
- pValue = ((Map.Entry) pValue).getValue();
- }
-
- CachedResponse cached = (CachedResponse) pValue;
- return (cached != null ? cached.size() : 0);
- }
-
- @Override
- public V put(K pKey, V pValue) {
- currentSize += sizeOf(pValue);
-
- V old = super.put(pKey, pValue);
- if (old != null) {
- currentSize -= sizeOf(old);
- }
- return old;
- }
-
- @Override
- public V remove(Object pKey) {
- V old = super.remove(pKey);
- if (old != null) {
- currentSize -= sizeOf(old);
- }
- return old;
- }
-
- @Override
- protected boolean removeEldestEntry(Map.Entry pEldest) {
- if (maxSize <= currentSize) { // NOTE: maxSize here is mem size
- removeLRU();
- }
- return false;
- }
-
- @Override
- public void removeLRU() {
- while (maxSize <= currentSize) { // NOTE: maxSize here is mem size
- super.removeLRU();
- }
- }
- }
-
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.lang.Validate;
+import com.twelvemonkeys.net.MIMEUtil;
+import com.twelvemonkeys.net.HTTPUtil;
+import com.twelvemonkeys.util.LRUHashMap;
+import com.twelvemonkeys.util.NullMap;
+
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A "simple" HTTP cache.
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: HTTPCache.java#4 $
+ * @todo OMPTIMIZE: Cache parsed vary-info objects, not the properties-files
+ * @todo BUG: Better filename handling, as some filenames become too long..
+ * - Use a mix of parameters and hashcode + lenght with fixed (max) lenght?
+ * (Hashcodes of Strings are constant).
+ * - Store full filenames in .vary, instead of just extension, and use
+ * short filenames? (and only one .vary per dir).
+ *
+ *
+ * @todo TEST: Battle-testing using some URL-hammer tool and maybe a profiler
+ * @todo ETag/Conditional (If-None-Match) support!
+ * @todo Rewrite to use java.util.concurrent Locks (if possible) for performance
+ * Maybe use ConcurrentHashMap instead fo synchronized HashMap?
+ * @todo Rewrite to use NIO for performance
+ * @todo Allow no tempdir for in-memory only cache
+ * @todo Specify max size of disk-cache
+ */
+public class HTTPCache {
+ /**
+ * The HTTP header {@code "Cache-Control"}
+ */
+ protected static final String HEADER_CACHE_CONTROL = "Cache-Control";
+ /**
+ * The HTTP header {@code "Content-Type"}
+ */
+ protected static final String HEADER_CONTENT_TYPE = "Content-Type";
+ /**
+ * The HTTP header {@code "Date"}
+ */
+ protected static final String HEADER_DATE = "Date";
+ /**
+ * The HTTP header {@code "ETag"}
+ */
+ protected static final String HEADER_ETAG = "ETag";
+ /**
+ * The HTTP header {@code "Expires"}
+ */
+ protected static final String HEADER_EXPIRES = "Expires";
+ /**
+ * The HTTP header {@code "If-Modified-Since"}
+ */
+ protected static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+ /**
+ * The HTTP header {@code "If-None-Match"}
+ */
+ protected static final String HEADER_IF_NONE_MATCH = "If-None-Match";
+ /**
+ * The HTTP header {@code "Last-Modified"}
+ */
+ protected static final String HEADER_LAST_MODIFIED = "Last-Modified";
+ /**
+ * The HTTP header {@code "Pragma"}
+ */
+ protected static final String HEADER_PRAGMA = "Pragma";
+ /**
+ * The HTTP header {@code "Vary"}
+ */
+ protected static final String HEADER_VARY = "Vary";
+ /**
+ * The HTTP header {@code "Warning"}
+ */
+ protected static final String HEADER_WARNING = "Warning";
+ /**
+ * HTTP extension header {@code "X-Cached-At"}
+ */
+ protected static final String HEADER_CACHED_TIME = "X-Cached-At";
+
+ /**
+ * The file extension for header files ({@code ".headers"})
+ */
+ protected static final String FILE_EXT_HEADERS = ".headers";
+ /**
+ * The file extension for varation-info files ({@code ".vary"})
+ */
+ protected static final String FILE_EXT_VARY = ".vary";
+
+ /**
+ * The directory used for the disk-based cache
+ */
+ private File tempDir;
+
+ /**
+ * Indicates wether the disk-based cache should be deleted when the
+ * container shuts down/VM exits
+ */
+ private boolean deleteCacheOnExit;
+
+ /**
+ * In-memory content cache
+ */
+ private final Map contentCache;
+ /**
+ * In-memory enity cache
+ */
+ private final Map entityCache;
+ /**
+ * In-memory varyiation-info cache
+ */
+ private final Map varyCache;
+
+ private long defaultExpiryTime = -1;
+
+ private final Logger logger;
+
+ // Internal constructor for sublcasses only
+ protected HTTPCache(
+ final File pTempFolder,
+ final long pDefaultCacheExpiryTime,
+ final int pMaxMemCacheSize,
+ final int pMaxCachedEntites,
+ final boolean pDeleteCacheOnExit,
+ final Logger pLogger
+ ) {
+ Validate.notNull(pTempFolder, "temp folder");
+ Validate.isTrue(pTempFolder.exists() || pTempFolder.mkdirs(), pTempFolder.getAbsolutePath(), "Could not create required temp directory: %s");
+ Validate.isTrue(pTempFolder.canRead() && pTempFolder.canWrite(), pTempFolder.getAbsolutePath(), "Must have read/write access to temp folder: %s");
+
+ Validate.isTrue(pDefaultCacheExpiryTime >= 0, pDefaultCacheExpiryTime, "Negative expiry time: %d");
+ Validate.isTrue(pMaxMemCacheSize >= 0, pDefaultCacheExpiryTime, "Negative maximum memory cache size: %d");
+ Validate.isTrue(pMaxCachedEntites >= 0, pDefaultCacheExpiryTime, "Negative maximum number of cached entries: %d");
+
+ defaultExpiryTime = pDefaultCacheExpiryTime;
+
+ if (pMaxMemCacheSize > 0) {
+// Map backing = new SizedLRUMap(pMaxMemCacheSize); // size in bytes
+// contentCache = new TimeoutMap(backing, null, pDefaultCacheExpiryTime);
+ contentCache = new SizedLRUMap(pMaxMemCacheSize); // size in bytes
+ }
+ else {
+ contentCache = new NullMap();
+ }
+
+ entityCache = new LRUHashMap(pMaxCachedEntites);
+ varyCache = new LRUHashMap(pMaxCachedEntites);
+
+ deleteCacheOnExit = pDeleteCacheOnExit;
+
+ tempDir = pTempFolder;
+
+ logger = pLogger != null ? pLogger : Logger.getLogger(getClass().getName());
+ }
+
+ /**
+ * Creates an {@code HTTPCache}.
+ *
+ * @param pTempFolder the temp folder for this cache.
+ * @param pDefaultCacheExpiryTime Default expiry time for cached entities,
+ * {@code >= 0}
+ * @param pMaxMemCacheSize Maximum size of in-memory cache for content
+ * in bytes, {@code >= 0} ({@code 0} means no
+ * in-memory cache)
+ * @param pMaxCachedEntites Maximum number of entities in cache
+ * @param pDeleteCacheOnExit specifies wether the file cache should be
+ * deleted when the application or VM shuts down
+ * @throws IllegalArgumentException if {@code pName} or {@code pContext} is
+ * {@code null} or if any of {@code pDefaultCacheExpiryTime},
+ * {@code pMaxMemCacheSize} or {@code pMaxCachedEntites} are
+ * negative,
+ * or if the directory as given in the context attribute
+ * {@code "javax.servlet.context.tempdir"} does not exist, and
+ * cannot be created.
+ */
+ public HTTPCache(final File pTempFolder,
+ final long pDefaultCacheExpiryTime,
+ final int pMaxMemCacheSize, final int pMaxCachedEntites,
+ final boolean pDeleteCacheOnExit) {
+ this(pTempFolder, pDefaultCacheExpiryTime, pMaxMemCacheSize, pMaxCachedEntites, pDeleteCacheOnExit, null);
+ }
+
+
+ /**
+ * Creates an {@code HTTPCache}.
+ *
+ * @param pName Name of this cache (should be unique per application).
+ * Used for temp folder
+ * @param pContext Servlet context for the application.
+ * @param pDefaultCacheExpiryTime Default expiry time for cached entities,
+ * {@code >= 0}
+ * @param pMaxMemCacheSize Maximum size of in-memory cache for content
+ * in bytes, {@code >= 0} ({@code 0} means no
+ * in-memory cache)
+ * @param pMaxCachedEntites Maximum number of entities in cache
+ * @param pDeleteCacheOnExit specifies wether the file cache should be
+ * deleted when the application or VM shuts down
+ * @throws IllegalArgumentException if {@code pName} or {@code pContext} is
+ * {@code null} or if any of {@code pDefaultCacheExpiryTime},
+ * {@code pMaxMemCacheSize} or {@code pMaxCachedEntites} are
+ * negative,
+ * or if the directory as given in the context attribute
+ * {@code "javax.servlet.context.tempdir"} does not exist, and
+ * cannot be created.
+ * @deprecated Use {@link #HTTPCache(File, long, int, int, boolean)} instead.
+ */
+ public HTTPCache(final String pName, final ServletContext pContext,
+ final int pDefaultCacheExpiryTime, final int pMaxMemCacheSize,
+ final int pMaxCachedEntites, final boolean pDeleteCacheOnExit) {
+ this(
+ getTempFolder(pName, pContext),
+ pDefaultCacheExpiryTime, pMaxMemCacheSize, pMaxCachedEntites, pDeleteCacheOnExit,
+ new CacheFilter.ServletContextLoggerAdapter(pName, pContext)
+ );
+ }
+
+ private static File getTempFolder(String pName, ServletContext pContext) {
+ Validate.notNull(pName, "name");
+ Validate.isTrue(!StringUtil.isEmpty(pName), pName, "empty name: '%s'");
+ Validate.notNull(pContext, "context");
+
+ File tempRoot = (File) pContext.getAttribute("javax.servlet.context.tempdir");
+ if (tempRoot == null) {
+ throw new IllegalStateException("Missing context attribute \"javax.servlet.context.tempdir\"");
+ }
+
+ return new File(tempRoot, pName);
+ }
+
+ public String toString() {
+ StringBuilder buf = new StringBuilder(getClass().getSimpleName());
+ buf.append("[");
+ buf.append("Temp dir: ");
+ buf.append(tempDir.getAbsolutePath());
+ if (deleteCacheOnExit) {
+ buf.append(" (non-persistent)");
+ }
+ else {
+ buf.append(" (persistent)");
+ }
+ buf.append(", EntityCache: {");
+ buf.append(entityCache.size());
+ buf.append(" entries in a ");
+ buf.append(entityCache.getClass().getName());
+ buf.append("}, VaryCache: {");
+ buf.append(varyCache.size());
+ buf.append(" entries in a ");
+ buf.append(varyCache.getClass().getName());
+ buf.append("}, ContentCache: {");
+ buf.append(contentCache.size());
+ buf.append(" entries in a ");
+ buf.append(contentCache.getClass().getName());
+ buf.append("}]");
+
+ return buf.toString();
+ }
+
+ void log(final String pMessage) {
+ logger.log(Level.INFO, pMessage);
+ }
+
+ void log(final String pMessage, Throwable pException) {
+ logger.log(Level.WARNING, pMessage, pException);
+ }
+
+ /**
+ * Looks up the {@code CachedEntity} for the given request.
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ * @param pResolver the resolver
+ * @throws java.io.IOException if an I/O error occurs
+ * @throws CacheException if the cached entity can't be resolved for some reason
+ */
+ public void doCached(final CacheRequest pRequest, final CacheResponse pResponse, final ResponseResolver pResolver) throws IOException, CacheException {
+ // TODO: Expire cached items on PUT/POST/DELETE/PURGE
+ // If not cachable request, resolve directly
+ if (!isCacheable(pRequest)) {
+ pResolver.resolve(pRequest, pResponse);
+ }
+ else {
+ // Generate cacheURI
+ String cacheURI = generateCacheURI(pRequest);
+// System.out.println(" ## HTTPCache ## Request Id (cacheURI): " + cacheURI);
+
+ // Get/create cached entity
+ CachedEntity cached;
+ synchronized (entityCache) {
+ cached = entityCache.get(cacheURI);
+ if (cached == null) {
+ cached = new CachedEntityImpl(cacheURI, this);
+ entityCache.put(cacheURI, cached);
+ }
+ }
+
+ // else if (not cached || stale), resolve through wrapped (caching) response
+ // else render to response
+
+ // TODO: This is a bottleneck for uncachable resources. Should not
+ // synchronize, if we know (HOW?) the resource is not cachable.
+ synchronized (cached) {
+ if (cached.isStale(pRequest) /* TODO: NOT CACHED?! */) {
+ // Go fetch...
+ WritableCachedResponse cachedResponse = cached.createCachedResponse();
+ pResolver.resolve(pRequest, cachedResponse);
+
+ if (isCachable(cachedResponse)) {
+// System.out.println("Registering content: " + cachedResponse.getCachedResponse());
+ registerContent(cacheURI, pRequest, cachedResponse.getCachedResponse());
+ }
+ else {
+ // TODO: What about non-cachable responses? We need to either remove them from cache, or mark them as stale...
+ // Best is probably to mark as non-cacheable for later, and NOT store content (performance)
+// System.out.println("Non-cacheable response: " + cachedResponse);
+
+ // TODO: Write, but should really do this unbuffered.... And some resolver might be able to do just that?
+ // Might need a resolver.isWriteThroughForUncachableResources() method...
+ pResponse.setStatus(cachedResponse.getStatus());
+ cachedResponse.writeHeadersTo(pResponse);
+ cachedResponse.writeContentsTo(pResponse.getOutputStream());
+ return;
+ }
+ }
+ }
+
+ cached.render(pRequest, pResponse);
+ }
+ }
+
+ protected void invalidate(CacheRequest pRequest) {
+ // Generate cacheURI
+ String cacheURI = generateCacheURI(pRequest);
+
+ // Get/create cached entity
+ CachedEntity cached;
+ synchronized (entityCache) {
+ cached = entityCache.get(cacheURI);
+ if (cached != null) {
+ // TODO; Remove all variants
+ entityCache.remove(cacheURI);
+ }
+ }
+
+ }
+
+ private boolean isCacheable(final CacheRequest pRequest) {
+ // TODO: Support public/private cache (a cache probably have to be one of the two, when created)
+ // TODO: Only private caches should cache requests with Authorization
+
+ // TODO: OptimizeMe!
+ // It's probably best to cache the "cacheableness" of a request and a resource separately
+ List cacheControlValues = pRequest.getHeaders().get(HEADER_CACHE_CONTROL);
+ if (cacheControlValues != null) {
+ Map cacheControl = new HashMap();
+ for (String cc : cacheControlValues) {
+ List directives = Arrays.asList(cc.split(","));
+ for (String directive : directives) {
+ directive = directive.trim();
+ if (directive.length() > 0) {
+ String[] directiveParts = directive.split("=", 2);
+ cacheControl.put(directiveParts[0], directiveParts.length > 1 ? directiveParts[1] : null);
+ }
+ }
+ }
+
+ if (cacheControl.containsKey("no-cache") || cacheControl.containsKey("no-store")) {
+ return false;
+ }
+
+ /*
+ "no-cache" ; Section 14.9.1
+ | "no-store" ; Section 14.9.2
+ | "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4
+ | "max-stale" [ "=" delta-seconds ] ; Section 14.9.3
+ | "min-fresh" "=" delta-seconds ; Section 14.9.3
+ | "no-transform" ; Section 14.9.5
+ | "only-if-cached"
+ */
+ }
+
+ return true;
+ }
+
+ private boolean isCachable(final CacheResponse pResponse) {
+ if (pResponse.getStatus() != HttpServletResponse.SC_OK) {
+ return false;
+ }
+
+ // Vary: *
+ List values = pResponse.getHeaders().get(HTTPCache.HEADER_VARY);
+ if (values != null) {
+ for (String value : values) {
+ if ("*".equals(value)) {
+ return false;
+ }
+ }
+ }
+
+ // Cache-Control: no-cache, no-store, must-revalidate
+ values = pResponse.getHeaders().get(HTTPCache.HEADER_CACHE_CONTROL);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")
+ || StringUtil.contains(value, "no-store")
+ || StringUtil.contains(value, "must-revalidate")) {
+ return false;
+ }
+ }
+ }
+
+ // Pragma: no-cache
+ values = pResponse.getHeaders().get(HTTPCache.HEADER_PRAGMA);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Allows a server-side cache mechanism to peek at the real file.
+ * Default implementation return {@code null}.
+ *
+ * @param pRequest the request
+ * @return {@code null}, always
+ */
+ protected File getRealFile(final CacheRequest pRequest) {
+ // TODO: Create callback for this? Only possible for server-side cache... Maybe we can get away without this?
+ // For now: Default implementation that returns null
+ return null;
+/*
+ String contextRelativeURI = ServletUtil.getContextRelativeURI(pRequest);
+ // System.out.println(" ## HTTPCache ## Context relative URI: " + contextRelativeURI);
+
+ String path = mContext.getRealPath(contextRelativeURI);
+ // System.out.println(" ## HTTPCache ## Real path: " + path);
+
+ if (path != null) {
+ return new File(path);
+ }
+
+ return null;
+*/
+ }
+
+ private File getCachedFile(final String pCacheURI, final CacheRequest pRequest) {
+ File file = null;
+
+ // Get base dir
+ File base = new File(tempDir, "./" + pCacheURI);
+ final String basePath = base.getAbsolutePath();
+ File directory = base.getParentFile();
+
+ // Get list of files that are candidates
+ File[] candidates = directory.listFiles(new FileFilter() {
+ public boolean accept(File pFile) {
+ return pFile.getAbsolutePath().startsWith(basePath)
+ && !pFile.getName().endsWith(FILE_EXT_HEADERS)
+ && !pFile.getName().endsWith(FILE_EXT_VARY);
+ }
+ });
+
+ // Negotiation
+ if (candidates != null) {
+ String extension = getVaryExtension(pCacheURI, pRequest);
+ //System.out.println("-- Vary ext: " + extension);
+ if (extension != null) {
+ for (File candidate : candidates) {
+ //System.out.println("-- Candidate: " + candidates[i]);
+
+ if (extension.equals("ANY") || extension.equals(FileUtil.getExtension(candidate))) {
+ //System.out.println("-- Candidate selected");
+ file = candidate;
+ break;
+ }
+ }
+ }
+ }
+ else if (base.exists()) {
+ //System.out.println("-- File not a directory: " + directory);
+ log("File not a directory: " + directory);
+ }
+
+ return file;
+ }
+
+ private String getVaryExtension(final String pCacheURI, final CacheRequest pRequest) {
+ Properties variations = getVaryProperties(pCacheURI);
+
+ String[] varyHeaders = StringUtil.toStringArray(variations.getProperty(HEADER_VARY, ""));
+// System.out.println("-- Vary: \"" + variations.getProperty(HEADER_VARY) + "\"");
+
+ String varyKey = createVaryKey(varyHeaders, pRequest);
+// System.out.println("-- Vary key: \"" + varyKey + "\"");
+
+ // If no vary, just go with any version...
+ return StringUtil.isEmpty(varyKey) ? "ANY" : variations.getProperty(varyKey, null);
+ }
+
+ private String createVaryKey(final String[] pVaryHeaders, final CacheRequest pRequest) {
+ if (pVaryHeaders == null) {
+ return null;
+ }
+
+ StringBuilder headerValues = new StringBuilder();
+ for (String varyHeader : pVaryHeaders) {
+ List varies = pRequest.getHeaders().get(varyHeader);
+ String headerValue = varies != null && varies.size() > 0 ? varies.get(0) : null;
+
+ headerValues.append(varyHeader);
+ headerValues.append("__V_");
+ headerValues.append(createSafeHeader(headerValue));
+ }
+
+ return headerValues.toString();
+ }
+
+ private void storeVaryProperties(final String pCacheURI, final Properties pVariations) {
+ synchronized (pVariations) {
+ try {
+ File file = getVaryPropertiesFile(pCacheURI);
+ if (!file.exists() && deleteCacheOnExit) {
+ file.deleteOnExit();
+ }
+
+ FileOutputStream out = new FileOutputStream(file);
+ try {
+ pVariations.store(out, pCacheURI + " Vary info");
+ }
+ finally {
+ out.close();
+ }
+ }
+ catch (IOException ioe) {
+ log("Error: Could not store Vary info: " + ioe);
+ }
+ }
+ }
+
+ private Properties getVaryProperties(final String pCacheURI) {
+ Properties variations;
+
+ synchronized (varyCache) {
+ variations = varyCache.get(pCacheURI);
+ if (variations == null) {
+ variations = loadVaryProperties(pCacheURI);
+ varyCache.put(pCacheURI, variations);
+ }
+ }
+
+ return variations;
+ }
+
+ private Properties loadVaryProperties(final String pCacheURI) {
+ // Read Vary info, for content negotiation
+ Properties variations = new Properties();
+ File vary = getVaryPropertiesFile(pCacheURI);
+ if (vary.exists()) {
+ try {
+ FileInputStream in = new FileInputStream(vary);
+ try {
+ variations.load(in);
+ }
+ finally {
+ in.close();
+ }
+ }
+ catch (IOException ioe) {
+ log("Error: Could not load Vary info: " + ioe);
+ }
+ }
+ return variations;
+ }
+
+ private File getVaryPropertiesFile(final String pCacheURI) {
+ return new File(tempDir, "./" + pCacheURI + FILE_EXT_VARY);
+ }
+
+ private static String generateCacheURI(final CacheRequest pRequest) {
+ StringBuilder buffer = new StringBuilder();
+
+ // Note: As the '/'s are not replaced, the directory structure will be recreated
+ // TODO: Old mehtod relied on context relativization, that must now be handled byt the ServletCacheRequest
+// String contextRelativeURI = ServletUtil.getContextRelativeURI(pRequest);
+ String contextRelativeURI = pRequest.getRequestURI().getPath();
+ buffer.append(contextRelativeURI);
+
+ // Create directory for all resources
+ if (contextRelativeURI.charAt(contextRelativeURI.length() - 1) != '/') {
+ buffer.append('/');
+ }
+
+ // Get parameters from request, and recreate query to avoid unneccessary
+ // regeneration/caching when parameters are out of order
+ // Also makes caching work for POST
+ appendSortedRequestParams(pRequest, buffer);
+
+ return buffer.toString();
+ }
+
+ private static void appendSortedRequestParams(final CacheRequest pRequest, final StringBuilder pBuffer) {
+ Set names = pRequest.getParameters().keySet();
+ if (names.isEmpty()) {
+ pBuffer.append("defaultVersion");
+ return;
+ }
+
+ // We now have parameters
+ pBuffer.append('_'); // append '_' for '?', to avoid clash with default
+
+ // Create a sorted map
+ SortedMap> sortedQueryMap = new TreeMap>();
+ for (String name : names) {
+ List values = pRequest.getParameters().get(name);
+
+ sortedQueryMap.put(name, values);
+ }
+
+ // Iterate over sorted map, and append to stringbuffer
+ for (Iterator>> iterator = sortedQueryMap.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry> entry = iterator.next();
+ pBuffer.append(createSafe(entry.getKey()));
+
+ List values = entry.getValue();
+ if (values != null && values.size() > 0) {
+ pBuffer.append("_V"); // =
+ for (int i = 0; i < values.size(); i++) {
+ String value = values.get(i);
+ if (i != 0) {
+ pBuffer.append(',');
+ }
+ pBuffer.append(createSafe(value));
+ }
+ }
+
+ if (iterator.hasNext()) {
+ pBuffer.append("_P"); // &
+ }
+ }
+ }
+
+ private static String createSafe(final String pKey) {
+ return pKey.replace('/', '-')
+ .replace('&', '-') // In case they are encoded
+ .replace('#', '-')
+ .replace(';', '-');
+ }
+
+ private static String createSafeHeader(final String pHeaderValue) {
+ if (pHeaderValue == null) {
+ return "NULL";
+ }
+
+ return pHeaderValue.replace(' ', '_')
+ .replace(':', '_')
+ .replace('=', '_');
+ }
+
+ /**
+ * Registers content for the given URI in the cache.
+ *
+ * @param pCacheURI the cache URI
+ * @param pRequest the request
+ * @param pCachedResponse the cached response
+ * @throws IOException if the content could not be cached
+ */
+ void registerContent(
+ final String pCacheURI,
+ final CacheRequest pRequest,
+ final CachedResponse pCachedResponse
+ ) throws IOException {
+ // System.out.println(" ## HTTPCache ## Registering content for " + pCacheURI);
+
+// pRequest.removeAttribute(ATTRIB_IS_STALE);
+// pRequest.setAttribute(ATTRIB_CACHED_RESPONSE, pCachedResponse);
+
+ if ("HEAD".equals(pRequest.getMethod())) {
+ // System.out.println(" ## HTTPCache ## Was HEAD request, will NOT store content.");
+ return;
+ }
+
+ // TODO: Several resources may have same extension...
+ String extension = MIMEUtil.getExtension(pCachedResponse.getHeaderValue(HEADER_CONTENT_TYPE));
+ if (extension == null) {
+ extension = "[NULL]";
+ }
+
+ synchronized (contentCache) {
+ contentCache.put(pCacheURI + '.' + extension, pCachedResponse);
+
+ // This will be the default version
+ if (!contentCache.containsKey(pCacheURI)) {
+ contentCache.put(pCacheURI, pCachedResponse);
+ }
+ }
+
+ // Write the cached content to disk
+ File content = new File(tempDir, "./" + pCacheURI + '.' + extension);
+ if (deleteCacheOnExit && !content.exists()) {
+ content.deleteOnExit();
+ }
+
+ File parent = content.getParentFile();
+ if (!(parent.exists() || parent.mkdirs())) {
+ log("Could not create directory " + parent.getAbsolutePath());
+
+ // TODO: Make sure vary-info is still created in memory
+
+ return;
+ }
+
+ OutputStream mContentStream = new BufferedOutputStream(new FileOutputStream(content));
+
+ try {
+ pCachedResponse.writeContentsTo(mContentStream);
+ }
+ finally {
+ try {
+ mContentStream.close();
+ }
+ catch (IOException e) {
+ log("Error closing content stream: " + e.getMessage(), e);
+ }
+ }
+
+ // Write the cached headers to disk (in pseudo-properties-format)
+ File headers = new File(content.getAbsolutePath() + FILE_EXT_HEADERS);
+ if (deleteCacheOnExit && !headers.exists()) {
+ headers.deleteOnExit();
+ }
+
+ FileWriter writer = new FileWriter(headers);
+ PrintWriter headerWriter = new PrintWriter(writer);
+ try {
+ String[] names = pCachedResponse.getHeaderNames();
+
+ for (String name : names) {
+ String[] values = pCachedResponse.getHeaderValues(name);
+
+ headerWriter.print(name);
+ headerWriter.print(": ");
+ headerWriter.println(StringUtil.toCSVString(values, "\\"));
+ }
+ }
+ finally {
+ headerWriter.flush();
+ try {
+ writer.close();
+ }
+ catch (IOException e) {
+ log("Error closing header stream: " + e.getMessage(), e);
+ }
+ }
+
+ // TODO: Make this more robust, if some weird entity is not
+ // consistent in it's vary-headers..
+ // (sometimes Vary, sometimes not, or somtimes different Vary headers).
+
+ // Write extra Vary info to disk
+ String[] varyHeaders = pCachedResponse.getHeaderValues(HEADER_VARY);
+
+ // If no variations, then don't store vary info
+ if (varyHeaders != null && varyHeaders.length > 0) {
+ Properties variations = getVaryProperties(pCacheURI);
+
+ String vary = StringUtil.toCSVString(varyHeaders);
+ variations.setProperty(HEADER_VARY, vary);
+
+ // Create Vary-key and map to file extension...
+ String varyKey = createVaryKey(varyHeaders, pRequest);
+// System.out.println("varyKey: " + varyKey);
+// System.out.println("extension: " + extension);
+ variations.setProperty(varyKey, extension);
+
+ storeVaryProperties(pCacheURI, variations);
+ }
+ }
+
+ /**
+ * @param pCacheURI the cache URI
+ * @param pRequest the request
+ * @return a {@code CachedResponse} object
+ */
+ CachedResponse getContent(final String pCacheURI, final CacheRequest pRequest) {
+// System.err.println(" ## HTTPCache ## Looking up content for " + pCacheURI);
+// Thread.dumpStack();
+
+ String extension = getVaryExtension(pCacheURI, pRequest);
+
+ CachedResponse response;
+ synchronized (contentCache) {
+// System.out.println(" ## HTTPCache ## Looking up content with ext: \"" + extension + "\" from memory cache (" + contentCache /*.size()*/ + " entries)...");
+ if ("ANY".equals(extension)) {
+ response = contentCache.get(pCacheURI);
+ }
+ else {
+ response = contentCache.get(pCacheURI + '.' + extension);
+ }
+
+ if (response == null) {
+// System.out.println(" ## HTTPCache ## Content not found in memory cache.");
+//
+// System.out.println(" ## HTTPCache ## Looking up content from disk cache...");
+ // Read from disk-cache
+ response = readFromDiskCache(pCacheURI, pRequest);
+ }
+
+// if (response == null) {
+// System.out.println(" ## HTTPCache ## Content not found in disk cache.");
+// }
+// else {
+// System.out.println(" ## HTTPCache ## Content for " + pCacheURI + " found: " + response);
+// }
+ }
+
+ return response;
+ }
+
+ private CachedResponse readFromDiskCache(String pCacheURI, CacheRequest pRequest) {
+ CachedResponse response = null;
+ try {
+ File content = getCachedFile(pCacheURI, pRequest);
+ if (content != null && content.exists()) {
+ // Read contents
+ byte[] contents = FileUtil.read(content);
+
+ // Read headers
+ File headers = new File(content.getAbsolutePath() + FILE_EXT_HEADERS);
+ int headerSize = (int) headers.length();
+
+ BufferedReader reader = new BufferedReader(new FileReader(headers));
+ LinkedHashMap> headerMap = new LinkedHashMap>();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ int colIdx = line.indexOf(':');
+ String name;
+ String value;
+ if (colIdx >= 0) {
+ name = line.substring(0, colIdx);
+ value = line.substring(colIdx + 2); // ": "
+ }
+ else {
+ name = line;
+ value = "";
+ }
+
+ headerMap.put(name, Arrays.asList(StringUtil.toStringArray(value, "\\")));
+ }
+
+ response = new CachedResponseImpl(HttpServletResponse.SC_OK, headerMap, headerSize, contents);
+ contentCache.put(pCacheURI + '.' + FileUtil.getExtension(content), response);
+ }
+ }
+ catch (IOException e) {
+ log("Error reading from cache: " + e.getMessage(), e);
+ }
+ return response;
+ }
+
+ boolean isContentStale(final String pCacheURI, final CacheRequest pRequest) {
+ // NOTE: Content is either stale or not, for the duration of one request, unless re-fetched
+ // Means that we must retry after a registerContent(), if caching as request-attribute
+ Boolean stale;
+// stale = (Boolean) pRequest.getAttribute(ATTRIB_IS_STALE);
+// if (stale != null) {
+// return stale;
+// }
+
+ stale = isContentStaleImpl(pCacheURI, pRequest);
+// pRequest.setAttribute(ATTRIB_IS_STALE, stale);
+
+ return stale;
+ }
+
+ private boolean isContentStaleImpl(final String pCacheURI, final CacheRequest pRequest) {
+ CachedResponse response = getContent(pCacheURI, pRequest);
+
+ if (response == null) {
+ // System.out.println(" ## HTTPCache ## Content is stale (no content).");
+ return true;
+ }
+
+ // TODO: Get max-age=... from REQUEST too!
+
+ // TODO: What about time skew? Now should be (roughly) same as:
+ // long now = pRequest.getDateHeader("Date");
+ // TODO: If the time differs (server "now" vs client "now"), should we
+ // take that into consideration when testing for stale content?
+ // Probably, yes.
+ // TODO: Define rules for how to handle time skews
+
+ // Set timestamp check
+ // NOTE: HTTP Dates are always in GMT time zone
+ long now = (System.currentTimeMillis() / 1000L) * 1000L;
+ long expires = getDateHeader(response.getHeaderValue(HEADER_EXPIRES));
+ //long lastModified = getDateHeader(response, HEADER_LAST_MODIFIED);
+ long lastModified = getDateHeader(response.getHeaderValue(HEADER_CACHED_TIME));
+
+ // If expires header is not set, compute it
+ if (expires == -1L) {
+ /*
+ // Note: Not all content has Last-Modified header. We should then
+ // use lastModified() of the cached file, to compute expires time.
+ if (lastModified == -1L) {
+ File cached = getCachedFile(pCacheURI, pRequest);
+ if (cached != null && cached.exists()) {
+ lastModified = cached.lastModified();
+ //// System.out.println(" ## HTTPCache ## Last-Modified is " + HTTPUtil.formatHTTPDate(lastModified) + ", using cachedFile.lastModified()");
+ }
+ }
+ */
+
+ // If Cache-Control: max-age is present, use it, otherwise default
+ int maxAge = getIntHeader(response, HEADER_CACHE_CONTROL, "max-age");
+ if (maxAge == -1) {
+ expires = lastModified + defaultExpiryTime;
+ //// System.out.println(" ## HTTPCache ## Expires is " + HTTPUtil.formatHTTPDate(expires) + ", using lastModified + defaultExpiry");
+ }
+ else {
+ expires = lastModified + (maxAge * 1000L); // max-age is seconds
+ //// System.out.println(" ## HTTPCache ## Expires is " + HTTPUtil.formatHTTPDate(expires) + ", using lastModified + maxAge");
+ }
+ }
+ /*
+ else {
+ // System.out.println(" ## HTTPCache ## Expires header is " + response.getHeaderValue(HEADER_EXPIRES));
+ }
+ */
+
+ // Expired?
+ if (expires < now) {
+ // System.out.println(" ## HTTPCache ## Content is stale (content expired: "
+ // + HTTPUtil.formatHTTPDate(expires) + " before " + HTTPUtil.formatHTTPDate(now) + ").");
+ return true;
+ }
+
+ /*
+ if (lastModified == -1L) {
+ // Note: Not all content has Last-Modified header. We should then
+ // use lastModified() of the cached file, to compute expires time.
+ File cached = getCachedFile(pCacheURI, pRequest);
+ if (cached != null && cached.exists()) {
+ lastModified = cached.lastModified();
+ //// System.out.println(" ## HTTPCache ## Last-Modified is " + HTTPUtil.formatHTTPDate(lastModified) + ", using cachedFile.lastModified()");
+ }
+ }
+ */
+
+ // Get the real file for this request, if any
+ File real = getRealFile(pRequest);
+ //noinspection RedundantIfStatement
+ if (real != null && real.exists() && real.lastModified() > lastModified) {
+ // System.out.println(" ## HTTPCache ## Content is stale (new content"
+ // + HTTPUtil.formatHTTPDate(lastModified) + " before " + HTTPUtil.formatHTTPDate(real.lastModified()) + ").");
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Parses a cached header with directive to an int.
+ * E.g: Cache-Control: max-age=60, returns 60
+ *
+ * @param pCached the cached response
+ * @param pHeaderName the header name (e.g: {@code CacheControl})
+ * @param pDirective the directive (e.g: {@code max-age}
+ * @return the int value, or {@code -1} if not found
+ */
+ private int getIntHeader(final CachedResponse pCached, final String pHeaderName, final String pDirective) {
+ String[] headerValues = pCached.getHeaderValues(pHeaderName);
+ int value = -1;
+
+ if (headerValues != null) {
+ for (String headerValue : headerValues) {
+ if (pDirective == null) {
+ if (!StringUtil.isEmpty(headerValue)) {
+ value = Integer.parseInt(headerValue);
+ }
+ break;
+ }
+ else {
+ int start = headerValue.indexOf(pDirective);
+
+ // Directive found
+ if (start >= 0) {
+
+ int end = headerValue.lastIndexOf(',');
+ if (end < start) {
+ end = headerValue.length();
+ }
+
+ headerValue = headerValue.substring(start, end);
+
+ if (!StringUtil.isEmpty(headerValue)) {
+ value = Integer.parseInt(headerValue);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ return value;
+ }
+
+ /**
+ * Utility to read a date header from a cached response.
+ *
+ * @param pHeaderValue the header value
+ * @return the parsed date as a long, or {@code -1L} if not found
+ * @see javax.servlet.http.HttpServletRequest#getDateHeader(String)
+ */
+ static long getDateHeader(final String pHeaderValue) {
+ long date = -1L;
+ if (pHeaderValue != null) {
+ date = HTTPUtil.parseHTTPDate(pHeaderValue);
+ }
+ return date;
+ }
+
+ // TODO: Extract and make public?
+ final static class SizedLRUMap extends LRUHashMap {
+ int currentSize;
+ int maxSize;
+
+ public SizedLRUMap(int pMaxSize) {
+ //super(true);
+ super(); // Note: super.maxSize doesn't count...
+ maxSize = pMaxSize;
+ }
+
+
+ // In super (LRUMap?) this could just return 1...
+ protected int sizeOf(Object pValue) {
+ // HACK: As this is used as a backing for a TimeoutMap, the values
+ // will themselves be Entries...
+ while (pValue instanceof Map.Entry) {
+ pValue = ((Map.Entry) pValue).getValue();
+ }
+
+ CachedResponse cached = (CachedResponse) pValue;
+ return (cached != null ? cached.size() : 0);
+ }
+
+ @Override
+ public V put(K pKey, V pValue) {
+ currentSize += sizeOf(pValue);
+
+ V old = super.put(pKey, pValue);
+ if (old != null) {
+ currentSize -= sizeOf(old);
+ }
+ return old;
+ }
+
+ @Override
+ public V remove(Object pKey) {
+ V old = super.remove(pKey);
+ if (old != null) {
+ currentSize -= sizeOf(old);
+ }
+ return old;
+ }
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry pEldest) {
+ if (maxSize <= currentSize) { // NOTE: maxSize here is mem size
+ removeLRU();
+ }
+ return false;
+ }
+
+ @Override
+ public void removeLRU() {
+ while (maxSize <= currentSize) { // NOTE: maxSize here is mem size
+ super.removeLRU();
+ }
+ }
+ }
+
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java
index 1576cc38..f71f3d8b 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java
@@ -1,273 +1,273 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.net.HTTPUtil;
-import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletResponseWrapper;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-
-/**
- * CacheResponseWrapper class description.
- *
- * Based on ideas and code found in the ONJava article
- * Two
- * Servlet Filters Every Web Application Should Have
- * by Jayson Falkner.
- *
- * @author Jayson Falkner
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: SerlvetCacheResponseWrapper.java#2 $
- */
-class SerlvetCacheResponseWrapper extends HttpServletResponseWrapper {
- private ServletResponseStreamDelegate streamDelegate;
-
- private CacheResponse cacheResponse;
-
- private Boolean cacheable;
- private int status;
-
- public SerlvetCacheResponseWrapper(final HttpServletResponse pServletResponse, final CacheResponse pResponse) {
- super(pServletResponse);
- cacheResponse = pResponse;
- init();
- }
-
-
- /*
- NOTE: This class defers determining if a response is cacheable until the
- output stream is needed.
- This it the reason for the somewhat complicated logic in the add/setHeader
- methods below.
- */
- private void init() {
- cacheable = null;
- status = SC_OK;
- streamDelegate = new ServletResponseStreamDelegate(this) {
- protected OutputStream createOutputStream() throws IOException {
- // Test if this request is really cacheable, otherwise,
- // just write through to underlying response, and don't cache
- if (isCacheable()) {
- return cacheResponse.getOutputStream();
- }
- else {
- // TODO: We need to tell the cache about this, somehow...
- writeHeaders(cacheResponse, (HttpServletResponse) getResponse());
- return super.getOutputStream();
- }
- }
- };
- }
-
- private void writeHeaders(final CacheResponse pResponse, final HttpServletResponse pServletResponse) {
- Map> headers = pResponse.getHeaders();
- for (Map.Entry> header : headers.entrySet()) {
- for (int i = 0; i < header.getValue().size(); i++) {
- String value = header.getValue().get(i);
- if (i == 0) {
- pServletResponse.setHeader(header.getKey(), value);
- }
- else {
- pServletResponse.addHeader(header.getKey(), value);
- }
- }
- }
- }
-
- public boolean isCacheable() {
- // NOTE: Intentionally not synchronized
- if (cacheable == null) {
- cacheable = isCacheableImpl();
- }
-
- return cacheable;
- }
-
- private boolean isCacheableImpl() {
- // TODO: This code is duped in the cache...
- if (status != SC_OK) {
- return false;
- }
-
- // Vary: *
- List values = cacheResponse.getHeaders().get(HTTPCache.HEADER_VARY);
- if (values != null) {
- for (String value : values) {
- if ("*".equals(value)) {
- return false;
- }
- }
- }
-
- // Cache-Control: no-cache, no-store, must-revalidate
- values = cacheResponse.getHeaders().get(HTTPCache.HEADER_CACHE_CONTROL);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")
- || StringUtil.contains(value, "no-store")
- || StringUtil.contains(value, "must-revalidate")) {
- return false;
- }
- }
- }
-
- // Pragma: no-cache
- values = cacheResponse.getHeaders().get(HTTPCache.HEADER_PRAGMA);
- if (values != null) {
- for (String value : values) {
- if (StringUtil.contains(value, "no-cache")) {
- return false;
- }
- }
- }
-
- return true;
- }
-
- public void flushBuffer() throws IOException {
- streamDelegate.flushBuffer();
- }
-
- public void resetBuffer() {
- // Servlet 2.3
- streamDelegate.resetBuffer();
- }
-
- public void reset() {
- if (Boolean.FALSE.equals(cacheable)) {
- super.reset();
- }
- // No else, might be cacheable after all..
- init();
- }
-
- public ServletOutputStream getOutputStream() throws IOException {
- return streamDelegate.getOutputStream();
- }
-
- public PrintWriter getWriter() throws IOException {
- return streamDelegate.getWriter();
- }
-
- public boolean containsHeader(String name) {
- return cacheResponse.getHeaders().get(name) != null;
- }
-
- public void sendError(int pStatusCode, String msg) throws IOException {
- // NOT cacheable
- status = pStatusCode;
- super.sendError(pStatusCode, msg);
- }
-
- public void sendError(int pStatusCode) throws IOException {
- // NOT cacheable
- status = pStatusCode;
- super.sendError(pStatusCode);
- }
-
- public void setStatus(int pStatusCode, String sm) {
- // NOTE: This method is deprecated
- setStatus(pStatusCode);
- }
-
- public void setStatus(int pStatusCode) {
- // NOT cacheable unless pStatusCode == 200 (or a FEW others?)
- if (pStatusCode != SC_OK) {
- status = pStatusCode;
- super.setStatus(pStatusCode);
- }
- }
-
- public void sendRedirect(String pLocation) throws IOException {
- // NOT cacheable
- status = SC_MOVED_TEMPORARILY;
- super.sendRedirect(pLocation);
- }
-
- public void setDateHeader(String pName, long pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setDateHeader(pName, pValue);
- }
- cacheResponse.setHeader(pName, HTTPUtil.formatHTTPDate(pValue));
- }
-
- public void addDateHeader(String pName, long pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addDateHeader(pName, pValue);
- }
- cacheResponse.addHeader(pName, HTTPUtil.formatHTTPDate(pValue));
- }
-
- public void setHeader(String pName, String pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setHeader(pName, pValue);
- }
- cacheResponse.setHeader(pName, pValue);
- }
-
- public void addHeader(String pName, String pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addHeader(pName, pValue);
- }
- cacheResponse.addHeader(pName, pValue);
- }
-
- public void setIntHeader(String pName, int pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.setIntHeader(pName, pValue);
- }
- cacheResponse.setHeader(pName, String.valueOf(pValue));
- }
-
- public void addIntHeader(String pName, int pValue) {
- // If in write-trough-mode, set headers directly
- if (Boolean.FALSE.equals(cacheable)) {
- super.addIntHeader(pName, pValue);
- }
- cacheResponse.addHeader(pName, String.valueOf(pValue));
- }
-
- public final void setContentType(String type) {
- setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.net.HTTPUtil;
+import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CacheResponseWrapper class description.
+ *
+ * Based on ideas and code found in the ONJava article
+ * Two
+ * Servlet Filters Every Web Application Should Have
+ * by Jayson Falkner.
+ *
+ * @author Jayson Falkner
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: SerlvetCacheResponseWrapper.java#2 $
+ */
+class SerlvetCacheResponseWrapper extends HttpServletResponseWrapper {
+ private ServletResponseStreamDelegate streamDelegate;
+
+ private CacheResponse cacheResponse;
+
+ private Boolean cacheable;
+ private int status;
+
+ public SerlvetCacheResponseWrapper(final HttpServletResponse pServletResponse, final CacheResponse pResponse) {
+ super(pServletResponse);
+ cacheResponse = pResponse;
+ init();
+ }
+
+
+ /*
+ NOTE: This class defers determining if a response is cacheable until the
+ output stream is needed.
+ This it the reason for the somewhat complicated logic in the add/setHeader
+ methods below.
+ */
+ private void init() {
+ cacheable = null;
+ status = SC_OK;
+ streamDelegate = new ServletResponseStreamDelegate(this) {
+ protected OutputStream createOutputStream() throws IOException {
+ // Test if this request is really cacheable, otherwise,
+ // just write through to underlying response, and don't cache
+ if (isCacheable()) {
+ return cacheResponse.getOutputStream();
+ }
+ else {
+ // TODO: We need to tell the cache about this, somehow...
+ writeHeaders(cacheResponse, (HttpServletResponse) getResponse());
+ return super.getOutputStream();
+ }
+ }
+ };
+ }
+
+ private void writeHeaders(final CacheResponse pResponse, final HttpServletResponse pServletResponse) {
+ Map> headers = pResponse.getHeaders();
+ for (Map.Entry> header : headers.entrySet()) {
+ for (int i = 0; i < header.getValue().size(); i++) {
+ String value = header.getValue().get(i);
+ if (i == 0) {
+ pServletResponse.setHeader(header.getKey(), value);
+ }
+ else {
+ pServletResponse.addHeader(header.getKey(), value);
+ }
+ }
+ }
+ }
+
+ public boolean isCacheable() {
+ // NOTE: Intentionally not synchronized
+ if (cacheable == null) {
+ cacheable = isCacheableImpl();
+ }
+
+ return cacheable;
+ }
+
+ private boolean isCacheableImpl() {
+ // TODO: This code is duped in the cache...
+ if (status != SC_OK) {
+ return false;
+ }
+
+ // Vary: *
+ List values = cacheResponse.getHeaders().get(HTTPCache.HEADER_VARY);
+ if (values != null) {
+ for (String value : values) {
+ if ("*".equals(value)) {
+ return false;
+ }
+ }
+ }
+
+ // Cache-Control: no-cache, no-store, must-revalidate
+ values = cacheResponse.getHeaders().get(HTTPCache.HEADER_CACHE_CONTROL);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")
+ || StringUtil.contains(value, "no-store")
+ || StringUtil.contains(value, "must-revalidate")) {
+ return false;
+ }
+ }
+ }
+
+ // Pragma: no-cache
+ values = cacheResponse.getHeaders().get(HTTPCache.HEADER_PRAGMA);
+ if (values != null) {
+ for (String value : values) {
+ if (StringUtil.contains(value, "no-cache")) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public void flushBuffer() throws IOException {
+ streamDelegate.flushBuffer();
+ }
+
+ public void resetBuffer() {
+ // Servlet 2.3
+ streamDelegate.resetBuffer();
+ }
+
+ public void reset() {
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.reset();
+ }
+ // No else, might be cacheable after all..
+ init();
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ return streamDelegate.getOutputStream();
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return streamDelegate.getWriter();
+ }
+
+ public boolean containsHeader(String name) {
+ return cacheResponse.getHeaders().get(name) != null;
+ }
+
+ public void sendError(int pStatusCode, String msg) throws IOException {
+ // NOT cacheable
+ status = pStatusCode;
+ super.sendError(pStatusCode, msg);
+ }
+
+ public void sendError(int pStatusCode) throws IOException {
+ // NOT cacheable
+ status = pStatusCode;
+ super.sendError(pStatusCode);
+ }
+
+ public void setStatus(int pStatusCode, String sm) {
+ // NOTE: This method is deprecated
+ setStatus(pStatusCode);
+ }
+
+ public void setStatus(int pStatusCode) {
+ // NOT cacheable unless pStatusCode == 200 (or a FEW others?)
+ if (pStatusCode != SC_OK) {
+ status = pStatusCode;
+ super.setStatus(pStatusCode);
+ }
+ }
+
+ public void sendRedirect(String pLocation) throws IOException {
+ // NOT cacheable
+ status = SC_MOVED_TEMPORARILY;
+ super.sendRedirect(pLocation);
+ }
+
+ public void setDateHeader(String pName, long pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setDateHeader(pName, pValue);
+ }
+ cacheResponse.setHeader(pName, HTTPUtil.formatHTTPDate(pValue));
+ }
+
+ public void addDateHeader(String pName, long pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addDateHeader(pName, pValue);
+ }
+ cacheResponse.addHeader(pName, HTTPUtil.formatHTTPDate(pValue));
+ }
+
+ public void setHeader(String pName, String pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setHeader(pName, pValue);
+ }
+ cacheResponse.setHeader(pName, pValue);
+ }
+
+ public void addHeader(String pName, String pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addHeader(pName, pValue);
+ }
+ cacheResponse.addHeader(pName, pValue);
+ }
+
+ public void setIntHeader(String pName, int pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.setIntHeader(pName, pValue);
+ }
+ cacheResponse.setHeader(pName, String.valueOf(pValue));
+ }
+
+ public void addIntHeader(String pName, int pValue) {
+ // If in write-trough-mode, set headers directly
+ if (Boolean.FALSE.equals(cacheable)) {
+ super.addIntHeader(pName, pValue);
+ }
+ cacheResponse.addHeader(pName, String.valueOf(pValue));
+ }
+
+ public final void setContentType(String type) {
+ setHeader(HTTPCache.HEADER_CONTENT_TYPE, type);
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java
index e9b124de..ded04f68 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java
@@ -1,77 +1,77 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import java.io.OutputStream;
-
-/**
- * WritableCachedResponse
- *
- * @author Harald Kuhr
- * @version $Id: WritableCachedResponse.java#2 $
- */
-public interface WritableCachedResponse extends CachedResponse, CacheResponse {
- /**
- * Gets the {@code OutputStream} for this cached response.
- * This allows a client to write to the cached response.
- *
- * @return the {@code OutputStream} for this response.
- */
- OutputStream getOutputStream();
-
- /**
- * Sets a header key/value pair for this response.
- * Any prior header value for the given header key will be overwritten.
- *
- * @see #addHeader(String, String)
- *
- * @param pName the header name
- * @param pValue the header value
- */
- void setHeader(String pName, String pValue);
-
- /**
- * Adds a header key/value pair for this response.
- * If a value allready exists for the given key, the value will be appended.
- *
- * @see #setHeader(String, String)
- *
- * @param pName the header name
- * @param pValue the header value
- */
- void addHeader(String pName, String pValue);
-
- /**
- * Returns the final (immutable) {@code CachedResponse} created by this
- * {@code WritableCachedResponse}.
- *
- * @return the {@code CachedResponse}
- */
- CachedResponse getCachedResponse();
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import java.io.OutputStream;
+
+/**
+ * WritableCachedResponse
+ *
+ * @author Harald Kuhr
+ * @version $Id: WritableCachedResponse.java#2 $
+ */
+public interface WritableCachedResponse extends CachedResponse, CacheResponse {
+ /**
+ * Gets the {@code OutputStream} for this cached response.
+ * This allows a client to write to the cached response.
+ *
+ * @return the {@code OutputStream} for this response.
+ */
+ OutputStream getOutputStream();
+
+ /**
+ * Sets a header key/value pair for this response.
+ * Any prior header value for the given header key will be overwritten.
+ *
+ * @see #addHeader(String, String)
+ *
+ * @param pName the header name
+ * @param pValue the header value
+ */
+ void setHeader(String pName, String pValue);
+
+ /**
+ * Adds a header key/value pair for this response.
+ * If a value allready exists for the given key, the value will be appended.
+ *
+ * @see #setHeader(String, String)
+ *
+ * @param pName the header name
+ * @param pValue the header value
+ */
+ void addHeader(String pName, String pValue);
+
+ /**
+ * Returns the final (immutable) {@code CachedResponse} created by this
+ * {@code WritableCachedResponse}.
+ *
+ * @return the {@code CachedResponse}
+ */
+ CachedResponse getCachedResponse();
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java
index 19085b9c..c73ebf99 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java
@@ -1,186 +1,186 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.cache;
-
-import com.twelvemonkeys.io.FastByteArrayOutputStream;
-import com.twelvemonkeys.net.HTTPUtil;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * WritableCachedResponseImpl
- *
- * @author Harald Kuhr
- * @version $Id: WritableCachedResponseImpl.java#3 $
- */
-class WritableCachedResponseImpl implements WritableCachedResponse {
- private final CachedResponseImpl cachedResponse;
-
- /**
- * Creates a {@code WritableCachedResponseImpl}.
- */
- protected WritableCachedResponseImpl() {
- cachedResponse = new CachedResponseImpl();
- // Hmmm..
- setHeader(HTTPCache.HEADER_CACHED_TIME, HTTPUtil.formatHTTPDate(System.currentTimeMillis()));
- }
-
- public CachedResponse getCachedResponse() {
- return cachedResponse;
- }
-
- public void setHeader(String pName, String pValue) {
- setHeader(pName, pValue, false);
- }
-
- public void addHeader(String pName, String pValue) {
- setHeader(pName, pValue, true);
- }
-
- public Map> getHeaders() {
- return cachedResponse.headers;
- }
-
- /**
- *
- * @param pName the header name
- * @param pValue the new header value
- * @param pAdd {@code true} if the value should add to the list of values, not replace existing value
- */
- private void setHeader(String pName, String pValue, boolean pAdd) {
- // System.out.println(" ++ CachedResponse ++ " + (pAdd ? "addHeader(" : "setHeader(") + pName + ", " + pValue + ")");
- // If adding, get list and append, otherwise replace list
- List values = pAdd ? cachedResponse.headers.get(pName) : null;
-
- if (values == null) {
- values = new ArrayList();
-
- if (pAdd) {
- // Add length of pName
- cachedResponse.headersSize += (pName != null ? pName.length() : 0);
- }
- else {
- // Remove length of potential replaced old values + pName
- String[] oldValues = getHeaderValues(pName);
-
- if (oldValues != null) {
- for (String oldValue : oldValues) {
- cachedResponse.headersSize -= oldValue.length();
- }
- }
- else {
- cachedResponse.headersSize += (pName != null ? pName.length() : 0);
- }
- }
- }
-
- // Add value, if not null
- if (pValue != null) {
- values.add(pValue);
-
- // Add length of pValue
- cachedResponse.headersSize += pValue.length();
- }
-
- // Always add to headers
- cachedResponse.headers.put(pName, values);
- }
-
- public OutputStream getOutputStream() {
- // TODO: Hmm.. Smells like DCL..?
- if (cachedResponse.content == null) {
- createOutputStream();
- }
- return cachedResponse.content;
- }
-
- public void setStatus(int pStatusCode) {
- cachedResponse.status = pStatusCode;
- }
-
- public int getStatus() {
- return cachedResponse.getStatus();
- }
-
- private synchronized void createOutputStream() {
- ByteArrayOutputStream cache = cachedResponse.content;
- if (cache == null) {
- String contentLengthStr = getHeaderValue("Content-Length");
- if (contentLengthStr != null) {
- int contentLength = Integer.parseInt(contentLengthStr);
- cache = new FastByteArrayOutputStream(contentLength);
- }
- else {
- cache = new FastByteArrayOutputStream(1024);
- }
- cachedResponse.content = cache;
- }
- }
-
- public void writeHeadersTo(CacheResponse pResponse) {
- cachedResponse.writeHeadersTo(pResponse);
- }
-
- public void writeContentsTo(OutputStream pStream) throws IOException {
- cachedResponse.writeContentsTo(pStream);
- }
-
- public String[] getHeaderNames() {
- return cachedResponse.getHeaderNames();
- }
-
- public String[] getHeaderValues(String pHeaderName) {
- return cachedResponse.getHeaderValues(pHeaderName);
- }
-
- public String getHeaderValue(String pHeaderName) {
- return cachedResponse.getHeaderValue(pHeaderName);
- }
-
- public int size() {
- return cachedResponse.size();
- }
-
- public boolean equals(Object pOther) {
- if (pOther instanceof WritableCachedResponse) {
- // Take advantage of faster implementation
- return cachedResponse.equals(((WritableCachedResponse) pOther).getCachedResponse());
- }
- return cachedResponse.equals(pOther);
- }
-
- public int hashCode() {
- return cachedResponse.hashCode();
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.cache;
+
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.net.HTTPUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * WritableCachedResponseImpl
+ *
+ * @author Harald Kuhr
+ * @version $Id: WritableCachedResponseImpl.java#3 $
+ */
+class WritableCachedResponseImpl implements WritableCachedResponse {
+ private final CachedResponseImpl cachedResponse;
+
+ /**
+ * Creates a {@code WritableCachedResponseImpl}.
+ */
+ protected WritableCachedResponseImpl() {
+ cachedResponse = new CachedResponseImpl();
+ // Hmmm..
+ setHeader(HTTPCache.HEADER_CACHED_TIME, HTTPUtil.formatHTTPDate(System.currentTimeMillis()));
+ }
+
+ public CachedResponse getCachedResponse() {
+ return cachedResponse;
+ }
+
+ public void setHeader(String pName, String pValue) {
+ setHeader(pName, pValue, false);
+ }
+
+ public void addHeader(String pName, String pValue) {
+ setHeader(pName, pValue, true);
+ }
+
+ public Map> getHeaders() {
+ return cachedResponse.headers;
+ }
+
+ /**
+ *
+ * @param pName the header name
+ * @param pValue the new header value
+ * @param pAdd {@code true} if the value should add to the list of values, not replace existing value
+ */
+ private void setHeader(String pName, String pValue, boolean pAdd) {
+ // System.out.println(" ++ CachedResponse ++ " + (pAdd ? "addHeader(" : "setHeader(") + pName + ", " + pValue + ")");
+ // If adding, get list and append, otherwise replace list
+ List values = pAdd ? cachedResponse.headers.get(pName) : null;
+
+ if (values == null) {
+ values = new ArrayList();
+
+ if (pAdd) {
+ // Add length of pName
+ cachedResponse.headersSize += (pName != null ? pName.length() : 0);
+ }
+ else {
+ // Remove length of potential replaced old values + pName
+ String[] oldValues = getHeaderValues(pName);
+
+ if (oldValues != null) {
+ for (String oldValue : oldValues) {
+ cachedResponse.headersSize -= oldValue.length();
+ }
+ }
+ else {
+ cachedResponse.headersSize += (pName != null ? pName.length() : 0);
+ }
+ }
+ }
+
+ // Add value, if not null
+ if (pValue != null) {
+ values.add(pValue);
+
+ // Add length of pValue
+ cachedResponse.headersSize += pValue.length();
+ }
+
+ // Always add to headers
+ cachedResponse.headers.put(pName, values);
+ }
+
+ public OutputStream getOutputStream() {
+ // TODO: Hmm.. Smells like DCL..?
+ if (cachedResponse.content == null) {
+ createOutputStream();
+ }
+ return cachedResponse.content;
+ }
+
+ public void setStatus(int pStatusCode) {
+ cachedResponse.status = pStatusCode;
+ }
+
+ public int getStatus() {
+ return cachedResponse.getStatus();
+ }
+
+ private synchronized void createOutputStream() {
+ ByteArrayOutputStream cache = cachedResponse.content;
+ if (cache == null) {
+ String contentLengthStr = getHeaderValue("Content-Length");
+ if (contentLengthStr != null) {
+ int contentLength = Integer.parseInt(contentLengthStr);
+ cache = new FastByteArrayOutputStream(contentLength);
+ }
+ else {
+ cache = new FastByteArrayOutputStream(1024);
+ }
+ cachedResponse.content = cache;
+ }
+ }
+
+ public void writeHeadersTo(CacheResponse pResponse) {
+ cachedResponse.writeHeadersTo(pResponse);
+ }
+
+ public void writeContentsTo(OutputStream pStream) throws IOException {
+ cachedResponse.writeContentsTo(pStream);
+ }
+
+ public String[] getHeaderNames() {
+ return cachedResponse.getHeaderNames();
+ }
+
+ public String[] getHeaderValues(String pHeaderName) {
+ return cachedResponse.getHeaderValues(pHeaderName);
+ }
+
+ public String getHeaderValue(String pHeaderName) {
+ return cachedResponse.getHeaderValue(pHeaderName);
+ }
+
+ public int size() {
+ return cachedResponse.size();
+ }
+
+ public boolean equals(Object pOther) {
+ if (pOther instanceof WritableCachedResponse) {
+ // Take advantage of faster implementation
+ return cachedResponse.equals(((WritableCachedResponse) pOther).getCachedResponse());
+ }
+ return cachedResponse.equals(pOther);
+ }
+
+ public int hashCode() {
+ return cachedResponse.hashCode();
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java
index 4195692d..ca5642ab 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java
@@ -1,42 +1,42 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-/**
- * FileSizeExceededException
- *
- *
- * @author Harald Kuhr
- * @version $Id: FileSizeExceededException.java#1 $
- */
-public class FileSizeExceededException extends FileUploadException {
- public FileSizeExceededException(Throwable pCause) {
- super(pCause.getMessage(), pCause);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+/**
+ * FileSizeExceededException
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: FileSizeExceededException.java#1 $
+ */
+public class FileSizeExceededException extends FileUploadException {
+ public FileSizeExceededException(Throwable pCause) {
+ super(pCause.getMessage(), pCause);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java
index 9048b854..57dc89e3 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java
@@ -1,52 +1,52 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import javax.servlet.ServletException;
-
-/**
- * FileUploadException
- *
- *
- * @author Harald Kuhr
- * @version $Id: FileUploadException.java#1 $
- */
-public class FileUploadException extends ServletException {
- public FileUploadException(String pMessage) {
- super(pMessage);
- }
-
- public FileUploadException(String pMessage, Throwable pCause) {
- super(pMessage, pCause);
- }
-
- public FileUploadException(Throwable pCause) {
- super(pCause.getMessage(), pCause);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import javax.servlet.ServletException;
+
+/**
+ * FileUploadException
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: FileUploadException.java#1 $
+ */
+public class FileUploadException extends ServletException {
+ public FileUploadException(String pMessage) {
+ super(pMessage);
+ }
+
+ public FileUploadException(String pMessage, Throwable pCause) {
+ super(pMessage, pCause);
+ }
+
+ public FileUploadException(Throwable pCause) {
+ super(pCause.getMessage(), pCause);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java
index 96489fa1..a891ac63 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java
@@ -1,125 +1,125 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import com.twelvemonkeys.servlet.GenericFilter;
-import com.twelvemonkeys.servlet.ServletUtil;
-import com.twelvemonkeys.io.FileUtil;
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.io.File;
-import java.net.URL;
-import java.net.MalformedURLException;
-
-/**
- * A servlet {@code Filter} for processing HTTP file upload requests, as
- * specified by
- * Form-based File Upload in HTML (RFC1867).
- *
- * @see HttpFileUploadRequest
- *
- * @author Harald Kuhr
- * @version $Id: FileUploadFilter.java#1 $
- */
-public class FileUploadFilter extends GenericFilter {
- private File uploadDir;
- private long maxFileSize = 1024 * 1024; // 1 MByte
-
- /**
- * This method is called by the server before the filter goes into service,
- * and here it determines the file upload directory.
- *
- * @throws ServletException
- */
- public void init() throws ServletException {
- // Get the name of the upload directory.
- String uploadDirParam = getInitParameter("uploadDir");
-
- if (!StringUtil.isEmpty(uploadDirParam)) {
- try {
- URL uploadDirURL = getServletContext().getResource(uploadDirParam);
- uploadDir = FileUtil.toFile(uploadDirURL);
- }
- catch (MalformedURLException e) {
- throw new ServletException(e.getMessage(), e);
- }
- }
-
- if (uploadDir == null) {
- uploadDir = ServletUtil.getTempDir(getServletContext());
- }
- }
-
- /**
- * Sets max filesize allowed for upload.
- *
- *
- * @param pMaxSize
- */
- public void setMaxFileSize(long pMaxSize) {
- log("maxFileSize=" + pMaxSize);
- maxFileSize = pMaxSize;
- }
-
- /**
- * Examines the request content type, and if it is a
- * {@code multipart/*} request, wraps the request with a
- * {@code HttpFileUploadRequest}.
- *
- * @param pRequest The servlet request
- * @param pResponse The servlet response
- * @param pChain The filter chain
- *
- * @throws ServletException
- * @throws IOException
- */
- public void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- HttpServletRequest request = (HttpServletRequest) pRequest;
-
- // Get the content type from the request
- String contentType = request.getContentType();
-
- // If the content type is multipart, wrap
- if (isMultipartFileUpload(contentType)) {
- pRequest = new HttpFileUploadRequestWrapper(request, uploadDir, maxFileSize);
- }
-
- pChain.doFilter(pRequest, pResponse);
- }
-
- private boolean isMultipartFileUpload(String pContentType) {
- return pContentType != null && pContentType.startsWith("multipart/");
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import com.twelvemonkeys.servlet.GenericFilter;
+import com.twelvemonkeys.servlet.ServletUtil;
+import com.twelvemonkeys.io.FileUtil;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
+import java.io.File;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+/**
+ * A servlet {@code Filter} for processing HTTP file upload requests, as
+ * specified by
+ * Form-based File Upload in HTML (RFC1867).
+ *
+ * @see HttpFileUploadRequest
+ *
+ * @author Harald Kuhr
+ * @version $Id: FileUploadFilter.java#1 $
+ */
+public class FileUploadFilter extends GenericFilter {
+ private File uploadDir;
+ private long maxFileSize = 1024 * 1024; // 1 MByte
+
+ /**
+ * This method is called by the server before the filter goes into service,
+ * and here it determines the file upload directory.
+ *
+ * @throws ServletException
+ */
+ public void init() throws ServletException {
+ // Get the name of the upload directory.
+ String uploadDirParam = getInitParameter("uploadDir");
+
+ if (!StringUtil.isEmpty(uploadDirParam)) {
+ try {
+ URL uploadDirURL = getServletContext().getResource(uploadDirParam);
+ uploadDir = FileUtil.toFile(uploadDirURL);
+ }
+ catch (MalformedURLException e) {
+ throw new ServletException(e.getMessage(), e);
+ }
+ }
+
+ if (uploadDir == null) {
+ uploadDir = ServletUtil.getTempDir(getServletContext());
+ }
+ }
+
+ /**
+ * Sets max filesize allowed for upload.
+ *
+ *
+ * @param pMaxSize
+ */
+ public void setMaxFileSize(long pMaxSize) {
+ log("maxFileSize=" + pMaxSize);
+ maxFileSize = pMaxSize;
+ }
+
+ /**
+ * Examines the request content type, and if it is a
+ * {@code multipart/*} request, wraps the request with a
+ * {@code HttpFileUploadRequest}.
+ *
+ * @param pRequest The servlet request
+ * @param pResponse The servlet response
+ * @param pChain The filter chain
+ *
+ * @throws ServletException
+ * @throws IOException
+ */
+ public void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ HttpServletRequest request = (HttpServletRequest) pRequest;
+
+ // Get the content type from the request
+ String contentType = request.getContentType();
+
+ // If the content type is multipart, wrap
+ if (isMultipartFileUpload(contentType)) {
+ pRequest = new HttpFileUploadRequestWrapper(request, uploadDir, maxFileSize);
+ }
+
+ pChain.doFilter(pRequest, pResponse);
+ }
+
+ private boolean isMultipartFileUpload(String pContentType) {
+ return pContentType != null && pContentType.startsWith("multipart/");
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java
index 64c27ef7..32d58cbd 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java
@@ -1,63 +1,63 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import javax.servlet.http.HttpServletRequest;
-
-/**
- * This interface represents an HTTP file upload request, as specified by
- * Form-based File Upload in HTML (RFC1867).
- *
- * @author Harald Kuhr
- * @version $Id: HttpFileUploadRequest.java#1 $
- */
-public interface HttpFileUploadRequest extends HttpServletRequest {
- /**
- * Returns the value of a request parameter as an {@code UploadedFile},
- * or {@code null} if the parameter does not exist.
- * You should only use this method when you are sure the parameter has only
- * one value.
- *
- * @param pName the name of the requested parameter
- * @return a {@code UoploadedFile} or {@code null}
- *
- * @see #getUploadedFiles(String)
- */
- UploadedFile getUploadedFile(String pName);
-
- /**
- * Returns an array of {@code UploadedFile} objects containing all the
- * values for the given request parameter,
- * or {@code null} if the parameter does not exist.
- *
- * @param pName the name of the requested parameter
- * @return an array of {@code UoploadedFile}s or {@code null}
- */
- UploadedFile[] getUploadedFiles(String pName);
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * This interface represents an HTTP file upload request, as specified by
+ * Form-based File Upload in HTML (RFC1867).
+ *
+ * @author Harald Kuhr
+ * @version $Id: HttpFileUploadRequest.java#1 $
+ */
+public interface HttpFileUploadRequest extends HttpServletRequest {
+ /**
+ * Returns the value of a request parameter as an {@code UploadedFile},
+ * or {@code null} if the parameter does not exist.
+ * You should only use this method when you are sure the parameter has only
+ * one value.
+ *
+ * @param pName the name of the requested parameter
+ * @return a {@code UoploadedFile} or {@code null}
+ *
+ * @see #getUploadedFiles(String)
+ */
+ UploadedFile getUploadedFile(String pName);
+
+ /**
+ * Returns an array of {@code UploadedFile} objects containing all the
+ * values for the given request parameter,
+ * or {@code null} if the parameter does not exist.
+ *
+ * @param pName the name of the requested parameter
+ * @return an array of {@code UoploadedFile}s or {@code null}
+ */
+ UploadedFile[] getUploadedFiles(String pName);
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java
index d3b5adb8..60080507 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java
@@ -1,154 +1,154 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import org.apache.commons.fileupload.*;
-import org.apache.commons.fileupload.servlet.ServletRequestContext;
-import org.apache.commons.fileupload.disk.DiskFileItemFactory;
-
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.ServletException;
-import java.io.File;
-import java.util.*;
-
-/**
- * An {@code HttpFileUploadRequest} implementation, based on
- * Jakarta Commons FileUpload.
- *
- * @author Harald Kuhr
- * @version $Id: HttpFileUploadRequestWrapper.java#1 $
- */
-class HttpFileUploadRequestWrapper extends HttpServletRequestWrapper implements HttpFileUploadRequest {
-
- private final Map parameters = new HashMap();
- private final Map files = new HashMap();
-
- public HttpFileUploadRequestWrapper(HttpServletRequest pRequest, File pUploadDir, long pMaxSize) throws ServletException {
- super(pRequest);
-
- DiskFileItemFactory factory = new DiskFileItemFactory(
- 128 * 1024, // 128 KByte
- new File(pUploadDir.getAbsolutePath())
- );
- FileUpload upload = new FileUpload(factory);
- upload.setSizeMax(pMaxSize);
-
- // TODO: Defer request parsing??
- try {
- //noinspection unchecked
- List items = upload.parseRequest(new ServletRequestContext(pRequest));
- for (FileItem item : items) {
- if (item.isFormField()) {
- processFormField(item.getFieldName(), item.getString());
- }
- else {
- processeFile(item);
- }
- }
- }
- catch (FileUploadBase.SizeLimitExceededException e) {
- throw new FileSizeExceededException(e);
- }
- catch (org.apache.commons.fileupload.FileUploadException e) {
- throw new FileUploadException(e);
- }
- }
-
- private void processeFile(final FileItem pItem) {
- UploadedFile value = new UploadedFileImpl(pItem);
- String name = pItem.getFieldName();
-
- UploadedFile[] values;
- UploadedFile[] oldValues = files.get(name);
-
- if (oldValues != null) {
- values = new UploadedFile[oldValues.length + 1];
- System.arraycopy(oldValues, 0, values, 0, oldValues.length);
- values[oldValues.length] = value;
- }
- else {
- values = new UploadedFile[] {value};
- }
-
- files.put(name, values);
-
- // Also add to normal fields
- processFormField(name, value.getName());
- }
-
- private void processFormField(String pName, String pValue) {
- // Multiple parameter values are not that common, so it's
- // probably faster to just use arrays...
- // TODO: Research and document...
- String[] values;
- String[] oldValues = parameters.get(pName);
-
- if (oldValues != null) {
- values = new String[oldValues.length + 1];
- System.arraycopy(oldValues, 0, values, 0, oldValues.length);
- values[oldValues.length] = pValue;
- }
- else {
- values = new String[] {pValue};
- }
-
- parameters.put(pName, values);
- }
-
- public Map getParameterMap() {
- // TODO: The spec dicates immutable map, but what about the value arrays?!
- // Probably just leave as-is, for performance
- return Collections.unmodifiableMap(parameters);
- }
-
- public Enumeration getParameterNames() {
- return Collections.enumeration(parameters.keySet());
- }
-
- public String getParameter(String pString) {
- String[] values = getParameterValues(pString);
- return values != null ? values[0] : null;
- }
-
- public String[] getParameterValues(String pString) {
- // TODO: Optimize?
- return parameters.get(pString).clone();
- }
-
- public UploadedFile getUploadedFile(String pName) {
- UploadedFile[] files = getUploadedFiles(pName);
- return files != null ? files[0] : null;
- }
-
- public UploadedFile[] getUploadedFiles(String pName) {
- // TODO: Optimize?
- return files.get(pName).clone();
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import org.apache.commons.fileupload.*;
+import org.apache.commons.fileupload.servlet.ServletRequestContext;
+import org.apache.commons.fileupload.disk.DiskFileItemFactory;
+
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.ServletException;
+import java.io.File;
+import java.util.*;
+
+/**
+ * An {@code HttpFileUploadRequest} implementation, based on
+ * Jakarta Commons FileUpload.
+ *
+ * @author Harald Kuhr
+ * @version $Id: HttpFileUploadRequestWrapper.java#1 $
+ */
+class HttpFileUploadRequestWrapper extends HttpServletRequestWrapper implements HttpFileUploadRequest {
+
+ private final Map parameters = new HashMap();
+ private final Map files = new HashMap();
+
+ public HttpFileUploadRequestWrapper(HttpServletRequest pRequest, File pUploadDir, long pMaxSize) throws ServletException {
+ super(pRequest);
+
+ DiskFileItemFactory factory = new DiskFileItemFactory(
+ 128 * 1024, // 128 KByte
+ new File(pUploadDir.getAbsolutePath())
+ );
+ FileUpload upload = new FileUpload(factory);
+ upload.setSizeMax(pMaxSize);
+
+ // TODO: Defer request parsing??
+ try {
+ //noinspection unchecked
+ List items = upload.parseRequest(new ServletRequestContext(pRequest));
+ for (FileItem item : items) {
+ if (item.isFormField()) {
+ processFormField(item.getFieldName(), item.getString());
+ }
+ else {
+ processeFile(item);
+ }
+ }
+ }
+ catch (FileUploadBase.SizeLimitExceededException e) {
+ throw new FileSizeExceededException(e);
+ }
+ catch (org.apache.commons.fileupload.FileUploadException e) {
+ throw new FileUploadException(e);
+ }
+ }
+
+ private void processeFile(final FileItem pItem) {
+ UploadedFile value = new UploadedFileImpl(pItem);
+ String name = pItem.getFieldName();
+
+ UploadedFile[] values;
+ UploadedFile[] oldValues = files.get(name);
+
+ if (oldValues != null) {
+ values = new UploadedFile[oldValues.length + 1];
+ System.arraycopy(oldValues, 0, values, 0, oldValues.length);
+ values[oldValues.length] = value;
+ }
+ else {
+ values = new UploadedFile[] {value};
+ }
+
+ files.put(name, values);
+
+ // Also add to normal fields
+ processFormField(name, value.getName());
+ }
+
+ private void processFormField(String pName, String pValue) {
+ // Multiple parameter values are not that common, so it's
+ // probably faster to just use arrays...
+ // TODO: Research and document...
+ String[] values;
+ String[] oldValues = parameters.get(pName);
+
+ if (oldValues != null) {
+ values = new String[oldValues.length + 1];
+ System.arraycopy(oldValues, 0, values, 0, oldValues.length);
+ values[oldValues.length] = pValue;
+ }
+ else {
+ values = new String[] {pValue};
+ }
+
+ parameters.put(pName, values);
+ }
+
+ public Map getParameterMap() {
+ // TODO: The spec dicates immutable map, but what about the value arrays?!
+ // Probably just leave as-is, for performance
+ return Collections.unmodifiableMap(parameters);
+ }
+
+ public Enumeration getParameterNames() {
+ return Collections.enumeration(parameters.keySet());
+ }
+
+ public String getParameter(String pString) {
+ String[] values = getParameterValues(pString);
+ return values != null ? values[0] : null;
+ }
+
+ public String[] getParameterValues(String pString) {
+ // TODO: Optimize?
+ return parameters.get(pString).clone();
+ }
+
+ public UploadedFile getUploadedFile(String pName) {
+ UploadedFile[] files = getUploadedFiles(pName);
+ return files != null ? files[0] : null;
+ }
+
+ public UploadedFile[] getUploadedFiles(String pName) {
+ // TODO: Optimize?
+ return files.get(pName).clone();
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java
index 6eed9409..508f12f2 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java
@@ -1,86 +1,86 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import java.io.File;
-import java.io.InputStream;
-import java.io.IOException;
-
-/**
- * This class represents an uploaded file.
- *
- * @author Harald Kuhr
- * @version $Id: UploadedFile.java#1 $
- */
-public interface UploadedFile {
- /**
- * Returns the length of file, in bytes.
- *
- * @return length of file
- */
- long length();
-
- /**
- * Returns the original file name (from client).
- *
- * @return original name
- */
- String getName();
-
- /**
- * Returns the content type of the file.
- *
- * @return the content type
- */
- String getContentType();
-
- /**
- * Returns the file data, as an {@code InputStream}.
- * The file data may be read from disk, or from an in-memory source,
- * depending on implementation.
- *
- * @return an {@code InputStream} containing the file data
- * @throws IOException
- * @throws RuntimeException
- */
- InputStream getInputStream() throws IOException;
-
- /**
- * Writes the file data to the given {@code File}.
- * Note that implementations are free to optimize this to a rename
- * operation, if the file is allready cached to disk.
- *
- * @param pFile the {@code File} (file name) to write to.
- * @throws IOException
- * @throws RuntimeException
- */
- void writeTo(File pFile) throws IOException;
-
- // TODO: void delete()?
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This class represents an uploaded file.
+ *
+ * @author Harald Kuhr
+ * @version $Id: UploadedFile.java#1 $
+ */
+public interface UploadedFile {
+ /**
+ * Returns the length of file, in bytes.
+ *
+ * @return length of file
+ */
+ long length();
+
+ /**
+ * Returns the original file name (from client).
+ *
+ * @return original name
+ */
+ String getName();
+
+ /**
+ * Returns the content type of the file.
+ *
+ * @return the content type
+ */
+ String getContentType();
+
+ /**
+ * Returns the file data, as an {@code InputStream}.
+ * The file data may be read from disk, or from an in-memory source,
+ * depending on implementation.
+ *
+ * @return an {@code InputStream} containing the file data
+ * @throws IOException
+ * @throws RuntimeException
+ */
+ InputStream getInputStream() throws IOException;
+
+ /**
+ * Writes the file data to the given {@code File}.
+ * Note that implementations are free to optimize this to a rename
+ * operation, if the file is allready cached to disk.
+ *
+ * @param pFile the {@code File} (file name) to write to.
+ * @throws IOException
+ * @throws RuntimeException
+ */
+ void writeTo(File pFile) throws IOException;
+
+ // TODO: void delete()?
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java
index a6f8343b..e2d66267 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java
@@ -1,91 +1,91 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.fileupload;
-
-import org.apache.commons.fileupload.FileItem;
-import org.apache.commons.fileupload.FileUploadException;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.File;
-
-/**
- * An {@code UploadedFile} implementation, based on
- * Jakarta Commons FileUpload.
- *
- * @author Harald Kuhr
- * @version $Id: UploadedFileImpl.java#1 $
- */
-class UploadedFileImpl implements UploadedFile {
- private final FileItem item;
-
- public UploadedFileImpl(FileItem pItem) {
- if (pItem == null) {
- throw new IllegalArgumentException("fileitem == null");
- }
-
- item = pItem;
- }
-
- public String getContentType() {
- return item.getContentType();
- }
-
- public InputStream getInputStream() throws IOException {
- return item.getInputStream();
- }
-
- public String getName() {
- return item.getName();
- }
-
- public long length() {
- return item.getSize();
- }
-
- public void writeTo(File pFile) throws IOException {
- try {
- item.write(pFile);
- }
- catch(RuntimeException e) {
- throw e;
- }
- catch (IOException e) {
- throw e;
- }
- catch (FileUploadException e) {
- // We deliberately change this exception to an IOException, as it really is
- throw (IOException) new IOException(e.getMessage()).initCause(e);
- }
- catch (Exception e) {
- // Should not really happen, ever
- throw new RuntimeException(e.getMessage(), e);
- }
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.fileupload;
+
+import org.apache.commons.fileupload.FileItem;
+import org.apache.commons.fileupload.FileUploadException;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * An {@code UploadedFile} implementation, based on
+ * Jakarta Commons FileUpload.
+ *
+ * @author Harald Kuhr
+ * @version $Id: UploadedFileImpl.java#1 $
+ */
+class UploadedFileImpl implements UploadedFile {
+ private final FileItem item;
+
+ public UploadedFileImpl(FileItem pItem) {
+ if (pItem == null) {
+ throw new IllegalArgumentException("fileitem == null");
+ }
+
+ item = pItem;
+ }
+
+ public String getContentType() {
+ return item.getContentType();
+ }
+
+ public InputStream getInputStream() throws IOException {
+ return item.getInputStream();
+ }
+
+ public String getName() {
+ return item.getName();
+ }
+
+ public long length() {
+ return item.getSize();
+ }
+
+ public void writeTo(File pFile) throws IOException {
+ try {
+ item.write(pFile);
+ }
+ catch(RuntimeException e) {
+ throw e;
+ }
+ catch (IOException e) {
+ throw e;
+ }
+ catch (FileUploadException e) {
+ // We deliberately change this exception to an IOException, as it really is
+ throw (IOException) new IOException(e.getMessage()).initCause(e);
+ }
+ catch (Exception e) {
+ // Should not really happen, ever
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java
index 96d8cf2d..99486fa8 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java
@@ -1,141 +1,141 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.gzip;
-
-import com.twelvemonkeys.servlet.GenericFilter;
-
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-
-/**
- * A filter to reduce the output size of web resources.
- *
- * The HTTP protocol supports compression of the content to reduce network
- * bandwidth. The important headers involved, are the {@code Accept-Encoding}
- * request header, and the {@code Content-Encoding} response header.
- * This feature can be used to further reduce the number of bytes transferred
- * over the network, at the cost of some extra processing time at both endpoints.
- * Most modern browsers supports compression in GZIP format, which is fairly
- * efficient in cost/compression ratio.
- *
- * The filter tests for the presence of an {@code Accept-Encoding} header with a
- * value of {@code "gzip"} (several different encoding header values are
- * possible in one header). If not present, the filter simply passes the
- * request/response pair through, leaving it untouched. If present, the
- * {@code Content-Encoding} header is set, with the value {@code "gzip"},
- * and the response is wrapped.
- * The response output stream is wrapped in a
- * {@link java.util.zip.GZIPOutputStream} which performs the GZIP encoding.
- * For efficiency, the filter does not buffer the response, but writes through
- * the gzipped output stream.
- *
- * Configuration
- * To use {@code GZIPFilter} in your web-application, you simply need to add it
- * to your web descriptor ({@code web.xml}). If using a servlet container that
- * supports the Servlet 2.4 spec, the new {@code dispatcher} element should be
- * used, and set to {@code REQUEST/FORWARD}, to make sure the filter is invoked
- * only once for requests.
- * If using an older web descriptor, set the {@code init-param}
- * {@code "once-per-request"} to {@code "true"} (this will have the same effect,
- * but might perform slightly worse than the 2.4 version).
- * Please see the examples below.
- * Servlet 2.4 version, filter section:
- *
- * <!-- GZIP Filter Configuration -->
- * <filter>
- * <filter-name>gzip</filter-name>
- * <filter-class>com.twelvemonkeys.servlet.GZIPFilter</filter-class>
- * </filter>
- *
- * Filter-mapping section:
- *
- * <!-- GZIP Filter Mapping -->
- * <filter-mapping>
- * <filter-name>gzip</filter-name>
- * <url-pattern>*.html</url-pattern>
- * <dispatcher>REQUEST</dispatcher>
- * <dispatcher>FORWARD</dispatcher>
- * </filter-mapping>
- * <filter-mapping>
- * <filter-name>gzip</filter-name>
- * <url-pattern>*.jsp< /url-pattern>
- * <dispatcher>REQUEST</dispatcher>
- * <dispatcher>FORWARD</dispatcher>
- * </filter-mapping>
- *
- *
- * Based on ideas and code found in the ONJava article
- * Two
- * Servlet Filters Every Web Application Should Have
- * by Jayson Falkner.
- *
- *
- * @author Jayson Falkner
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: GZIPFilter.java#1 $
- */
-public class GZIPFilter extends GenericFilter {
-
- {
- oncePerRequest = true;
- }
-
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- // Can only filter HTTP responses
- if (pRequest instanceof HttpServletRequest) {
- HttpServletRequest request = (HttpServletRequest) pRequest;
- HttpServletResponse response = (HttpServletResponse) pResponse;
-
- // If GZIP is supported, use compression
- String accept = request.getHeader("Accept-Encoding");
- if (accept != null && accept.contains("gzip")) {
- //System.out.println("GZIP supported, compressing.");
- GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response);
-
- try {
- pChain.doFilter(pRequest, wrapped);
- }
- finally {
- wrapped.flushResponse();
- }
-
- return;
- }
- }
-
- // Else, continue chain
- pChain.doFilter(pRequest, pResponse);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.gzip;
+
+import com.twelvemonkeys.servlet.GenericFilter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+/**
+ * A filter to reduce the output size of web resources.
+ *
+ * The HTTP protocol supports compression of the content to reduce network
+ * bandwidth. The important headers involved, are the {@code Accept-Encoding}
+ * request header, and the {@code Content-Encoding} response header.
+ * This feature can be used to further reduce the number of bytes transferred
+ * over the network, at the cost of some extra processing time at both endpoints.
+ * Most modern browsers supports compression in GZIP format, which is fairly
+ * efficient in cost/compression ratio.
+ *
+ * The filter tests for the presence of an {@code Accept-Encoding} header with a
+ * value of {@code "gzip"} (several different encoding header values are
+ * possible in one header). If not present, the filter simply passes the
+ * request/response pair through, leaving it untouched. If present, the
+ * {@code Content-Encoding} header is set, with the value {@code "gzip"},
+ * and the response is wrapped.
+ * The response output stream is wrapped in a
+ * {@link java.util.zip.GZIPOutputStream} which performs the GZIP encoding.
+ * For efficiency, the filter does not buffer the response, but writes through
+ * the gzipped output stream.
+ *
+ * Configuration
+ * To use {@code GZIPFilter} in your web-application, you simply need to add it
+ * to your web descriptor ({@code web.xml}). If using a servlet container that
+ * supports the Servlet 2.4 spec, the new {@code dispatcher} element should be
+ * used, and set to {@code REQUEST/FORWARD}, to make sure the filter is invoked
+ * only once for requests.
+ * If using an older web descriptor, set the {@code init-param}
+ * {@code "once-per-request"} to {@code "true"} (this will have the same effect,
+ * but might perform slightly worse than the 2.4 version).
+ * Please see the examples below.
+ * Servlet 2.4 version, filter section:
+ *
+ * <!-- GZIP Filter Configuration -->
+ * <filter>
+ * <filter-name>gzip</filter-name>
+ * <filter-class>com.twelvemonkeys.servlet.GZIPFilter</filter-class>
+ * </filter>
+ *
+ * Filter-mapping section:
+ *
+ * <!-- GZIP Filter Mapping -->
+ * <filter-mapping>
+ * <filter-name>gzip</filter-name>
+ * <url-pattern>*.html</url-pattern>
+ * <dispatcher>REQUEST</dispatcher>
+ * <dispatcher>FORWARD</dispatcher>
+ * </filter-mapping>
+ * <filter-mapping>
+ * <filter-name>gzip</filter-name>
+ * <url-pattern>*.jsp< /url-pattern>
+ * <dispatcher>REQUEST</dispatcher>
+ * <dispatcher>FORWARD</dispatcher>
+ * </filter-mapping>
+ *
+ *
+ * Based on ideas and code found in the ONJava article
+ * Two
+ * Servlet Filters Every Web Application Should Have
+ * by Jayson Falkner.
+ *
+ *
+ * @author Jayson Falkner
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: GZIPFilter.java#1 $
+ */
+public class GZIPFilter extends GenericFilter {
+
+ {
+ oncePerRequest = true;
+ }
+
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ // Can only filter HTTP responses
+ if (pRequest instanceof HttpServletRequest) {
+ HttpServletRequest request = (HttpServletRequest) pRequest;
+ HttpServletResponse response = (HttpServletResponse) pResponse;
+
+ // If GZIP is supported, use compression
+ String accept = request.getHeader("Accept-Encoding");
+ if (accept != null && accept.contains("gzip")) {
+ //System.out.println("GZIP supported, compressing.");
+ GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response);
+
+ try {
+ pChain.doFilter(pRequest, wrapped);
+ }
+ finally {
+ wrapped.flushResponse();
+ }
+
+ return;
+ }
+ }
+
+ // Else, continue chain
+ pChain.doFilter(pRequest, pResponse);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java b/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java
index 4ee781da..84bc0357 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java
@@ -1,147 +1,147 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.gzip;
-
-import com.twelvemonkeys.servlet.OutputStreamAdapter;
-
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.util.zip.GZIPOutputStream;
-
-/**
- * GZIPResponseWrapper class description.
- *
- * Based on ideas and code found in the ONJava article
- * Two Servlet Filters Every Web Application Should Have
- * by Jayson Falkner.
- *
- * @author Jayson Falkner
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: GZIPResponseWrapper.java#1 $
- */
-public class GZIPResponseWrapper extends HttpServletResponseWrapper {
- // TODO: Remove/update ETags if needed? Read the spec (RFC 2616) on Vary/ETag for caching
-
- protected ServletOutputStream out;
- protected PrintWriter writer;
- protected GZIPOutputStream gzipOut;
- protected int contentLength = -1;
-
- public GZIPResponseWrapper(final HttpServletResponse response) {
- super(response);
-
- response.addHeader("Content-Encoding", "gzip");
- response.addHeader("Vary", "Accept");
- }
-
- public ServletOutputStream createOutputStream() throws IOException {
- // FIX: Write directly to servlet output stream, for faster responses.
- // Relies on chunked streams, or buffering in the servlet engine.
- if (contentLength >= 0) {
- gzipOut = new GZIPOutputStream(getResponse().getOutputStream(), contentLength);
- }
- else {
- gzipOut = new GZIPOutputStream(getResponse().getOutputStream());
- }
-
- // Wrap in ServletOutputStream and return
- return new OutputStreamAdapter(gzipOut);
- }
-
- // TODO: Move this to flushbuffer or something? Hmmm..
- public void flushResponse() throws IOException {
- try {
- // Finish GZIP encodig
- if (gzipOut != null) {
- gzipOut.finish();
- }
-
- flushBuffer();
- }
- finally {
- // Close stream
- if (writer != null) {
- writer.close();
- }
- else {
- if (out != null) {
- out.close();
- }
- }
- }
- }
-
- public void flushBuffer() throws IOException {
- if (writer != null) {
- writer.flush();
- }
- else if (out != null) {
- out.flush();
- }
- }
-
- public ServletOutputStream getOutputStream() throws IOException {
- if (writer != null) {
- throw new IllegalStateException("getWriter() has already been called!");
- }
-
- if (out == null) {
- out = createOutputStream();
- }
-
- return out;
- }
-
- public PrintWriter getWriter() throws IOException {
- if (writer != null) {
- return (writer);
- }
-
- if (out != null) {
- throw new IllegalStateException("getOutputStream() has already been called!");
- }
-
- out = createOutputStream();
-
- // TODO: This is wrong. Should use getCharacterEncoding() or "ISO-8859-1" if getCE returns null.
- writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
-
- return writer;
- }
-
- public void setContentLength(int pLength) {
- // NOTE: Do not call super, as we will shrink the size.
- contentLength = pLength;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.gzip;
+
+import com.twelvemonkeys.servlet.OutputStreamAdapter;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * GZIPResponseWrapper class description.
+ *
+ * Based on ideas and code found in the ONJava article
+ * Two Servlet Filters Every Web Application Should Have
+ * by Jayson Falkner.
+ *
+ * @author Jayson Falkner
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: GZIPResponseWrapper.java#1 $
+ */
+public class GZIPResponseWrapper extends HttpServletResponseWrapper {
+ // TODO: Remove/update ETags if needed? Read the spec (RFC 2616) on Vary/ETag for caching
+
+ protected ServletOutputStream out;
+ protected PrintWriter writer;
+ protected GZIPOutputStream gzipOut;
+ protected int contentLength = -1;
+
+ public GZIPResponseWrapper(final HttpServletResponse response) {
+ super(response);
+
+ response.addHeader("Content-Encoding", "gzip");
+ response.addHeader("Vary", "Accept");
+ }
+
+ public ServletOutputStream createOutputStream() throws IOException {
+ // FIX: Write directly to servlet output stream, for faster responses.
+ // Relies on chunked streams, or buffering in the servlet engine.
+ if (contentLength >= 0) {
+ gzipOut = new GZIPOutputStream(getResponse().getOutputStream(), contentLength);
+ }
+ else {
+ gzipOut = new GZIPOutputStream(getResponse().getOutputStream());
+ }
+
+ // Wrap in ServletOutputStream and return
+ return new OutputStreamAdapter(gzipOut);
+ }
+
+ // TODO: Move this to flushbuffer or something? Hmmm..
+ public void flushResponse() throws IOException {
+ try {
+ // Finish GZIP encodig
+ if (gzipOut != null) {
+ gzipOut.finish();
+ }
+
+ flushBuffer();
+ }
+ finally {
+ // Close stream
+ if (writer != null) {
+ writer.close();
+ }
+ else {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+ }
+
+ public void flushBuffer() throws IOException {
+ if (writer != null) {
+ writer.flush();
+ }
+ else if (out != null) {
+ out.flush();
+ }
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (writer != null) {
+ throw new IllegalStateException("getWriter() has already been called!");
+ }
+
+ if (out == null) {
+ out = createOutputStream();
+ }
+
+ return out;
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ if (writer != null) {
+ return (writer);
+ }
+
+ if (out != null) {
+ throw new IllegalStateException("getOutputStream() has already been called!");
+ }
+
+ out = createOutputStream();
+
+ // TODO: This is wrong. Should use getCharacterEncoding() or "ISO-8859-1" if getCE returns null.
+ writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
+
+ return writer;
+ }
+
+ public void setContentLength(int pLength) {
+ // NOTE: Do not call super, as we will shrink the size.
+ contentLength = pLength;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java
index 7bee9158..11d231c1 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java
@@ -1,72 +1,72 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-
-import javax.servlet.ServletRequest;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-
-/**
- * AWTImageFilterAdapter
- *
- * @author Harald Kuhr
- * @version $Id: AWTImageFilterAdapter.java#1 $
- *
- */
-public class AWTImageFilterAdapter extends ImageFilter {
-
- private java.awt.image.ImageFilter imageFilter = null;
-
- public void setImageFilter(String pFilterClass) {
- try {
- Class filterClass = Class.forName(pFilterClass);
- imageFilter = (java.awt.image.ImageFilter) filterClass.newInstance();
- }
- catch (ClassNotFoundException e) {
- log("Could not load filter class.", e);
- }
- catch (InstantiationException e) {
- log("Could not instantiate filter.", e);
- }
- catch (IllegalAccessException e) {
- log("Could not access filter class.", e);
- }
- }
-
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- // Filter
- Image img = ImageUtil.filter(pImage, imageFilter);
-
- // Create BufferedImage & return
- return ImageUtil.toBuffered(img, BufferedImage.TYPE_INT_RGB); // TODO: This is ok for JPEG only...
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+
+import javax.servlet.ServletRequest;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * AWTImageFilterAdapter
+ *
+ * @author Harald Kuhr
+ * @version $Id: AWTImageFilterAdapter.java#1 $
+ *
+ */
+public class AWTImageFilterAdapter extends ImageFilter {
+
+ private java.awt.image.ImageFilter imageFilter = null;
+
+ public void setImageFilter(String pFilterClass) {
+ try {
+ Class filterClass = Class.forName(pFilterClass);
+ imageFilter = (java.awt.image.ImageFilter) filterClass.newInstance();
+ }
+ catch (ClassNotFoundException e) {
+ log("Could not load filter class.", e);
+ }
+ catch (InstantiationException e) {
+ log("Could not instantiate filter.", e);
+ }
+ catch (IllegalAccessException e) {
+ log("Could not access filter class.", e);
+ }
+ }
+
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ // Filter
+ Image img = ImageUtil.filter(pImage, imageFilter);
+
+ // Create BufferedImage & return
+ return ImageUtil.toBuffered(img, BufferedImage.TYPE_INT_RGB); // TODO: This is ok for JPEG only...
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java
index d64540e6..cc0b52f9 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java
@@ -1,67 +1,67 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import javax.servlet.ServletRequest;
-import java.awt.image.BufferedImage;
-import java.awt.image.BufferedImageOp;
-import java.awt.image.RenderedImage;
-
-/**
- * BufferedImageOpAdapter
- *
- * @author Harald Kuhr
- * @version $Id: BufferedImageOpAdapter.java#1 $
- *
- */
-public class BufferedImageOpAdapter extends ImageFilter {
-
- private BufferedImageOp filter = null;
-
- public void setImageFilter(String pFilterClass) {
- try {
- Class filterClass = Class.forName(pFilterClass);
- filter = (BufferedImageOp) filterClass.newInstance();
- }
- catch (ClassNotFoundException e) {
- log("Could not instantiate filter class.", e);
- }
- catch (InstantiationException e) {
- log("Could not instantiate filter.", e);
- }
- catch (IllegalAccessException e) {
- log("Could not access filter class.", e);
- }
- }
-
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- // Filter & return
- return filter.filter(pImage, null);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import javax.servlet.ServletRequest;
+import java.awt.image.BufferedImage;
+import java.awt.image.BufferedImageOp;
+import java.awt.image.RenderedImage;
+
+/**
+ * BufferedImageOpAdapter
+ *
+ * @author Harald Kuhr
+ * @version $Id: BufferedImageOpAdapter.java#1 $
+ *
+ */
+public class BufferedImageOpAdapter extends ImageFilter {
+
+ private BufferedImageOp filter = null;
+
+ public void setImageFilter(String pFilterClass) {
+ try {
+ Class filterClass = Class.forName(pFilterClass);
+ filter = (BufferedImageOp) filterClass.newInstance();
+ }
+ catch (ClassNotFoundException e) {
+ log("Could not instantiate filter class.", e);
+ }
+ catch (InstantiationException e) {
+ log("Could not instantiate filter.", e);
+ }
+ catch (IllegalAccessException e) {
+ log("Could not access filter class.", e);
+ }
+ }
+
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ // Filter & return
+ return filter.filter(pImage, null);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java
index 22b40225..47ae42dd 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java
@@ -1,211 +1,211 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.servlet.GenericServlet;
-
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import java.io.IOException;
-import java.util.zip.CRC32;
-
-/**
- * Creates a minimal 1 x 1 pixel PNG image, in a color specified by the
- * {@code "color"} parameter. The color is HTML-style #RRGGBB, with two
- * digits hex number for red, green and blue (the hash, '#', is optional).
- *
- * The class does only byte manipulation, there is no server-side image
- * processing involving AWT ({@code Toolkit} class) of any kind.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: ColorServlet.java#2 $
- */
-public class ColorServlet extends GenericServlet {
- private final static String RGB_PARAME = "color";
-
- // A minimal, one color indexed PNG
- private final static byte[] PNG_IMG = new byte[]{
- (byte) 0x89, (byte) 'P', (byte) 'N', (byte) 'G', // PNG signature (8 bytes)
- 0x0d, 0x0a, 0x1a, 0x0a,
-
- 0x00, 0x00, 0x00, 0x0d, // IHDR length (13)
- (byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R', // Image header
- 0x00, 0x00, 0x00, 0x01, // width
- 0x00, 0x00, 0x00, 0x01, // height
- 0x01, 0x03, 0x00, 0x00, 0x00, // bits, color type, compression, filter, interlace
- 0x25, (byte) 0xdb, 0x56, (byte) 0xca, // IHDR CRC
-
- 0x00, 0x00, 0x00, 0x03, // PLTE length (3)
- (byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E', // Palette
- 0x00, 0x00, (byte) 0xff, // red, green, blue (updated by this servlet)
- (byte) 0x8a, (byte) 0x78, (byte) 0xd2, 0x57, // PLTE CRC
-
- 0x00, 0x00, 0x00, 0x0a, // IDAT length (10)
- (byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T', // Image data
- 0x78, (byte) 0xda, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
- (byte) 0xe5, 0x27, (byte) 0xde, (byte) 0xfc, // IDAT CRC
-
-
- 0x00, 0x00, 0x00, 0x00, // IEND length (0)
- (byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D', // Image end
- (byte) 0xae, (byte) 0x42, (byte) 0x60, (byte) 0x82 // IEND CRC
- };
-
- private final static int PLTE_CHUNK_START = 37; // after chunk length
- private final static int PLTE_CHUNK_LENGTH = 7; // chunk name & data
-
- private final static int RED_IDX = 4;
- private final static int GREEN_IDX = RED_IDX + 1;
- private final static int BLUE_IDX = GREEN_IDX + 1;
-
- private final CRC32 crc = new CRC32();
-
- /**
- * Creates a ColorDroplet.
- */
- public ColorServlet() {
- super();
- }
-
- /**
- * Renders the 1 x 1 single color PNG to the response.
- *
- * @see ColorServlet class description
- *
- * @param pRequest the request
- * @param pResponse the response
- *
- * @throws IOException
- * @throws ServletException
- */
- public void service(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
- int red = 0;
- int green = 0;
- int blue = 0;
-
- // Get color parameter and parse color
- String rgb = pRequest.getParameter(RGB_PARAME);
- if (rgb != null && rgb.length() >= 6 && rgb.length() <= 7) {
- int index = 0;
-
- // If the hash ('#') character is included, skip it.
- if (rgb.length() == 7) {
- index++;
- }
-
- try {
- // Two digit hex for each color
- String r = rgb.substring(index, index += 2);
- red = Integer.parseInt(r, 0x10);
-
- String g = rgb.substring(index, index += 2);
- green = Integer.parseInt(g, 0x10);
-
- String b = rgb.substring(index, index += 2);
- blue = Integer.parseInt(b, 0x10);
- }
- catch (NumberFormatException nfe) {
- log("Wrong color format for ColorDroplet: " + rgb + ". Must be RRGGBB.");
- }
- }
-
- // Set MIME type for PNG
- pResponse.setContentType("image/png");
- ServletOutputStream out = pResponse.getOutputStream();
-
- try {
- // Write header (and palette chunk length)
- out.write(PNG_IMG, 0, PLTE_CHUNK_START);
-
- // Create palette chunk, excl lenght, and write
- byte[] palette = makePalette(red, green, blue);
- out.write(palette);
-
- // Write image data until end
- int pos = PLTE_CHUNK_START + PLTE_CHUNK_LENGTH + 4;
- out.write(PNG_IMG, pos, PNG_IMG.length - pos);
- }
- finally {
- out.flush();
- }
- }
-
- /**
- * Updates the CRC for a byte array. Note that the byte array must be at
- * least {@code pOff + pLen + 4} bytes long, as the CRC is stored in the
- * 4 last bytes.
- *
- * @param pBytes the bytes to create CRC for
- * @param pOff the offset into the byte array to create CRC for
- * @param pLen the length of the byte array to create CRC for
- */
- private void updateCRC(byte[] pBytes, int pOff, int pLen) {
- int value;
-
- synchronized (crc) {
- crc.reset();
- crc.update(pBytes, pOff, pLen);
- value = (int) crc.getValue();
- }
-
- pBytes[pOff + pLen ] = (byte) ((value >> 24) & 0xff);
- pBytes[pOff + pLen + 1] = (byte) ((value >> 16) & 0xff);
- pBytes[pOff + pLen + 2] = (byte) ((value >> 8) & 0xff);
- pBytes[pOff + pLen + 3] = (byte) ( value & 0xff);
- }
-
- /**
- * Creates a PNG palette (PLTE) chunk with one color.
- * The palette chunk data is always 3 bytes in length (one byte per color
- * component).
- * The returned byte array is then {@code 4 + 3 + 4 = 11} bytes,
- * including chunk header, data and CRC.
- *
- * @param pRed the red component
- * @param pGreen the reen component
- * @param pBlue the blue component
- *
- * @return the bytes for the PLTE chunk, including CRC (but not length)
- */
- private byte[] makePalette(int pRed, int pGreen, int pBlue) {
- byte[] palette = new byte[PLTE_CHUNK_LENGTH + 4];
- System.arraycopy(PNG_IMG, PLTE_CHUNK_START, palette, 0, PLTE_CHUNK_LENGTH);
-
- palette[RED_IDX] = (byte) pRed;
- palette[GREEN_IDX] = (byte) pGreen;
- palette[BLUE_IDX] = (byte) pBlue;
-
- updateCRC(palette, 0, PLTE_CHUNK_LENGTH);
-
- return palette;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.servlet.GenericServlet;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.util.zip.CRC32;
+
+/**
+ * Creates a minimal 1 x 1 pixel PNG image, in a color specified by the
+ * {@code "color"} parameter. The color is HTML-style #RRGGBB, with two
+ * digits hex number for red, green and blue (the hash, '#', is optional).
+ *
+ * The class does only byte manipulation, there is no server-side image
+ * processing involving AWT ({@code Toolkit} class) of any kind.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: ColorServlet.java#2 $
+ */
+public class ColorServlet extends GenericServlet {
+ private final static String RGB_PARAME = "color";
+
+ // A minimal, one color indexed PNG
+ private final static byte[] PNG_IMG = new byte[]{
+ (byte) 0x89, (byte) 'P', (byte) 'N', (byte) 'G', // PNG signature (8 bytes)
+ 0x0d, 0x0a, 0x1a, 0x0a,
+
+ 0x00, 0x00, 0x00, 0x0d, // IHDR length (13)
+ (byte) 'I', (byte) 'H', (byte) 'D', (byte) 'R', // Image header
+ 0x00, 0x00, 0x00, 0x01, // width
+ 0x00, 0x00, 0x00, 0x01, // height
+ 0x01, 0x03, 0x00, 0x00, 0x00, // bits, color type, compression, filter, interlace
+ 0x25, (byte) 0xdb, 0x56, (byte) 0xca, // IHDR CRC
+
+ 0x00, 0x00, 0x00, 0x03, // PLTE length (3)
+ (byte) 'P', (byte) 'L', (byte) 'T', (byte) 'E', // Palette
+ 0x00, 0x00, (byte) 0xff, // red, green, blue (updated by this servlet)
+ (byte) 0x8a, (byte) 0x78, (byte) 0xd2, 0x57, // PLTE CRC
+
+ 0x00, 0x00, 0x00, 0x0a, // IDAT length (10)
+ (byte) 'I', (byte) 'D', (byte) 'A', (byte) 'T', // Image data
+ 0x78, (byte) 0xda, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01,
+ (byte) 0xe5, 0x27, (byte) 0xde, (byte) 0xfc, // IDAT CRC
+
+
+ 0x00, 0x00, 0x00, 0x00, // IEND length (0)
+ (byte) 'I', (byte) 'E', (byte) 'N', (byte) 'D', // Image end
+ (byte) 0xae, (byte) 0x42, (byte) 0x60, (byte) 0x82 // IEND CRC
+ };
+
+ private final static int PLTE_CHUNK_START = 37; // after chunk length
+ private final static int PLTE_CHUNK_LENGTH = 7; // chunk name & data
+
+ private final static int RED_IDX = 4;
+ private final static int GREEN_IDX = RED_IDX + 1;
+ private final static int BLUE_IDX = GREEN_IDX + 1;
+
+ private final CRC32 crc = new CRC32();
+
+ /**
+ * Creates a ColorDroplet.
+ */
+ public ColorServlet() {
+ super();
+ }
+
+ /**
+ * Renders the 1 x 1 single color PNG to the response.
+ *
+ * @see ColorServlet class description
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ public void service(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
+ int red = 0;
+ int green = 0;
+ int blue = 0;
+
+ // Get color parameter and parse color
+ String rgb = pRequest.getParameter(RGB_PARAME);
+ if (rgb != null && rgb.length() >= 6 && rgb.length() <= 7) {
+ int index = 0;
+
+ // If the hash ('#') character is included, skip it.
+ if (rgb.length() == 7) {
+ index++;
+ }
+
+ try {
+ // Two digit hex for each color
+ String r = rgb.substring(index, index += 2);
+ red = Integer.parseInt(r, 0x10);
+
+ String g = rgb.substring(index, index += 2);
+ green = Integer.parseInt(g, 0x10);
+
+ String b = rgb.substring(index, index += 2);
+ blue = Integer.parseInt(b, 0x10);
+ }
+ catch (NumberFormatException nfe) {
+ log("Wrong color format for ColorDroplet: " + rgb + ". Must be RRGGBB.");
+ }
+ }
+
+ // Set MIME type for PNG
+ pResponse.setContentType("image/png");
+ ServletOutputStream out = pResponse.getOutputStream();
+
+ try {
+ // Write header (and palette chunk length)
+ out.write(PNG_IMG, 0, PLTE_CHUNK_START);
+
+ // Create palette chunk, excl lenght, and write
+ byte[] palette = makePalette(red, green, blue);
+ out.write(palette);
+
+ // Write image data until end
+ int pos = PLTE_CHUNK_START + PLTE_CHUNK_LENGTH + 4;
+ out.write(PNG_IMG, pos, PNG_IMG.length - pos);
+ }
+ finally {
+ out.flush();
+ }
+ }
+
+ /**
+ * Updates the CRC for a byte array. Note that the byte array must be at
+ * least {@code pOff + pLen + 4} bytes long, as the CRC is stored in the
+ * 4 last bytes.
+ *
+ * @param pBytes the bytes to create CRC for
+ * @param pOff the offset into the byte array to create CRC for
+ * @param pLen the length of the byte array to create CRC for
+ */
+ private void updateCRC(byte[] pBytes, int pOff, int pLen) {
+ int value;
+
+ synchronized (crc) {
+ crc.reset();
+ crc.update(pBytes, pOff, pLen);
+ value = (int) crc.getValue();
+ }
+
+ pBytes[pOff + pLen ] = (byte) ((value >> 24) & 0xff);
+ pBytes[pOff + pLen + 1] = (byte) ((value >> 16) & 0xff);
+ pBytes[pOff + pLen + 2] = (byte) ((value >> 8) & 0xff);
+ pBytes[pOff + pLen + 3] = (byte) ( value & 0xff);
+ }
+
+ /**
+ * Creates a PNG palette (PLTE) chunk with one color.
+ * The palette chunk data is always 3 bytes in length (one byte per color
+ * component).
+ * The returned byte array is then {@code 4 + 3 + 4 = 11} bytes,
+ * including chunk header, data and CRC.
+ *
+ * @param pRed the red component
+ * @param pGreen the reen component
+ * @param pBlue the blue component
+ *
+ * @return the bytes for the PLTE chunk, including CRC (but not length)
+ */
+ private byte[] makePalette(int pRed, int pGreen, int pBlue) {
+ byte[] palette = new byte[PLTE_CHUNK_LENGTH + 4];
+ System.arraycopy(PNG_IMG, PLTE_CHUNK_START, palette, 0, PLTE_CHUNK_LENGTH);
+
+ palette[RED_IDX] = (byte) pRed;
+ palette[GREEN_IDX] = (byte) pGreen;
+ palette[BLUE_IDX] = (byte) pBlue;
+
+ updateCRC(palette, 0, PLTE_CHUNK_LENGTH);
+
+ return palette;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java
index 6dec5d60..7fa3f02c 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java
@@ -1,59 +1,59 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import javax.servlet.ServletRequest;
-import java.awt.image.RenderedImage;
-import java.awt.image.BufferedImage;
-import java.io.IOException;
-
-/**
- * ComposeFilter
- *
- *
- * @author Harald Kuhr
- * @version $Id: ComposeFilter.java#1 $
- */
-public class ComposeFilter extends ImageFilter {
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
- // 1. Load different image, locally (using ServletContext.getResource)
- // - Allow loading other filtered sources, or servlets? For example to
- // apply filename or timestamps?
- // - Allow applying text directly? Variables?
- // 2. Apply transformations from config
- // - Relative positioning
- // - Relative scaling
- // - Repeat (fill-pattern)?
- // - Rotation?
- // - Transparency?
- // - Background or foreground (layers)?
- // 3. Apply loaded image to original image (or vice versa?).
- return pImage;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import javax.servlet.ServletRequest;
+import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+
+/**
+ * ComposeFilter
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: ComposeFilter.java#1 $
+ */
+public class ComposeFilter extends ImageFilter {
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
+ // 1. Load different image, locally (using ServletContext.getResource)
+ // - Allow loading other filtered sources, or servlets? For example to
+ // apply filename or timestamps?
+ // - Allow applying text directly? Variables?
+ // 2. Apply transformations from config
+ // - Relative positioning
+ // - Relative scaling
+ // - Repeat (fill-pattern)?
+ // - Rotation?
+ // - Transparency?
+ // - Background or foreground (layers)?
+ // 3. Apply loaded image to original image (or vice versa?).
+ return pImage;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java
index 90920683..b0d33327 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java
@@ -1,439 +1,439 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.lang.StringUtil;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.imageio.ImageIO;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.IndexColorModel;
-import java.awt.image.RenderedImage;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.util.*;
-
-/**
- * This filter implements server side content negotiation and transcoding for
- * images.
- *
- * @todo Add support for automatic recognition of known browsers, to avoid
- * unneccessary conversion (as IE supports PNG, the latests FireFox supports
- * JPEG and GIF, etc. even though they both don't explicitly list these formats
- * in their Accept headers).
- *
- * @author Harald Kuhr
- * @version $Id: ContentNegotiationFilter.java#1 $
- */
-public class ContentNegotiationFilter extends ImageFilter {
-
- private final static String MIME_TYPE_IMAGE_PREFIX = "image/";
- private static final String MIME_TYPE_IMAGE_ANY = MIME_TYPE_IMAGE_PREFIX + "*";
- private static final String MIME_TYPE_ANY = "*/*";
- private static final String HTTP_HEADER_ACCEPT = "Accept";
- private static final String HTTP_HEADER_VARY = "Vary";
- protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
-
- private static final String FORMAT_JPEG = "image/jpeg";
- private static final String FORMAT_WBMP = "image/wbmp";
- private static final String FORMAT_GIF = "image/gif";
- private static final String FORMAT_PNG = "image/png";
-
- private final static String[] sKnownFormats = new String[] {
- FORMAT_JPEG, FORMAT_PNG, FORMAT_GIF, FORMAT_WBMP
- };
- private float[] knownFormatQuality = new float[] {
- 1f, 1f, 0.99f, 0.5f
- };
-
- private HashMap formatQuality; // HashMap, as I need to clone this for each request
- private final Object lock = new Object();
-
- /*
- private Pattern[] mKnownAgentPatterns;
- private String[] mKnownAgentAccpets;
- */
- {
- // Hack: Make sure the filter don't trigger all the time
- // See: super.trigger(ServletRequest)
- triggerParams = new String[] {};
- }
-
- /*
- public void setAcceptMappings(String pPropertiesFile) {
- // NOTE: Supposed to be:
- // =
- // .accept=
-
- Properties mappings = new Properties();
- try {
- mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
-
- List patterns = new ArrayList();
- List accepts = new ArrayList();
-
- for (Iterator iterator = mappings.keySet().iterator(); iterator.hasNext();) {
- String agent = (String) iterator.next();
- if (agent.endsWith(".accept")) {
- continue;
- }
-
- try {
- patterns.add(Pattern.compile((String) mappings.get(agent)));
-
- // TODO: Consider preparsing ACCEPT header??
- accepts.add(mappings.get(agent + ".accept"));
- }
- catch (PatternSyntaxException e) {
- log("Could not parse User-Agent identification for " + agent, e);
- }
-
- mKnownAgentPatterns = (Pattern[]) patterns.toArray(new Pattern[patterns.size()]);
- mKnownAgentAccpets = (String[]) accepts.toArray(new String[accepts.size()]);
- }
- }
- catch (IOException e) {
- log("Could not read accetp-mappings properties file: " + pPropertiesFile, e);
- }
- }
- */
-
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- // NOTE: super invokes trigger() and image specific doFilter() if needed
- super.doFilterImpl(pRequest, pResponse, pChain);
-
- if (pResponse instanceof HttpServletResponse) {
- // Update the Vary HTTP header field
- ((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT);
- //((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_USER_AGENT);
- }
- }
-
- /**
- * Makes sure the filter triggers for unknown file formats.
- *
- * @param pRequest the request
- * @return {@code true} if the filter should execute, {@code false}
- * otherwise
- */
- protected boolean trigger(ServletRequest pRequest) {
- boolean trigger = false;
-
- if (pRequest instanceof HttpServletRequest) {
- HttpServletRequest request = (HttpServletRequest) pRequest;
- String accept = getAcceptedFormats(request);
- String originalFormat = getServletContext().getMimeType(request.getRequestURI());
-
- //System.out.println("Accept: " + accept);
- //System.out.println("Original format: " + originalFormat);
-
- // Only override original format if it is not accpeted by the client
- // Note: Only explicit matches are okay, */* or image/* is not.
- if (!StringUtil.contains(accept, originalFormat)) {
- trigger = true;
- }
- }
-
- // Call super, to allow content negotiation even though format is supported
- return trigger || super.trigger(pRequest);
- }
-
- private String getAcceptedFormats(HttpServletRequest pRequest) {
- return pRequest.getHeader(HTTP_HEADER_ACCEPT);
- }
-
- /*
- private String getAcceptedFormats(HttpServletRequest pRequest) {
- String accept = pRequest.getHeader(HTTP_HEADER_ACCEPT);
-
- // Check if User-Agent is in list of known agents
- if (mKnownAgentPatterns != null) {
- String agent = pRequest.getHeader(HTTP_HEADER_USER_AGENT);
- for (int i = 0; i < mKnownAgentPatterns.length; i++) {
- Pattern pattern = mKnownAgentPatterns[i];
- if (pattern.matcher(agent).matches()) {
- // Merge known with real accpet, in case plugins add extra capabilities
- accept = mergeAccept(mKnownAgentAccpets[i], accept);
- System.out.println("--> User-Agent: " + agent + " accepts: " + accept);
- return accept;
- }
- }
- }
-
- System.out.println("No agent match, defaulting to Accept header: " + accept);
- return accept;
- }
-
- private String mergeAccept(String pKnown, String pAccept) {
- // TODO: Make sure there are no duplicates...
- return pKnown + ", " + pAccept;
- }
- */
-
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
- if (pRequest instanceof HttpServletRequest) {
- HttpServletRequest request = (HttpServletRequest) pRequest;
-
- Map formatQuality = getFormatQualityMapping();
-
- // TODO: Consider adding original format, and use as fallback in worst case?
- // TODO: Original format should have some boost, to avoid unneccesary convertsion?
-
- // Update source quality settings from image properties
- adjustQualityFromImage(formatQuality, pImage);
- //System.out.println("Source quality mapping: " + formatQuality);
-
- adjustQualityFromAccept(formatQuality, request);
- //System.out.println("Final media scores: " + formatQuality);
-
- // Find the formats with the highest quality factor, and use the first (predictable)
- String acceptable = findBestFormat(formatQuality);
-
- //System.out.println("Acceptable: " + acceptable);
-
- // Send HTTP 406 Not Acceptable
- if (acceptable == null) {
- if (pResponse instanceof HttpServletResponse) {
- ((HttpServletResponse) pResponse).sendError(HttpURLConnection.HTTP_NOT_ACCEPTABLE);
- }
- return null;
- }
- else {
- // TODO: Only if the format was changed!
- // Let other filters/caches/proxies know we changed the image
- }
-
- // Set format
- pResponse.setOutputContentType(acceptable);
- //System.out.println("Set format: " + acceptable);
- }
-
- return pImage;
- }
-
- private Map getFormatQualityMapping() {
- synchronized(lock) {
- if (formatQuality == null) {
- formatQuality = new HashMap();
-
- // Use ImageIO to find formats we can actually write
- String[] formats = ImageIO.getWriterMIMETypes();
-
- // All known formats qs are initially 1.0
- // Others should be 0.1 or something like that...
- for (String format : formats) {
- formatQuality.put(format, getKnownFormatQuality(format));
- }
- }
- }
- //noinspection unchecked
- return (Map) formatQuality.clone();
- }
-
- /**
- * Finds the best available format.
- *
- * @param pFormatQuality the format to quality mapping
- * @return the mime type of the best available format
- */
- private static String findBestFormat(Map pFormatQuality) {
- String acceptable = null;
- float acceptQuality = 0.0f;
- for (Map.Entry entry : pFormatQuality.entrySet()) {
- float qValue = entry.getValue();
- if (qValue > acceptQuality) {
- acceptQuality = qValue;
- acceptable = entry.getKey();
- }
- }
-
- //System.out.println("Accepted format: " + acceptable);
- //System.out.println("Accepted quality: " + acceptQuality);
- return acceptable;
- }
-
- /**
- * Adjust quality from HTTP Accept header
- *
- * @param pFormatQuality the format to quality mapping
- * @param pRequest the request
- */
- private void adjustQualityFromAccept(Map pFormatQuality, HttpServletRequest pRequest) {
- // Multiply all q factors with qs factors
- // No q=.. should be interpreted as q=1.0
-
- // Apache does some extras; if both explicit types and wildcards
- // (without qaulity factor) are present, */* is interpreted as
- // */*;q=0.01 and image/* is interpreted as image/*;q=0.02
- // See: http://httpd.apache.org/docs-2.0/content-negotiation.html
-
- String accept = getAcceptedFormats(pRequest);
- //System.out.println("Accept: " + accept);
-
- float anyImageFactor = getQualityFactor(accept, MIME_TYPE_IMAGE_ANY);
- anyImageFactor = (anyImageFactor == 1) ? 0.02f : anyImageFactor;
-
- float anyFactor = getQualityFactor(accept, MIME_TYPE_ANY);
- anyFactor = (anyFactor == 1) ? 0.01f : anyFactor;
-
- for (String format : pFormatQuality.keySet()) {
- //System.out.println("Trying format: " + format);
-
- String formatMIME = MIME_TYPE_IMAGE_PREFIX + format;
- float qFactor = getQualityFactor(accept, formatMIME);
- qFactor = (qFactor == 0f) ? Math.max(anyFactor, anyImageFactor) : qFactor;
- adjustQuality(pFormatQuality, format, qFactor);
- }
- }
-
- /**
- *
- * @param pAccept the accpet header value
- * @param pContentType the content type to get the quality factor for
- * @return the q factor of the given format, according to the accept header
- */
- private static float getQualityFactor(String pAccept, String pContentType) {
- float qFactor = 0;
- int foundIndex = pAccept.indexOf(pContentType);
- if (foundIndex >= 0) {
- int startQIndex = foundIndex + pContentType.length();
- if (startQIndex < pAccept.length() && pAccept.charAt(startQIndex) == ';') {
- while (startQIndex < pAccept.length() && pAccept.charAt(startQIndex++) == ' ') {
- // Skip over whitespace
- }
-
- if (pAccept.charAt(startQIndex++) == 'q' && pAccept.charAt(startQIndex++) == '=') {
- int endQIndex = pAccept.indexOf(',', startQIndex);
- if (endQIndex < 0) {
- endQIndex = pAccept.length();
- }
-
- try {
- qFactor = Float.parseFloat(pAccept.substring(startQIndex, endQIndex));
- //System.out.println("Found qFactor " + qFactor);
- }
- catch (NumberFormatException e) {
- // TODO: Determine what to do here.. Maybe use a very low value?
- // Ahem.. The specs don't say anything about how to interpret a wrong q factor..
- //System.out.println("Unparseable q setting; " + e.getMessage());
- }
- }
- // TODO: Determine what to do here.. Maybe use a very low value?
- // Unparseable q value, use 0
- }
- else {
- // Else, assume quality is 1.0
- qFactor = 1;
- }
- }
- return qFactor;
- }
-
-
- /**
- * Adjusts source quality settings from image properties.
- *
- * @param pFormatQuality the format to quality mapping
- * @param pImage the image
- */
- private static void adjustQualityFromImage(Map pFormatQuality, BufferedImage pImage) {
- // NOTE: The values are all made-up. May need tuning.
-
- // If pImage.getColorModel() instanceof IndexColorModel
- // JPEG qs*=0.6
- // If NOT binary or 2 color index
- // WBMP qs*=0.5
- // Else
- // GIF qs*=0.02
- // PNG qs*=0.9 // JPEG is smaller/faster
- if (pImage.getColorModel() instanceof IndexColorModel) {
- adjustQuality(pFormatQuality, FORMAT_JPEG, 0.6f);
-
- if (pImage.getType() != BufferedImage.TYPE_BYTE_BINARY || ((IndexColorModel) pImage.getColorModel()).getMapSize() != 2) {
- adjustQuality(pFormatQuality, FORMAT_WBMP, 0.5f);
- }
- }
- else {
- adjustQuality(pFormatQuality, FORMAT_GIF, 0.01f);
- adjustQuality(pFormatQuality, FORMAT_PNG, 0.99f); // JPEG is smaller/faster
- }
-
- // If pImage.getColorModel().hasTransparentPixels()
- // JPEG qs*=0.05
- // WBMP qs*=0.05
- // If NOT transparency == BITMASK
- // GIF qs*=0.8
- if (ImageUtil.hasTransparentPixels(pImage, true)) {
- adjustQuality(pFormatQuality, FORMAT_JPEG, 0.009f);
- adjustQuality(pFormatQuality, FORMAT_WBMP, 0.009f);
-
- if (pImage.getColorModel().getTransparency() != Transparency.BITMASK) {
- adjustQuality(pFormatQuality, FORMAT_GIF, 0.8f);
- }
- }
- }
-
- /**
- * Updates the quality in the map.
- *
- * @param pFormatQuality Map
- * @param pFormat the format
- * @param pFactor the quality factor
- */
- private static void adjustQuality(Map pFormatQuality, String pFormat, float pFactor) {
- Float oldValue = pFormatQuality.get(pFormat);
- if (oldValue != null) {
- pFormatQuality.put(pFormat, oldValue * pFactor);
- //System.out.println("New vallue after multiplying with " + pFactor + " is " + pFormatQuality.get(pFormat));
- }
- }
-
-
- /**
- * Gets the initial quality if this is a known format, otherwise 0.1
- *
- * @param pFormat the format name
- * @return the q factor of the given format
- */
- private float getKnownFormatQuality(String pFormat) {
- for (int i = 0; i < sKnownFormats.length; i++) {
- if (pFormat.equals(sKnownFormats[i])) {
- return knownFormatQuality[i];
- }
- }
- return 0.1f;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.lang.StringUtil;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.*;
+
+/**
+ * This filter implements server side content negotiation and transcoding for
+ * images.
+ *
+ * @todo Add support for automatic recognition of known browsers, to avoid
+ * unneccessary conversion (as IE supports PNG, the latests FireFox supports
+ * JPEG and GIF, etc. even though they both don't explicitly list these formats
+ * in their Accept headers).
+ *
+ * @author Harald Kuhr
+ * @version $Id: ContentNegotiationFilter.java#1 $
+ */
+public class ContentNegotiationFilter extends ImageFilter {
+
+ private final static String MIME_TYPE_IMAGE_PREFIX = "image/";
+ private static final String MIME_TYPE_IMAGE_ANY = MIME_TYPE_IMAGE_PREFIX + "*";
+ private static final String MIME_TYPE_ANY = "*/*";
+ private static final String HTTP_HEADER_ACCEPT = "Accept";
+ private static final String HTTP_HEADER_VARY = "Vary";
+ protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
+
+ private static final String FORMAT_JPEG = "image/jpeg";
+ private static final String FORMAT_WBMP = "image/wbmp";
+ private static final String FORMAT_GIF = "image/gif";
+ private static final String FORMAT_PNG = "image/png";
+
+ private final static String[] sKnownFormats = new String[] {
+ FORMAT_JPEG, FORMAT_PNG, FORMAT_GIF, FORMAT_WBMP
+ };
+ private float[] knownFormatQuality = new float[] {
+ 1f, 1f, 0.99f, 0.5f
+ };
+
+ private HashMap formatQuality; // HashMap, as I need to clone this for each request
+ private final Object lock = new Object();
+
+ /*
+ private Pattern[] mKnownAgentPatterns;
+ private String[] mKnownAgentAccpets;
+ */
+ {
+ // Hack: Make sure the filter don't trigger all the time
+ // See: super.trigger(ServletRequest)
+ triggerParams = new String[] {};
+ }
+
+ /*
+ public void setAcceptMappings(String pPropertiesFile) {
+ // NOTE: Supposed to be:
+ // =
+ // .accept=
+
+ Properties mappings = new Properties();
+ try {
+ mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
+
+ List patterns = new ArrayList();
+ List accepts = new ArrayList();
+
+ for (Iterator iterator = mappings.keySet().iterator(); iterator.hasNext();) {
+ String agent = (String) iterator.next();
+ if (agent.endsWith(".accept")) {
+ continue;
+ }
+
+ try {
+ patterns.add(Pattern.compile((String) mappings.get(agent)));
+
+ // TODO: Consider preparsing ACCEPT header??
+ accepts.add(mappings.get(agent + ".accept"));
+ }
+ catch (PatternSyntaxException e) {
+ log("Could not parse User-Agent identification for " + agent, e);
+ }
+
+ mKnownAgentPatterns = (Pattern[]) patterns.toArray(new Pattern[patterns.size()]);
+ mKnownAgentAccpets = (String[]) accepts.toArray(new String[accepts.size()]);
+ }
+ }
+ catch (IOException e) {
+ log("Could not read accetp-mappings properties file: " + pPropertiesFile, e);
+ }
+ }
+ */
+
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ // NOTE: super invokes trigger() and image specific doFilter() if needed
+ super.doFilterImpl(pRequest, pResponse, pChain);
+
+ if (pResponse instanceof HttpServletResponse) {
+ // Update the Vary HTTP header field
+ ((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_ACCEPT);
+ //((HttpServletResponse) pResponse).addHeader(HTTP_HEADER_VARY, HTTP_HEADER_USER_AGENT);
+ }
+ }
+
+ /**
+ * Makes sure the filter triggers for unknown file formats.
+ *
+ * @param pRequest the request
+ * @return {@code true} if the filter should execute, {@code false}
+ * otherwise
+ */
+ protected boolean trigger(ServletRequest pRequest) {
+ boolean trigger = false;
+
+ if (pRequest instanceof HttpServletRequest) {
+ HttpServletRequest request = (HttpServletRequest) pRequest;
+ String accept = getAcceptedFormats(request);
+ String originalFormat = getServletContext().getMimeType(request.getRequestURI());
+
+ //System.out.println("Accept: " + accept);
+ //System.out.println("Original format: " + originalFormat);
+
+ // Only override original format if it is not accpeted by the client
+ // Note: Only explicit matches are okay, */* or image/* is not.
+ if (!StringUtil.contains(accept, originalFormat)) {
+ trigger = true;
+ }
+ }
+
+ // Call super, to allow content negotiation even though format is supported
+ return trigger || super.trigger(pRequest);
+ }
+
+ private String getAcceptedFormats(HttpServletRequest pRequest) {
+ return pRequest.getHeader(HTTP_HEADER_ACCEPT);
+ }
+
+ /*
+ private String getAcceptedFormats(HttpServletRequest pRequest) {
+ String accept = pRequest.getHeader(HTTP_HEADER_ACCEPT);
+
+ // Check if User-Agent is in list of known agents
+ if (mKnownAgentPatterns != null) {
+ String agent = pRequest.getHeader(HTTP_HEADER_USER_AGENT);
+ for (int i = 0; i < mKnownAgentPatterns.length; i++) {
+ Pattern pattern = mKnownAgentPatterns[i];
+ if (pattern.matcher(agent).matches()) {
+ // Merge known with real accpet, in case plugins add extra capabilities
+ accept = mergeAccept(mKnownAgentAccpets[i], accept);
+ System.out.println("--> User-Agent: " + agent + " accepts: " + accept);
+ return accept;
+ }
+ }
+ }
+
+ System.out.println("No agent match, defaulting to Accept header: " + accept);
+ return accept;
+ }
+
+ private String mergeAccept(String pKnown, String pAccept) {
+ // TODO: Make sure there are no duplicates...
+ return pKnown + ", " + pAccept;
+ }
+ */
+
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException {
+ if (pRequest instanceof HttpServletRequest) {
+ HttpServletRequest request = (HttpServletRequest) pRequest;
+
+ Map formatQuality = getFormatQualityMapping();
+
+ // TODO: Consider adding original format, and use as fallback in worst case?
+ // TODO: Original format should have some boost, to avoid unneccesary convertsion?
+
+ // Update source quality settings from image properties
+ adjustQualityFromImage(formatQuality, pImage);
+ //System.out.println("Source quality mapping: " + formatQuality);
+
+ adjustQualityFromAccept(formatQuality, request);
+ //System.out.println("Final media scores: " + formatQuality);
+
+ // Find the formats with the highest quality factor, and use the first (predictable)
+ String acceptable = findBestFormat(formatQuality);
+
+ //System.out.println("Acceptable: " + acceptable);
+
+ // Send HTTP 406 Not Acceptable
+ if (acceptable == null) {
+ if (pResponse instanceof HttpServletResponse) {
+ ((HttpServletResponse) pResponse).sendError(HttpURLConnection.HTTP_NOT_ACCEPTABLE);
+ }
+ return null;
+ }
+ else {
+ // TODO: Only if the format was changed!
+ // Let other filters/caches/proxies know we changed the image
+ }
+
+ // Set format
+ pResponse.setOutputContentType(acceptable);
+ //System.out.println("Set format: " + acceptable);
+ }
+
+ return pImage;
+ }
+
+ private Map getFormatQualityMapping() {
+ synchronized(lock) {
+ if (formatQuality == null) {
+ formatQuality = new HashMap();
+
+ // Use ImageIO to find formats we can actually write
+ String[] formats = ImageIO.getWriterMIMETypes();
+
+ // All known formats qs are initially 1.0
+ // Others should be 0.1 or something like that...
+ for (String format : formats) {
+ formatQuality.put(format, getKnownFormatQuality(format));
+ }
+ }
+ }
+ //noinspection unchecked
+ return (Map) formatQuality.clone();
+ }
+
+ /**
+ * Finds the best available format.
+ *
+ * @param pFormatQuality the format to quality mapping
+ * @return the mime type of the best available format
+ */
+ private static String findBestFormat(Map pFormatQuality) {
+ String acceptable = null;
+ float acceptQuality = 0.0f;
+ for (Map.Entry entry : pFormatQuality.entrySet()) {
+ float qValue = entry.getValue();
+ if (qValue > acceptQuality) {
+ acceptQuality = qValue;
+ acceptable = entry.getKey();
+ }
+ }
+
+ //System.out.println("Accepted format: " + acceptable);
+ //System.out.println("Accepted quality: " + acceptQuality);
+ return acceptable;
+ }
+
+ /**
+ * Adjust quality from HTTP Accept header
+ *
+ * @param pFormatQuality the format to quality mapping
+ * @param pRequest the request
+ */
+ private void adjustQualityFromAccept(Map pFormatQuality, HttpServletRequest pRequest) {
+ // Multiply all q factors with qs factors
+ // No q=.. should be interpreted as q=1.0
+
+ // Apache does some extras; if both explicit types and wildcards
+ // (without qaulity factor) are present, */* is interpreted as
+ // */*;q=0.01 and image/* is interpreted as image/*;q=0.02
+ // See: http://httpd.apache.org/docs-2.0/content-negotiation.html
+
+ String accept = getAcceptedFormats(pRequest);
+ //System.out.println("Accept: " + accept);
+
+ float anyImageFactor = getQualityFactor(accept, MIME_TYPE_IMAGE_ANY);
+ anyImageFactor = (anyImageFactor == 1) ? 0.02f : anyImageFactor;
+
+ float anyFactor = getQualityFactor(accept, MIME_TYPE_ANY);
+ anyFactor = (anyFactor == 1) ? 0.01f : anyFactor;
+
+ for (String format : pFormatQuality.keySet()) {
+ //System.out.println("Trying format: " + format);
+
+ String formatMIME = MIME_TYPE_IMAGE_PREFIX + format;
+ float qFactor = getQualityFactor(accept, formatMIME);
+ qFactor = (qFactor == 0f) ? Math.max(anyFactor, anyImageFactor) : qFactor;
+ adjustQuality(pFormatQuality, format, qFactor);
+ }
+ }
+
+ /**
+ *
+ * @param pAccept the accpet header value
+ * @param pContentType the content type to get the quality factor for
+ * @return the q factor of the given format, according to the accept header
+ */
+ private static float getQualityFactor(String pAccept, String pContentType) {
+ float qFactor = 0;
+ int foundIndex = pAccept.indexOf(pContentType);
+ if (foundIndex >= 0) {
+ int startQIndex = foundIndex + pContentType.length();
+ if (startQIndex < pAccept.length() && pAccept.charAt(startQIndex) == ';') {
+ while (startQIndex < pAccept.length() && pAccept.charAt(startQIndex++) == ' ') {
+ // Skip over whitespace
+ }
+
+ if (pAccept.charAt(startQIndex++) == 'q' && pAccept.charAt(startQIndex++) == '=') {
+ int endQIndex = pAccept.indexOf(',', startQIndex);
+ if (endQIndex < 0) {
+ endQIndex = pAccept.length();
+ }
+
+ try {
+ qFactor = Float.parseFloat(pAccept.substring(startQIndex, endQIndex));
+ //System.out.println("Found qFactor " + qFactor);
+ }
+ catch (NumberFormatException e) {
+ // TODO: Determine what to do here.. Maybe use a very low value?
+ // Ahem.. The specs don't say anything about how to interpret a wrong q factor..
+ //System.out.println("Unparseable q setting; " + e.getMessage());
+ }
+ }
+ // TODO: Determine what to do here.. Maybe use a very low value?
+ // Unparseable q value, use 0
+ }
+ else {
+ // Else, assume quality is 1.0
+ qFactor = 1;
+ }
+ }
+ return qFactor;
+ }
+
+
+ /**
+ * Adjusts source quality settings from image properties.
+ *
+ * @param pFormatQuality the format to quality mapping
+ * @param pImage the image
+ */
+ private static void adjustQualityFromImage(Map pFormatQuality, BufferedImage pImage) {
+ // NOTE: The values are all made-up. May need tuning.
+
+ // If pImage.getColorModel() instanceof IndexColorModel
+ // JPEG qs*=0.6
+ // If NOT binary or 2 color index
+ // WBMP qs*=0.5
+ // Else
+ // GIF qs*=0.02
+ // PNG qs*=0.9 // JPEG is smaller/faster
+ if (pImage.getColorModel() instanceof IndexColorModel) {
+ adjustQuality(pFormatQuality, FORMAT_JPEG, 0.6f);
+
+ if (pImage.getType() != BufferedImage.TYPE_BYTE_BINARY || ((IndexColorModel) pImage.getColorModel()).getMapSize() != 2) {
+ adjustQuality(pFormatQuality, FORMAT_WBMP, 0.5f);
+ }
+ }
+ else {
+ adjustQuality(pFormatQuality, FORMAT_GIF, 0.01f);
+ adjustQuality(pFormatQuality, FORMAT_PNG, 0.99f); // JPEG is smaller/faster
+ }
+
+ // If pImage.getColorModel().hasTransparentPixels()
+ // JPEG qs*=0.05
+ // WBMP qs*=0.05
+ // If NOT transparency == BITMASK
+ // GIF qs*=0.8
+ if (ImageUtil.hasTransparentPixels(pImage, true)) {
+ adjustQuality(pFormatQuality, FORMAT_JPEG, 0.009f);
+ adjustQuality(pFormatQuality, FORMAT_WBMP, 0.009f);
+
+ if (pImage.getColorModel().getTransparency() != Transparency.BITMASK) {
+ adjustQuality(pFormatQuality, FORMAT_GIF, 0.8f);
+ }
+ }
+ }
+
+ /**
+ * Updates the quality in the map.
+ *
+ * @param pFormatQuality Map
+ * @param pFormat the format
+ * @param pFactor the quality factor
+ */
+ private static void adjustQuality(Map pFormatQuality, String pFormat, float pFactor) {
+ Float oldValue = pFormatQuality.get(pFormat);
+ if (oldValue != null) {
+ pFormatQuality.put(pFormat, oldValue * pFactor);
+ //System.out.println("New vallue after multiplying with " + pFactor + " is " + pFormatQuality.get(pFormat));
+ }
+ }
+
+
+ /**
+ * Gets the initial quality if this is a known format, otherwise 0.1
+ *
+ * @param pFormat the format name
+ * @return the q factor of the given format
+ */
+ private float getKnownFormatQuality(String pFormat) {
+ for (int i = 0; i < sKnownFormats.length; i++) {
+ if (pFormat.equals(sKnownFormats[i])) {
+ return knownFormatQuality[i];
+ }
+ }
+ return 0.1f;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java
index a1040a42..d761c5c4 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java
@@ -1,232 +1,232 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.ServletRequest;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-
-/**
- * This Servlet is able to render a cropped part of an image.
- *
- *
- *
- * Parameters:
- *
- * - {@code cropX}
- * - integer, the new left edge of the image.
- *
- {@code cropY}
- * - integer, the new top of the image.
- *
- {@code cropWidth}
- * - integer, the new width of the image.
- *
- {@code cropHeight}
- * - integer, the new height of the image.
- *
- {@code cropUniform}
- * - boolean, wether or not uniform scalnig should be used. Default is
- * {@code true}.
- *
- {@code cropUnits}
- * - string, one of {@code PIXELS}, {@code PERCENT}.
- * {@code PIXELS} is default.
- *
- *
- *
- *
- {@code image}
- * - string, the URL of the image to scale.
- *
- *
- {@code scaleX}
- * - integer, the new width of the image.
- *
- *
- {@code scaleY}
- * - integer, the new height of the image.
- *
- *
- {@code scaleUniform}
- * - boolean, wether or not uniform scalnig should be used. Default is
- * {@code true}.
- *
- *
- {@code scaleUnits}
- * - string, one of {@code PIXELS}, {@code PERCENT}.
- * {@code PIXELS} is default.
- *
- *
- {@code scaleQuality}
- * - string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
- * {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
- * {@code SCALE_DEFAULT} is default.
- *
- *
- *
- * @example
- * <IMG src="/crop/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=500&cropUniform=true">
- *
- * @example
- * <IMG src="/crop/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=50&cropUnits=PERCENT">
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: CropFilter.java#1 $
- */
-public class CropFilter extends ScaleFilter {
- /** {@code cropX}*/
- protected final static String PARAM_CROP_X = "cropX";
- /** {@code cropY}*/
- protected final static String PARAM_CROP_Y = "cropY";
- /** {@code cropWidth}*/
- protected final static String PARAM_CROP_WIDTH = "cropWidth";
- /** {@code cropHeight}*/
- protected final static String PARAM_CROP_HEIGHT = "cropHeight";
- /** {@code cropUniform}*/
- protected final static String PARAM_CROP_UNIFORM = "cropUniform";
- /** {@code cropUnits}*/
- protected final static String PARAM_CROP_UNITS = "cropUnits";
-
- /**
- * Reads the image from the requested URL, scales it, crops it, and returns
- * it in the
- * Servlet stream. See above for details on parameters.
- */
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- // Get crop coordinates
- int x = ServletUtil.getIntParameter(pRequest, PARAM_CROP_X, -1);
- int y = ServletUtil.getIntParameter(pRequest, PARAM_CROP_Y, -1);
- int width = ServletUtil.getIntParameter(pRequest, PARAM_CROP_WIDTH, -1);
- int height = ServletUtil.getIntParameter(pRequest, PARAM_CROP_HEIGHT, -1);
-
- boolean uniform =
- ServletUtil.getBooleanParameter(pRequest, PARAM_CROP_UNIFORM, false);
-
- int units = getUnits(ServletUtil.getParameter(pRequest, PARAM_CROP_UNITS, null));
-
- // Get crop bounds
- Rectangle bounds =
- getBounds(x, y, width, height, units, uniform, pImage);
-
- // Return cropped version
- return pImage.getSubimage((int) bounds.getX(), (int) bounds.getY(),
- (int) bounds.getWidth(),
- (int) bounds.getHeight());
- //return scaled.getSubimage(x, y, width, height);
- }
-
- protected Rectangle getBounds(int pX, int pY, int pWidth, int pHeight,
- int pUnits, boolean pUniform,
- BufferedImage pImg) {
- // Algoritm:
- // Try to get x and y (default 0,0).
- // Try to get width and height (default width-x, height-y)
- //
- // If percent, get ratio
- //
- // If uniform
- //
-
- int oldWidth = pImg.getWidth();
- int oldHeight = pImg.getHeight();
- float ratio;
-
- if (pUnits == UNITS_PERCENT) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Non-uniform
- pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
- pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / 100f;
- pWidth = (int) ((float) oldWidth * ratio);
- pHeight = (int) ((float) oldHeight * ratio);
-
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / 100f;
- pWidth = (int) ((float) oldWidth * ratio);
- pHeight = (int) ((float) oldHeight * ratio);
- }
- // Else: No crop
- }
- //else if (UNITS_PIXELS.equalsIgnoreCase(pUnits)) {
- else if (pUnits == UNITS_PIXELS) {
- // Uniform
- if (pUniform) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Compute both ratios
- ratio = (float) pWidth / (float) oldWidth;
- float heightRatio = (float) pHeight / (float) oldHeight;
-
- // Find the largest ratio, and use that for both
- if (heightRatio < ratio) {
- ratio = heightRatio;
- pWidth = (int) ((float) oldWidth * ratio);
- }
- else {
- pHeight = (int) ((float) oldHeight * ratio);
- }
-
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / (float) oldWidth;
- pHeight = (int) ((float) oldHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / (float) oldHeight;
- pWidth = (int) ((float) oldWidth * ratio);
- }
- // Else: No crop
- }
- }
- // Else: No crop
-
- // Not specified, or outside bounds: Use original dimensions
- if (pWidth < 0 || (pX < 0 && pWidth > oldWidth)
- || (pX >= 0 && (pX + pWidth) > oldWidth)) {
- pWidth = (pX >= 0 ? oldWidth - pX : oldWidth);
- }
- if (pHeight < 0 || (pY < 0 && pHeight > oldHeight)
- || (pY >= 0 && (pY + pHeight) > oldHeight)) {
- pHeight = (pY >= 0 ? oldHeight - pY : oldHeight);
- }
-
- // Center
- if (pX < 0) {
- pX = (pImg.getWidth() - pWidth) / 2;
- }
- if (pY < 0) {
- pY = (pImg.getHeight() - pHeight) / 2;
- }
-
- //System.out.println("x: " + pX + " y: " + pY
- // + " w: " + pWidth + " h " + pHeight);
-
- return new Rectangle(pX, pY, pWidth, pHeight);
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.ServletRequest;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * This Servlet is able to render a cropped part of an image.
+ *
+ *
+ *
+ * Parameters:
+ *
+ * - {@code cropX}
+ * - integer, the new left edge of the image.
+ *
- {@code cropY}
+ * - integer, the new top of the image.
+ *
- {@code cropWidth}
+ * - integer, the new width of the image.
+ *
- {@code cropHeight}
+ * - integer, the new height of the image.
+ *
- {@code cropUniform}
+ * - boolean, wether or not uniform scalnig should be used. Default is
+ * {@code true}.
+ *
- {@code cropUnits}
+ * - string, one of {@code PIXELS}, {@code PERCENT}.
+ * {@code PIXELS} is default.
+ *
+ *
+ *
+ *
- {@code image}
+ * - string, the URL of the image to scale.
+ *
+ *
- {@code scaleX}
+ * - integer, the new width of the image.
+ *
+ *
- {@code scaleY}
+ * - integer, the new height of the image.
+ *
+ *
- {@code scaleUniform}
+ * - boolean, wether or not uniform scalnig should be used. Default is
+ * {@code true}.
+ *
+ *
- {@code scaleUnits}
+ * - string, one of {@code PIXELS}, {@code PERCENT}.
+ * {@code PIXELS} is default.
+ *
+ *
- {@code scaleQuality}
+ * - string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
+ * {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
+ * {@code SCALE_DEFAULT} is default.
+ *
+ *
+ *
+ * @example
+ * <IMG src="/crop/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=500&cropUniform=true">
+ *
+ * @example
+ * <IMG src="/crop/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&cropWidth=50&cropUnits=PERCENT">
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: CropFilter.java#1 $
+ */
+public class CropFilter extends ScaleFilter {
+ /** {@code cropX}*/
+ protected final static String PARAM_CROP_X = "cropX";
+ /** {@code cropY}*/
+ protected final static String PARAM_CROP_Y = "cropY";
+ /** {@code cropWidth}*/
+ protected final static String PARAM_CROP_WIDTH = "cropWidth";
+ /** {@code cropHeight}*/
+ protected final static String PARAM_CROP_HEIGHT = "cropHeight";
+ /** {@code cropUniform}*/
+ protected final static String PARAM_CROP_UNIFORM = "cropUniform";
+ /** {@code cropUnits}*/
+ protected final static String PARAM_CROP_UNITS = "cropUnits";
+
+ /**
+ * Reads the image from the requested URL, scales it, crops it, and returns
+ * it in the
+ * Servlet stream. See above for details on parameters.
+ */
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ // Get crop coordinates
+ int x = ServletUtil.getIntParameter(pRequest, PARAM_CROP_X, -1);
+ int y = ServletUtil.getIntParameter(pRequest, PARAM_CROP_Y, -1);
+ int width = ServletUtil.getIntParameter(pRequest, PARAM_CROP_WIDTH, -1);
+ int height = ServletUtil.getIntParameter(pRequest, PARAM_CROP_HEIGHT, -1);
+
+ boolean uniform =
+ ServletUtil.getBooleanParameter(pRequest, PARAM_CROP_UNIFORM, false);
+
+ int units = getUnits(ServletUtil.getParameter(pRequest, PARAM_CROP_UNITS, null));
+
+ // Get crop bounds
+ Rectangle bounds =
+ getBounds(x, y, width, height, units, uniform, pImage);
+
+ // Return cropped version
+ return pImage.getSubimage((int) bounds.getX(), (int) bounds.getY(),
+ (int) bounds.getWidth(),
+ (int) bounds.getHeight());
+ //return scaled.getSubimage(x, y, width, height);
+ }
+
+ protected Rectangle getBounds(int pX, int pY, int pWidth, int pHeight,
+ int pUnits, boolean pUniform,
+ BufferedImage pImg) {
+ // Algoritm:
+ // Try to get x and y (default 0,0).
+ // Try to get width and height (default width-x, height-y)
+ //
+ // If percent, get ratio
+ //
+ // If uniform
+ //
+
+ int oldWidth = pImg.getWidth();
+ int oldHeight = pImg.getHeight();
+ float ratio;
+
+ if (pUnits == UNITS_PERCENT) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Non-uniform
+ pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
+ pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / 100f;
+ pWidth = (int) ((float) oldWidth * ratio);
+ pHeight = (int) ((float) oldHeight * ratio);
+
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / 100f;
+ pWidth = (int) ((float) oldWidth * ratio);
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+ // Else: No crop
+ }
+ //else if (UNITS_PIXELS.equalsIgnoreCase(pUnits)) {
+ else if (pUnits == UNITS_PIXELS) {
+ // Uniform
+ if (pUniform) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Compute both ratios
+ ratio = (float) pWidth / (float) oldWidth;
+ float heightRatio = (float) pHeight / (float) oldHeight;
+
+ // Find the largest ratio, and use that for both
+ if (heightRatio < ratio) {
+ ratio = heightRatio;
+ pWidth = (int) ((float) oldWidth * ratio);
+ }
+ else {
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / (float) oldWidth;
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / (float) oldHeight;
+ pWidth = (int) ((float) oldWidth * ratio);
+ }
+ // Else: No crop
+ }
+ }
+ // Else: No crop
+
+ // Not specified, or outside bounds: Use original dimensions
+ if (pWidth < 0 || (pX < 0 && pWidth > oldWidth)
+ || (pX >= 0 && (pX + pWidth) > oldWidth)) {
+ pWidth = (pX >= 0 ? oldWidth - pX : oldWidth);
+ }
+ if (pHeight < 0 || (pY < 0 && pHeight > oldHeight)
+ || (pY >= 0 && (pY + pHeight) > oldHeight)) {
+ pHeight = (pY >= 0 ? oldHeight - pY : oldHeight);
+ }
+
+ // Center
+ if (pX < 0) {
+ pX = (pImg.getWidth() - pWidth) / 2;
+ }
+ if (pY < 0) {
+ pY = (pImg.getHeight() - pHeight) / 2;
+ }
+
+ //System.out.println("x: " + pX + " y: " + pY
+ // + " w: " + pWidth + " h " + pHeight);
+
+ return new Rectangle(pX, pY, pWidth, pHeight);
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java
index b0a9e6c0..68970fb4 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java
@@ -1,217 +1,217 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.GenericFilter;
-
-import javax.servlet.*;
-import javax.servlet.http.HttpServletResponse;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import java.io.IOException;
-
-/**
- * Abstract base class for image filters. Automatically decoding and encoding of
- * the image is handled in the {@code doFilterImpl} method.
- *
- * @see #doFilter(java.awt.image.BufferedImage,javax.servlet.ServletRequest,ImageServletResponse)
- *
- * @author Harald Kuhr
- * @version $Id: ImageFilter.java#2 $
- *
- */
-public abstract class ImageFilter extends GenericFilter {
- // TODO: Take the design back to the drawing board (see ImageServletResponseImpl)
- // - Allow multiple filters to set size attribute
- // - Allow a later filter to reset, to get pass-through given certain criteria...
- // - Or better yet, allow a filter to decide if it wants to decode, based on image metadata on the original image (ie: width/height)
-
- protected String[] triggerParams = null;
-
- /**
- * The {@code doFilterImpl} method is called once, or each time a
- * request/response pair is passed through the chain, depending on the
- * {@link #oncePerRequest} member variable.
- *
- * @see #oncePerRequest
- * @see com.twelvemonkeys.servlet.GenericFilter#doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilter
- * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
- *
- * @param pRequest the servlet request
- * @param pResponse the servlet response
- * @param pChain the filter chain
- *
- * @throws IOException
- * @throws ServletException
- */
- protected void doFilterImpl(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pChain)
- throws IOException, ServletException {
-
- //System.out.println("Starting filtering...");
- // Test for trigger params
- if (!trigger(pRequest)) {
- //System.out.println("Passing request on to next in chain (skipping " + getFilterName() + ")...");
- // Pass the request on
- pChain.doFilter(pRequest, pResponse);
- }
- else {
- // If already wrapped, the image will be encoded later in the chain
- // Or, if this is first filter in chain, we must encode when done
- boolean encode = !(pResponse instanceof ImageServletResponse);
-
- // For images, we do post filtering only and need to wrap the response
- ImageServletResponse imageResponse = createImageServletResponse(pRequest, pResponse);
-
- //System.out.println("Passing request on to next in chain...");
- // Pass the request on
- pChain.doFilter(pRequest, imageResponse);
-
- //System.out.println("Post filtering...");
-
- // Get image
- //System.out.println("Getting image from ImageServletResponse...");
- // Get the image from the wrapped response
- RenderedImage image = imageResponse.getImage();
- //System.out.println("Got image: " + image);
-
- // Note: Image will be null if this is a HEAD request, the
- // If-Modified-Since header is present, or similar.
- if (image != null) {
- // Do the image filtering
- //System.out.println("Filtering image (" + getFilterName() + ")...");
- image = doFilter(ImageUtil.toBuffered(image), pRequest, imageResponse);
- //System.out.println("Done filtering.");
-
- //System.out.println("Making image available...");
- // Make image available to other filters (avoid unnecessary serializing/deserializing)
- imageResponse.setImage(image);
- //System.out.println("Done.");
- }
- if (encode) {
- //System.out.println("Encoding image...");
- // Encode image to original response
- if (image != null) {
- // TODO: Be smarter than this...
- // TODO: Make sure ETag is same, if image content is the same...
- // Use ETag of original response (or derived from)
- // Use last modified of original response? Or keep original resource's, don't set at all?
- // TODO: Why weak ETag?
- String etag = "W/\"" + Integer.toHexString(hashCode()) + "-" + Integer.toHexString(image.hashCode()) + "\"";
- // TODO: This breaks for wrapped instances, need to either unwrap or test for HttpSR...
- ((HttpServletResponse) pResponse).setHeader("ETag", etag);
- ((HttpServletResponse) pResponse).setDateHeader("Last-Modified", (System.currentTimeMillis() / 1000) * 1000);
- }
-
- imageResponse.flush();
- //System.out.println("Done encoding.");
- }
- }
- //System.out.println("Filtering done.");
- }
-
- /**
- * Creates the image servlet response for this response.
- *
- * @param pResponse the original response
- * @param pRequest the original request
- * @return the new response, or {@code pResponse} if the response is already wrapped
- *
- * @see com.twelvemonkeys.servlet.image.ImageServletResponseWrapper
- */
- private ImageServletResponse createImageServletResponse(final ServletRequest pRequest, final ServletResponse pResponse) {
- if (pResponse instanceof ImageServletResponseImpl) {
- ImageServletResponseImpl response = (ImageServletResponseImpl) pResponse;
-// response.setRequest(pRequest);
- return response;
- }
-
- return new ImageServletResponseImpl(pRequest, pResponse, getServletContext());
- }
-
- /**
- * Tests if the filter should do image filtering/processing.
- *
- * This default implementation uses {@link #triggerParams} to test if:
- *
- * - {@code mTriggerParams == null}
- * - {@code return true}
- * - {@code mTriggerParams != null}, loop through parameters, and test
- * if {@code pRequest} contains the parameter. If match
- * - {@code return true}
- * - Otherwise
- * - {@code return false}
- *
- *
- *
- * @param pRequest the servlet request
- * @return {@code true} if the filter should do image filtering
- */
- protected boolean trigger(final ServletRequest pRequest) {
- // If triggerParams not set, assume always trigger
- if (triggerParams == null) {
- return true;
- }
-
- // Trigger only for certain request parameters
- for (String triggerParam : triggerParams) {
- if (pRequest.getParameter(triggerParam) != null) {
- return true;
- }
- }
-
- // Didn't trigger
- return false;
- }
-
- /**
- * Sets the trigger parameters.
- * The parameter is supposed to be a comma-separated string of parameter
- * names.
- *
- * @param pTriggerParams a comma-separated string of parameter names.
- */
- // TODO: Make it an @InitParam, and make sure we may set String[]/Collection as parameter?
- public void setTriggerParams(final String pTriggerParams) {
- triggerParams = StringUtil.toStringArray(pTriggerParams);
- }
-
- /**
- * Filters the image for this request.
- *
- * @param pImage the image to filter
- * @param pRequest the servlet request
- * @param pResponse the servlet response
- *
- * @return the filtered image
- * @throws java.io.IOException if an I/O error occurs during filtering
- */
- protected abstract RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException;
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.GenericFilter;
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+
+/**
+ * Abstract base class for image filters. Automatically decoding and encoding of
+ * the image is handled in the {@code doFilterImpl} method.
+ *
+ * @see #doFilter(java.awt.image.BufferedImage,javax.servlet.ServletRequest,ImageServletResponse)
+ *
+ * @author Harald Kuhr
+ * @version $Id: ImageFilter.java#2 $
+ *
+ */
+public abstract class ImageFilter extends GenericFilter {
+ // TODO: Take the design back to the drawing board (see ImageServletResponseImpl)
+ // - Allow multiple filters to set size attribute
+ // - Allow a later filter to reset, to get pass-through given certain criteria...
+ // - Or better yet, allow a filter to decide if it wants to decode, based on image metadata on the original image (ie: width/height)
+
+ protected String[] triggerParams = null;
+
+ /**
+ * The {@code doFilterImpl} method is called once, or each time a
+ * request/response pair is passed through the chain, depending on the
+ * {@link #oncePerRequest} member variable.
+ *
+ * @see #oncePerRequest
+ * @see com.twelvemonkeys.servlet.GenericFilter#doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) doFilter
+ * @see Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) Filter.doFilter
+ *
+ * @param pRequest the servlet request
+ * @param pResponse the servlet response
+ * @param pChain the filter chain
+ *
+ * @throws IOException
+ * @throws ServletException
+ */
+ protected void doFilterImpl(final ServletRequest pRequest, final ServletResponse pResponse, final FilterChain pChain)
+ throws IOException, ServletException {
+
+ //System.out.println("Starting filtering...");
+ // Test for trigger params
+ if (!trigger(pRequest)) {
+ //System.out.println("Passing request on to next in chain (skipping " + getFilterName() + ")...");
+ // Pass the request on
+ pChain.doFilter(pRequest, pResponse);
+ }
+ else {
+ // If already wrapped, the image will be encoded later in the chain
+ // Or, if this is first filter in chain, we must encode when done
+ boolean encode = !(pResponse instanceof ImageServletResponse);
+
+ // For images, we do post filtering only and need to wrap the response
+ ImageServletResponse imageResponse = createImageServletResponse(pRequest, pResponse);
+
+ //System.out.println("Passing request on to next in chain...");
+ // Pass the request on
+ pChain.doFilter(pRequest, imageResponse);
+
+ //System.out.println("Post filtering...");
+
+ // Get image
+ //System.out.println("Getting image from ImageServletResponse...");
+ // Get the image from the wrapped response
+ RenderedImage image = imageResponse.getImage();
+ //System.out.println("Got image: " + image);
+
+ // Note: Image will be null if this is a HEAD request, the
+ // If-Modified-Since header is present, or similar.
+ if (image != null) {
+ // Do the image filtering
+ //System.out.println("Filtering image (" + getFilterName() + ")...");
+ image = doFilter(ImageUtil.toBuffered(image), pRequest, imageResponse);
+ //System.out.println("Done filtering.");
+
+ //System.out.println("Making image available...");
+ // Make image available to other filters (avoid unnecessary serializing/deserializing)
+ imageResponse.setImage(image);
+ //System.out.println("Done.");
+ }
+ if (encode) {
+ //System.out.println("Encoding image...");
+ // Encode image to original response
+ if (image != null) {
+ // TODO: Be smarter than this...
+ // TODO: Make sure ETag is same, if image content is the same...
+ // Use ETag of original response (or derived from)
+ // Use last modified of original response? Or keep original resource's, don't set at all?
+ // TODO: Why weak ETag?
+ String etag = "W/\"" + Integer.toHexString(hashCode()) + "-" + Integer.toHexString(image.hashCode()) + "\"";
+ // TODO: This breaks for wrapped instances, need to either unwrap or test for HttpSR...
+ ((HttpServletResponse) pResponse).setHeader("ETag", etag);
+ ((HttpServletResponse) pResponse).setDateHeader("Last-Modified", (System.currentTimeMillis() / 1000) * 1000);
+ }
+
+ imageResponse.flush();
+ //System.out.println("Done encoding.");
+ }
+ }
+ //System.out.println("Filtering done.");
+ }
+
+ /**
+ * Creates the image servlet response for this response.
+ *
+ * @param pResponse the original response
+ * @param pRequest the original request
+ * @return the new response, or {@code pResponse} if the response is already wrapped
+ *
+ * @see com.twelvemonkeys.servlet.image.ImageServletResponseWrapper
+ */
+ private ImageServletResponse createImageServletResponse(final ServletRequest pRequest, final ServletResponse pResponse) {
+ if (pResponse instanceof ImageServletResponseImpl) {
+ ImageServletResponseImpl response = (ImageServletResponseImpl) pResponse;
+// response.setRequest(pRequest);
+ return response;
+ }
+
+ return new ImageServletResponseImpl(pRequest, pResponse, getServletContext());
+ }
+
+ /**
+ * Tests if the filter should do image filtering/processing.
+ *
+ * This default implementation uses {@link #triggerParams} to test if:
+ *
+ * - {@code mTriggerParams == null}
+ * - {@code return true}
+ * - {@code mTriggerParams != null}, loop through parameters, and test
+ * if {@code pRequest} contains the parameter. If match
+ * - {@code return true}
+ * - Otherwise
+ * - {@code return false}
+ *
+ *
+ *
+ * @param pRequest the servlet request
+ * @return {@code true} if the filter should do image filtering
+ */
+ protected boolean trigger(final ServletRequest pRequest) {
+ // If triggerParams not set, assume always trigger
+ if (triggerParams == null) {
+ return true;
+ }
+
+ // Trigger only for certain request parameters
+ for (String triggerParam : triggerParams) {
+ if (pRequest.getParameter(triggerParam) != null) {
+ return true;
+ }
+ }
+
+ // Didn't trigger
+ return false;
+ }
+
+ /**
+ * Sets the trigger parameters.
+ * The parameter is supposed to be a comma-separated string of parameter
+ * names.
+ *
+ * @param pTriggerParams a comma-separated string of parameter names.
+ */
+ // TODO: Make it an @InitParam, and make sure we may set String[]/Collection as parameter?
+ public void setTriggerParams(final String pTriggerParams) {
+ triggerParams = StringUtil.toStringArray(pTriggerParams);
+ }
+
+ /**
+ * Filters the image for this request.
+ *
+ * @param pImage the image to filter
+ * @param pRequest the servlet request
+ * @param pResponse the servlet response
+ *
+ * @return the filtered image
+ * @throws java.io.IOException if an I/O error occurs during filtering
+ */
+ protected abstract RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) throws IOException;
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java
index bb4c1022..93143ffb 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java
@@ -1,55 +1,55 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import javax.servlet.*;
-
-/**
- * This exception is a subclass of ServletException, and acts just as a marker
- * for exceptions thrown by the ImageServlet API.
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: ImageServletException.java#2 $
- */
-public class ImageServletException extends ServletException {
-
- public ImageServletException(String pMessage) {
- super(pMessage);
- }
-
- public ImageServletException(Throwable pThrowable) {
- super(pThrowable);
- }
-
- public ImageServletException(String pMessage, Throwable pThrowable) {
- super(pMessage, pThrowable);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import javax.servlet.*;
+
+/**
+ * This exception is a subclass of ServletException, and acts just as a marker
+ * for exceptions thrown by the ImageServlet API.
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: ImageServletException.java#2 $
+ */
+public class ImageServletException extends ServletException {
+
+ public ImageServletException(String pMessage) {
+ super(pMessage);
+ }
+
+ public ImageServletException(Throwable pThrowable) {
+ super(pThrowable);
+ }
+
+ public ImageServletException(String pMessage, Throwable pThrowable) {
+ super(pMessage, pThrowable);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java
index fe8ffd84..25fdf89e 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java
@@ -1,193 +1,193 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import javax.servlet.ServletResponse;
-import java.io.IOException;
-import java.awt.image.RenderedImage;
-import java.awt.image.BufferedImage;
-
-/**
- * ImageServletResponse.
- *
- * The request attributes regarding image size and source region (AOI) are used
- * in the decoding process, and must be set before the first invocation of
- * {@link #getImage()} to have any effect.
- *
- * @author Harald Kuhr
- * @version $Id: ImageServletResponse.java#4 $
- */
-public interface ImageServletResponse extends ServletResponse {
- /**
- * Request attribute of type {@link java.awt.Dimension} controlling image
- * size.
- * If either {@code width} or {@code height} is negative, the size is
- * computed, using uniform scaling.
- * Else, if {@code SIZE_UNIFORM} is {@code true}, the size will be
- * computed to the largest possible area (with correct aspect ratio)
- * fitting inside the target area.
- * Otherwise, the image is scaled to the given size, with no regard to
- * aspect ratio.
- *
- * Defaults to {@code null} (original image size).
- */
- String ATTRIB_SIZE = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE";
-
- /**
- * Request attribute of type {@link Boolean} controlling image sizing.
- *
- * Defaults to {@code Boolean.TRUE}.
- */
- String ATTRIB_SIZE_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_UNIFORM";
-
- /**
- * Request attribute of type {@link Boolean} controlling image sizing.
- *
- * Defaults to {@code Boolean.FALSE}.
- */
- String ATTRIB_SIZE_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_PERCENT";
-
- /**
- * Request attribute of type {@link java.awt.Rectangle} controlling image
- * source region (area of interest).
- *
- * Defaults to {@code null} (the entire image).
- */
- String ATTRIB_AOI = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI";
-
- /**
- * Request attribute of type {@link Boolean} controlling image AOI.
- *
- * Defaults to {@code Boolean.FALSE}.
- */
- String ATTRIB_AOI_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_UNIFORM";
-
- /**
- * Request attribute of type {@link Boolean} controlling image AOI.
- *
- * Defaults to {@code Boolean.FALSE}.
- */
- String ATTRIB_AOI_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_PERCENT";
-
- /**
- * Request attribute of type {@link java.awt.Color} controlling background
- * color for any transparent/translucent areas of the image.
- *
- * Defaults to {@code null} (keeps the transparent areas transparent).
- */
- String ATTRIB_BG_COLOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.BG_COLOR";
-
- /**
- * Request attribute of type {@link Float} controlling image output compression/quality.
- * Used for formats that accepts compression or quality settings,
- * like JPEG (quality), PNG (compression only) etc.
- *
- * Defaults to {@code 0.8f} for JPEG.
- */
- String ATTRIB_OUTPUT_QUALITY = "com.twelvemonkeys.servlet.image.ImageServletResponse.OUTPUT_QUALITY";
-
- /**
- * Request attribute of type {@link Double} controlling image read
- * subsampling factor. Controls the maximum sample pixels in each direction,
- * that is read per pixel in the output image, if the result will be
- * downscaled.
- * Larger values will result in better quality, at the expense of higher
- * memory consumption and CPU usage.
- * However, using values above {@code 3.0} will usually not improve image
- * quality.
- * Legal values are in the range {@code [1.0 .. positive infinity>}.
- *
- * Defaults to {@code 2.0}.
- */
- String ATTRIB_READ_SUBSAMPLING_FACTOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.READ_SUBSAMPLING_FACTOR";
-
- /**
- * Request attribute of type {@link Integer} controlling image resample
- * algorithm.
- * Legal values are {@link java.awt.Image#SCALE_DEFAULT SCALE_DEFAULT},
- * {@link java.awt.Image#SCALE_FAST SCALE_FAST} or
- * {@link java.awt.Image#SCALE_SMOOTH SCALE_SMOOTH}.
- *
- * Note: When using a value of {@code SCALE_FAST}, you should also use a
- * subsampling factor of {@code 1.0}, for fast read/scale.
- * Otherwise, use a subsampling factor of {@code 2.0} for better quality.
- *
- * Defaults to {@code SCALE_DEFAULT}.
- */
- String ATTRIB_IMAGE_RESAMPLE_ALGORITHM = "com.twelvemonkeys.servlet.image.ImageServletResponse.IMAGE_RESAMPLE_ALGORITHM";
-
- /**
- * Gets the image format for this response, such as "image/gif" or "image/jpeg".
- * If not set, the default format is that of the original image.
- *
- * @return the image format for this response.
- * @see #setOutputContentType(String)
- */
- String getOutputContentType();
-
- /**
- * Sets the image format for this response, such as "image/gif" or "image/jpeg".
- *
- * As an example, a custom filter could do content negotiation based on the
- * request header fields and write the image back in an appropriate format.
- *
- * If not set, the default format is that of the original image.
- *
- * @param pImageFormat the image format for this response.
- */
- void setOutputContentType(String pImageFormat);
-
- //TODO: ?? void setCompressionQuality(float pQualityFactor);
- //TODO: ?? float getCompressionQuality();
-
- /**
- * Writes the image to the original {@code ServletOutputStream}.
- * If no format is {@linkplain #setOutputContentType(String) set} in this response,
- * the image is encoded in the same format as the original image.
- *
- * @throws java.io.IOException if an I/O exception occurs during writing
- */
- void flush() throws IOException;
-
- /**
- * Gets the decoded image from the response.
- *
- * @return a {@code BufferedImage} or {@code null} if the image could not be read.
- *
- * @throws java.io.IOException if an I/O exception occurs during reading
- */
- BufferedImage getImage() throws IOException;
-
- /**
- * Sets the image for this response.
- *
- * @param pImage the new response image.
- */
- void setImage(RenderedImage pImage);
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
+
+/**
+ * ImageServletResponse.
+ *
+ * The request attributes regarding image size and source region (AOI) are used
+ * in the decoding process, and must be set before the first invocation of
+ * {@link #getImage()} to have any effect.
+ *
+ * @author Harald Kuhr
+ * @version $Id: ImageServletResponse.java#4 $
+ */
+public interface ImageServletResponse extends ServletResponse {
+ /**
+ * Request attribute of type {@link java.awt.Dimension} controlling image
+ * size.
+ * If either {@code width} or {@code height} is negative, the size is
+ * computed, using uniform scaling.
+ * Else, if {@code SIZE_UNIFORM} is {@code true}, the size will be
+ * computed to the largest possible area (with correct aspect ratio)
+ * fitting inside the target area.
+ * Otherwise, the image is scaled to the given size, with no regard to
+ * aspect ratio.
+ *
+ * Defaults to {@code null} (original image size).
+ */
+ String ATTRIB_SIZE = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE";
+
+ /**
+ * Request attribute of type {@link Boolean} controlling image sizing.
+ *
+ * Defaults to {@code Boolean.TRUE}.
+ */
+ String ATTRIB_SIZE_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_UNIFORM";
+
+ /**
+ * Request attribute of type {@link Boolean} controlling image sizing.
+ *
+ * Defaults to {@code Boolean.FALSE}.
+ */
+ String ATTRIB_SIZE_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.SIZE_PERCENT";
+
+ /**
+ * Request attribute of type {@link java.awt.Rectangle} controlling image
+ * source region (area of interest).
+ *
+ * Defaults to {@code null} (the entire image).
+ */
+ String ATTRIB_AOI = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI";
+
+ /**
+ * Request attribute of type {@link Boolean} controlling image AOI.
+ *
+ * Defaults to {@code Boolean.FALSE}.
+ */
+ String ATTRIB_AOI_UNIFORM = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_UNIFORM";
+
+ /**
+ * Request attribute of type {@link Boolean} controlling image AOI.
+ *
+ * Defaults to {@code Boolean.FALSE}.
+ */
+ String ATTRIB_AOI_PERCENT = "com.twelvemonkeys.servlet.image.ImageServletResponse.AOI_PERCENT";
+
+ /**
+ * Request attribute of type {@link java.awt.Color} controlling background
+ * color for any transparent/translucent areas of the image.
+ *
+ * Defaults to {@code null} (keeps the transparent areas transparent).
+ */
+ String ATTRIB_BG_COLOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.BG_COLOR";
+
+ /**
+ * Request attribute of type {@link Float} controlling image output compression/quality.
+ * Used for formats that accepts compression or quality settings,
+ * like JPEG (quality), PNG (compression only) etc.
+ *
+ * Defaults to {@code 0.8f} for JPEG.
+ */
+ String ATTRIB_OUTPUT_QUALITY = "com.twelvemonkeys.servlet.image.ImageServletResponse.OUTPUT_QUALITY";
+
+ /**
+ * Request attribute of type {@link Double} controlling image read
+ * subsampling factor. Controls the maximum sample pixels in each direction,
+ * that is read per pixel in the output image, if the result will be
+ * downscaled.
+ * Larger values will result in better quality, at the expense of higher
+ * memory consumption and CPU usage.
+ * However, using values above {@code 3.0} will usually not improve image
+ * quality.
+ * Legal values are in the range {@code [1.0 .. positive infinity>}.
+ *
+ * Defaults to {@code 2.0}.
+ */
+ String ATTRIB_READ_SUBSAMPLING_FACTOR = "com.twelvemonkeys.servlet.image.ImageServletResponse.READ_SUBSAMPLING_FACTOR";
+
+ /**
+ * Request attribute of type {@link Integer} controlling image resample
+ * algorithm.
+ * Legal values are {@link java.awt.Image#SCALE_DEFAULT SCALE_DEFAULT},
+ * {@link java.awt.Image#SCALE_FAST SCALE_FAST} or
+ * {@link java.awt.Image#SCALE_SMOOTH SCALE_SMOOTH}.
+ *
+ * Note: When using a value of {@code SCALE_FAST}, you should also use a
+ * subsampling factor of {@code 1.0}, for fast read/scale.
+ * Otherwise, use a subsampling factor of {@code 2.0} for better quality.
+ *
+ * Defaults to {@code SCALE_DEFAULT}.
+ */
+ String ATTRIB_IMAGE_RESAMPLE_ALGORITHM = "com.twelvemonkeys.servlet.image.ImageServletResponse.IMAGE_RESAMPLE_ALGORITHM";
+
+ /**
+ * Gets the image format for this response, such as "image/gif" or "image/jpeg".
+ * If not set, the default format is that of the original image.
+ *
+ * @return the image format for this response.
+ * @see #setOutputContentType(String)
+ */
+ String getOutputContentType();
+
+ /**
+ * Sets the image format for this response, such as "image/gif" or "image/jpeg".
+ *
+ * As an example, a custom filter could do content negotiation based on the
+ * request header fields and write the image back in an appropriate format.
+ *
+ * If not set, the default format is that of the original image.
+ *
+ * @param pImageFormat the image format for this response.
+ */
+ void setOutputContentType(String pImageFormat);
+
+ //TODO: ?? void setCompressionQuality(float pQualityFactor);
+ //TODO: ?? float getCompressionQuality();
+
+ /**
+ * Writes the image to the original {@code ServletOutputStream}.
+ * If no format is {@linkplain #setOutputContentType(String) set} in this response,
+ * the image is encoded in the same format as the original image.
+ *
+ * @throws java.io.IOException if an I/O exception occurs during writing
+ */
+ void flush() throws IOException;
+
+ /**
+ * Gets the decoded image from the response.
+ *
+ * @return a {@code BufferedImage} or {@code null} if the image could not be read.
+ *
+ * @throws java.io.IOException if an I/O exception occurs during reading
+ */
+ BufferedImage getImage() throws IOException;
+
+ /**
+ * Sets the image for this response.
+ *
+ * @param pImage the new response image.
+ */
+ void setImage(RenderedImage pImage);
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java
index 2191885b..0c83380d 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java
@@ -1,805 +1,805 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.io.FastByteArrayOutputStream;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
-import com.twelvemonkeys.servlet.ServletUtil;
-import com.twelvemonkeys.servlet.image.aoi.AreaOfInterest;
-import com.twelvemonkeys.servlet.image.aoi.AreaOfInterestFactory;
-
-import javax.imageio.*;
-import javax.imageio.stream.ImageInputStream;
-import javax.imageio.stream.ImageOutputStream;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpServletResponseWrapper;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.IndexColorModel;
-import java.awt.image.RenderedImage;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.lang.reflect.Method;
-import java.net.URL;
-import java.util.Iterator;
-
-/**
- * This {@link ImageServletResponse} implementation can be used with image
- * requests, to have the image immediately decoded to a {@code BufferedImage}.
- * The image may be optionally subsampled, scaled and/or cropped.
- * The response also automatically handles writing the image back to the underlying response stream
- * in the preferred format, when the response is flushed.
- *
- * @author Harald Kuhr
- * @version $Id: ImageServletResponseImpl.java#10 $
- */
-// TODO: Refactor out HTTP specifics (if possible).
-// TODO: Is it a good ide to throw IIOException?
-// TODO: This implementation has a problem if two filters does scaling, as the second will overwrite the SIZE attribute
-// TODO: Allow different scaling algorithm based on input image (use case: IndexColorModel does not scale well using default, smooth may be slow for large images)
-// TODO: Support pluggable pre- and post-processing steps
-class ImageServletResponseImpl extends HttpServletResponseWrapper implements ImageServletResponse {
- private ServletRequest originalRequest;
- private final ServletContext context;
- private final ServletResponseStreamDelegate streamDelegate;
-
- private FastByteArrayOutputStream bufferedOut;
-
- private RenderedImage image;
- private String outputContentType;
-
- private String originalContentType;
- private int originalContentLength = -1;
-
- /**
- * Creates an {@code ImageServletResponseImpl}.
- *
- * @param pRequest the request
- * @param pResponse the response
- * @param pContext the servlet context
- */
- public ImageServletResponseImpl(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final ServletContext pContext) {
- super(pResponse);
- originalRequest = pRequest;
- streamDelegate = new ServletResponseStreamDelegate(pResponse) {
- @Override
- protected OutputStream createOutputStream() throws IOException {
- if (originalContentLength >= 0) {
- bufferedOut = new FastByteArrayOutputStream(originalContentLength);
- }
- else {
- bufferedOut = new FastByteArrayOutputStream(0);
- }
-
- return bufferedOut;
- }
- };
- context = pContext;
- }
-
- /**
- * Creates an {@code ImageServletResponseImpl}.
- *
- * @param pRequest the request
- * @param pResponse the response
- * @param pContext the servlet context
- *
- * @throws ClassCastException if {@code pRequest} is not an {@link javax.servlet.http.HttpServletRequest} or
- * {@code pResponse} is not an {@link javax.servlet.http.HttpServletResponse}.
- */
- public ImageServletResponseImpl(final ServletRequest pRequest, final ServletResponse pResponse, final ServletContext pContext) {
- // Cheat for now...
- this((HttpServletRequest) pRequest, (HttpServletResponse) pResponse, pContext);
- }
-
- /**
- * Called by the container, do not invoke.
- *
- * @param pMimeType the content (MIME) type
- */
- public void setContentType(final String pMimeType) {
- // Throw exception is already set
- if (originalContentType != null) {
- throw new IllegalStateException("ContentType already set.");
- }
-
- originalContentType = pMimeType;
- }
-
- /**
- * Called by the container. Do not invoke.
- *
- * @return the response's {@code OutputStream}
- * @throws IOException
- */
- public ServletOutputStream getOutputStream() throws IOException {
- return streamDelegate.getOutputStream();
- }
-
- /**
- * Called by the container. Do not invoke.
- *
- * @return the response's {@code PrintWriter}
- * @throws IOException
- */
- public PrintWriter getWriter() throws IOException {
- return streamDelegate.getWriter();
- }
-
- /**
- * Called by the container. Do not invoke.
- *
- * @param pLength the content length
- */
- public void setContentLength(final int pLength) {
- if (originalContentLength != -1) {
- throw new IllegalStateException("ContentLength already set.");
- }
-
- originalContentLength = pLength;
- }
-
- @Override
- public void setHeader(String name, String value) {
- // NOTE: Clients could also specify content type/content length using the setHeader method, special handling
- if (name != null && name.equals("Content-Length")) {
- setContentLength(Integer.valueOf(value)); // Value might be too large, but we don't support that anyway
- }
- else if (name != null && name.equals("Content-Type")) {
- setContentType(value);
- }
- else {
- super.setHeader(name, value);
- }
- }
-
- /**
- * Writes the image to the original {@code ServletOutputStream}.
- * If no format is set in this response, the image is encoded in the same
- * format as the original image.
- *
- * @throws IOException if an I/O exception occurs during writing
- */
- public void flush() throws IOException {
- String outputType = getOutputContentType();
-
- // Force transcoding, if no other filtering is done
- if (outputType != null && !outputType.equals(originalContentType)) {
- getImage();
- }
-
- if (image != null) {
- Iterator writers = ImageIO.getImageWritersByMIMEType(outputType);
- if (writers.hasNext()) {
- super.setContentType(outputType);
- OutputStream out = super.getOutputStream();
- try {
- ImageWriter writer = (ImageWriter) writers.next();
- try {
- ImageWriteParam param = writer.getDefaultWriteParam();
- ///////////////////
- // POST-PROCESS
- // For known formats that don't support transparency, convert to opaque
- if (isNonAlphaFormat(outputType) && image.getColorModel().getTransparency() != Transparency.OPAQUE) {
- image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_RGB);
- }
-
- Float requestQuality = (Float) originalRequest.getAttribute(ImageServletResponse.ATTRIB_OUTPUT_QUALITY);
-
- // The default JPEG quality is not good enough, so always adjust compression/quality
- if ((requestQuality != null || "jpeg".equalsIgnoreCase(getFormatNameSafe(writer))) && param.canWriteCompressed()) {
- // TODO: See http://blog.apokalyptik.com/2009/09/16/quality-time-with-your-jpegs/ for better adjusting the (default) JPEG quality
- // OR: Use the metadata of the original image
-
- param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
-
- // WORKAROUND: Known bug in GIFImageWriter in certain JDK versions, compression type is not set by default
- if (param.getCompressionTypes() != null && param.getCompressionType() == null) {
- param.setCompressionType(param.getCompressionTypes()[0]); // Just choose any, to keep param happy
- }
-
- param.setCompressionQuality(requestQuality != null ? requestQuality : 0.8f);
- }
-
- if ("gif".equalsIgnoreCase(getFormatNameSafe(writer)) && !(image.getColorModel() instanceof IndexColorModel)
- /*&& image.getColorModel().getTransparency() != Transparency.OPAQUE*/) {
- // WORKAROUND: Bug in GIFImageWriter may throw NPE if transparent pixels
- // See: http://bugs.sun.com/view_bug.do?bug_id=6287936
- image = ImageUtil.createIndexed(
- ImageUtil.toBuffered(image), 256, null,
- (image.getColorModel().getTransparency() == Transparency.OPAQUE ? ImageUtil.TRANSPARENCY_OPAQUE : ImageUtil.TRANSPARENCY_BITMASK) | ImageUtil.DITHER_DIFFUSION_ALTSCANS
- );
- }
- //////////////////
- ImageOutputStream stream = ImageIO.createImageOutputStream(out);
-
- writer.setOutput(stream);
- try {
- writer.write(null, new IIOImage(image, null, null), param);
- }
- finally {
- stream.close();
- }
- }
- finally {
- writer.dispose();
- }
- }
- finally {
- out.flush();
- }
- }
- else {
- context.log("ERROR: No writer for content-type: " + outputType);
- throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ").");
- }
- }
- else {
- super.setContentType(originalContentType);
-
- ServletOutputStream out = super.getOutputStream();
-
- try {
- if (bufferedOut != null) {
- bufferedOut.writeTo(out);
- }
- }
- finally {
- out.flush();
- }
- }
- }
-
- private boolean isNonAlphaFormat(String outputType) {
- return "image/jpeg".equals(outputType) || "image/jpg".equals(outputType) ||
- "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType);
- }
-
- private String getFormatNameSafe(final ImageWriter pWriter) {
- try {
- return pWriter.getOriginatingProvider().getFormatNames()[0];
- }
- catch (RuntimeException e) {
- // NPE, AIOOBE, etc..
- return null;
- }
- }
-
- public String getOutputContentType() {
- return outputContentType != null ? outputContentType : originalContentType;
- }
-
- public void setOutputContentType(final String pImageFormat) {
- outputContentType = pImageFormat;
- }
-
- /**
- * Sets the image for this response.
- *
- * @param pImage the {@code RenderedImage} that will be written to the
- * response stream
- */
- public void setImage(final RenderedImage pImage) {
- image = pImage;
- }
-
- /**
- * Gets the decoded image from the response.
- *
- * @return a {@code BufferedImage} or {@code null} if the image could
- * not be read.
- *
- * @throws java.io.IOException if an I/O exception occurs during reading
- */
- public BufferedImage getImage() throws IOException {
- if (image == null) {
- // No content, no image
- if (bufferedOut == null) {
- return null;
- }
-
- // Read from the byte buffer
- InputStream byteStream = bufferedOut.createInputStream();
- ImageInputStream input = null;
- try {
- input = ImageIO.createImageInputStream(byteStream);
- Iterator readers = ImageIO.getImageReaders(input);
- if (readers.hasNext()) {
- // Get the correct reader
- ImageReader reader = (ImageReader) readers.next();
- try {
- reader.setInput(input);
-
- ImageReadParam param = reader.getDefaultReadParam();
-
- // Get default size
- int originalWidth = reader.getWidth(0);
- int originalHeight = reader.getHeight(0);
-//////////////////
-// PRE-PROCESS (prepare): param, size, format?, request, response?
- // TODO: AOI strategy?
- // Extract AOI from request
- Rectangle aoi = extractAOIFromRequest(originalWidth, originalHeight, originalRequest);
-
- if (aoi != null) {
- param.setSourceRegion(aoi);
- originalWidth = aoi.width;
- originalHeight = aoi.height;
- }
-
- // TODO: Size and subsampling strategy?
- // If possible, extract size from request
- Dimension size = extractSizeFromRequest(originalWidth, originalHeight, originalRequest);
- double readSubSamplingFactor = getReadSubsampleFactorFromRequest(originalRequest);
-
- if (size != null) {
- //System.out.println("Size: " + size);
- if (param.canSetSourceRenderSize()) {
- param.setSourceRenderSize(size);
- }
- else {
- int subX = (int) Math.max(originalWidth / (size.width * readSubSamplingFactor), 1.0);
- int subY = (int) Math.max(originalHeight / (size.height * readSubSamplingFactor), 1.0);
-
- if (subX > 1 || subY > 1) {
- param.setSourceSubsampling(subX, subY, subX > 1 ? subX / 2 : 0, subY > 1 ? subY / 2 : 0);
- }
- }
- }
-
- // Need base URI for SVG with links/stylesheets etc
- maybeSetBaseURIFromRequest(param);
-
-/////////////////////
-
- // Finally, read the image using the supplied parameter
- BufferedImage image = reader.read(0, param);
-
- // TODO: If we sub-sampled, it would be a good idea to blur before resampling,
- // to avoid jagged lines artifacts
-
- // If reader doesn't support dynamic sizing, scale now
- image = resampleImage(image, size);
-
- // Fill bgcolor behind image, if transparent
- extractAndSetBackgroundColor(image); // TODO: Move to flush/POST-PROCESS
-
- // Set image
- this.image = image;
- }
- finally {
- reader.dispose();
- }
- }
- else {
- context.log("ERROR: No suitable image reader found (content-type: " + originalContentType + ").");
- context.log("ERROR: Available formats: " + getFormatsString());
-
- throw new IIOException("Unable to transcode image: No suitable image reader found (content-type: " + originalContentType + ").");
- }
-
- // Free resources, as the image is now either read, or unreadable
- bufferedOut = null;
- }
- finally {
- if (input != null) {
- input.close();
- }
- }
- }
-
- // Image is usually a BufferedImage, but may also be a RenderedImage
- return image != null ? ImageUtil.toBuffered(image) : null;
- }
-
- private BufferedImage resampleImage(final BufferedImage image, final Dimension size) {
- if (image != null && size != null && (image.getWidth() != size.width || image.getHeight() != size.height)) {
- int resampleAlgorithm = getResampleAlgorithmFromRequest();
-
- // TODO: One possibility is to NOT handle index color here, and only handle it later, IF NEEDED (read: GIF,
- // possibly also for PNG) when we know the output format (flush method).
- // This will make the filter faster (and better quality, possibly at the expense of more bytes being sent
- // over the wire) in the general case. Who uses GIF nowadays anyway?
- // Also, this means we could either keep the original IndexColorModel in the filter, or go through the
- // expensive operation of re-calculating the optimal palette for the new image (the latter might improve quality).
-
- // NOTE: Only use createScaled if IndexColorModel, as it's more expensive due to color conversion
-/* if (image.getColorModel() instanceof IndexColorModel) {
-// return ImageUtil.createScaled(image, size.width, size.height, resampleAlgorithm);
- BufferedImage resampled = ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm);
- return ImageUtil.createIndexed(resampled, (IndexColorModel) image.getColorModel(), null, ImageUtil.DITHER_NONE | ImageUtil.TRANSPARENCY_BITMASK);
-// return ImageUtil.createIndexed(resampled, 256, null, ImageUtil.COLOR_SELECTION_QUALITY | ImageUtil.DITHER_NONE | ImageUtil.TRANSPARENCY_BITMASK);
- }
- else {
- */
- return ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm);
-// }
- }
- return image;
- }
-
- int getResampleAlgorithmFromRequest() {
- Object algorithm = originalRequest.getAttribute(ATTRIB_IMAGE_RESAMPLE_ALGORITHM);
- if (algorithm instanceof Integer && ((Integer) algorithm == Image.SCALE_SMOOTH || (Integer) algorithm == Image.SCALE_FAST || (Integer) algorithm == Image.SCALE_DEFAULT)) {
- return (Integer) algorithm;
- }
- else {
- if (algorithm != null) {
- context.log("WARN: Illegal image resampling algorithm: " + algorithm);
- }
-
- return BufferedImage.SCALE_DEFAULT;
- }
- }
-
- private double getReadSubsampleFactorFromRequest(final ServletRequest pOriginalRequest) {
- double subsampleFactor;
-
- Object factor = pOriginalRequest.getAttribute(ATTRIB_READ_SUBSAMPLING_FACTOR);
- if (factor instanceof Number && ((Number) factor).doubleValue() >= 1.0) {
- subsampleFactor = ((Number) factor).doubleValue();
- }
- else {
- if (factor != null) {
- context.log("WARN: Illegal read subsampling factor: " + factor);
- }
-
- subsampleFactor = 2.0;
- }
-
- return subsampleFactor;
- }
-
- private void extractAndSetBackgroundColor(final BufferedImage pImage) {
- // TODO: bgColor request attribute instead of parameter?
- if (pImage.getColorModel().hasAlpha()) {
- String bgColor = originalRequest.getParameter("bg.color");
- if (bgColor != null) {
- Color color = StringUtil.toColor(bgColor);
-
- Graphics2D g = pImage.createGraphics();
- try {
- g.setColor(color);
- g.setComposite(AlphaComposite.DstOver);
- g.fillRect(0, 0, pImage.getWidth(), pImage.getHeight());
- }
- finally {
- g.dispose();
- }
- }
- }
- }
-
- private static String getFormatsString() {
- String[] formats = ImageIO.getReaderFormatNames();
- StringBuilder buf = new StringBuilder();
- for (int i = 0; i < formats.length; i++) {
- String format = formats[i];
- if (i > 0) {
- buf.append(", ");
- }
- buf.append(format);
- }
- return buf.toString();
- }
-
- private void maybeSetBaseURIFromRequest(final ImageReadParam pParam) {
- if (originalRequest instanceof HttpServletRequest) {
- try {
- // If there's a setBaseURI method, we'll try to use that (uses reflection, to avoid dependency on plugins)
- Method setBaseURI;
- try {
- setBaseURI = pParam.getClass().getMethod("setBaseURI", String.class);
- }
- catch (NoSuchMethodException ignore) {
- return;
- }
-
- // Get URL for resource and set as base
- String baseURI = ServletUtil.getContextRelativeURI((HttpServletRequest) originalRequest);
-
- URL resourceURL = context.getResource(baseURI);
-
- if (resourceURL == null) {
- resourceURL = ServletUtil.getRealURL(context, baseURI);
- }
-
- if (resourceURL != null) {
- setBaseURI.invoke(pParam, resourceURL.toExternalForm());
- }
- else {
- context.log("WARN: Resource URL not found for URI: " + baseURI);
- }
- }
- catch (Exception e) {
- context.log("WARN: Could not set base URI: ", e);
- }
- }
- }
-
- private Dimension extractSizeFromRequest(final int pDefaultWidth, final int pDefaultHeight, final ServletRequest pOriginalRequest) {
- // TODO: Allow extraction from request parameters
- /*
- int sizeW = ServletUtil.getIntParameter(originalRequest, "size.w", -1);
- int sizeH = ServletUtil.getIntParameter(originalRequest, "size.h", -1);
- boolean sizePercent = ServletUtil.getBooleanParameter(originalRequest, "size.percent", false);
- boolean sizeUniform = ServletUtil.getBooleanParameter(originalRequest, "size.uniform", true);
- */
- Dimension size = (Dimension) pOriginalRequest.getAttribute(ATTRIB_SIZE);
- int sizeW = size != null ? size.width : -1;
- int sizeH = size != null ? size.height : -1;
-
- Boolean b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_SIZE_PERCENT);
- boolean sizePercent = b != null && b; // default: false
-
- b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_SIZE_UNIFORM);
- boolean sizeUniform = b == null || b; // default: true
-
- if (sizeW >= 0 || sizeH >= 0) {
- size = getSize(pDefaultWidth, pDefaultHeight, sizeW, sizeH, sizePercent, sizeUniform);
- }
-
- return size;
- }
-
- private Rectangle extractAOIFromRequest(final int pDefaultWidth, final int pDefaultHeight, final ServletRequest pOriginalRequest) {
- // TODO: Allow extraction from request parameters
- /*
- int aoiX = ServletUtil.getIntParameter(originalRequest, "aoi.x", -1);
- int aoiY = ServletUtil.getIntParameter(originalRequest, "aoi.y", -1);
- int aoiW = ServletUtil.getIntParameter(originalRequest, "aoi.w", -1);
- int aoiH = ServletUtil.getIntParameter(originalRequest, "aoi.h", -1);
- boolean aoiPercent = ServletUtil.getBooleanParameter(originalRequest, "aoi.percent", false);
- boolean aoiUniform = ServletUtil.getBooleanParameter(originalRequest, "aoi.uniform", false);
- */
- Rectangle aoi = (Rectangle) pOriginalRequest.getAttribute(ATTRIB_AOI);
- int aoiX = aoi != null ? aoi.x : -1;
- int aoiY = aoi != null ? aoi.y : -1;
- int aoiW = aoi != null ? aoi.width : -1;
- int aoiH = aoi != null ? aoi.height : -1;
-
- Boolean b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_AOI_PERCENT);
- boolean aoiPercent = b != null && b; // default: false
-
- b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_AOI_UNIFORM);
- boolean aoiUniform = b != null && b; // default: false
-
- if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) {
-
- AreaOfInterest areaOfInterest = AreaOfInterestFactory.getDefault().
- createAreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform);
- aoi = areaOfInterest.getAOI(new Rectangle(aoiX, aoiY, aoiW, aoiH));
- return aoi;
- }
-
- return null;
- }
-
- // TODO: Move these to ImageUtil or similar, as they are often used...
- // TODO: Consider separate methods for percent and pixels
- /**
- * Gets the dimensions (height and width) of the scaled image. The
- * dimensions are computed based on the old image's dimensions, the units
- * used for specifying new dimensions and whether or not uniform scaling
- * should be used (se algorithm below).
- *
- * @param pOriginalWidth the original width of the image
- * @param pOriginalHeight the original height of the image
- * @param pWidth the new width of the image, or -1 if unknown
- * @param pHeight the new height of the image, or -1 if unknown
- * @param pPercent the constant specifying units for width and height
- * parameter (UNITS_PIXELS or UNITS_PERCENT)
- * @param pUniform boolean specifying uniform scale or not
- * @return a Dimension object, with the correct width and heigth
- * in pixels, for the scaled version of the image.
- */
- static Dimension getSize(int pOriginalWidth, int pOriginalHeight,
- int pWidth, int pHeight,
- boolean pPercent, boolean pUniform) {
-
- // If uniform, make sure width and height are scaled the same amount
- // (use ONLY height or ONLY width).
- //
- // Algorithm:
- // if uniform
- // if newHeight not set
- // find ratio newWidth / oldWidth
- // oldHeight *= ratio
- // else if newWidth not set
- // find ratio newWidth / oldWidth
- // oldHeight *= ratio
- // else
- // find both ratios and use the smallest one
- // (this will be the largest version of the image that fits
- // inside the rectangle given)
- // (if PERCENT, just use smallest percentage).
- //
- // If units is percent, we only need old height and width
-
- float ratio;
-
- if (pPercent) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Non-uniform
- pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
- pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / 100f;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / 100f;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- // Else: No scale
- }
- else {
- if (pUniform) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Compute both ratios
- ratio = (float) pWidth / (float) pOriginalWidth;
- float heightRatio = (float) pHeight / (float) pOriginalHeight;
-
- // Find the largest ratio, and use that for both
- if (heightRatio < ratio) {
- ratio = heightRatio;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- }
- else {
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / (float) pOriginalWidth;
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / (float) pOriginalHeight;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- }
- // Else: No scale
- }
- }
-
- // Default is no scale, just work as a proxy
- if (pWidth < 0) {
- pWidth = pOriginalWidth;
- }
- if (pHeight < 0) {
- pHeight = pOriginalHeight;
- }
-
- // Create new Dimension object and return
- return new Dimension(pWidth, pHeight);
- }
-
- static Rectangle getAOI(int pOriginalWidth, int pOriginalHeight,
- int pX, int pY, int pWidth, int pHeight,
- boolean pPercent, boolean pMaximizeToAspect) {
- // Algorithm:
- // Try to get x and y (default 0,0).
- // Try to get width and height (default width-x, height-y)
- //
- // If percent, get ratio
- //
- // If uniform
- //
-
- float ratio;
-
- if (pPercent) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Non-uniform
- pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
- pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / 100f;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / 100f;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- // Else: No crop
- }
- else {
- // Uniform
- if (pMaximizeToAspect) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Compute both ratios
- ratio = (float) pWidth / (float) pHeight;
- float originalRatio = (float) pOriginalWidth / (float) pOriginalHeight;
- if (ratio > originalRatio) {
- pWidth = pOriginalWidth;
- pHeight = Math.round((float) pOriginalWidth / ratio);
- }
- else {
- pHeight = pOriginalHeight;
- pWidth = Math.round((float) pOriginalHeight * ratio);
- }
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / (float) pOriginalWidth;
- pHeight = Math.round((float) pOriginalHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / (float) pOriginalHeight;
- pWidth = Math.round((float) pOriginalWidth * ratio);
- }
- // Else: No crop
- }
- }
-
- // Not specified, or outside bounds: Use original dimensions
- if (pWidth < 0 || (pX < 0 && pWidth > pOriginalWidth)
- || (pX >= 0 && (pX + pWidth) > pOriginalWidth)) {
- pWidth = (pX >= 0 ? pOriginalWidth - pX : pOriginalWidth);
- }
- if (pHeight < 0 || (pY < 0 && pHeight > pOriginalHeight)
- || (pY >= 0 && (pY + pHeight) > pOriginalHeight)) {
- pHeight = (pY >= 0 ? pOriginalHeight - pY : pOriginalHeight);
- }
-
- // Center
- if (pX < 0) {
- pX = (pOriginalWidth - pWidth) / 2;
- }
- if (pY < 0) {
- pY = (pOriginalHeight - pHeight) / 2;
- }
-
-// System.out.println("x: " + pX + " y: " + pY
-// + " w: " + pWidth + " h " + pHeight);
-
- return new Rectangle(pX, pY, pWidth, pHeight);
- }
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.io.FastByteArrayOutputStream;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
+import com.twelvemonkeys.servlet.ServletUtil;
+import com.twelvemonkeys.servlet.image.aoi.AreaOfInterest;
+import com.twelvemonkeys.servlet.image.aoi.AreaOfInterestFactory;
+
+import javax.imageio.*;
+import javax.imageio.stream.ImageInputStream;
+import javax.imageio.stream.ImageOutputStream;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.IndexColorModel;
+import java.awt.image.RenderedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.Iterator;
+
+/**
+ * This {@link ImageServletResponse} implementation can be used with image
+ * requests, to have the image immediately decoded to a {@code BufferedImage}.
+ * The image may be optionally subsampled, scaled and/or cropped.
+ * The response also automatically handles writing the image back to the underlying response stream
+ * in the preferred format, when the response is flushed.
+ *
+ * @author Harald Kuhr
+ * @version $Id: ImageServletResponseImpl.java#10 $
+ */
+// TODO: Refactor out HTTP specifics (if possible).
+// TODO: Is it a good ide to throw IIOException?
+// TODO: This implementation has a problem if two filters does scaling, as the second will overwrite the SIZE attribute
+// TODO: Allow different scaling algorithm based on input image (use case: IndexColorModel does not scale well using default, smooth may be slow for large images)
+// TODO: Support pluggable pre- and post-processing steps
+class ImageServletResponseImpl extends HttpServletResponseWrapper implements ImageServletResponse {
+ private ServletRequest originalRequest;
+ private final ServletContext context;
+ private final ServletResponseStreamDelegate streamDelegate;
+
+ private FastByteArrayOutputStream bufferedOut;
+
+ private RenderedImage image;
+ private String outputContentType;
+
+ private String originalContentType;
+ private int originalContentLength = -1;
+
+ /**
+ * Creates an {@code ImageServletResponseImpl}.
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ * @param pContext the servlet context
+ */
+ public ImageServletResponseImpl(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final ServletContext pContext) {
+ super(pResponse);
+ originalRequest = pRequest;
+ streamDelegate = new ServletResponseStreamDelegate(pResponse) {
+ @Override
+ protected OutputStream createOutputStream() throws IOException {
+ if (originalContentLength >= 0) {
+ bufferedOut = new FastByteArrayOutputStream(originalContentLength);
+ }
+ else {
+ bufferedOut = new FastByteArrayOutputStream(0);
+ }
+
+ return bufferedOut;
+ }
+ };
+ context = pContext;
+ }
+
+ /**
+ * Creates an {@code ImageServletResponseImpl}.
+ *
+ * @param pRequest the request
+ * @param pResponse the response
+ * @param pContext the servlet context
+ *
+ * @throws ClassCastException if {@code pRequest} is not an {@link javax.servlet.http.HttpServletRequest} or
+ * {@code pResponse} is not an {@link javax.servlet.http.HttpServletResponse}.
+ */
+ public ImageServletResponseImpl(final ServletRequest pRequest, final ServletResponse pResponse, final ServletContext pContext) {
+ // Cheat for now...
+ this((HttpServletRequest) pRequest, (HttpServletResponse) pResponse, pContext);
+ }
+
+ /**
+ * Called by the container, do not invoke.
+ *
+ * @param pMimeType the content (MIME) type
+ */
+ public void setContentType(final String pMimeType) {
+ // Throw exception is already set
+ if (originalContentType != null) {
+ throw new IllegalStateException("ContentType already set.");
+ }
+
+ originalContentType = pMimeType;
+ }
+
+ /**
+ * Called by the container. Do not invoke.
+ *
+ * @return the response's {@code OutputStream}
+ * @throws IOException
+ */
+ public ServletOutputStream getOutputStream() throws IOException {
+ return streamDelegate.getOutputStream();
+ }
+
+ /**
+ * Called by the container. Do not invoke.
+ *
+ * @return the response's {@code PrintWriter}
+ * @throws IOException
+ */
+ public PrintWriter getWriter() throws IOException {
+ return streamDelegate.getWriter();
+ }
+
+ /**
+ * Called by the container. Do not invoke.
+ *
+ * @param pLength the content length
+ */
+ public void setContentLength(final int pLength) {
+ if (originalContentLength != -1) {
+ throw new IllegalStateException("ContentLength already set.");
+ }
+
+ originalContentLength = pLength;
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ // NOTE: Clients could also specify content type/content length using the setHeader method, special handling
+ if (name != null && name.equals("Content-Length")) {
+ setContentLength(Integer.valueOf(value)); // Value might be too large, but we don't support that anyway
+ }
+ else if (name != null && name.equals("Content-Type")) {
+ setContentType(value);
+ }
+ else {
+ super.setHeader(name, value);
+ }
+ }
+
+ /**
+ * Writes the image to the original {@code ServletOutputStream}.
+ * If no format is set in this response, the image is encoded in the same
+ * format as the original image.
+ *
+ * @throws IOException if an I/O exception occurs during writing
+ */
+ public void flush() throws IOException {
+ String outputType = getOutputContentType();
+
+ // Force transcoding, if no other filtering is done
+ if (outputType != null && !outputType.equals(originalContentType)) {
+ getImage();
+ }
+
+ if (image != null) {
+ Iterator writers = ImageIO.getImageWritersByMIMEType(outputType);
+ if (writers.hasNext()) {
+ super.setContentType(outputType);
+ OutputStream out = super.getOutputStream();
+ try {
+ ImageWriter writer = (ImageWriter) writers.next();
+ try {
+ ImageWriteParam param = writer.getDefaultWriteParam();
+ ///////////////////
+ // POST-PROCESS
+ // For known formats that don't support transparency, convert to opaque
+ if (isNonAlphaFormat(outputType) && image.getColorModel().getTransparency() != Transparency.OPAQUE) {
+ image = ImageUtil.toBuffered(image, BufferedImage.TYPE_INT_RGB);
+ }
+
+ Float requestQuality = (Float) originalRequest.getAttribute(ImageServletResponse.ATTRIB_OUTPUT_QUALITY);
+
+ // The default JPEG quality is not good enough, so always adjust compression/quality
+ if ((requestQuality != null || "jpeg".equalsIgnoreCase(getFormatNameSafe(writer))) && param.canWriteCompressed()) {
+ // TODO: See http://blog.apokalyptik.com/2009/09/16/quality-time-with-your-jpegs/ for better adjusting the (default) JPEG quality
+ // OR: Use the metadata of the original image
+
+ param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+
+ // WORKAROUND: Known bug in GIFImageWriter in certain JDK versions, compression type is not set by default
+ if (param.getCompressionTypes() != null && param.getCompressionType() == null) {
+ param.setCompressionType(param.getCompressionTypes()[0]); // Just choose any, to keep param happy
+ }
+
+ param.setCompressionQuality(requestQuality != null ? requestQuality : 0.8f);
+ }
+
+ if ("gif".equalsIgnoreCase(getFormatNameSafe(writer)) && !(image.getColorModel() instanceof IndexColorModel)
+ /*&& image.getColorModel().getTransparency() != Transparency.OPAQUE*/) {
+ // WORKAROUND: Bug in GIFImageWriter may throw NPE if transparent pixels
+ // See: http://bugs.sun.com/view_bug.do?bug_id=6287936
+ image = ImageUtil.createIndexed(
+ ImageUtil.toBuffered(image), 256, null,
+ (image.getColorModel().getTransparency() == Transparency.OPAQUE ? ImageUtil.TRANSPARENCY_OPAQUE : ImageUtil.TRANSPARENCY_BITMASK) | ImageUtil.DITHER_DIFFUSION_ALTSCANS
+ );
+ }
+ //////////////////
+ ImageOutputStream stream = ImageIO.createImageOutputStream(out);
+
+ writer.setOutput(stream);
+ try {
+ writer.write(null, new IIOImage(image, null, null), param);
+ }
+ finally {
+ stream.close();
+ }
+ }
+ finally {
+ writer.dispose();
+ }
+ }
+ finally {
+ out.flush();
+ }
+ }
+ else {
+ context.log("ERROR: No writer for content-type: " + outputType);
+ throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ").");
+ }
+ }
+ else {
+ super.setContentType(originalContentType);
+
+ ServletOutputStream out = super.getOutputStream();
+
+ try {
+ if (bufferedOut != null) {
+ bufferedOut.writeTo(out);
+ }
+ }
+ finally {
+ out.flush();
+ }
+ }
+ }
+
+ private boolean isNonAlphaFormat(String outputType) {
+ return "image/jpeg".equals(outputType) || "image/jpg".equals(outputType) ||
+ "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType);
+ }
+
+ private String getFormatNameSafe(final ImageWriter pWriter) {
+ try {
+ return pWriter.getOriginatingProvider().getFormatNames()[0];
+ }
+ catch (RuntimeException e) {
+ // NPE, AIOOBE, etc..
+ return null;
+ }
+ }
+
+ public String getOutputContentType() {
+ return outputContentType != null ? outputContentType : originalContentType;
+ }
+
+ public void setOutputContentType(final String pImageFormat) {
+ outputContentType = pImageFormat;
+ }
+
+ /**
+ * Sets the image for this response.
+ *
+ * @param pImage the {@code RenderedImage} that will be written to the
+ * response stream
+ */
+ public void setImage(final RenderedImage pImage) {
+ image = pImage;
+ }
+
+ /**
+ * Gets the decoded image from the response.
+ *
+ * @return a {@code BufferedImage} or {@code null} if the image could
+ * not be read.
+ *
+ * @throws java.io.IOException if an I/O exception occurs during reading
+ */
+ public BufferedImage getImage() throws IOException {
+ if (image == null) {
+ // No content, no image
+ if (bufferedOut == null) {
+ return null;
+ }
+
+ // Read from the byte buffer
+ InputStream byteStream = bufferedOut.createInputStream();
+ ImageInputStream input = null;
+ try {
+ input = ImageIO.createImageInputStream(byteStream);
+ Iterator readers = ImageIO.getImageReaders(input);
+ if (readers.hasNext()) {
+ // Get the correct reader
+ ImageReader reader = (ImageReader) readers.next();
+ try {
+ reader.setInput(input);
+
+ ImageReadParam param = reader.getDefaultReadParam();
+
+ // Get default size
+ int originalWidth = reader.getWidth(0);
+ int originalHeight = reader.getHeight(0);
+//////////////////
+// PRE-PROCESS (prepare): param, size, format?, request, response?
+ // TODO: AOI strategy?
+ // Extract AOI from request
+ Rectangle aoi = extractAOIFromRequest(originalWidth, originalHeight, originalRequest);
+
+ if (aoi != null) {
+ param.setSourceRegion(aoi);
+ originalWidth = aoi.width;
+ originalHeight = aoi.height;
+ }
+
+ // TODO: Size and subsampling strategy?
+ // If possible, extract size from request
+ Dimension size = extractSizeFromRequest(originalWidth, originalHeight, originalRequest);
+ double readSubSamplingFactor = getReadSubsampleFactorFromRequest(originalRequest);
+
+ if (size != null) {
+ //System.out.println("Size: " + size);
+ if (param.canSetSourceRenderSize()) {
+ param.setSourceRenderSize(size);
+ }
+ else {
+ int subX = (int) Math.max(originalWidth / (size.width * readSubSamplingFactor), 1.0);
+ int subY = (int) Math.max(originalHeight / (size.height * readSubSamplingFactor), 1.0);
+
+ if (subX > 1 || subY > 1) {
+ param.setSourceSubsampling(subX, subY, subX > 1 ? subX / 2 : 0, subY > 1 ? subY / 2 : 0);
+ }
+ }
+ }
+
+ // Need base URI for SVG with links/stylesheets etc
+ maybeSetBaseURIFromRequest(param);
+
+/////////////////////
+
+ // Finally, read the image using the supplied parameter
+ BufferedImage image = reader.read(0, param);
+
+ // TODO: If we sub-sampled, it would be a good idea to blur before resampling,
+ // to avoid jagged lines artifacts
+
+ // If reader doesn't support dynamic sizing, scale now
+ image = resampleImage(image, size);
+
+ // Fill bgcolor behind image, if transparent
+ extractAndSetBackgroundColor(image); // TODO: Move to flush/POST-PROCESS
+
+ // Set image
+ this.image = image;
+ }
+ finally {
+ reader.dispose();
+ }
+ }
+ else {
+ context.log("ERROR: No suitable image reader found (content-type: " + originalContentType + ").");
+ context.log("ERROR: Available formats: " + getFormatsString());
+
+ throw new IIOException("Unable to transcode image: No suitable image reader found (content-type: " + originalContentType + ").");
+ }
+
+ // Free resources, as the image is now either read, or unreadable
+ bufferedOut = null;
+ }
+ finally {
+ if (input != null) {
+ input.close();
+ }
+ }
+ }
+
+ // Image is usually a BufferedImage, but may also be a RenderedImage
+ return image != null ? ImageUtil.toBuffered(image) : null;
+ }
+
+ private BufferedImage resampleImage(final BufferedImage image, final Dimension size) {
+ if (image != null && size != null && (image.getWidth() != size.width || image.getHeight() != size.height)) {
+ int resampleAlgorithm = getResampleAlgorithmFromRequest();
+
+ // TODO: One possibility is to NOT handle index color here, and only handle it later, IF NEEDED (read: GIF,
+ // possibly also for PNG) when we know the output format (flush method).
+ // This will make the filter faster (and better quality, possibly at the expense of more bytes being sent
+ // over the wire) in the general case. Who uses GIF nowadays anyway?
+ // Also, this means we could either keep the original IndexColorModel in the filter, or go through the
+ // expensive operation of re-calculating the optimal palette for the new image (the latter might improve quality).
+
+ // NOTE: Only use createScaled if IndexColorModel, as it's more expensive due to color conversion
+/* if (image.getColorModel() instanceof IndexColorModel) {
+// return ImageUtil.createScaled(image, size.width, size.height, resampleAlgorithm);
+ BufferedImage resampled = ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm);
+ return ImageUtil.createIndexed(resampled, (IndexColorModel) image.getColorModel(), null, ImageUtil.DITHER_NONE | ImageUtil.TRANSPARENCY_BITMASK);
+// return ImageUtil.createIndexed(resampled, 256, null, ImageUtil.COLOR_SELECTION_QUALITY | ImageUtil.DITHER_NONE | ImageUtil.TRANSPARENCY_BITMASK);
+ }
+ else {
+ */
+ return ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm);
+// }
+ }
+ return image;
+ }
+
+ int getResampleAlgorithmFromRequest() {
+ Object algorithm = originalRequest.getAttribute(ATTRIB_IMAGE_RESAMPLE_ALGORITHM);
+ if (algorithm instanceof Integer && ((Integer) algorithm == Image.SCALE_SMOOTH || (Integer) algorithm == Image.SCALE_FAST || (Integer) algorithm == Image.SCALE_DEFAULT)) {
+ return (Integer) algorithm;
+ }
+ else {
+ if (algorithm != null) {
+ context.log("WARN: Illegal image resampling algorithm: " + algorithm);
+ }
+
+ return BufferedImage.SCALE_DEFAULT;
+ }
+ }
+
+ private double getReadSubsampleFactorFromRequest(final ServletRequest pOriginalRequest) {
+ double subsampleFactor;
+
+ Object factor = pOriginalRequest.getAttribute(ATTRIB_READ_SUBSAMPLING_FACTOR);
+ if (factor instanceof Number && ((Number) factor).doubleValue() >= 1.0) {
+ subsampleFactor = ((Number) factor).doubleValue();
+ }
+ else {
+ if (factor != null) {
+ context.log("WARN: Illegal read subsampling factor: " + factor);
+ }
+
+ subsampleFactor = 2.0;
+ }
+
+ return subsampleFactor;
+ }
+
+ private void extractAndSetBackgroundColor(final BufferedImage pImage) {
+ // TODO: bgColor request attribute instead of parameter?
+ if (pImage.getColorModel().hasAlpha()) {
+ String bgColor = originalRequest.getParameter("bg.color");
+ if (bgColor != null) {
+ Color color = StringUtil.toColor(bgColor);
+
+ Graphics2D g = pImage.createGraphics();
+ try {
+ g.setColor(color);
+ g.setComposite(AlphaComposite.DstOver);
+ g.fillRect(0, 0, pImage.getWidth(), pImage.getHeight());
+ }
+ finally {
+ g.dispose();
+ }
+ }
+ }
+ }
+
+ private static String getFormatsString() {
+ String[] formats = ImageIO.getReaderFormatNames();
+ StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < formats.length; i++) {
+ String format = formats[i];
+ if (i > 0) {
+ buf.append(", ");
+ }
+ buf.append(format);
+ }
+ return buf.toString();
+ }
+
+ private void maybeSetBaseURIFromRequest(final ImageReadParam pParam) {
+ if (originalRequest instanceof HttpServletRequest) {
+ try {
+ // If there's a setBaseURI method, we'll try to use that (uses reflection, to avoid dependency on plugins)
+ Method setBaseURI;
+ try {
+ setBaseURI = pParam.getClass().getMethod("setBaseURI", String.class);
+ }
+ catch (NoSuchMethodException ignore) {
+ return;
+ }
+
+ // Get URL for resource and set as base
+ String baseURI = ServletUtil.getContextRelativeURI((HttpServletRequest) originalRequest);
+
+ URL resourceURL = context.getResource(baseURI);
+
+ if (resourceURL == null) {
+ resourceURL = ServletUtil.getRealURL(context, baseURI);
+ }
+
+ if (resourceURL != null) {
+ setBaseURI.invoke(pParam, resourceURL.toExternalForm());
+ }
+ else {
+ context.log("WARN: Resource URL not found for URI: " + baseURI);
+ }
+ }
+ catch (Exception e) {
+ context.log("WARN: Could not set base URI: ", e);
+ }
+ }
+ }
+
+ private Dimension extractSizeFromRequest(final int pDefaultWidth, final int pDefaultHeight, final ServletRequest pOriginalRequest) {
+ // TODO: Allow extraction from request parameters
+ /*
+ int sizeW = ServletUtil.getIntParameter(originalRequest, "size.w", -1);
+ int sizeH = ServletUtil.getIntParameter(originalRequest, "size.h", -1);
+ boolean sizePercent = ServletUtil.getBooleanParameter(originalRequest, "size.percent", false);
+ boolean sizeUniform = ServletUtil.getBooleanParameter(originalRequest, "size.uniform", true);
+ */
+ Dimension size = (Dimension) pOriginalRequest.getAttribute(ATTRIB_SIZE);
+ int sizeW = size != null ? size.width : -1;
+ int sizeH = size != null ? size.height : -1;
+
+ Boolean b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_SIZE_PERCENT);
+ boolean sizePercent = b != null && b; // default: false
+
+ b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_SIZE_UNIFORM);
+ boolean sizeUniform = b == null || b; // default: true
+
+ if (sizeW >= 0 || sizeH >= 0) {
+ size = getSize(pDefaultWidth, pDefaultHeight, sizeW, sizeH, sizePercent, sizeUniform);
+ }
+
+ return size;
+ }
+
+ private Rectangle extractAOIFromRequest(final int pDefaultWidth, final int pDefaultHeight, final ServletRequest pOriginalRequest) {
+ // TODO: Allow extraction from request parameters
+ /*
+ int aoiX = ServletUtil.getIntParameter(originalRequest, "aoi.x", -1);
+ int aoiY = ServletUtil.getIntParameter(originalRequest, "aoi.y", -1);
+ int aoiW = ServletUtil.getIntParameter(originalRequest, "aoi.w", -1);
+ int aoiH = ServletUtil.getIntParameter(originalRequest, "aoi.h", -1);
+ boolean aoiPercent = ServletUtil.getBooleanParameter(originalRequest, "aoi.percent", false);
+ boolean aoiUniform = ServletUtil.getBooleanParameter(originalRequest, "aoi.uniform", false);
+ */
+ Rectangle aoi = (Rectangle) pOriginalRequest.getAttribute(ATTRIB_AOI);
+ int aoiX = aoi != null ? aoi.x : -1;
+ int aoiY = aoi != null ? aoi.y : -1;
+ int aoiW = aoi != null ? aoi.width : -1;
+ int aoiH = aoi != null ? aoi.height : -1;
+
+ Boolean b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_AOI_PERCENT);
+ boolean aoiPercent = b != null && b; // default: false
+
+ b = (Boolean) pOriginalRequest.getAttribute(ATTRIB_AOI_UNIFORM);
+ boolean aoiUniform = b != null && b; // default: false
+
+ if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) {
+
+ AreaOfInterest areaOfInterest = AreaOfInterestFactory.getDefault().
+ createAreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform);
+ aoi = areaOfInterest.getAOI(new Rectangle(aoiX, aoiY, aoiW, aoiH));
+ return aoi;
+ }
+
+ return null;
+ }
+
+ // TODO: Move these to ImageUtil or similar, as they are often used...
+ // TODO: Consider separate methods for percent and pixels
+ /**
+ * Gets the dimensions (height and width) of the scaled image. The
+ * dimensions are computed based on the old image's dimensions, the units
+ * used for specifying new dimensions and whether or not uniform scaling
+ * should be used (se algorithm below).
+ *
+ * @param pOriginalWidth the original width of the image
+ * @param pOriginalHeight the original height of the image
+ * @param pWidth the new width of the image, or -1 if unknown
+ * @param pHeight the new height of the image, or -1 if unknown
+ * @param pPercent the constant specifying units for width and height
+ * parameter (UNITS_PIXELS or UNITS_PERCENT)
+ * @param pUniform boolean specifying uniform scale or not
+ * @return a Dimension object, with the correct width and heigth
+ * in pixels, for the scaled version of the image.
+ */
+ static Dimension getSize(int pOriginalWidth, int pOriginalHeight,
+ int pWidth, int pHeight,
+ boolean pPercent, boolean pUniform) {
+
+ // If uniform, make sure width and height are scaled the same amount
+ // (use ONLY height or ONLY width).
+ //
+ // Algorithm:
+ // if uniform
+ // if newHeight not set
+ // find ratio newWidth / oldWidth
+ // oldHeight *= ratio
+ // else if newWidth not set
+ // find ratio newWidth / oldWidth
+ // oldHeight *= ratio
+ // else
+ // find both ratios and use the smallest one
+ // (this will be the largest version of the image that fits
+ // inside the rectangle given)
+ // (if PERCENT, just use smallest percentage).
+ //
+ // If units is percent, we only need old height and width
+
+ float ratio;
+
+ if (pPercent) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Non-uniform
+ pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
+ pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / 100f;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / 100f;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ // Else: No scale
+ }
+ else {
+ if (pUniform) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Compute both ratios
+ ratio = (float) pWidth / (float) pOriginalWidth;
+ float heightRatio = (float) pHeight / (float) pOriginalHeight;
+
+ // Find the largest ratio, and use that for both
+ if (heightRatio < ratio) {
+ ratio = heightRatio;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ }
+ else {
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / (float) pOriginalWidth;
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / (float) pOriginalHeight;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ }
+ // Else: No scale
+ }
+ }
+
+ // Default is no scale, just work as a proxy
+ if (pWidth < 0) {
+ pWidth = pOriginalWidth;
+ }
+ if (pHeight < 0) {
+ pHeight = pOriginalHeight;
+ }
+
+ // Create new Dimension object and return
+ return new Dimension(pWidth, pHeight);
+ }
+
+ static Rectangle getAOI(int pOriginalWidth, int pOriginalHeight,
+ int pX, int pY, int pWidth, int pHeight,
+ boolean pPercent, boolean pMaximizeToAspect) {
+ // Algorithm:
+ // Try to get x and y (default 0,0).
+ // Try to get width and height (default width-x, height-y)
+ //
+ // If percent, get ratio
+ //
+ // If uniform
+ //
+
+ float ratio;
+
+ if (pPercent) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Non-uniform
+ pWidth = Math.round((float) pOriginalWidth * (float) pWidth / 100f);
+ pHeight = Math.round((float) pOriginalHeight * (float) pHeight / 100f);
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / 100f;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / 100f;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ // Else: No crop
+ }
+ else {
+ // Uniform
+ if (pMaximizeToAspect) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Compute both ratios
+ ratio = (float) pWidth / (float) pHeight;
+ float originalRatio = (float) pOriginalWidth / (float) pOriginalHeight;
+ if (ratio > originalRatio) {
+ pWidth = pOriginalWidth;
+ pHeight = Math.round((float) pOriginalWidth / ratio);
+ }
+ else {
+ pHeight = pOriginalHeight;
+ pWidth = Math.round((float) pOriginalHeight * ratio);
+ }
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / (float) pOriginalWidth;
+ pHeight = Math.round((float) pOriginalHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / (float) pOriginalHeight;
+ pWidth = Math.round((float) pOriginalWidth * ratio);
+ }
+ // Else: No crop
+ }
+ }
+
+ // Not specified, or outside bounds: Use original dimensions
+ if (pWidth < 0 || (pX < 0 && pWidth > pOriginalWidth)
+ || (pX >= 0 && (pX + pWidth) > pOriginalWidth)) {
+ pWidth = (pX >= 0 ? pOriginalWidth - pX : pOriginalWidth);
+ }
+ if (pHeight < 0 || (pY < 0 && pHeight > pOriginalHeight)
+ || (pY >= 0 && (pY + pHeight) > pOriginalHeight)) {
+ pHeight = (pY >= 0 ? pOriginalHeight - pY : pOriginalHeight);
+ }
+
+ // Center
+ if (pX < 0) {
+ pX = (pOriginalWidth - pWidth) / 2;
+ }
+ if (pY < 0) {
+ pY = (pOriginalHeight - pHeight) / 2;
+ }
+
+// System.out.println("x: " + pX + " y: " + pY
+// + " w: " + pWidth + " h " + pHeight);
+
+ return new Rectangle(pX, pY, pWidth, pHeight);
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java
index c2700cad..107a1a3f 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java
@@ -1,46 +1,46 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import javax.servlet.ServletRequest;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-
-/**
- * An {@code ImageFilter} that does nothing. Useful for debugging purposes.
- *
- * @author Harald Kuhr
- * @version $Id: NullImageFilter.java $
- *
- */
-public final class NullImageFilter extends ImageFilter {
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- return pImage;
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import javax.servlet.ServletRequest;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * An {@code ImageFilter} that does nothing. Useful for debugging purposes.
+ *
+ * @author Harald Kuhr
+ * @version $Id: NullImageFilter.java $
+ *
+ */
+public final class NullImageFilter extends ImageFilter {
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ return pImage;
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java
index 98b692ce..cc74d4b5 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java
@@ -1,202 +1,202 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.ServletRequest;
-import java.awt.*;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-
-/**
- * This Servlet is able to render a cropped part of an image.
- *
- *
- *
- * Parameters:
- *
- * - {@code cropX}
- * - integer, the new left edge of the image.
- *
- {@code cropY}
- * - integer, the new top of the image.
- *
- {@code cropWidth}
- * - integer, the new width of the image.
- *
- {@code cropHeight}
- * - integer, the new height of the image.
- *
- *
- *
- *
- *
- * @example
- * JPEG:
- * <IMG src="/scale/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=500&uniform=true">
- *
- * PNG:
- * <IMG src="/scale/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=50&units=PERCENT">
- *
- * @todo Correct rounding errors, resulting in black borders when rotating 90
- * degrees, and one of width or height is odd length...
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: RotateFilter.java#1 $
- */
-
-public class RotateFilter extends ImageFilter {
- /** {@code angle}*/
- protected final static String PARAM_ANGLE = "angle";
- /** {@code angleUnits (RADIANS|DEGREES)}*/
- protected final static String PARAM_ANGLE_UNITS = "angleUnits";
- /** {@code crop}*/
- protected final static String PARAM_CROP = "rotateCrop";
- /** {@code bgcolor}*/
- protected final static String PARAM_BGCOLOR = "rotateBgcolor";
-
- /** {@code degrees}*/
- private final static String ANGLE_DEGREES = "degrees";
- /** {@code radians}*/
- //private final static String ANGLE_RADIANS = "radians";
-
- /**
- * Reads the image from the requested URL, rotates it, and returns
- * it in the
- * Servlet stream. See above for details on parameters.
- */
-
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- // Get angle
- double ang = getAngle(pRequest);
-
- // Get bounds
- Rectangle2D rect = getBounds(pRequest, pImage, ang);
- int width = (int) rect.getWidth();
- int height = (int) rect.getHeight();
-
- // Create result image
- BufferedImage res = ImageUtil.createTransparent(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = res.createGraphics();
-
- // Get background color and clear
- String str = pRequest.getParameter(PARAM_BGCOLOR);
- if (!StringUtil.isEmpty(str)) {
- Color bgcolor = StringUtil.toColor(str);
- g.setBackground(bgcolor);
- g.clearRect(0, 0, width, height);
- }
-
- // Set mHints (why do I always get jagged edgdes?)
- RenderingHints hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
- hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
- hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
- hints.add(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
-
- g.setRenderingHints(hints);
-
- // Rotate around center
- AffineTransform at = AffineTransform
- .getRotateInstance(ang, width / 2.0, height / 2.0);
-
- // Move to center
- at.translate(width / 2.0 - pImage.getWidth() / 2.0,
- height / 2.0 - pImage.getHeight() / 2.0);
-
- // Draw it, centered
- g.drawImage(pImage, at, null);
-
- return res;
- }
-
- /**
- * Gets the angle of rotation.
- */
-
- private double getAngle(ServletRequest pReq) {
- double angle = 0.0;
- String str = pReq.getParameter(PARAM_ANGLE);
- if (!StringUtil.isEmpty(str)) {
- angle = Double.parseDouble(str);
-
- // Convert to radians, if needed
- str = pReq.getParameter(PARAM_ANGLE_UNITS);
- if (!StringUtil.isEmpty(str)
- && ANGLE_DEGREES.equalsIgnoreCase(str)) {
- angle = Math.toRadians(angle);
- }
- }
-
- return angle;
- }
-
- /**
- * Get the bounding rectangle of the rotated image.
- */
-
- private Rectangle2D getBounds(ServletRequest pReq, BufferedImage pImage,
- double pAng) {
- // Get dimensions of original image
- int width = pImage.getWidth(); // loads the image
- int height = pImage.getHeight();
-
- // Test if we want to crop image (default)
- // if true
- // - find the largest bounding box INSIDE the rotated image,
- // that matches the original proportions (nearest 90deg)
- // (scale up to fit dimensions?)
- // else
- // - find the smallest bounding box OUTSIDE the rotated image.
- // - that matches the original proportions (nearest 90deg) ?
- // (scale down to fit dimensions?)
- AffineTransform at =
- AffineTransform.getRotateInstance(pAng, width / 2.0, height / 2.0);
-
- Rectangle2D orig = new Rectangle(width, height);
- Shape rotated = at.createTransformedShape(orig);
-
- if (ServletUtil.getBooleanParameter(pReq, PARAM_CROP, false)) {
- // TODO: Inside box
- return rotated.getBounds2D();
- }
- else {
- return rotated.getBounds2D();
- }
- }
-}
-
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.ServletRequest;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+
+/**
+ * This Servlet is able to render a cropped part of an image.
+ *
+ *
+ *
+ * Parameters:
+ *
+ * - {@code cropX}
+ * - integer, the new left edge of the image.
+ *
- {@code cropY}
+ * - integer, the new top of the image.
+ *
- {@code cropWidth}
+ * - integer, the new width of the image.
+ *
- {@code cropHeight}
+ * - integer, the new height of the image.
+ *
+ *
+ *
+ *
+ *
+ * @example
+ * JPEG:
+ * <IMG src="/scale/test.jpg?image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=500&uniform=true">
+ *
+ * PNG:
+ * <IMG src="/scale/test.png?cache=false&image=http://www.iconmedialab.com/images/random/home_image_12.jpg&width=50&units=PERCENT">
+ *
+ * @todo Correct rounding errors, resulting in black borders when rotating 90
+ * degrees, and one of width or height is odd length...
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: RotateFilter.java#1 $
+ */
+
+public class RotateFilter extends ImageFilter {
+ /** {@code angle}*/
+ protected final static String PARAM_ANGLE = "angle";
+ /** {@code angleUnits (RADIANS|DEGREES)}*/
+ protected final static String PARAM_ANGLE_UNITS = "angleUnits";
+ /** {@code crop}*/
+ protected final static String PARAM_CROP = "rotateCrop";
+ /** {@code bgcolor}*/
+ protected final static String PARAM_BGCOLOR = "rotateBgcolor";
+
+ /** {@code degrees}*/
+ private final static String ANGLE_DEGREES = "degrees";
+ /** {@code radians}*/
+ //private final static String ANGLE_RADIANS = "radians";
+
+ /**
+ * Reads the image from the requested URL, rotates it, and returns
+ * it in the
+ * Servlet stream. See above for details on parameters.
+ */
+
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ // Get angle
+ double ang = getAngle(pRequest);
+
+ // Get bounds
+ Rectangle2D rect = getBounds(pRequest, pImage, ang);
+ int width = (int) rect.getWidth();
+ int height = (int) rect.getHeight();
+
+ // Create result image
+ BufferedImage res = ImageUtil.createTransparent(width, height, BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = res.createGraphics();
+
+ // Get background color and clear
+ String str = pRequest.getParameter(PARAM_BGCOLOR);
+ if (!StringUtil.isEmpty(str)) {
+ Color bgcolor = StringUtil.toColor(str);
+ g.setBackground(bgcolor);
+ g.clearRect(0, 0, width, height);
+ }
+
+ // Set mHints (why do I always get jagged edgdes?)
+ RenderingHints hints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
+ hints.add(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
+ hints.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
+ hints.add(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC));
+
+ g.setRenderingHints(hints);
+
+ // Rotate around center
+ AffineTransform at = AffineTransform
+ .getRotateInstance(ang, width / 2.0, height / 2.0);
+
+ // Move to center
+ at.translate(width / 2.0 - pImage.getWidth() / 2.0,
+ height / 2.0 - pImage.getHeight() / 2.0);
+
+ // Draw it, centered
+ g.drawImage(pImage, at, null);
+
+ return res;
+ }
+
+ /**
+ * Gets the angle of rotation.
+ */
+
+ private double getAngle(ServletRequest pReq) {
+ double angle = 0.0;
+ String str = pReq.getParameter(PARAM_ANGLE);
+ if (!StringUtil.isEmpty(str)) {
+ angle = Double.parseDouble(str);
+
+ // Convert to radians, if needed
+ str = pReq.getParameter(PARAM_ANGLE_UNITS);
+ if (!StringUtil.isEmpty(str)
+ && ANGLE_DEGREES.equalsIgnoreCase(str)) {
+ angle = Math.toRadians(angle);
+ }
+ }
+
+ return angle;
+ }
+
+ /**
+ * Get the bounding rectangle of the rotated image.
+ */
+
+ private Rectangle2D getBounds(ServletRequest pReq, BufferedImage pImage,
+ double pAng) {
+ // Get dimensions of original image
+ int width = pImage.getWidth(); // loads the image
+ int height = pImage.getHeight();
+
+ // Test if we want to crop image (default)
+ // if true
+ // - find the largest bounding box INSIDE the rotated image,
+ // that matches the original proportions (nearest 90deg)
+ // (scale up to fit dimensions?)
+ // else
+ // - find the smallest bounding box OUTSIDE the rotated image.
+ // - that matches the original proportions (nearest 90deg) ?
+ // (scale down to fit dimensions?)
+ AffineTransform at =
+ AffineTransform.getRotateInstance(pAng, width / 2.0, height / 2.0);
+
+ Rectangle2D orig = new Rectangle(width, height);
+ Shape rotated = at.createTransformedShape(orig);
+
+ if (ServletUtil.getBooleanParameter(pReq, PARAM_CROP, false)) {
+ // TODO: Inside box
+ return rotated.getBounds2D();
+ }
+ else {
+ return rotated.getBounds2D();
+ }
+ }
+}
+
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java
index 825fa77c..0451cd34 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java
@@ -1,322 +1,322 @@
-/*
- * Copyright (c) 2008, Harald Kuhr
- * 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 "TwelveMonkeys" 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 OWNER 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.
- */
-
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.image.ImageUtil;
-import com.twelvemonkeys.lang.StringUtil;
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.ServletRequest;
-import java.awt.*;
-import java.awt.image.BufferedImage;
-import java.awt.image.RenderedImage;
-import java.lang.reflect.Field;
-
-
-/**
- * This filter renders a scaled version of an image read from a
- * given URL. The image can be output as a GIF, JPEG or PNG image
- * or similar.
- *
- *
- *
- * Parameters:
- *
- * - {@code scaleX}
- * - integer, the new width of the image.
- *
- {@code scaleY}
- * - integer, the new height of the image.
- *
- {@code scaleUniform}
- * - boolean, wether or not uniform scalnig should be used. Default is
- * {@code true}.
- *
- {@code scaleUnits}
- * - string, one of {@code PIXELS}, {@code PERCENT}.
- * {@code PIXELS} is default.
- *
- {@code scaleQuality}
- * - string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
- * {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
- * {@code SCALE_DEFAULT} is default (see
- * {@link java.awt.Image#getScaledInstance(int,int,int)}, {@link java.awt.Image}
- * for more details).
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: ScaleFilter.java#1 $
- *
- * @example <IMG src="/scale/test.jpg?scaleX=500&scaleUniform=false">
- * @example <IMG src="/scale/test.png?scaleY=50&scaleUnits=PERCENT">
- */
-public class ScaleFilter extends ImageFilter {
-
- /**
- * Width and height are absolute pixels. The default.
- */
- public static final int UNITS_PIXELS = 1;
- /**
- * Width and height are percentage of original width and height.
- */
- public static final int UNITS_PERCENT = 5;
- /**
- * Ahh, good choice!
- */
- //private static final int UNITS_METRIC = 42;
- /**
- * The root of all evil...
- */
- //private static final int UNITS_INCHES = 666;
- /**
- * Unknown units.
- */
- public static final int UNITS_UNKNOWN = 0;
-
- /**
- * {@code scaleQuality}
- */
- protected final static String PARAM_SCALE_QUALITY = "scaleQuality";
- /**
- * {@code scaleUnits}
- */
- protected final static String PARAM_SCALE_UNITS = "scaleUnits";
- /**
- * {@code scaleUniform}
- */
- protected final static String PARAM_SCALE_UNIFORM = "scaleUniform";
- /**
- * {@code scaleX}
- */
- protected final static String PARAM_SCALE_X = "scaleX";
- /**
- * {@code scaleY}
- */
- protected final static String PARAM_SCALE_Y = "scaleY";
- /**
- * {@code image}
- */
- protected final static String PARAM_IMAGE = "image";
-
- /** */
- protected int defaultScaleQuality = Image.SCALE_DEFAULT;
-
- /**
- * Reads the image from the requested URL, scales it, and returns it in the
- * Servlet stream. See above for details on parameters.
- */
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
-
- // Get quality setting
- // SMOOTH | FAST | REPLICATE | DEFAULT | AREA_AVERAGING
- // See Image (mHints)
- int quality = getQuality(pRequest.getParameter(PARAM_SCALE_QUALITY));
-
- // Get units, default is pixels
- // PIXELS | PERCENT | METRIC | INCHES
- int units = getUnits(pRequest.getParameter(PARAM_SCALE_UNITS));
- if (units == UNITS_UNKNOWN) {
- log("Unknown units for scale, returning original.");
- return pImage;
- }
-
- // Use uniform scaling? Default is true
- boolean uniformScale = ServletUtil.getBooleanParameter(pRequest, PARAM_SCALE_UNIFORM, true);
-
- // Get dimensions
- int width = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_X, -1);
- int height = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_Y, -1);
-
- // Get dimensions for scaled image
- Dimension dim = getDimensions(pImage, width, height, units, uniformScale);
-
- width = (int) dim.getWidth();
- height = (int) dim.getHeight();
-
- // Return scaled instance directly
- return ImageUtil.createScaled(pImage, width, height, quality);
- }
-
- /**
- * Gets the quality constant for the scaling, from the string argument.
- *
- * @param pQualityStr The string representation of the scale quality
- * constant.
- * @return The matching quality constant, or the default quality if none
- * was found.
- * @see java.awt.Image
- * @see java.awt.Image#getScaledInstance(int,int,int)
- */
- protected int getQuality(String pQualityStr) {
- if (!StringUtil.isEmpty(pQualityStr)) {
- try {
- // Get quality constant from Image using reflection
- Class cl = Image.class;
- Field field = cl.getField(pQualityStr.toUpperCase());
-
- return field.getInt(null);
- }
- catch (IllegalAccessException ia) {
- log("Unable to get quality.", ia);
- }
- catch (NoSuchFieldException nsf) {
- log("Unable to get quality.", nsf);
- }
- }
-
- return defaultScaleQuality;
- }
-
- public void setDefaultScaleQuality(String pDefaultScaleQuality) {
- defaultScaleQuality = getQuality(pDefaultScaleQuality);
- }
-
- /**
- * Gets the units constant for the width and height arguments, from the
- * given string argument.
- *
- * @param pUnitStr The string representation of the units constant,
- * can be one of "PIXELS" or "PERCENT".
- * @return The mathcing units constant, or UNITS_UNKNOWN if none was found.
- */
- protected int getUnits(String pUnitStr) {
- if (StringUtil.isEmpty(pUnitStr)
- || pUnitStr.equalsIgnoreCase("PIXELS")) {
- return UNITS_PIXELS;
- }
- else if (pUnitStr.equalsIgnoreCase("PERCENT")) {
- return UNITS_PERCENT;
- }
- else {
- return UNITS_UNKNOWN;
- }
- }
-
- /**
- * Gets the dimensions (height and width) of the scaled image. The
- * dimensions are computed based on the old image's dimensions, the units
- * used for specifying new dimensions and whether or not uniform scaling
- * should be used (se algorithm below).
- *
- * @param pImage the image to be scaled
- * @param pWidth the new width of the image, or -1 if unknown
- * @param pHeight the new height of the image, or -1 if unknown
- * @param pUnits the constant specifying units for width and height
- * parameter (UNITS_PIXELS or UNITS_PERCENT)
- * @param pUniformScale boolean specifying uniform scale or not
- * @return a Dimension object, with the correct width and heigth
- * in pixels, for the scaled version of the image.
- */
- protected Dimension getDimensions(Image pImage, int pWidth, int pHeight,
- int pUnits, boolean pUniformScale) {
-
- // If uniform, make sure width and height are scaled the same ammount
- // (use ONLY height or ONLY width).
- //
- // Algoritm:
- // if uniform
- // if newHeight not set
- // find ratio newWidth / oldWidth
- // oldHeight *= ratio
- // else if newWidth not set
- // find ratio newWidth / oldWidth
- // oldHeight *= ratio
- // else
- // find both ratios and use the smallest one
- // (this will be the largest version of the image that fits
- // inside the rectangle given)
- // (if PERCENT, just use smallest percentage).
- //
- // If units is percent, we only need old height and width
-
- int oldWidth = ImageUtil.getWidth(pImage);
- int oldHeight = ImageUtil.getHeight(pImage);
- float ratio;
-
- if (pUnits == UNITS_PERCENT) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Non-uniform
- pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
- pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / 100f;
- pWidth = (int) ((float) oldWidth * ratio);
- pHeight = (int) ((float) oldHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / 100f;
- pWidth = (int) ((float) oldWidth * ratio);
- pHeight = (int) ((float) oldHeight * ratio);
- }
- // Else: No scale
- }
- else if (pUnits == UNITS_PIXELS) {
- if (pUniformScale) {
- if (pWidth >= 0 && pHeight >= 0) {
- // Compute both ratios
- ratio = (float) pWidth / (float) oldWidth;
- float heightRatio = (float) pHeight / (float) oldHeight;
-
- // Find the largest ratio, and use that for both
- if (heightRatio < ratio) {
- ratio = heightRatio;
- pWidth = (int) ((float) oldWidth * ratio);
- }
- else {
- pHeight = (int) ((float) oldHeight * ratio);
- }
-
- }
- else if (pWidth >= 0) {
- // Find ratio from pWidth
- ratio = (float) pWidth / (float) oldWidth;
- pHeight = (int) ((float) oldHeight * ratio);
- }
- else if (pHeight >= 0) {
- // Find ratio from pHeight
- ratio = (float) pHeight / (float) oldHeight;
- pWidth = (int) ((float) oldWidth * ratio);
- }
- // Else: No scale
- }
- }
-
- // Default is no scale, just work as a proxy
- if (pWidth < 0) {
- pWidth = oldWidth;
- }
- if (pHeight < 0) {
- pHeight = oldHeight;
- }
-
- // Create new Dimension object and return
- return new Dimension(pWidth, pHeight);
- }
-}
+/*
+ * Copyright (c) 2008, Harald Kuhr
+ * 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 "TwelveMonkeys" 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 OWNER 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.
+ */
+
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.image.ImageUtil;
+import com.twelvemonkeys.lang.StringUtil;
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.ServletRequest;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.RenderedImage;
+import java.lang.reflect.Field;
+
+
+/**
+ * This filter renders a scaled version of an image read from a
+ * given URL. The image can be output as a GIF, JPEG or PNG image
+ * or similar.
+ *
+ *
+ *
+ * Parameters:
+ *
+ * - {@code scaleX}
+ * - integer, the new width of the image.
+ *
- {@code scaleY}
+ * - integer, the new height of the image.
+ *
- {@code scaleUniform}
+ * - boolean, wether or not uniform scalnig should be used. Default is
+ * {@code true}.
+ *
- {@code scaleUnits}
+ * - string, one of {@code PIXELS}, {@code PERCENT}.
+ * {@code PIXELS} is default.
+ *
- {@code scaleQuality}
+ * - string, one of {@code SCALE_SMOOTH}, {@code SCALE_FAST},
+ * {@code SCALE_REPLICATE}, {@code SCALE_AREA_AVERAGING}.
+ * {@code SCALE_DEFAULT} is default (see
+ * {@link java.awt.Image#getScaledInstance(int,int,int)}, {@link java.awt.Image}
+ * for more details).
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: ScaleFilter.java#1 $
+ *
+ * @example <IMG src="/scale/test.jpg?scaleX=500&scaleUniform=false">
+ * @example <IMG src="/scale/test.png?scaleY=50&scaleUnits=PERCENT">
+ */
+public class ScaleFilter extends ImageFilter {
+
+ /**
+ * Width and height are absolute pixels. The default.
+ */
+ public static final int UNITS_PIXELS = 1;
+ /**
+ * Width and height are percentage of original width and height.
+ */
+ public static final int UNITS_PERCENT = 5;
+ /**
+ * Ahh, good choice!
+ */
+ //private static final int UNITS_METRIC = 42;
+ /**
+ * The root of all evil...
+ */
+ //private static final int UNITS_INCHES = 666;
+ /**
+ * Unknown units.
+ */
+ public static final int UNITS_UNKNOWN = 0;
+
+ /**
+ * {@code scaleQuality}
+ */
+ protected final static String PARAM_SCALE_QUALITY = "scaleQuality";
+ /**
+ * {@code scaleUnits}
+ */
+ protected final static String PARAM_SCALE_UNITS = "scaleUnits";
+ /**
+ * {@code scaleUniform}
+ */
+ protected final static String PARAM_SCALE_UNIFORM = "scaleUniform";
+ /**
+ * {@code scaleX}
+ */
+ protected final static String PARAM_SCALE_X = "scaleX";
+ /**
+ * {@code scaleY}
+ */
+ protected final static String PARAM_SCALE_Y = "scaleY";
+ /**
+ * {@code image}
+ */
+ protected final static String PARAM_IMAGE = "image";
+
+ /** */
+ protected int defaultScaleQuality = Image.SCALE_DEFAULT;
+
+ /**
+ * Reads the image from the requested URL, scales it, and returns it in the
+ * Servlet stream. See above for details on parameters.
+ */
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+
+ // Get quality setting
+ // SMOOTH | FAST | REPLICATE | DEFAULT | AREA_AVERAGING
+ // See Image (mHints)
+ int quality = getQuality(pRequest.getParameter(PARAM_SCALE_QUALITY));
+
+ // Get units, default is pixels
+ // PIXELS | PERCENT | METRIC | INCHES
+ int units = getUnits(pRequest.getParameter(PARAM_SCALE_UNITS));
+ if (units == UNITS_UNKNOWN) {
+ log("Unknown units for scale, returning original.");
+ return pImage;
+ }
+
+ // Use uniform scaling? Default is true
+ boolean uniformScale = ServletUtil.getBooleanParameter(pRequest, PARAM_SCALE_UNIFORM, true);
+
+ // Get dimensions
+ int width = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_X, -1);
+ int height = ServletUtil.getIntParameter(pRequest, PARAM_SCALE_Y, -1);
+
+ // Get dimensions for scaled image
+ Dimension dim = getDimensions(pImage, width, height, units, uniformScale);
+
+ width = (int) dim.getWidth();
+ height = (int) dim.getHeight();
+
+ // Return scaled instance directly
+ return ImageUtil.createScaled(pImage, width, height, quality);
+ }
+
+ /**
+ * Gets the quality constant for the scaling, from the string argument.
+ *
+ * @param pQualityStr The string representation of the scale quality
+ * constant.
+ * @return The matching quality constant, or the default quality if none
+ * was found.
+ * @see java.awt.Image
+ * @see java.awt.Image#getScaledInstance(int,int,int)
+ */
+ protected int getQuality(String pQualityStr) {
+ if (!StringUtil.isEmpty(pQualityStr)) {
+ try {
+ // Get quality constant from Image using reflection
+ Class cl = Image.class;
+ Field field = cl.getField(pQualityStr.toUpperCase());
+
+ return field.getInt(null);
+ }
+ catch (IllegalAccessException ia) {
+ log("Unable to get quality.", ia);
+ }
+ catch (NoSuchFieldException nsf) {
+ log("Unable to get quality.", nsf);
+ }
+ }
+
+ return defaultScaleQuality;
+ }
+
+ public void setDefaultScaleQuality(String pDefaultScaleQuality) {
+ defaultScaleQuality = getQuality(pDefaultScaleQuality);
+ }
+
+ /**
+ * Gets the units constant for the width and height arguments, from the
+ * given string argument.
+ *
+ * @param pUnitStr The string representation of the units constant,
+ * can be one of "PIXELS" or "PERCENT".
+ * @return The mathcing units constant, or UNITS_UNKNOWN if none was found.
+ */
+ protected int getUnits(String pUnitStr) {
+ if (StringUtil.isEmpty(pUnitStr)
+ || pUnitStr.equalsIgnoreCase("PIXELS")) {
+ return UNITS_PIXELS;
+ }
+ else if (pUnitStr.equalsIgnoreCase("PERCENT")) {
+ return UNITS_PERCENT;
+ }
+ else {
+ return UNITS_UNKNOWN;
+ }
+ }
+
+ /**
+ * Gets the dimensions (height and width) of the scaled image. The
+ * dimensions are computed based on the old image's dimensions, the units
+ * used for specifying new dimensions and whether or not uniform scaling
+ * should be used (se algorithm below).
+ *
+ * @param pImage the image to be scaled
+ * @param pWidth the new width of the image, or -1 if unknown
+ * @param pHeight the new height of the image, or -1 if unknown
+ * @param pUnits the constant specifying units for width and height
+ * parameter (UNITS_PIXELS or UNITS_PERCENT)
+ * @param pUniformScale boolean specifying uniform scale or not
+ * @return a Dimension object, with the correct width and heigth
+ * in pixels, for the scaled version of the image.
+ */
+ protected Dimension getDimensions(Image pImage, int pWidth, int pHeight,
+ int pUnits, boolean pUniformScale) {
+
+ // If uniform, make sure width and height are scaled the same ammount
+ // (use ONLY height or ONLY width).
+ //
+ // Algoritm:
+ // if uniform
+ // if newHeight not set
+ // find ratio newWidth / oldWidth
+ // oldHeight *= ratio
+ // else if newWidth not set
+ // find ratio newWidth / oldWidth
+ // oldHeight *= ratio
+ // else
+ // find both ratios and use the smallest one
+ // (this will be the largest version of the image that fits
+ // inside the rectangle given)
+ // (if PERCENT, just use smallest percentage).
+ //
+ // If units is percent, we only need old height and width
+
+ int oldWidth = ImageUtil.getWidth(pImage);
+ int oldHeight = ImageUtil.getHeight(pImage);
+ float ratio;
+
+ if (pUnits == UNITS_PERCENT) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Non-uniform
+ pWidth = (int) ((float) oldWidth * (float) pWidth / 100f);
+ pHeight = (int) ((float) oldHeight * (float) pHeight / 100f);
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / 100f;
+ pWidth = (int) ((float) oldWidth * ratio);
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / 100f;
+ pWidth = (int) ((float) oldWidth * ratio);
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+ // Else: No scale
+ }
+ else if (pUnits == UNITS_PIXELS) {
+ if (pUniformScale) {
+ if (pWidth >= 0 && pHeight >= 0) {
+ // Compute both ratios
+ ratio = (float) pWidth / (float) oldWidth;
+ float heightRatio = (float) pHeight / (float) oldHeight;
+
+ // Find the largest ratio, and use that for both
+ if (heightRatio < ratio) {
+ ratio = heightRatio;
+ pWidth = (int) ((float) oldWidth * ratio);
+ }
+ else {
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+
+ }
+ else if (pWidth >= 0) {
+ // Find ratio from pWidth
+ ratio = (float) pWidth / (float) oldWidth;
+ pHeight = (int) ((float) oldHeight * ratio);
+ }
+ else if (pHeight >= 0) {
+ // Find ratio from pHeight
+ ratio = (float) pHeight / (float) oldHeight;
+ pWidth = (int) ((float) oldWidth * ratio);
+ }
+ // Else: No scale
+ }
+ }
+
+ // Default is no scale, just work as a proxy
+ if (pWidth < 0) {
+ pWidth = oldWidth;
+ }
+ if (pHeight < 0) {
+ pHeight = oldHeight;
+ }
+
+ // Create new Dimension object and return
+ return new Dimension(pWidth, pHeight);
+ }
+}
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java
index d965decc..b93f9826 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java
@@ -1,154 +1,154 @@
-package com.twelvemonkeys.servlet.image;
-
-import com.twelvemonkeys.servlet.ServletUtil;
-
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import java.awt.image.RenderedImage;
-import java.awt.image.BufferedImage;
-import java.awt.*;
-import java.io.IOException;
-
-/**
- * A {@link javax.servlet.Filter} that extracts request parameters, and sets the
- * corresponding request attributes from {@link ImageServletResponse}.
- * Only affects how the image is decoded, and must be applied before any
- * other image filters in the chain.
- *
- * @see ImageServletResponse#ATTRIB_SIZE
- * @see ImageServletResponse#ATTRIB_AOI
- *
- * @author Harald Kuhr
- * @version $Id: SourceRenderFilter.java#1 $
- */
-public class SourceRenderFilter extends ImageFilter {
- private String sizeWidthParam = "size.w";
- private String sizeHeightParam = "size.h";
- private String sizePercentParam = "size.percent";
- private String sizeUniformParam = "size.uniform";
-
- private String regionWidthParam = "aoi.w";
- private String regionHeightParam = "aoi.h";
- private String regionLeftParam = "aoi.x";
- private String regionTopParam = "aoi.y";
- private String regionPercentParam = "aoi.percent";
- private String regionUniformParam = "aoi.uniform";
-
- public void setRegionHeightParam(String pRegionHeightParam) {
- regionHeightParam = pRegionHeightParam;
- }
-
- public void setRegionWidthParam(String pRegionWidthParam) {
- regionWidthParam = pRegionWidthParam;
- }
-
- public void setRegionLeftParam(String pRegionLeftParam) {
- regionLeftParam = pRegionLeftParam;
- }
-
- public void setRegionTopParam(String pRegionTopParam) {
- regionTopParam = pRegionTopParam;
- }
-
- public void setSizeHeightParam(String pSizeHeightParam) {
- sizeHeightParam = pSizeHeightParam;
- }
-
- public void setSizeWidthParam(String pSizeWidthParam) {
- sizeWidthParam = pSizeWidthParam;
- }
-
- public void setRegionPercentParam(String pRegionPercentParam) {
- regionPercentParam = pRegionPercentParam;
- }
-
- public void setRegionUniformParam(String pRegionUniformParam) {
- regionUniformParam = pRegionUniformParam;
- }
-
- public void setSizePercentParam(String pSizePercentParam) {
- sizePercentParam = pSizePercentParam;
- }
-
- public void setSizeUniformParam(String pSizeUniformParam) {
- sizeUniformParam = pSizeUniformParam;
- }
-
- public void init() throws ServletException {
- if (triggerParams == null) {
- // Add all params as triggers
- triggerParams = new String[]{sizeWidthParam, sizeHeightParam,
- sizeUniformParam, sizePercentParam,
- regionLeftParam, regionTopParam,
- regionWidthParam, regionHeightParam,
- regionUniformParam, regionPercentParam};
- }
- }
-
- /**
- * Extracts request parameters, and sets the corresponding request
- * attributes if specified.
- *
- * @param pRequest
- * @param pResponse
- * @param pChain
- * @throws IOException
- * @throws ServletException
- */
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- // TODO: Max size configuration, to avoid DOS attacks? OutOfMemory
-
- // Size parameters
- int width = ServletUtil.getIntParameter(pRequest, sizeWidthParam, -1);
- int height = ServletUtil.getIntParameter(pRequest, sizeHeightParam, -1);
- if (width > 0 || height > 0) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE, new Dimension(width, height));
- }
-
- // Size uniform/percent
- boolean uniform = ServletUtil.getBooleanParameter(pRequest, sizeUniformParam, true);
- if (!uniform) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.FALSE);
- }
- boolean percent = ServletUtil.getBooleanParameter(pRequest, sizePercentParam, false);
- if (percent) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
- }
-
- // Area of interest parameters
- int x = ServletUtil.getIntParameter(pRequest, regionLeftParam, -1); // Default is center
- int y = ServletUtil.getIntParameter(pRequest, regionTopParam, -1); // Default is center
- width = ServletUtil.getIntParameter(pRequest, regionWidthParam, -1);
- height = ServletUtil.getIntParameter(pRequest, regionHeightParam, -1);
- if (width > 0 || height > 0) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_AOI, new Rectangle(x, y, width, height));
- }
-
- // AOI uniform/percent
- uniform = ServletUtil.getBooleanParameter(pRequest, regionUniformParam, false);
- if (uniform) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.TRUE);
- }
- percent = ServletUtil.getBooleanParameter(pRequest, regionPercentParam, false);
- if (percent) {
- pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
- }
-
- super.doFilterImpl(pRequest, pResponse, pChain);
- }
-
- /**
- * This implementation does no filtering, and simply returns the image
- * passed in.
- *
- * @param pImage
- * @param pRequest
- * @param pResponse
- * @return {@code pImage}
- */
- protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
- return pImage;
- }
+package com.twelvemonkeys.servlet.image;
+
+import com.twelvemonkeys.servlet.ServletUtil;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import java.awt.image.RenderedImage;
+import java.awt.image.BufferedImage;
+import java.awt.*;
+import java.io.IOException;
+
+/**
+ * A {@link javax.servlet.Filter} that extracts request parameters, and sets the
+ * corresponding request attributes from {@link ImageServletResponse}.
+ * Only affects how the image is decoded, and must be applied before any
+ * other image filters in the chain.
+ *
+ * @see ImageServletResponse#ATTRIB_SIZE
+ * @see ImageServletResponse#ATTRIB_AOI
+ *
+ * @author Harald Kuhr
+ * @version $Id: SourceRenderFilter.java#1 $
+ */
+public class SourceRenderFilter extends ImageFilter {
+ private String sizeWidthParam = "size.w";
+ private String sizeHeightParam = "size.h";
+ private String sizePercentParam = "size.percent";
+ private String sizeUniformParam = "size.uniform";
+
+ private String regionWidthParam = "aoi.w";
+ private String regionHeightParam = "aoi.h";
+ private String regionLeftParam = "aoi.x";
+ private String regionTopParam = "aoi.y";
+ private String regionPercentParam = "aoi.percent";
+ private String regionUniformParam = "aoi.uniform";
+
+ public void setRegionHeightParam(String pRegionHeightParam) {
+ regionHeightParam = pRegionHeightParam;
+ }
+
+ public void setRegionWidthParam(String pRegionWidthParam) {
+ regionWidthParam = pRegionWidthParam;
+ }
+
+ public void setRegionLeftParam(String pRegionLeftParam) {
+ regionLeftParam = pRegionLeftParam;
+ }
+
+ public void setRegionTopParam(String pRegionTopParam) {
+ regionTopParam = pRegionTopParam;
+ }
+
+ public void setSizeHeightParam(String pSizeHeightParam) {
+ sizeHeightParam = pSizeHeightParam;
+ }
+
+ public void setSizeWidthParam(String pSizeWidthParam) {
+ sizeWidthParam = pSizeWidthParam;
+ }
+
+ public void setRegionPercentParam(String pRegionPercentParam) {
+ regionPercentParam = pRegionPercentParam;
+ }
+
+ public void setRegionUniformParam(String pRegionUniformParam) {
+ regionUniformParam = pRegionUniformParam;
+ }
+
+ public void setSizePercentParam(String pSizePercentParam) {
+ sizePercentParam = pSizePercentParam;
+ }
+
+ public void setSizeUniformParam(String pSizeUniformParam) {
+ sizeUniformParam = pSizeUniformParam;
+ }
+
+ public void init() throws ServletException {
+ if (triggerParams == null) {
+ // Add all params as triggers
+ triggerParams = new String[]{sizeWidthParam, sizeHeightParam,
+ sizeUniformParam, sizePercentParam,
+ regionLeftParam, regionTopParam,
+ regionWidthParam, regionHeightParam,
+ regionUniformParam, regionPercentParam};
+ }
+ }
+
+ /**
+ * Extracts request parameters, and sets the corresponding request
+ * attributes if specified.
+ *
+ * @param pRequest
+ * @param pResponse
+ * @param pChain
+ * @throws IOException
+ * @throws ServletException
+ */
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ // TODO: Max size configuration, to avoid DOS attacks? OutOfMemory
+
+ // Size parameters
+ int width = ServletUtil.getIntParameter(pRequest, sizeWidthParam, -1);
+ int height = ServletUtil.getIntParameter(pRequest, sizeHeightParam, -1);
+ if (width > 0 || height > 0) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE, new Dimension(width, height));
+ }
+
+ // Size uniform/percent
+ boolean uniform = ServletUtil.getBooleanParameter(pRequest, sizeUniformParam, true);
+ if (!uniform) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.FALSE);
+ }
+ boolean percent = ServletUtil.getBooleanParameter(pRequest, sizePercentParam, false);
+ if (percent) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
+ }
+
+ // Area of interest parameters
+ int x = ServletUtil.getIntParameter(pRequest, regionLeftParam, -1); // Default is center
+ int y = ServletUtil.getIntParameter(pRequest, regionTopParam, -1); // Default is center
+ width = ServletUtil.getIntParameter(pRequest, regionWidthParam, -1);
+ height = ServletUtil.getIntParameter(pRequest, regionHeightParam, -1);
+ if (width > 0 || height > 0) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_AOI, new Rectangle(x, y, width, height));
+ }
+
+ // AOI uniform/percent
+ uniform = ServletUtil.getBooleanParameter(pRequest, regionUniformParam, false);
+ if (uniform) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.TRUE);
+ }
+ percent = ServletUtil.getBooleanParameter(pRequest, regionPercentParam, false);
+ if (percent) {
+ pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE);
+ }
+
+ super.doFilterImpl(pRequest, pResponse, pChain);
+ }
+
+ /**
+ * This implementation does no filtering, and simply returns the image
+ * passed in.
+ *
+ * @param pImage
+ * @param pRequest
+ * @param pResponse
+ * @return {@code pImage}
+ */
+ protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) {
+ return pImage;
+ }
}
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java
index 9aeedc02..d18e9deb 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java
@@ -1,33 +1,33 @@
-/**
- * Contains various image-outputting filters, that should run under any
- * servlet engine.
- *
- * Some of these methods may require use of the native graphics libraries
- * supported by the JVM, like the X libraries on Unix systems, and should be
- * run with JRE 1.4 or later, and with the option:
- *
- * - {@code -Djawa.awt.headless=true}
- *
- * See the document
- * AWT Enhancements and bugtraq report
- * 4281163 for more information on this issue.
- *
- * If you cannot use JRE 1.4 or later, or do not want to use the X
- * libraries, one possibility is to use the
- * PJA package (com.eteks.pja),
- * and start the JVM with the following options:
- *
- * - {@code -Xbootclasspath/a:<path to pja.jar>}
- * - {@code -Dawt.toolkit=com.eteks.awt.PJAToolkit}
- * - {@code -Djava.awt.graphicsenv=com.eteks.java2d.PJAGraphicsEnvironment}
- * - {@code -Djava.awt.fonts=<path where True Type fonts files will be loaded from>}
- *
- *
- * Please note that creation of PNG images (from bytes or URL's) are only
- * supported in JRE 1.3 and later, trying to load them from an earlier version,
- * will result in errors.
- *
- * @see com.twelvemonkeys.servlet.image.ImageServlet
- * @see com.twelvemonkeys.servlet.image.ImagePainterServlet
- */
+/**
+ * Contains various image-outputting filters, that should run under any
+ * servlet engine.
+ *
+ * Some of these methods may require use of the native graphics libraries
+ * supported by the JVM, like the X libraries on Unix systems, and should be
+ * run with JRE 1.4 or later, and with the option:
+ *
+ * - {@code -Djawa.awt.headless=true}
+ *
+ * See the document
+ * AWT Enhancements and bugtraq report
+ * 4281163 for more information on this issue.
+ *
+ * If you cannot use JRE 1.4 or later, or do not want to use the X
+ * libraries, one possibility is to use the
+ * PJA package (com.eteks.pja),
+ * and start the JVM with the following options:
+ *
+ * - {@code -Xbootclasspath/a:<path to pja.jar>}
+ * - {@code -Dawt.toolkit=com.eteks.awt.PJAToolkit}
+ * - {@code -Djava.awt.graphicsenv=com.eteks.java2d.PJAGraphicsEnvironment}
+ * - {@code -Djava.awt.fonts=<path where True Type fonts files will be loaded from>}
+ *
+ *
+ * Please note that creation of PNG images (from bytes or URL's) are only
+ * supported in JRE 1.3 and later, trying to load them from an earlier version,
+ * will result in errors.
+ *
+ * @see com.twelvemonkeys.servlet.image.ImageServlet
+ * @see com.twelvemonkeys.servlet.image.ImagePainterServlet
+ */
package com.twelvemonkeys.servlet.image;
\ No newline at end of file
diff --git a/servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java b/servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java
index c14599d4..09d358cb 100755
--- a/servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java
+++ b/servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java
@@ -1,4 +1,4 @@
-/**
- * Contains servlet support classes.
- */
-package com.twelvemonkeys.servlet;
+/**
+ * Contains servlet support classes.
+ */
+package com.twelvemonkeys.servlet;
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java
index 42501e1e..88570a3c 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java
@@ -1,438 +1,438 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.ObjectAbstractTestCase;
-import org.junit.Test;
-
-import javax.servlet.*;
-import java.io.*;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-import static org.junit.Assert.*;
-
-/**
- * FilterAbstractTestCase
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java#1 $
- */
-public abstract class FilterAbstractTestCase extends ObjectAbstractTestCase {
- protected Object makeObject() {
- return makeFilter();
- }
-
- protected abstract Filter makeFilter();
-
- // TODO: Is it a good thing to have an API like this?
- protected FilterConfig makeFilterConfig() {
- return makeFilterConfig(new HashMap());
- }
-
- protected FilterConfig makeFilterConfig(Map pParams) {
- return new MockFilterConfig(pParams);
- }
-
- protected ServletRequest makeRequest() {
- return new MockServletRequest();
- }
-
- protected ServletResponse makeResponse() {
- return new MockServletResponse();
- }
-
- protected FilterChain makeFilterChain() {
- return new MockFilterChain();
- }
-
- @Test
- public void testInitNull() {
- Filter filter = makeFilter();
-
- // The spec seems to be a little unclear on this issue, but anyway,
- // the container should never invoke init(null)...
- try {
- filter.init(null);
- fail("Should throw Exception on init(null)");
- }
- catch (IllegalArgumentException e) {
- // Good
- }
- catch (NullPointerException e) {
- // Bad (but not unreasonable)
- }
- catch (ServletException e) {
- // Hmmm.. The jury is still out.
- }
- }
-
- @Test
- public void testInit() {
- Filter filter = makeFilter();
-
- try {
- filter.init(makeFilterConfig());
- }
- catch (ServletException e) {
- assertNotNull(e.getMessage());
- }
- finally {
- filter.destroy();
- }
- }
-
- @Test
- public void testLifeCycle() throws ServletException {
- Filter filter = makeFilter();
-
- try {
- filter.init(makeFilterConfig());
- }
- finally {
- filter.destroy();
- }
- }
-
- @Test
- public void testFilterBasic() throws ServletException, IOException {
- Filter filter = makeFilter();
-
- try {
- filter.init(makeFilterConfig());
-
- filter.doFilter(makeRequest(), makeResponse(), makeFilterChain());
- }
- finally {
- filter.destroy();
- }
- }
-
- @Test
- public void testDestroy() {
- // TODO: Implement
- }
-
- static class MockFilterConfig implements FilterConfig {
- private final Map params;
-
- MockFilterConfig(Map pParams) {
- if (pParams == null) {
- throw new IllegalArgumentException("params == null");
- }
- params = pParams;
- }
-
- public String getFilterName() {
- return "mock-filter";
- }
-
- public String getInitParameter(String pName) {
- return params.get(pName);
- }
-
- public Enumeration getInitParameterNames() {
- return Collections.enumeration(params.keySet());
- }
-
- public ServletContext getServletContext() {
- return new MockServletContext();
- }
-
- private static class MockServletContext implements ServletContext {
- private final Map attributes;
- private final Map params;
-
- MockServletContext() {
- attributes = new HashMap();
- params = new HashMap();
- }
-
- public Object getAttribute(String s) {
- return attributes.get(s);
- }
-
- public Enumeration getAttributeNames() {
- return Collections.enumeration(attributes.keySet());
- }
-
- public ServletContext getContext(String s) {
- return null; // TODO: Implement
- }
-
- public String getInitParameter(String s) {
- return (String) params.get(s);
- }
-
- public Enumeration getInitParameterNames() {
- return Collections.enumeration(params.keySet());
- }
-
- public int getMajorVersion() {
- return 0; // TODO: Implement
- }
-
- public String getMimeType(String s) {
- return null; // TODO: Implement
- }
-
- public int getMinorVersion() {
- return 0; // TODO: Implement
- }
-
- public RequestDispatcher getNamedDispatcher(String s) {
- return null; // TODO: Implement
- }
-
- public String getRealPath(String s) {
- return null; // TODO: Implement
- }
-
- public RequestDispatcher getRequestDispatcher(String s) {
- return null; // TODO: Implement
- }
-
- public URL getResource(String s) throws MalformedURLException {
- return null; // TODO: Implement
- }
-
- public InputStream getResourceAsStream(String s) {
- return null; // TODO: Implement
- }
-
- public Set getResourcePaths(String s) {
- return null; // TODO: Implement
- }
-
- public String getServerInfo() {
- return null; // TODO: Implement
- }
-
- public Servlet getServlet(String s) throws ServletException {
- return null; // TODO: Implement
- }
-
- public String getServletContextName() {
- return "mock";
- }
-
- public Enumeration getServletNames() {
- return null; // TODO: Implement
- }
-
- public Enumeration getServlets() {
- return null; // TODO: Implement
- }
-
- public void log(Exception exception, String s) {
- }
-
- public void log(String s) {
- }
-
- public void log(String s, Throwable throwable) {
- }
-
- public void removeAttribute(String s) {
- attributes.remove(s);
- }
-
- public void setAttribute(String s, Object obj) {
- attributes.put(s, obj);
- }
- }
- }
-
- static class MockServletRequest implements ServletRequest {
- final private Map attributes;
-
- public MockServletRequest() {
- attributes = new HashMap();
- }
-
- public Object getAttribute(String pKey) {
- return attributes.get(pKey);
- }
-
- public Enumeration getAttributeNames() {
- return Collections.enumeration(attributes.keySet());
- }
-
- public String getCharacterEncoding() {
- return null; // TODO: Implement
- }
-
- public void setCharacterEncoding(String pMessage) throws UnsupportedEncodingException {
- // TODO: Implement
- }
-
- public int getContentLength() {
- return 0; // TODO: Implement
- }
-
- public String getContentType() {
- return null; // TODO: Implement
- }
-
- public ServletInputStream getInputStream() throws IOException {
- return null; // TODO: Implement
- }
-
- public String getParameter(String pMessage) {
- return null; // TODO: Implement
- }
-
- public Enumeration getParameterNames() {
- return null; // TODO: Implement
- }
-
- public String[] getParameterValues(String pMessage) {
- return new String[0]; // TODO: Implement
- }
-
- public Map getParameterMap() {
- return null; // TODO: Implement
- }
-
- public String getProtocol() {
- return null; // TODO: Implement
- }
-
- public String getScheme() {
- return null; // TODO: Implement
- }
-
- public String getServerName() {
- return null; // TODO: Implement
- }
-
- public int getServerPort() {
- return 0; // TODO: Implement
- }
-
- public BufferedReader getReader() throws IOException {
- return null; // TODO: Implement
- }
-
- public String getRemoteAddr() {
- return null; // TODO: Implement
- }
-
- public String getRemoteHost() {
- return null; // TODO: Implement
- }
-
- public void setAttribute(String pKey, Object pValue) {
- attributes.put(pKey, pValue);
- }
-
- public void removeAttribute(String pKey) {
- attributes.remove(pKey);
- }
-
- public Locale getLocale() {
- return null; // TODO: Implement
- }
-
- public Enumeration getLocales() {
- return null; // TODO: Implement
- }
-
- public boolean isSecure() {
- return false; // TODO: Implement
- }
-
- public RequestDispatcher getRequestDispatcher(String pMessage) {
- return null; // TODO: Implement
- }
-
- public String getRealPath(String pMessage) {
- return null; // TODO: Implement
- }
-
- public int getRemotePort() {
- throw new UnsupportedOperationException("Method getRemotePort not implemented");// TODO: Implement
- }
-
- public String getLocalName() {
- throw new UnsupportedOperationException("Method getLocalName not implemented");// TODO: Implement
- }
-
- public String getLocalAddr() {
- throw new UnsupportedOperationException("Method getLocalAddr not implemented");// TODO: Implement
- }
-
- public int getLocalPort() {
- throw new UnsupportedOperationException("Method getLocalPort not implemented");// TODO: Implement
- }
- }
-
- static class MockServletResponse implements ServletResponse {
- public void flushBuffer() throws IOException {
- // TODO: Implement
- }
-
- public int getBufferSize() {
- return 0; // TODO: Implement
- }
-
- public String getCharacterEncoding() {
- return null; // TODO: Implement
- }
-
- public String getContentType() {
- throw new UnsupportedOperationException("Method getContentType not implemented");// TODO: Implement
- }
-
- public Locale getLocale() {
- return null; // TODO: Implement
- }
-
- public ServletOutputStream getOutputStream() throws IOException {
- return null; // TODO: Implement
- }
-
- public PrintWriter getWriter() throws IOException {
- return null; // TODO: Implement
- }
-
- public void setCharacterEncoding(String charset) {
- throw new UnsupportedOperationException("Method setCharacterEncoding not implemented");// TODO: Implement
- }
-
- public boolean isCommitted() {
- return false; // TODO: Implement
- }
-
- public void reset() {
- // TODO: Implement
- }
-
- public void resetBuffer() {
- // TODO: Implement
- }
-
- public void setBufferSize(int pLength) {
- // TODO: Implement
- }
-
- public void setContentLength(int pLength) {
- // TODO: Implement
- }
-
- public void setContentType(String pMessage) {
- // TODO: Implement
- }
-
- public void setLocale(Locale pLocale) {
- // TODO: Implement
- }
- }
-
- static class MockFilterChain implements FilterChain {
- public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
- // TODO: Implement
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.ObjectAbstractTestCase;
+import org.junit.Test;
+
+import javax.servlet.*;
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * FilterAbstractTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java#1 $
+ */
+public abstract class FilterAbstractTestCase extends ObjectAbstractTestCase {
+ protected Object makeObject() {
+ return makeFilter();
+ }
+
+ protected abstract Filter makeFilter();
+
+ // TODO: Is it a good thing to have an API like this?
+ protected FilterConfig makeFilterConfig() {
+ return makeFilterConfig(new HashMap());
+ }
+
+ protected FilterConfig makeFilterConfig(Map pParams) {
+ return new MockFilterConfig(pParams);
+ }
+
+ protected ServletRequest makeRequest() {
+ return new MockServletRequest();
+ }
+
+ protected ServletResponse makeResponse() {
+ return new MockServletResponse();
+ }
+
+ protected FilterChain makeFilterChain() {
+ return new MockFilterChain();
+ }
+
+ @Test
+ public void testInitNull() {
+ Filter filter = makeFilter();
+
+ // The spec seems to be a little unclear on this issue, but anyway,
+ // the container should never invoke init(null)...
+ try {
+ filter.init(null);
+ fail("Should throw Exception on init(null)");
+ }
+ catch (IllegalArgumentException e) {
+ // Good
+ }
+ catch (NullPointerException e) {
+ // Bad (but not unreasonable)
+ }
+ catch (ServletException e) {
+ // Hmmm.. The jury is still out.
+ }
+ }
+
+ @Test
+ public void testInit() {
+ Filter filter = makeFilter();
+
+ try {
+ filter.init(makeFilterConfig());
+ }
+ catch (ServletException e) {
+ assertNotNull(e.getMessage());
+ }
+ finally {
+ filter.destroy();
+ }
+ }
+
+ @Test
+ public void testLifeCycle() throws ServletException {
+ Filter filter = makeFilter();
+
+ try {
+ filter.init(makeFilterConfig());
+ }
+ finally {
+ filter.destroy();
+ }
+ }
+
+ @Test
+ public void testFilterBasic() throws ServletException, IOException {
+ Filter filter = makeFilter();
+
+ try {
+ filter.init(makeFilterConfig());
+
+ filter.doFilter(makeRequest(), makeResponse(), makeFilterChain());
+ }
+ finally {
+ filter.destroy();
+ }
+ }
+
+ @Test
+ public void testDestroy() {
+ // TODO: Implement
+ }
+
+ static class MockFilterConfig implements FilterConfig {
+ private final Map params;
+
+ MockFilterConfig(Map pParams) {
+ if (pParams == null) {
+ throw new IllegalArgumentException("params == null");
+ }
+ params = pParams;
+ }
+
+ public String getFilterName() {
+ return "mock-filter";
+ }
+
+ public String getInitParameter(String pName) {
+ return params.get(pName);
+ }
+
+ public Enumeration getInitParameterNames() {
+ return Collections.enumeration(params.keySet());
+ }
+
+ public ServletContext getServletContext() {
+ return new MockServletContext();
+ }
+
+ private static class MockServletContext implements ServletContext {
+ private final Map attributes;
+ private final Map params;
+
+ MockServletContext() {
+ attributes = new HashMap();
+ params = new HashMap();
+ }
+
+ public Object getAttribute(String s) {
+ return attributes.get(s);
+ }
+
+ public Enumeration getAttributeNames() {
+ return Collections.enumeration(attributes.keySet());
+ }
+
+ public ServletContext getContext(String s) {
+ return null; // TODO: Implement
+ }
+
+ public String getInitParameter(String s) {
+ return (String) params.get(s);
+ }
+
+ public Enumeration getInitParameterNames() {
+ return Collections.enumeration(params.keySet());
+ }
+
+ public int getMajorVersion() {
+ return 0; // TODO: Implement
+ }
+
+ public String getMimeType(String s) {
+ return null; // TODO: Implement
+ }
+
+ public int getMinorVersion() {
+ return 0; // TODO: Implement
+ }
+
+ public RequestDispatcher getNamedDispatcher(String s) {
+ return null; // TODO: Implement
+ }
+
+ public String getRealPath(String s) {
+ return null; // TODO: Implement
+ }
+
+ public RequestDispatcher getRequestDispatcher(String s) {
+ return null; // TODO: Implement
+ }
+
+ public URL getResource(String s) throws MalformedURLException {
+ return null; // TODO: Implement
+ }
+
+ public InputStream getResourceAsStream(String s) {
+ return null; // TODO: Implement
+ }
+
+ public Set getResourcePaths(String s) {
+ return null; // TODO: Implement
+ }
+
+ public String getServerInfo() {
+ return null; // TODO: Implement
+ }
+
+ public Servlet getServlet(String s) throws ServletException {
+ return null; // TODO: Implement
+ }
+
+ public String getServletContextName() {
+ return "mock";
+ }
+
+ public Enumeration getServletNames() {
+ return null; // TODO: Implement
+ }
+
+ public Enumeration getServlets() {
+ return null; // TODO: Implement
+ }
+
+ public void log(Exception exception, String s) {
+ }
+
+ public void log(String s) {
+ }
+
+ public void log(String s, Throwable throwable) {
+ }
+
+ public void removeAttribute(String s) {
+ attributes.remove(s);
+ }
+
+ public void setAttribute(String s, Object obj) {
+ attributes.put(s, obj);
+ }
+ }
+ }
+
+ static class MockServletRequest implements ServletRequest {
+ final private Map attributes;
+
+ public MockServletRequest() {
+ attributes = new HashMap();
+ }
+
+ public Object getAttribute(String pKey) {
+ return attributes.get(pKey);
+ }
+
+ public Enumeration getAttributeNames() {
+ return Collections.enumeration(attributes.keySet());
+ }
+
+ public String getCharacterEncoding() {
+ return null; // TODO: Implement
+ }
+
+ public void setCharacterEncoding(String pMessage) throws UnsupportedEncodingException {
+ // TODO: Implement
+ }
+
+ public int getContentLength() {
+ return 0; // TODO: Implement
+ }
+
+ public String getContentType() {
+ return null; // TODO: Implement
+ }
+
+ public ServletInputStream getInputStream() throws IOException {
+ return null; // TODO: Implement
+ }
+
+ public String getParameter(String pMessage) {
+ return null; // TODO: Implement
+ }
+
+ public Enumeration getParameterNames() {
+ return null; // TODO: Implement
+ }
+
+ public String[] getParameterValues(String pMessage) {
+ return new String[0]; // TODO: Implement
+ }
+
+ public Map getParameterMap() {
+ return null; // TODO: Implement
+ }
+
+ public String getProtocol() {
+ return null; // TODO: Implement
+ }
+
+ public String getScheme() {
+ return null; // TODO: Implement
+ }
+
+ public String getServerName() {
+ return null; // TODO: Implement
+ }
+
+ public int getServerPort() {
+ return 0; // TODO: Implement
+ }
+
+ public BufferedReader getReader() throws IOException {
+ return null; // TODO: Implement
+ }
+
+ public String getRemoteAddr() {
+ return null; // TODO: Implement
+ }
+
+ public String getRemoteHost() {
+ return null; // TODO: Implement
+ }
+
+ public void setAttribute(String pKey, Object pValue) {
+ attributes.put(pKey, pValue);
+ }
+
+ public void removeAttribute(String pKey) {
+ attributes.remove(pKey);
+ }
+
+ public Locale getLocale() {
+ return null; // TODO: Implement
+ }
+
+ public Enumeration getLocales() {
+ return null; // TODO: Implement
+ }
+
+ public boolean isSecure() {
+ return false; // TODO: Implement
+ }
+
+ public RequestDispatcher getRequestDispatcher(String pMessage) {
+ return null; // TODO: Implement
+ }
+
+ public String getRealPath(String pMessage) {
+ return null; // TODO: Implement
+ }
+
+ public int getRemotePort() {
+ throw new UnsupportedOperationException("Method getRemotePort not implemented");// TODO: Implement
+ }
+
+ public String getLocalName() {
+ throw new UnsupportedOperationException("Method getLocalName not implemented");// TODO: Implement
+ }
+
+ public String getLocalAddr() {
+ throw new UnsupportedOperationException("Method getLocalAddr not implemented");// TODO: Implement
+ }
+
+ public int getLocalPort() {
+ throw new UnsupportedOperationException("Method getLocalPort not implemented");// TODO: Implement
+ }
+ }
+
+ static class MockServletResponse implements ServletResponse {
+ public void flushBuffer() throws IOException {
+ // TODO: Implement
+ }
+
+ public int getBufferSize() {
+ return 0; // TODO: Implement
+ }
+
+ public String getCharacterEncoding() {
+ return null; // TODO: Implement
+ }
+
+ public String getContentType() {
+ throw new UnsupportedOperationException("Method getContentType not implemented");// TODO: Implement
+ }
+
+ public Locale getLocale() {
+ return null; // TODO: Implement
+ }
+
+ public ServletOutputStream getOutputStream() throws IOException {
+ return null; // TODO: Implement
+ }
+
+ public PrintWriter getWriter() throws IOException {
+ return null; // TODO: Implement
+ }
+
+ public void setCharacterEncoding(String charset) {
+ throw new UnsupportedOperationException("Method setCharacterEncoding not implemented");// TODO: Implement
+ }
+
+ public boolean isCommitted() {
+ return false; // TODO: Implement
+ }
+
+ public void reset() {
+ // TODO: Implement
+ }
+
+ public void resetBuffer() {
+ // TODO: Implement
+ }
+
+ public void setBufferSize(int pLength) {
+ // TODO: Implement
+ }
+
+ public void setContentLength(int pLength) {
+ // TODO: Implement
+ }
+
+ public void setContentType(String pMessage) {
+ // TODO: Implement
+ }
+
+ public void setLocale(Locale pLocale) {
+ // TODO: Implement
+ }
+ }
+
+ static class MockFilterChain implements FilterChain {
+ public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
+ // TODO: Implement
+ }
+ }
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java
index 92a036b2..1e04e19b 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java
@@ -1,157 +1,157 @@
-package com.twelvemonkeys.servlet;
-
-import org.junit.Test;
-
-import javax.servlet.*;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.*;
-
-/**
- * GenericFilterTestCase
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java#1 $
- */
-public final class GenericFilterTestCase extends FilterAbstractTestCase {
- protected Filter makeFilter() {
- return new GenericFilterImpl();
- }
-
- @Test
- public void testInitOncePerRequest() {
- // Default FALSE
- GenericFilter filter = new GenericFilterImpl();
-
- try {
- filter.init(makeFilterConfig());
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- assertFalse("OncePerRequest should default to false", filter.oncePerRequest);
- filter.destroy();
-
- // TRUE
- filter = new GenericFilterImpl();
- Map params = new HashMap();
- params.put("once-per-request", "true");
-
- try {
- filter.init(makeFilterConfig(params));
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- assertTrue("oncePerRequest should be true", filter.oncePerRequest);
- filter.destroy();
-
- // TRUE
- filter = new GenericFilterImpl();
- params = new HashMap();
- params.put("oncePerRequest", "true");
-
- try {
- filter.init(makeFilterConfig(params));
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- assertTrue("oncePerRequest should be true", filter.oncePerRequest);
- filter.destroy();
- }
-
- @Test
- public void testFilterOnlyOnce() {
- final GenericFilterImpl filter = new GenericFilterImpl();
- filter.setOncePerRequest(true);
-
- try {
- filter.init(makeFilterConfig());
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- FilterChain chain = new MyFilterChain(new Filter[] {filter, filter, filter});
-
- try {
- chain.doFilter(makeRequest(), makeResponse());
- }
- catch (IOException e) {
- fail(e.getMessage());
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- assertEquals("Filter was invoked more than once!", 1, filter.invocationCount);
-
- filter.destroy();
- }
-
- @Test
- public void testFilterMultiple() {
- final GenericFilterImpl filter = new GenericFilterImpl();
-
- try {
- filter.init(makeFilterConfig());
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- FilterChain chain = new MyFilterChain(new Filter[] {
- filter, filter, filter, filter, filter
- });
-
- try {
- chain.doFilter(makeRequest(), makeResponse());
- }
- catch (IOException e) {
- fail(e.getMessage());
- }
- catch (ServletException e) {
- fail(e.getMessage());
- }
-
- assertEquals("Filter was invoked not invoked five times!", 5, filter.invocationCount);
-
- filter.destroy();
- }
-
- private static class GenericFilterImpl extends GenericFilter {
- int invocationCount;
- protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
- invocationCount++;
- pChain.doFilter(pRequest, pResponse);
- }
- }
-
- private static class MyFilterChain implements FilterChain {
-
- Filter[] mFilters;
- int mCurrentFilter;
-
- public MyFilterChain(Filter[] pFilters) {
- if (pFilters == null) {
- throw new IllegalArgumentException("filters == null");
- }
- mFilters = pFilters;
- mCurrentFilter = 0;
- }
-
- public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
- if (mCurrentFilter < mFilters.length) {
- mFilters[mCurrentFilter++].doFilter(pRequest, pResponse, this);
- }
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import org.junit.Test;
+
+import javax.servlet.*;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * GenericFilterTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java#1 $
+ */
+public final class GenericFilterTestCase extends FilterAbstractTestCase {
+ protected Filter makeFilter() {
+ return new GenericFilterImpl();
+ }
+
+ @Test
+ public void testInitOncePerRequest() {
+ // Default FALSE
+ GenericFilter filter = new GenericFilterImpl();
+
+ try {
+ filter.init(makeFilterConfig());
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ assertFalse("OncePerRequest should default to false", filter.oncePerRequest);
+ filter.destroy();
+
+ // TRUE
+ filter = new GenericFilterImpl();
+ Map params = new HashMap();
+ params.put("once-per-request", "true");
+
+ try {
+ filter.init(makeFilterConfig(params));
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ assertTrue("oncePerRequest should be true", filter.oncePerRequest);
+ filter.destroy();
+
+ // TRUE
+ filter = new GenericFilterImpl();
+ params = new HashMap();
+ params.put("oncePerRequest", "true");
+
+ try {
+ filter.init(makeFilterConfig(params));
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ assertTrue("oncePerRequest should be true", filter.oncePerRequest);
+ filter.destroy();
+ }
+
+ @Test
+ public void testFilterOnlyOnce() {
+ final GenericFilterImpl filter = new GenericFilterImpl();
+ filter.setOncePerRequest(true);
+
+ try {
+ filter.init(makeFilterConfig());
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ FilterChain chain = new MyFilterChain(new Filter[] {filter, filter, filter});
+
+ try {
+ chain.doFilter(makeRequest(), makeResponse());
+ }
+ catch (IOException e) {
+ fail(e.getMessage());
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ assertEquals("Filter was invoked more than once!", 1, filter.invocationCount);
+
+ filter.destroy();
+ }
+
+ @Test
+ public void testFilterMultiple() {
+ final GenericFilterImpl filter = new GenericFilterImpl();
+
+ try {
+ filter.init(makeFilterConfig());
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ FilterChain chain = new MyFilterChain(new Filter[] {
+ filter, filter, filter, filter, filter
+ });
+
+ try {
+ chain.doFilter(makeRequest(), makeResponse());
+ }
+ catch (IOException e) {
+ fail(e.getMessage());
+ }
+ catch (ServletException e) {
+ fail(e.getMessage());
+ }
+
+ assertEquals("Filter was invoked not invoked five times!", 5, filter.invocationCount);
+
+ filter.destroy();
+ }
+
+ private static class GenericFilterImpl extends GenericFilter {
+ int invocationCount;
+ protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
+ invocationCount++;
+ pChain.doFilter(pRequest, pResponse);
+ }
+ }
+
+ private static class MyFilterChain implements FilterChain {
+
+ Filter[] mFilters;
+ int mCurrentFilter;
+
+ public MyFilterChain(Filter[] pFilters) {
+ if (pFilters == null) {
+ throw new IllegalArgumentException("filters == null");
+ }
+ mFilters = pFilters;
+ mCurrentFilter = 0;
+ }
+
+ public void doFilter(ServletRequest pRequest, ServletResponse pResponse) throws IOException, ServletException {
+ if (mCurrentFilter < mFilters.length) {
+ mFilters[mCurrentFilter++].doFilter(pRequest, pResponse, this);
+ }
+ }
+ }
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java
index 2d594264..7a164636 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTest.java
@@ -1,199 +1,199 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.util.MapAbstractTestCase;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-import javax.servlet.*;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.*;
-
-/**
- * ServletConfigMapAdapterTestCase
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $
- */
-@RunWith(Suite.class)
-@Suite.SuiteClasses({AbstractServletConfigMapAdapterTest.ServletConfigMapTest.class, AbstractServletConfigMapAdapterTest.FilterConfigMapTest.class, AbstractServletConfigMapAdapterTest.ServletContextMapTest.class})
-public final class ServletConfigMapAdapterTest {
-}
-
-abstract class AbstractServletConfigMapAdapterTest extends MapAbstractTestCase {
-
- public boolean isPutAddSupported() {
- return false;
- }
-
- public boolean isPutChangeSupported() {
- return false;
- }
-
- public boolean isRemoveSupported() {
- return false;
- }
-
- public boolean isSetValueSupported() {
- return false;
- }
-
- private static class TestConfig implements ServletConfig, FilterConfig, ServletContext, Serializable, Cloneable {
- Map map = new HashMap();
-
- public String getServletName() {
- return "dummy"; // Not needed for this test
- }
-
- public String getFilterName() {
- return getServletName();
- }
-
- public String getServletContextName() {
- return getServletName();
- }
-
-
- public ServletContext getServletContext() {
- throw new UnsupportedOperationException("Method getSerlvetContext not implemented");
- }
-
- public String getInitParameter(String s) {
- return (String) map.get(s);
- }
-
- public Enumeration getInitParameterNames() {
- //noinspection unchecked
- return Collections.enumeration(map.keySet());
- }
-
- public ServletContext getContext(String uripath) {
- throw new UnsupportedOperationException("Method getContext not implemented");
- }
-
- public int getMajorVersion() {
- throw new UnsupportedOperationException("Method getMajorVersion not implemented");
- }
-
- public int getMinorVersion() {
- throw new UnsupportedOperationException("Method getMinorVersion not implemented");
- }
-
- public String getMimeType(String file) {
- throw new UnsupportedOperationException("Method getMimeType not implemented");
- }
-
- public Set getResourcePaths(String path) {
- throw new UnsupportedOperationException("Method getResourcePaths not implemented");
- }
-
- public URL getResource(String path) throws MalformedURLException {
- throw new UnsupportedOperationException("Method getResource not implemented");
- }
-
- public InputStream getResourceAsStream(String path) {
- throw new UnsupportedOperationException("Method getResourceAsStream not implemented");
- }
-
- public RequestDispatcher getRequestDispatcher(String path) {
- throw new UnsupportedOperationException("Method getRequestDispatcher not implemented");
- }
-
- public RequestDispatcher getNamedDispatcher(String name) {
- throw new UnsupportedOperationException("Method getNamedDispatcher not implemented");
- }
-
- public Servlet getServlet(String name) throws ServletException {
- throw new UnsupportedOperationException("Method getServlet not implemented");
- }
-
- public Enumeration getServlets() {
- throw new UnsupportedOperationException("Method getServlets not implemented");
- }
-
- public Enumeration getServletNames() {
- throw new UnsupportedOperationException("Method getServletNames not implemented");
- }
-
- public void log(String msg) {
- throw new UnsupportedOperationException("Method log not implemented");
- }
-
- public void log(Exception exception, String msg) {
- throw new UnsupportedOperationException("Method log not implemented");
- }
-
- public void log(String message, Throwable throwable) {
- throw new UnsupportedOperationException("Method log not implemented");
- }
-
- public String getRealPath(String path) {
- throw new UnsupportedOperationException("Method getRealPath not implemented");
- }
-
- public String getServerInfo() {
- throw new UnsupportedOperationException("Method getServerInfo not implemented");
- }
-
- public Object getAttribute(String name) {
- throw new UnsupportedOperationException("Method getAttribute not implemented");
- }
-
- public Enumeration getAttributeNames() {
- throw new UnsupportedOperationException("Method getAttributeNames not implemented");
- }
-
- public void setAttribute(String name, Object object) {
- throw new UnsupportedOperationException("Method setAttribute not implemented");
- }
-
- public void removeAttribute(String name) {
- throw new UnsupportedOperationException("Method removeAttribute not implemented");
- }
- }
-
- public static final class ServletConfigMapTest extends AbstractServletConfigMapAdapterTest {
-
- public Map makeEmptyMap() {
- ServletConfig config = new TestConfig();
- return new ServletConfigMapAdapter(config);
- }
-
- public Map makeFullMap() {
- ServletConfig config = new TestConfig();
- addSampleMappings(((TestConfig) config).map);
- return new ServletConfigMapAdapter(config);
- }
- }
-
- public static final class FilterConfigMapTest extends AbstractServletConfigMapAdapterTest {
-
- public Map makeEmptyMap() {
- FilterConfig config = new TestConfig();
- return new ServletConfigMapAdapter(config);
- }
-
- public Map makeFullMap() {
- FilterConfig config = new TestConfig();
- addSampleMappings(((TestConfig) config).map);
- return new ServletConfigMapAdapter(config);
- }
- }
-
- public static final class ServletContextMapTest extends AbstractServletConfigMapAdapterTest {
-
- public Map makeEmptyMap() {
- ServletContext config = new TestConfig();
- return new ServletConfigMapAdapter(config);
- }
-
- public Map makeFullMap() {
- FilterConfig config = new TestConfig();
- addSampleMappings(((TestConfig) config).map);
- return new ServletConfigMapAdapter(config);
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.util.MapAbstractTestCase;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import javax.servlet.*;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+/**
+ * ServletConfigMapAdapterTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({AbstractServletConfigMapAdapterTest.ServletConfigMapTest.class, AbstractServletConfigMapAdapterTest.FilterConfigMapTest.class, AbstractServletConfigMapAdapterTest.ServletContextMapTest.class})
+public final class ServletConfigMapAdapterTest {
+}
+
+abstract class AbstractServletConfigMapAdapterTest extends MapAbstractTestCase {
+
+ public boolean isPutAddSupported() {
+ return false;
+ }
+
+ public boolean isPutChangeSupported() {
+ return false;
+ }
+
+ public boolean isRemoveSupported() {
+ return false;
+ }
+
+ public boolean isSetValueSupported() {
+ return false;
+ }
+
+ private static class TestConfig implements ServletConfig, FilterConfig, ServletContext, Serializable, Cloneable {
+ Map map = new HashMap();
+
+ public String getServletName() {
+ return "dummy"; // Not needed for this test
+ }
+
+ public String getFilterName() {
+ return getServletName();
+ }
+
+ public String getServletContextName() {
+ return getServletName();
+ }
+
+
+ public ServletContext getServletContext() {
+ throw new UnsupportedOperationException("Method getSerlvetContext not implemented");
+ }
+
+ public String getInitParameter(String s) {
+ return (String) map.get(s);
+ }
+
+ public Enumeration getInitParameterNames() {
+ //noinspection unchecked
+ return Collections.enumeration(map.keySet());
+ }
+
+ public ServletContext getContext(String uripath) {
+ throw new UnsupportedOperationException("Method getContext not implemented");
+ }
+
+ public int getMajorVersion() {
+ throw new UnsupportedOperationException("Method getMajorVersion not implemented");
+ }
+
+ public int getMinorVersion() {
+ throw new UnsupportedOperationException("Method getMinorVersion not implemented");
+ }
+
+ public String getMimeType(String file) {
+ throw new UnsupportedOperationException("Method getMimeType not implemented");
+ }
+
+ public Set getResourcePaths(String path) {
+ throw new UnsupportedOperationException("Method getResourcePaths not implemented");
+ }
+
+ public URL getResource(String path) throws MalformedURLException {
+ throw new UnsupportedOperationException("Method getResource not implemented");
+ }
+
+ public InputStream getResourceAsStream(String path) {
+ throw new UnsupportedOperationException("Method getResourceAsStream not implemented");
+ }
+
+ public RequestDispatcher getRequestDispatcher(String path) {
+ throw new UnsupportedOperationException("Method getRequestDispatcher not implemented");
+ }
+
+ public RequestDispatcher getNamedDispatcher(String name) {
+ throw new UnsupportedOperationException("Method getNamedDispatcher not implemented");
+ }
+
+ public Servlet getServlet(String name) throws ServletException {
+ throw new UnsupportedOperationException("Method getServlet not implemented");
+ }
+
+ public Enumeration getServlets() {
+ throw new UnsupportedOperationException("Method getServlets not implemented");
+ }
+
+ public Enumeration getServletNames() {
+ throw new UnsupportedOperationException("Method getServletNames not implemented");
+ }
+
+ public void log(String msg) {
+ throw new UnsupportedOperationException("Method log not implemented");
+ }
+
+ public void log(Exception exception, String msg) {
+ throw new UnsupportedOperationException("Method log not implemented");
+ }
+
+ public void log(String message, Throwable throwable) {
+ throw new UnsupportedOperationException("Method log not implemented");
+ }
+
+ public String getRealPath(String path) {
+ throw new UnsupportedOperationException("Method getRealPath not implemented");
+ }
+
+ public String getServerInfo() {
+ throw new UnsupportedOperationException("Method getServerInfo not implemented");
+ }
+
+ public Object getAttribute(String name) {
+ throw new UnsupportedOperationException("Method getAttribute not implemented");
+ }
+
+ public Enumeration getAttributeNames() {
+ throw new UnsupportedOperationException("Method getAttributeNames not implemented");
+ }
+
+ public void setAttribute(String name, Object object) {
+ throw new UnsupportedOperationException("Method setAttribute not implemented");
+ }
+
+ public void removeAttribute(String name) {
+ throw new UnsupportedOperationException("Method removeAttribute not implemented");
+ }
+ }
+
+ public static final class ServletConfigMapTest extends AbstractServletConfigMapAdapterTest {
+
+ public Map makeEmptyMap() {
+ ServletConfig config = new TestConfig();
+ return new ServletConfigMapAdapter(config);
+ }
+
+ public Map makeFullMap() {
+ ServletConfig config = new TestConfig();
+ addSampleMappings(((TestConfig) config).map);
+ return new ServletConfigMapAdapter(config);
+ }
+ }
+
+ public static final class FilterConfigMapTest extends AbstractServletConfigMapAdapterTest {
+
+ public Map makeEmptyMap() {
+ FilterConfig config = new TestConfig();
+ return new ServletConfigMapAdapter(config);
+ }
+
+ public Map makeFullMap() {
+ FilterConfig config = new TestConfig();
+ addSampleMappings(((TestConfig) config).map);
+ return new ServletConfigMapAdapter(config);
+ }
+ }
+
+ public static final class ServletContextMapTest extends AbstractServletConfigMapAdapterTest {
+
+ public Map makeEmptyMap() {
+ ServletContext config = new TestConfig();
+ return new ServletConfigMapAdapter(config);
+ }
+
+ public Map makeFullMap() {
+ FilterConfig config = new TestConfig();
+ addSampleMappings(((TestConfig) config).map);
+ return new ServletConfigMapAdapter(config);
+ }
+ }
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java
index 5c97d226..f70d214c 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTest.java
@@ -1,95 +1,95 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.util.MapAbstractTestCase;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.*;
-
-import static org.mockito.Mockito.when;
-
-/**
- * ServletConfigMapAdapterTestCase
- *
- *
- * @author Harald Kuhr
- * @version $Id: ServletHeadersMapAdapterTestCase.java#1 $
- */
-public class ServletHeadersMapAdapterTest extends MapAbstractTestCase {
- private static final List HEADER_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
- private static final List HEADER_VALUE_DATE = Arrays.asList(new Date().toString());
- private static final List HEADER_VALUE_FOO = Arrays.asList("one", "two");
-
- public boolean isPutAddSupported() {
- return false;
- }
-
- public boolean isPutChangeSupported() {
- return false;
- }
-
- public boolean isRemoveSupported() {
- return false;
- }
-
- public boolean isSetValueSupported() {
- return false;
- }
-
- @Override
- public boolean isTestSerialization() {
- return false;
- }
-
- public Map makeEmptyMap() {
- HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
- when(request.getHeaderNames()).thenAnswer(returnEnumeration(Collections.emptyList()));
-
- return new ServletHeadersMapAdapter(request);
- }
-
- @Override
- public Map makeFullMap() {
- HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
- when(request.getHeaderNames()).thenAnswer(returnEnumeration(Arrays.asList(getSampleKeys())));
- when(request.getHeaders("Date")).thenAnswer(returnEnumeration(HEADER_VALUE_DATE));
- when(request.getHeaders("ETag")).thenAnswer(returnEnumeration(HEADER_VALUE_ETAG));
- when(request.getHeaders("X-Foo")).thenAnswer(returnEnumeration(HEADER_VALUE_FOO));
-
- return new ServletHeadersMapAdapter(request);
- }
-
- @Override
- public Object[] getSampleKeys() {
- return new String[] {"Date", "ETag", "X-Foo"};
- }
-
- @Override
- public Object[] getSampleValues() {
- return new Object[] {HEADER_VALUE_DATE, HEADER_VALUE_ETAG, HEADER_VALUE_FOO};
- }
-
- @Override
- public Object[] getNewSampleValues() {
- // Needs to be same length but different values
- return new Object[3];
- }
-
- protected static ReturnNewEnumeration returnEnumeration(final Collection collection) {
- return new ReturnNewEnumeration(collection);
- }
-
- private static class ReturnNewEnumeration implements Answer> {
- private final Collection collection;
-
- private ReturnNewEnumeration(final Collection collection) {
- this.collection = collection;
- }
-
- public Enumeration answer(InvocationOnMock invocation) throws Throwable {
- return Collections.enumeration(collection);
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.util.MapAbstractTestCase;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * ServletConfigMapAdapterTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: ServletHeadersMapAdapterTestCase.java#1 $
+ */
+public class ServletHeadersMapAdapterTest extends MapAbstractTestCase {
+ private static final List HEADER_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
+ private static final List HEADER_VALUE_DATE = Arrays.asList(new Date().toString());
+ private static final List HEADER_VALUE_FOO = Arrays.asList("one", "two");
+
+ public boolean isPutAddSupported() {
+ return false;
+ }
+
+ public boolean isPutChangeSupported() {
+ return false;
+ }
+
+ public boolean isRemoveSupported() {
+ return false;
+ }
+
+ public boolean isSetValueSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isTestSerialization() {
+ return false;
+ }
+
+ public Map makeEmptyMap() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ when(request.getHeaderNames()).thenAnswer(returnEnumeration(Collections.emptyList()));
+
+ return new ServletHeadersMapAdapter(request);
+ }
+
+ @Override
+ public Map makeFullMap() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ when(request.getHeaderNames()).thenAnswer(returnEnumeration(Arrays.asList(getSampleKeys())));
+ when(request.getHeaders("Date")).thenAnswer(returnEnumeration(HEADER_VALUE_DATE));
+ when(request.getHeaders("ETag")).thenAnswer(returnEnumeration(HEADER_VALUE_ETAG));
+ when(request.getHeaders("X-Foo")).thenAnswer(returnEnumeration(HEADER_VALUE_FOO));
+
+ return new ServletHeadersMapAdapter(request);
+ }
+
+ @Override
+ public Object[] getSampleKeys() {
+ return new String[] {"Date", "ETag", "X-Foo"};
+ }
+
+ @Override
+ public Object[] getSampleValues() {
+ return new Object[] {HEADER_VALUE_DATE, HEADER_VALUE_ETAG, HEADER_VALUE_FOO};
+ }
+
+ @Override
+ public Object[] getNewSampleValues() {
+ // Needs to be same length but different values
+ return new Object[3];
+ }
+
+ protected static ReturnNewEnumeration returnEnumeration(final Collection collection) {
+ return new ReturnNewEnumeration(collection);
+ }
+
+ private static class ReturnNewEnumeration implements Answer> {
+ private final Collection collection;
+
+ private ReturnNewEnumeration(final Collection collection) {
+ this.collection = collection;
+ }
+
+ public Enumeration answer(InvocationOnMock invocation) throws Throwable {
+ return Collections.enumeration(collection);
+ }
+ }
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java
index 9d412216..f042b325 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTest.java
@@ -1,96 +1,96 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.util.MapAbstractTestCase;
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import javax.servlet.http.HttpServletRequest;
-import java.util.*;
-
-import static org.mockito.Mockito.when;
-
-/**
- * ServletConfigMapAdapterTestCase
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java#1 $
- */
-public class ServletParametersMapAdapterTest extends MapAbstractTestCase {
- private static final List PARAM_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
- private static final List PARAM_VALUE_DATE = Arrays.asList(new Date().toString());
- private static final List PARAM_VALUE_FOO = Arrays.asList("one", "two");
-
- public boolean isPutAddSupported() {
- return false;
- }
-
- public boolean isPutChangeSupported() {
- return false;
- }
-
- public boolean isRemoveSupported() {
- return false;
- }
-
- public boolean isSetValueSupported() {
- return false;
- }
-
- @Override
- public boolean isTestSerialization() {
- return false;
- }
-
- public Map makeEmptyMap() {
- HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
- when(request.getParameterNames()).thenAnswer(returnEnumeration(Collections.emptyList()));
-
- return new ServletParametersMapAdapter(request);
- }
-
- @Override
- public Map makeFullMap() {
- HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
-
- when(request.getParameterNames()).thenAnswer(returnEnumeration(Arrays.asList("tag", "date", "foo")));
- when(request.getParameterValues("date")).thenReturn(PARAM_VALUE_DATE.toArray(new String[PARAM_VALUE_DATE.size()]));
- when(request.getParameterValues("tag")).thenReturn(PARAM_VALUE_ETAG.toArray(new String[PARAM_VALUE_ETAG.size()]));
- when(request.getParameterValues("foo")).thenReturn(PARAM_VALUE_FOO.toArray(new String[PARAM_VALUE_FOO.size()]));
-
- return new ServletParametersMapAdapter(request);
- }
-
- @Override
- public Object[] getSampleKeys() {
- return new String[] {"date", "tag", "foo"};
- }
-
- @Override
- public Object[] getSampleValues() {
- return new Object[] {PARAM_VALUE_DATE, PARAM_VALUE_ETAG, PARAM_VALUE_FOO};
- }
-
- @Override
- public Object[] getNewSampleValues() {
- // Needs to be same length but different values
- return new Object[3];
- }
-
- protected static ReturnNewEnumeration returnEnumeration(final Collection collection) {
- return new ReturnNewEnumeration(collection);
- }
-
- private static class ReturnNewEnumeration implements Answer> {
- private final Collection collection;
-
- private ReturnNewEnumeration(final Collection collection) {
- this.collection = collection;
- }
-
- public Enumeration answer(InvocationOnMock invocation) throws Throwable {
- return Collections.enumeration(collection);
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.util.MapAbstractTestCase;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+import static org.mockito.Mockito.when;
+
+/**
+ * ServletConfigMapAdapterTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java#1 $
+ */
+public class ServletParametersMapAdapterTest extends MapAbstractTestCase {
+ private static final List PARAM_VALUE_ETAG = Arrays.asList("\"1234567890abcdef\"");
+ private static final List PARAM_VALUE_DATE = Arrays.asList(new Date().toString());
+ private static final List PARAM_VALUE_FOO = Arrays.asList("one", "two");
+
+ public boolean isPutAddSupported() {
+ return false;
+ }
+
+ public boolean isPutChangeSupported() {
+ return false;
+ }
+
+ public boolean isRemoveSupported() {
+ return false;
+ }
+
+ public boolean isSetValueSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isTestSerialization() {
+ return false;
+ }
+
+ public Map makeEmptyMap() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+ when(request.getParameterNames()).thenAnswer(returnEnumeration(Collections.emptyList()));
+
+ return new ServletParametersMapAdapter(request);
+ }
+
+ @Override
+ public Map makeFullMap() {
+ HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+
+ when(request.getParameterNames()).thenAnswer(returnEnumeration(Arrays.asList("tag", "date", "foo")));
+ when(request.getParameterValues("date")).thenReturn(PARAM_VALUE_DATE.toArray(new String[PARAM_VALUE_DATE.size()]));
+ when(request.getParameterValues("tag")).thenReturn(PARAM_VALUE_ETAG.toArray(new String[PARAM_VALUE_ETAG.size()]));
+ when(request.getParameterValues("foo")).thenReturn(PARAM_VALUE_FOO.toArray(new String[PARAM_VALUE_FOO.size()]));
+
+ return new ServletParametersMapAdapter(request);
+ }
+
+ @Override
+ public Object[] getSampleKeys() {
+ return new String[] {"date", "tag", "foo"};
+ }
+
+ @Override
+ public Object[] getSampleValues() {
+ return new Object[] {PARAM_VALUE_DATE, PARAM_VALUE_ETAG, PARAM_VALUE_FOO};
+ }
+
+ @Override
+ public Object[] getNewSampleValues() {
+ // Needs to be same length but different values
+ return new Object[3];
+ }
+
+ protected static ReturnNewEnumeration returnEnumeration(final Collection collection) {
+ return new ReturnNewEnumeration(collection);
+ }
+
+ private static class ReturnNewEnumeration implements Answer> {
+ private final Collection collection;
+
+ private ReturnNewEnumeration(final Collection collection) {
+ this.collection = collection;
+ }
+
+ public Enumeration answer(InvocationOnMock invocation) throws Throwable {
+ return Collections.enumeration(collection);
+ }
+ }
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java
index a648c78b..222b5ea4 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java
@@ -1,23 +1,23 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.lang.ObjectAbstractTestCase;
-
-import javax.servlet.ServletResponse;
-
-/**
- * ServletResponseAbsrtactTestCase
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java#1 $
- */
-public abstract class ServletResponseAbsrtactTestCase extends ObjectAbstractTestCase {
- protected Object makeObject() {
- return makeServletResponse();
- }
-
- protected abstract ServletResponse makeServletResponse();
-
- // TODO: Implement
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.lang.ObjectAbstractTestCase;
+
+import javax.servlet.ServletResponse;
+
+/**
+ * ServletResponseAbsrtactTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java#1 $
+ */
+public abstract class ServletResponseAbsrtactTestCase extends ObjectAbstractTestCase {
+ protected Object makeObject() {
+ return makeServletResponse();
+ }
+
+ protected abstract ServletResponse makeServletResponse();
+
+ // TODO: Implement
+}
diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java
index a36b2b11..20bcca89 100755
--- a/servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java
+++ b/servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java
@@ -1,115 +1,115 @@
-package com.twelvemonkeys.servlet;
-
-import com.twelvemonkeys.io.OutputStreamAbstractTestCase;
-import org.junit.Test;
-
-import javax.servlet.Filter;
-import javax.servlet.ServletResponse;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import static org.junit.Assert.*;
-
-/**
- * TrimWhiteSpaceFilterTestCase
- *
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java#1 $
- */
-public class TrimWhiteSpaceFilterTestCase extends FilterAbstractTestCase {
- protected Filter makeFilter() {
- return new TrimWhiteSpaceFilter();
- }
-
- public static final class TrimWSFilterOutputStreamTestCase extends OutputStreamAbstractTestCase {
- protected OutputStream makeObject() {
- // NOTE: ByteArrayOutputStream does not implement flush or close...
- return makeOutputStream(new ByteArrayOutputStream(16));
- }
-
- protected OutputStream makeOutputStream(OutputStream pWrapped) {
- return new TrimWhiteSpaceFilter.TrimWSFilterOutputStream(pWrapped);
- }
-
- @Test
- public void testTrimWSOnlyWS() throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream(64);
- OutputStream trim = makeOutputStream(out);
-
- String input = " \n\n\t \t" + (char) 0x0a + ' ' + (char) 0x0d + "\r ";
-
- trim.write(input.getBytes());
- trim.flush();
- trim.close();
-
- assertEquals("Should be trimmed", "\"\"", '"' + new String(out.toByteArray()) + '"');
- }
-
- @Test
- public void testTrimWSLeading() throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream(64);
- OutputStream trim = makeOutputStream(out);
-
- byte[] input = " \n\n\t \t".getBytes();
- String trimmed = "\n "; // TODO: This is pr spec (the trailing space). But probably quite stupid...
-
- trim.write(input);
- trim.flush();
- trim.close();
-
- assertEquals("Should be trimmed", '"' + trimmed + '"', '"' + new String(out.toByteArray()) + '"');
- }
-
- @Test
- public void testTrimWSOffsetLength() throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream(64);
- OutputStream trim = makeOutputStream(out);
-
- // Kindly generated by http://lipsum.org/ :-)
- byte[] input = (" \n\tLorem ipsum dolor sit amet, consectetuer adipiscing elit.\n\r\n\r" +
- "Etiam arcu neque, \n\rmalesuada blandit,\t\n\r\n\r\n\n\n\r\n\r\r\n\n\t rutrum quis, molestie at, diam.\n" +
- " Nulla elementum elementum eros.\n \t\t\n\r" +
- "Ut rhoncus, turpis in pellentesque volutpat, sapien sem accumsan augue, a scelerisque nibh erat vel magna.\n" +
- " Phasellus diam orci, dignissim et, gravida vitae, venenatis eu, elit.\n" +
- "\t\t\tSuspendisse dictum enim at nisl. Integer magna erat, viverra sit amet, consectetuer nec, accumsan ut, mi.\n" +
- "\n\r\r\r\n\rNunc ultricies \n\n\n consectetuer mauris. " +
- "Nulla lectus mauris, viverra ac, pulvinar a, commodo quis, nulla.\n " +
- "Ut eget nulla. In est dolor, convallis \t non, tincidunt \tvestibulum, porttitor et, eros.\n " +
- "\t\t \t \n\rDonec vehicula ultrices nisl.").getBytes();
-
- String trimmed = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\n" +
- "Etiam arcu neque, malesuada blandit,\trutrum quis, molestie at, diam.\n" +
- "Nulla elementum elementum eros.\n" +
- "Ut rhoncus, turpis in pellentesque volutpat, sapien sem accumsan augue, a scelerisque nibh erat vel magna.\n" +
- "Phasellus diam orci, dignissim et, gravida vitae, venenatis eu, elit.\n" +
- "Suspendisse dictum enim at nisl. Integer magna erat, viverra sit amet, consectetuer nec, accumsan ut, mi.\n" +
- "Nunc ultricies consectetuer mauris. Nulla lectus mauris, viverra ac, pulvinar a, commodo quis, nulla.\n" +
- "Ut eget nulla. In est dolor, convallis non, tincidunt vestibulum, porttitor et, eros.\n" +
- "Donec vehicula ultrices nisl.";
-
- int chunkLenght = 5;
- int bytesLeft = input.length;
- while (bytesLeft > chunkLenght) {
- trim.write(input, input.length - bytesLeft, chunkLenght);
- bytesLeft -= chunkLenght;
- }
- trim.write(input, input.length - bytesLeft, bytesLeft);
-
- trim.flush();
- trim.close();
-
- assertEquals("Should be trimmed", '"' + trimmed + '"', '"' + new String(out.toByteArray()) + '"');
- }
-
- // TODO: Test that we DON'T remove too much...
- }
-
- public static final class TrimWSServletResponseWrapperTestCase extends ServletResponseAbsrtactTestCase {
- protected ServletResponse makeServletResponse() {
- return new TrimWhiteSpaceFilter.TrimWSServletResponseWrapper(new MockServletResponse());
- }
- }
-}
+package com.twelvemonkeys.servlet;
+
+import com.twelvemonkeys.io.OutputStreamAbstractTestCase;
+import org.junit.Test;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletResponse;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import static org.junit.Assert.*;
+
+/**
+ * TrimWhiteSpaceFilterTestCase
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java#1 $
+ */
+public class TrimWhiteSpaceFilterTestCase extends FilterAbstractTestCase {
+ protected Filter makeFilter() {
+ return new TrimWhiteSpaceFilter();
+ }
+
+ public static final class TrimWSFilterOutputStreamTestCase extends OutputStreamAbstractTestCase {
+ protected OutputStream makeObject() {
+ // NOTE: ByteArrayOutputStream does not implement flush or close...
+ return makeOutputStream(new ByteArrayOutputStream(16));
+ }
+
+ protected OutputStream makeOutputStream(OutputStream pWrapped) {
+ return new TrimWhiteSpaceFilter.TrimWSFilterOutputStream(pWrapped);
+ }
+
+ @Test
+ public void testTrimWSOnlyWS() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(64);
+ OutputStream trim = makeOutputStream(out);
+
+ String input = " \n\n\t \t" + (char) 0x0a + ' ' + (char) 0x0d + "\r ";
+
+ trim.write(input.getBytes());
+ trim.flush();
+ trim.close();
+
+ assertEquals("Should be trimmed", "\"\"", '"' + new String(out.toByteArray()) + '"');
+ }
+
+ @Test
+ public void testTrimWSLeading() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(64);
+ OutputStream trim = makeOutputStream(out);
+
+ byte[] input = " \n\n\t \t".getBytes();
+ String trimmed = "\n "; // TODO: This is pr spec (the trailing space). But probably quite stupid...
+
+ trim.write(input);
+ trim.flush();
+ trim.close();
+
+ assertEquals("Should be trimmed", '"' + trimmed + '"', '"' + new String(out.toByteArray()) + '"');
+ }
+
+ @Test
+ public void testTrimWSOffsetLength() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream(64);
+ OutputStream trim = makeOutputStream(out);
+
+ // Kindly generated by http://lipsum.org/ :-)
+ byte[] input = (" \n\tLorem ipsum dolor sit amet, consectetuer adipiscing elit.\n\r\n\r" +
+ "Etiam arcu neque, \n\rmalesuada blandit,\t\n\r\n\r\n\n\n\r\n\r\r\n\n\t rutrum quis, molestie at, diam.\n" +
+ " Nulla elementum elementum eros.\n \t\t\n\r" +
+ "Ut rhoncus, turpis in pellentesque volutpat, sapien sem accumsan augue, a scelerisque nibh erat vel magna.\n" +
+ " Phasellus diam orci, dignissim et, gravida vitae, venenatis eu, elit.\n" +
+ "\t\t\tSuspendisse dictum enim at nisl. Integer magna erat, viverra sit amet, consectetuer nec, accumsan ut, mi.\n" +
+ "\n\r\r\r\n\rNunc ultricies \n\n\n consectetuer mauris. " +
+ "Nulla lectus mauris, viverra ac, pulvinar a, commodo quis, nulla.\n " +
+ "Ut eget nulla. In est dolor, convallis \t non, tincidunt \tvestibulum, porttitor et, eros.\n " +
+ "\t\t \t \n\rDonec vehicula ultrices nisl.").getBytes();
+
+ String trimmed = "Lorem ipsum dolor sit amet, consectetuer adipiscing elit.\n" +
+ "Etiam arcu neque, malesuada blandit,\trutrum quis, molestie at, diam.\n" +
+ "Nulla elementum elementum eros.\n" +
+ "Ut rhoncus, turpis in pellentesque volutpat, sapien sem accumsan augue, a scelerisque nibh erat vel magna.\n" +
+ "Phasellus diam orci, dignissim et, gravida vitae, venenatis eu, elit.\n" +
+ "Suspendisse dictum enim at nisl. Integer magna erat, viverra sit amet, consectetuer nec, accumsan ut, mi.\n" +
+ "Nunc ultricies consectetuer mauris. Nulla lectus mauris, viverra ac, pulvinar a, commodo quis, nulla.\n" +
+ "Ut eget nulla. In est dolor, convallis non, tincidunt vestibulum, porttitor et, eros.\n" +
+ "Donec vehicula ultrices nisl.";
+
+ int chunkLenght = 5;
+ int bytesLeft = input.length;
+ while (bytesLeft > chunkLenght) {
+ trim.write(input, input.length - bytesLeft, chunkLenght);
+ bytesLeft -= chunkLenght;
+ }
+ trim.write(input, input.length - bytesLeft, bytesLeft);
+
+ trim.flush();
+ trim.close();
+
+ assertEquals("Should be trimmed", '"' + trimmed + '"', '"' + new String(out.toByteArray()) + '"');
+ }
+
+ // TODO: Test that we DON'T remove too much...
+ }
+
+ public static final class TrimWSServletResponseWrapperTestCase extends ServletResponseAbsrtactTestCase {
+ protected ServletResponse makeServletResponse() {
+ return new TrimWhiteSpaceFilter.TrimWSServletResponseWrapper(new MockServletResponse());
+ }
+ }
+}
diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java
new file mode 100644
index 00000000..e8d94133
--- /dev/null
+++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java
@@ -0,0 +1,341 @@
+package com.twelvemonkeys.servlet.image.aoi;
+
+import com.twelvemonkeys.servlet.image.aoi.DefaultAreaOfInterest;
+import com.twelvemonkeys.servlet.image.aoi.UniformAreaOfInterest;
+import org.junit.Test;
+
+import java.awt.*;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Erlend Hamnaberg
+ * @version $Revision: $
+ */
+public class AreaOfInterestTestCase {
+ private static final Dimension SQUARE_200_200 = new Dimension(200, 200);
+ private static final Dimension PORTRAIT_100_200 = new Dimension(100, 200);
+ private static final Dimension LANDSCAPE_200_100 = new Dimension(200, 100);
+ private static final Dimension SQUARE_100_100 = new Dimension(100, 100);
+ // -----------------------------------------------------------------------------------------------------------------
+ // Absolute AOI
+ // -----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void testGetAOIAbsolute() {
+ assertEquals(new Rectangle(10, 10, 100, 100), new DefaultAreaOfInterest(SQUARE_200_200).getAOI(10, 10, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIAbsoluteOverflowX() {
+ assertEquals(new Rectangle(10, 10, 90, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(10, 10, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIAbsoluteOverflowW() {
+
+ assertEquals(new Rectangle(0, 10, 100, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(0, 10, 110, 100));
+ }
+
+ @Test
+ public void testGetAOIAbsoluteOverflowY() {
+
+ assertEquals(new Rectangle(10, 10, 100, 90), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(10, 10, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIAbsoluteOverflowH() {
+
+ assertEquals(new Rectangle(10, 0, 100, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(10, 0, 100, 110));
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Uniform AOI centered
+ // -----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void testGetAOIUniformCenteredS2SUp() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 333, 333));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2SDown() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 33, 33));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2SNormalized() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2W() {
+ assertEquals(new Rectangle(0, 25, 100, 50), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2WNormalized() {
+ assertEquals(new Rectangle(0, 25, 100, 50), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2N() {
+ assertEquals(new Rectangle(25, 0, 50, 100), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 200));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredS2NNormalized() {
+ assertEquals(new Rectangle(25, 0, 50, 100), new UniformAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 50, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2S() {
+ assertEquals(new Rectangle(50, 0, 100, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 333, 333));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2SNormalized() {
+ assertEquals(new Rectangle(50, 0, 100, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2W() {
+ assertEquals(new Rectangle(0, 0, 200, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2WW() {
+ assertEquals(new Rectangle(0, 25, 200, 50), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 200, 50));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2WN() {
+ assertEquals(new Rectangle(25, 0, 150, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 75, 50));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2WNNormalized() {
+ assertEquals(new Rectangle(25, 0, 150, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 150, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2WNormalized() {
+ assertEquals(new Rectangle(0, 0, 200, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2N() {
+ assertEquals(new Rectangle(75, 0, 50, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 200));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredW2NNormalized() {
+ assertEquals(new Rectangle(75, 0, 50, 100), new UniformAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 50, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2S() {
+ assertEquals(new Rectangle(0, 50, 100, 100), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 333, 333));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2SNormalized() {
+ assertEquals(new Rectangle(0, 50, 100, 100), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2W() {
+ assertEquals(new Rectangle(0, 75, 100, 50), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2WNormalized() {
+ assertEquals(new Rectangle(0, 75, 100, 50), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2N() {
+ assertEquals(new Rectangle(0, 0, 100, 200), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 50, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2NN() {
+ assertEquals(new Rectangle(25, 0, 50, 200), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 25, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2NW() {
+ assertEquals(new Rectangle(0, 33, 100, 133), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 75, 100));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2NWNormalized() {
+ assertEquals(new Rectangle(0, 37, 100, 125), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 125));
+ }
+
+ @Test
+ public void testGetAOIUniformCenteredN2NNormalized() {
+ assertEquals(new Rectangle(0, 0, 100, 200), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 200));
+ }
+
+ // -----------------------------------------------------------------------------------------------------------------
+ // Absolute AOI centered
+ // -----------------------------------------------------------------------------------------------------------------
+
+ @Test
+ public void testGetAOICenteredS2SUp() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 333, 333));
+ }
+
+ @Test
+ public void testGetAOICenteredS2SDown() {
+ assertEquals(new Rectangle(33, 33, 33, 33), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 33, 33));
+ }
+
+ @Test
+ public void testGetAOICenteredS2SSame() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredS2WOverflow() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredS2W() {
+ assertEquals(new Rectangle(40, 45, 20, 10), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 20, 10));
+ }
+
+ @Test
+ public void testGetAOICenteredS2WMax() {
+ assertEquals(new Rectangle(0, 25, 100, 50), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredS2NOverflow() {
+ assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 100, 200));
+ }
+
+ @Test
+ public void testGetAOICenteredS2N() {
+ assertEquals(new Rectangle(45, 40, 10, 20), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 10, 20));
+ }
+
+ @Test
+ public void testGetAOICenteredS2NMax() {
+ assertEquals(new Rectangle(25, 0, 50, 100), new DefaultAreaOfInterest(SQUARE_100_100).getAOI(-1, -1, 50, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredW2SOverflow() {
+ assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 333, 333));
+ }
+
+ @Test
+ public void testGetAOICenteredW2S() {
+ assertEquals(new Rectangle(75, 25, 50, 50), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 50, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredW2SMax() {
+ assertEquals(new Rectangle(50, 0, 100, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredW2WOverflow() {
+ assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 300, 200));
+ }
+
+ @Test
+ public void testGetAOICenteredW2W() {
+ assertEquals(new Rectangle(50, 25, 100, 50), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredW2WW() {
+ assertEquals(new Rectangle(10, 40, 180, 20), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 180, 20));
+ }
+
+ @Test
+ public void testGetAOICenteredW2WN() {
+ assertEquals(new Rectangle(62, 25, 75, 50), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 75, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredW2WSame() {
+ assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredW2NOverflow() {
+ assertEquals(new Rectangle(50, 0, 100, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 100, 200));
+ }
+
+ @Test
+ public void testGetAOICenteredW2N() {
+ assertEquals(new Rectangle(83, 25, 33, 50), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 33, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredW2NMax() {
+ assertEquals(new Rectangle(75, 0, 50, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(-1, -1, 50, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredN2S() {
+ assertEquals(new Rectangle(33, 83, 33, 33), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 33, 33));
+ }
+
+ @Test
+ public void testGetAOICenteredN2SMax() {
+ assertEquals(new Rectangle(0, 50, 100, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredN2WOverflow() {
+ assertEquals(new Rectangle(0, 50, 100, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 200, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredN2W() {
+ assertEquals(new Rectangle(40, 95, 20, 10), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 20, 10));
+ }
+
+ @Test
+ public void testGetAOICenteredN2WMax() {
+ assertEquals(new Rectangle(0, 75, 100, 50), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 50));
+ }
+
+ @Test
+ public void testGetAOICenteredN2N() {
+ assertEquals(new Rectangle(45, 90, 10, 20), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 10, 20));
+ }
+
+ @Test
+ public void testGetAOICenteredN2NSame() {
+ assertEquals(new Rectangle(0, 0, 100, 200), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 200));
+ }
+
+ @Test
+ public void testGetAOICenteredN2NN() {
+ assertEquals(new Rectangle(37, 50, 25, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 25, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredN2NW() {
+ assertEquals(new Rectangle(12, 50, 75, 100), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 75, 100));
+ }
+
+ @Test
+ public void testGetAOICenteredN2NWMax() {
+ assertEquals(new Rectangle(0, 37, 100, 125), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 125));
+ }
+
+ @Test
+ public void testGetAOICenteredN2NMax() {
+ assertEquals(new Rectangle(0, 0, 100, 200), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 200));
+ }
+
+
+}