From 5131e02e6c14e12d10c059c976543394cd939146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=98yvind=20L=C3=B8kling?= Date: Tue, 5 Jan 2010 14:42:38 +0100 Subject: [PATCH 01/17] Released and tagg for internal use --- twelvemonkeys-servlet/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twelvemonkeys-servlet/pom.xml b/twelvemonkeys-servlet/pom.xml index 299f51f3..4a2a966a 100644 --- a/twelvemonkeys-servlet/pom.xml +++ b/twelvemonkeys-servlet/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.twelvemonkeys twelvemonkeys-servlet - 2.3-SNAPSHOT + 2.3-ece TwelveMonkeys Servlet From 03d8e06819469dce81fe922a01ab9cbd46c18575 Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Tue, 20 Apr 2010 11:07:43 +0200 Subject: [PATCH 02/17] line feed changes --- twelvemonkeys-imageio/reference/pom.xml | 60 +- twelvemonkeys-imageio/thumbsdb/pom.xml | 60 +- .../com/twelvemonkeys/image/EasyImage.java | 156 +- .../image/ExtendedImageConsumer.java | 122 +- .../twelvemonkeys/io/enc/DeflateEncoder.java | 180 +- .../twelvemonkeys/io/enc/InflateDecoder.java | 216 +- .../com/twelvemonkeys/io/enc/LZWDecoder.java | 90 +- .../com/twelvemonkeys/io/enc/LZWEncoder.java | 90 +- .../lang/MostUnfortunateException.java | 108 +- .../com/twelvemonkeys/lang/NativeLoader.java | 794 ++-- .../twelvemonkeys/lang/NativeResourceSPI.java | 198 +- .../com/twelvemonkeys/util/DebugUtil.java | 3514 ++++++++--------- .../util/regex/REWildcardStringParser.java | 796 ++-- .../io/enc/DeflateEncoderTestCase.java | 36 +- .../io/enc/InflateDecoderTestCase.java | 36 +- .../twelvemonkeys/servlet/DebugServlet.java | 236 +- .../twelvemonkeys/servlet/GenericFilter.java | 766 ++-- .../twelvemonkeys/servlet/GenericServlet.java | 174 +- .../twelvemonkeys/servlet/HttpServlet.java | 174 +- .../servlet/OutputStreamAdapter.java | 244 +- .../twelvemonkeys/servlet/ProxyServlet.java | 870 ++-- .../servlet/ServletConfigException.java | 184 +- .../servlet/ServletConfigMapAdapter.java | 566 +-- .../ServletResponseStreamDelegate.java | 228 +- .../twelvemonkeys/servlet/ServletUtil.java | 2120 +++++----- .../twelvemonkeys/servlet/ThrottleFilter.java | 620 +-- .../twelvemonkeys/servlet/TimingFilter.java | 224 +- .../servlet/TrimWhiteSpaceFilter.java | 474 +-- .../servlet/cache/CacheFilter.java | 396 +- .../servlet/cache/CacheResponseWrapper.java | 520 +-- .../servlet/cache/CachedEntity.java | 150 +- .../servlet/cache/CachedEntityImpl.java | 342 +- .../servlet/cache/CachedResponse.java | 190 +- .../servlet/cache/CachedResponseImpl.java | 440 +-- .../servlet/cache/HTTPCache.java | 2332 +++++------ .../cache/SerlvetCacheResponseWrapper.java | 544 +-- .../servlet/cache/WritableCachedResponse.java | 154 +- .../cache/WritableCachedResponseImpl.java | 374 +- .../fileupload/FileSizeExceededException.java | 84 +- .../fileupload/FileUploadException.java | 104 +- .../servlet/fileupload/FileUploadFilter.java | 274 +- .../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 | 292 +- .../servlet/image/AWTImageFilterAdapter.java | 144 +- .../servlet/image/BufferedImageOpAdapter.java | 134 +- .../servlet/image/ColorServlet.java | 424 +- .../servlet/image/ComposeFilter.java | 118 +- .../image/ContentNegotiationFilter.java | 872 ++-- .../servlet/image/CropFilter.java | 462 +-- .../servlet/image/ImageFilter.java | 398 +- .../servlet/image/ImageServletException.java | 110 +- .../servlet/image/ImageServletResponse.java | 386 +- .../image/ImageServletResponseImpl.java | 1468 +++---- .../servlet/image/NullImageFilter.java | 92 +- .../servlet/image/RotateFilter.java | 406 +- .../servlet/image/ScaleFilter.java | 644 +-- .../servlet/image/SourceRenderFilter.java | 306 +- .../servlet/image/TextRenderer.java | 696 ++-- .../servlet/image/package_info.java | 66 +- .../servlet/jsp/droplet/Droplet.java | 160 +- .../servlet/jsp/droplet/JspFragment.java | 88 +- .../servlet/jsp/droplet/Oparam.java | 58 +- .../servlet/jsp/droplet/Param.java | 84 +- .../servlet/jsp/droplet/package_info.java | 18 +- .../jsp/droplet/taglib/IncludeTag.java | 428 +- .../jsp/droplet/taglib/NestingHandler.java | 368 +- .../jsp/droplet/taglib/NestingValidator.java | 216 +- .../servlet/jsp/droplet/taglib/OparamTag.java | 476 +-- .../servlet/jsp/droplet/taglib/ParamTag.java | 282 +- .../jsp/droplet/taglib/ValueOfTEI.java | 100 +- .../jsp/droplet/taglib/ValueOfTag.java | 294 +- .../servlet/jsp/droplet/taglib/package.html | 20 +- .../servlet/jsp/package_info.java | 6 +- .../servlet/jsp/taglib/BodyReaderTag.java | 86 +- .../servlet/jsp/taglib/CSVToTableTag.java | 480 +-- .../servlet/jsp/taglib/ExBodyTagSupport.java | 572 +-- .../servlet/jsp/taglib/ExTag.java | 324 +- .../servlet/jsp/taglib/ExTagSupport.java | 578 +-- .../servlet/jsp/taglib/LastModifiedTEI.java | 42 +- .../servlet/jsp/taglib/LastModifiedTag.java | 108 +- .../servlet/jsp/taglib/TrimWhiteSpaceTag.java | 178 +- .../servlet/jsp/taglib/XMLTransformTag.java | 324 +- .../jsp/taglib/logic/ConditionalTagBase.java | 280 +- .../servlet/jsp/taglib/logic/EqualTag.java | 340 +- .../jsp/taglib/logic/IteratorProviderTEI.java | 82 +- .../jsp/taglib/logic/IteratorProviderTag.java | 174 +- .../servlet/jsp/taglib/logic/NotEqualTag.java | 336 +- .../servlet/jsp/taglib/package_info.java | 8 +- .../servlet/log4j/Log4JContextWrapper.java | 366 +- .../twelvemonkeys/servlet/package_info.java | 8 +- .../servlet/FilterAbstractTestCase.java | 876 ++-- .../servlet/GenericFilterTestCase.java | 302 +- .../ServletConfigMapAdapterTestCase.java | 384 +- .../ServletHeadersMapAdapterTestCase.java | 204 +- .../ServletParametersMapAdapterTestCase.java | 202 +- .../ServletResponseAbsrtactTestCase.java | 46 +- .../servlet/TrimWhiteSpaceFilterTestCase.java | 222 +- 101 files changed, 18222 insertions(+), 18222 deletions(-) diff --git a/twelvemonkeys-imageio/reference/pom.xml b/twelvemonkeys-imageio/reference/pom.xml index b65ecfdd..87623236 100644 --- a/twelvemonkeys-imageio/reference/pom.xml +++ b/twelvemonkeys-imageio/reference/pom.xml @@ -1,31 +1,31 @@ - - - 4.0.0 - com.twelvemonkeys.imageio - twelvemonkeys-imageio-reference - 2.2 - TwelveMonkeys ImageIO reference test cases - - Test cases for the JDK provided ImageReader implementations for reference. - - - - twelvemonkeys-imageio - com.twelvemonkeys - 2.2 - - - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - tests - - + + + 4.0.0 + com.twelvemonkeys.imageio + twelvemonkeys-imageio-reference + 2.2 + TwelveMonkeys ImageIO reference test cases + + Test cases for the JDK provided ImageReader implementations for reference. + + + + twelvemonkeys-imageio + com.twelvemonkeys + 2.2 + + + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + tests + + \ No newline at end of file diff --git a/twelvemonkeys-imageio/thumbsdb/pom.xml b/twelvemonkeys-imageio/thumbsdb/pom.xml index 6cf22a71..ced6edb9 100644 --- a/twelvemonkeys-imageio/thumbsdb/pom.xml +++ b/twelvemonkeys-imageio/thumbsdb/pom.xml @@ -1,31 +1,31 @@ - - - 4.0.0 - com.twelvemonkeys.imageio - twelvemonkeys-imageio-thumbsdb - 2.3-SNAPSHOT - TwelveMonkeys ImageIO Thumbs.db plugin - - ImageIO plugin for Windows Thumbs DB (Thumbs.db) format. - - - - twelvemonkeys-imageio - com.twelvemonkeys - 2.3-SNAPSHOT - - - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - tests - - + + + 4.0.0 + com.twelvemonkeys.imageio + twelvemonkeys-imageio-thumbsdb + 2.3-SNAPSHOT + TwelveMonkeys ImageIO Thumbs.db plugin + + ImageIO plugin for Windows Thumbs DB (Thumbs.db) format. + + + + twelvemonkeys-imageio + com.twelvemonkeys + 2.3-SNAPSHOT + + + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + tests + + \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/EasyImage.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/EasyImage.java index 71427244..dde4cfe6 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/EasyImage.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/EasyImage.java @@ -1,78 +1,78 @@ -/* - * 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.image; - -import javax.imageio.ImageIO; -import java.awt.*; -import java.awt.image.*; -import java.awt.image.renderable.RenderableImage; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * EasyImage - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/EasyImage.java#1 $ - */ -public class EasyImage extends BufferedImage { - public EasyImage(InputStream pInput) throws IOException { - this(ImageIO.read(pInput)); - } - - public EasyImage(BufferedImage pImage) { - this(pImage.getColorModel(), pImage.getRaster()); - } - - public EasyImage(RenderableImage pImage) { - this(pImage.createDefaultRendering()); - } - - public EasyImage(RenderedImage pImage) { - this(pImage.getColorModel(), pImage.copyData(pImage.getColorModel().createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()))); - } - - public EasyImage(ImageProducer pImage) { - this(new BufferedImageFactory(pImage).getBufferedImage()); - } - - public EasyImage(Image pImage) { - this(new BufferedImageFactory(pImage).getBufferedImage()); - } - - private EasyImage(ColorModel cm, WritableRaster raster) { - super(cm, raster, cm.isAlphaPremultiplied(), null); - } - - public boolean write(String pFormat, OutputStream pOutput) throws IOException { - return ImageIO.write(this, pFormat, pOutput); - } -} +/* + * 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.image; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.*; +import java.awt.image.renderable.RenderableImage; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * EasyImage + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/EasyImage.java#1 $ + */ +public class EasyImage extends BufferedImage { + public EasyImage(InputStream pInput) throws IOException { + this(ImageIO.read(pInput)); + } + + public EasyImage(BufferedImage pImage) { + this(pImage.getColorModel(), pImage.getRaster()); + } + + public EasyImage(RenderableImage pImage) { + this(pImage.createDefaultRendering()); + } + + public EasyImage(RenderedImage pImage) { + this(pImage.getColorModel(), pImage.copyData(pImage.getColorModel().createCompatibleWritableRaster(pImage.getWidth(), pImage.getHeight()))); + } + + public EasyImage(ImageProducer pImage) { + this(new BufferedImageFactory(pImage).getBufferedImage()); + } + + public EasyImage(Image pImage) { + this(new BufferedImageFactory(pImage).getBufferedImage()); + } + + private EasyImage(ColorModel cm, WritableRaster raster) { + super(cm, raster, cm.isAlphaPremultiplied(), null); + } + + public boolean write(String pFormat, OutputStream pOutput) throws IOException { + return ImageIO.write(this, pFormat, pOutput); + } +} diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java index 949d6932..7443d3f2 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java @@ -1,61 +1,61 @@ -/* - * 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.image; - -import java.awt.image.ImageConsumer; -import java.awt.image.ColorModel; - -/** - * ExtendedImageConsumer - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java#1 $ - */ -public interface ExtendedImageConsumer extends ImageConsumer { - /** - * - * @param pX - * @param pY - * @param pWidth - * @param pHeight - * @param pModel - * @param pPixels - * @param pOffset - * @param pScanSize - */ - public void setPixels(int pX, int pY, int pWidth, int pHeight, - ColorModel pModel, - short[] pPixels, int pOffset, int pScanSize); - - // Allow for packed and interleaved models - public void setPixels(int pX, int pY, int pWidth, int pHeight, - ColorModel pModel, - byte[] pPixels, int pOffset, int pScanSize); -} +/* + * 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.image; + +import java.awt.image.ImageConsumer; +import java.awt.image.ColorModel; + +/** + * ExtendedImageConsumer + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/ExtendedImageConsumer.java#1 $ + */ +public interface ExtendedImageConsumer extends ImageConsumer { + /** + * + * @param pX + * @param pY + * @param pWidth + * @param pHeight + * @param pModel + * @param pPixels + * @param pOffset + * @param pScanSize + */ + public void setPixels(int pX, int pY, int pWidth, int pHeight, + ColorModel pModel, + short[] pPixels, int pOffset, int pScanSize); + + // Allow for packed and interleaved models + public void setPixels(int pX, int pY, int pWidth, int pHeight, + ColorModel pModel, + byte[] pPixels, int pOffset, int pScanSize); +} diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java index 194669fe..0b99f958 100644 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/DeflateEncoder.java +++ b/twelvemonkeys-sandbox/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.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 mDeflater; - private final byte[] mBuffer = new byte[1024]; - - public DeflateEncoder() { -// this(new Deflater()); - 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"); - } - - mDeflater = pDeflater; - } - - public void encode(final OutputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) - throws IOException - { - System.out.println("DeflateEncoder.encode"); - mDeflater.setInput(pBuffer, pOffset, pLength); - flushInputToStream(pStream); - } - - private void flushInputToStream(final OutputStream pStream) throws IOException { - System.out.println("DeflateEncoder.flushInputToStream"); - - if (mDeflater.needsInput()) { - System.out.println("Foo"); - } - - while (!mDeflater.needsInput()) { - int deflated = mDeflater.deflate(mBuffer, 0, mBuffer.length); - pStream.write(mBuffer, 0, deflated); - System.out.println("flushed " + deflated); - } - } - -// public void flush() { -// mDeflater.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.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 mDeflater; + private final byte[] mBuffer = new byte[1024]; + + public DeflateEncoder() { +// this(new Deflater()); + 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"); + } + + mDeflater = pDeflater; + } + + public void encode(final OutputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) + throws IOException + { + System.out.println("DeflateEncoder.encode"); + mDeflater.setInput(pBuffer, pOffset, pLength); + flushInputToStream(pStream); + } + + private void flushInputToStream(final OutputStream pStream) throws IOException { + System.out.println("DeflateEncoder.flushInputToStream"); + + if (mDeflater.needsInput()) { + System.out.println("Foo"); + } + + while (!mDeflater.needsInput()) { + int deflated = mDeflater.deflate(mBuffer, 0, mBuffer.length); + pStream.write(mBuffer, 0, deflated); + System.out.println("flushed " + deflated); + } + } + +// public void flush() { +// mDeflater.finish(); +// } +} diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java index 04ebf7b5..57d8259a 100644 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/InflateDecoder.java @@ -1,109 +1,109 @@ -/* - * 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.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 mInflater; - - private final byte[] mBuffer; - - /** - * 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"); - } - - mInflater = pInflater; - mBuffer = new byte[1024]; - } - - public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { - try { - int decoded; - - while ((decoded = mInflater.inflate(pBuffer, 0, pBuffer.length)) == 0) { - if (mInflater.finished() || mInflater.needsDictionary()) { - return 0; - } - - if (mInflater.needsInput()) { - fill(pStream); - } - } - - 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(mBuffer, 0, mBuffer.length); - - if (available == -1) { - throw new EOFException("Unexpected end of ZLIB stream"); - } - - mInflater.setInput(mBuffer, 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.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 mInflater; + + private final byte[] mBuffer; + + /** + * 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"); + } + + mInflater = pInflater; + mBuffer = new byte[1024]; + } + + public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException { + try { + int decoded; + + while ((decoded = mInflater.inflate(pBuffer, 0, pBuffer.length)) == 0) { + if (mInflater.finished() || mInflater.needsDictionary()) { + return 0; + } + + if (mInflater.needsInput()) { + fill(pStream); + } + } + + 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(mBuffer, 0, mBuffer.length); + + if (available == -1) { + throw new EOFException("Unexpected end of ZLIB stream"); + } + + mInflater.setInput(mBuffer, 0, available); + } } \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java index b2d56869..7d9958cf 100644 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.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.io.enc; - -import java.io.InputStream; -import java.io.IOException; - -/** - * LZWDecoder. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java#2 $ - */ -final class LZWDecoder implements Decoder { - public int decode(InputStream pStream, byte[] pBuffer) throws IOException { - return 0; // TODO: Implement - // TODO: We probably need a GIF specific subclass - } +/* + * 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.InputStream; +import java.io.IOException; + +/** + * LZWDecoder. + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWDecoder.java#2 $ + */ +final class LZWDecoder implements Decoder { + public int decode(InputStream pStream, byte[] pBuffer) throws IOException { + return 0; // TODO: Implement + // TODO: We probably need a GIF specific subclass + } } \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java index a18faaa0..31b917fa 100644 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.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.io.enc; - -import java.io.OutputStream; -import java.io.IOException; - -/** - * LZWEncoder. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java#2 $ - */ -final class LZWEncoder implements Encoder { - public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException { - // TODO: Implement - // TODO: We probably need a GIF specific subclass - } +/* + * 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; + +/** + * LZWEncoder. + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/LZWEncoder.java#2 $ + */ +final class LZWEncoder implements Encoder { + public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException { + // TODO: Implement + // TODO: We probably need a GIF specific subclass + } } \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java index 120f1ff1..1c8814a9 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.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.lang; - -/** - * MostUnfortunateException - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java#1 $ - */ -class MostUnfortunateException extends RuntimeException { - public MostUnfortunateException() { - this("Most unfortunate."); - } - - public MostUnfortunateException(Throwable pCause) { - this(pCause.getMessage(), pCause); - } - - public MostUnfortunateException(String pMessage, Throwable pCause) { - this(pMessage); - initCause(pCause); - } - - public MostUnfortunateException(String pMessage) { - super("A most unfortunate exception has occured: " + pMessage); - } +/* + * 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; + +/** + * MostUnfortunateException + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/MostUnfortunateException.java#1 $ + */ +class MostUnfortunateException extends RuntimeException { + public MostUnfortunateException() { + this("Most unfortunate."); + } + + public MostUnfortunateException(Throwable pCause) { + this(pCause.getMessage(), pCause); + } + + public MostUnfortunateException(String pMessage, Throwable pCause) { + this(pMessage); + initCause(pCause); + } + + public MostUnfortunateException(String pMessage) { + super("A most unfortunate exception has occured: " + pMessage); + } } \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeLoader.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeLoader.java index 572edeba..98d2d589 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeLoader.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeLoader.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.lang; - -import com.twelvemonkeys.io.FileUtil; -import com.twelvemonkeys.util.FilterIterator; -import com.twelvemonkeys.util.service.ServiceRegistry; - -import java.io.*; -import java.util.Iterator; -import java.util.Arrays; - -/** - * 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(Arrays.asList(NativeResourceSPI.class).iterator()); - registerApplicationClasspathSPIs(); - } - - Iterator providers(String pNativeResource) { - return new FilterIterator(providers(NativeResourceSPI.class), - new NameFilter(pNativeResource)); - } - } - - private static class NameFilter implements FilterIterator.Filter { - private final String mName; - - NameFilter(String pName) { - if (pName == null) { - throw new IllegalArgumentException("name == null"); - } - mName = pName; - } - public boolean accept(NativeResourceSPI pElement) { - return mName.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.Iterator; +import java.util.Arrays; + +/** + * 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(Arrays.asList(NativeResourceSPI.class).iterator()); + registerApplicationClasspathSPIs(); + } + + Iterator providers(String pNativeResource) { + return new FilterIterator(providers(NativeResourceSPI.class), + new NameFilter(pNativeResource)); + } + } + + private static class NameFilter implements FilterIterator.Filter { + private final String mName; + + NameFilter(String pName) { + if (pName == null) { + throw new IllegalArgumentException("name == null"); + } + mName = pName; + } + public boolean accept(NativeResourceSPI pElement) { + return mName.equals(pElement.getResourceName()); + } + } } \ No newline at end of file diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java index 2290ef2a..5c130f23 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java @@ -1,99 +1,99 @@ -/* - * 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; - -/** - * Abstract base class for native reource providers to iplement. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java#1 $ - */ -public abstract class NativeResourceSPI { - - private final String mResourceName; - - /** - * Creates a {@code NativeResourceSPI} with the given name. - * - * The name will typically be a short string, with the common name of the - * library that is provided, like "JMagick", "JOGL" or similar. - * - * @param pResourceName name of the resource (native library) provided by - * this SPI. - * - * @throws IllegalArgumentException if {@code pResourceName == null} - */ - protected NativeResourceSPI(String pResourceName) { - if (pResourceName == null) { - throw new IllegalArgumentException("resourceName == null"); - } - - mResourceName = pResourceName; - } - - /** - * Returns the name of the resource (native library) provided by this SPI. - * - * The name will typically be a short string, with the common name of the - * library that is provided, like "JMagick", "JOGL" or similar. - *

- * NOTE: This method is intended for the SPI framework, and should not be - * invoked by client code. - * - * @return the name of the resource provided by this SPI - */ - public final String getResourceName() { - return mResourceName; - } - - /** - * Returns the path to the classpath resource that is suited for the given - * runtime configuration. - *

- * In the common case, the {@code pPlatform} parameter is - * normalized from the values found in - * {@code System.getProperty("os.name")} and - * {@code System.getProperty("os.arch")}. - * For unknown operating systems and architectures, {@code toString()} on - * the platforms's properties will return the the same value as these properties. - *

- * NOTE: This method is intended for the SPI framework, and should not be - * invoked by client code. - * - * @param pPlatform the current platform - * @return a {@code String} containing the path to a classpath resource or - * {@code null} if no resource is available. - * - * @see com.twelvemonkeys.lang.Platform.OperatingSystem - * @see com.twelvemonkeys.lang.Platform.Architecture - * @see System#getProperties() - */ - public abstract String getClassPathResource(final Platform pPlatform); -} +/* + * 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; + +/** + * Abstract base class for native reource providers to iplement. + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/NativeResourceSPI.java#1 $ + */ +public abstract class NativeResourceSPI { + + private final String mResourceName; + + /** + * Creates a {@code NativeResourceSPI} with the given name. + * + * The name will typically be a short string, with the common name of the + * library that is provided, like "JMagick", "JOGL" or similar. + * + * @param pResourceName name of the resource (native library) provided by + * this SPI. + * + * @throws IllegalArgumentException if {@code pResourceName == null} + */ + protected NativeResourceSPI(String pResourceName) { + if (pResourceName == null) { + throw new IllegalArgumentException("resourceName == null"); + } + + mResourceName = pResourceName; + } + + /** + * Returns the name of the resource (native library) provided by this SPI. + * + * The name will typically be a short string, with the common name of the + * library that is provided, like "JMagick", "JOGL" or similar. + *

+ * NOTE: This method is intended for the SPI framework, and should not be + * invoked by client code. + * + * @return the name of the resource provided by this SPI + */ + public final String getResourceName() { + return mResourceName; + } + + /** + * Returns the path to the classpath resource that is suited for the given + * runtime configuration. + *

+ * In the common case, the {@code pPlatform} parameter is + * normalized from the values found in + * {@code System.getProperty("os.name")} and + * {@code System.getProperty("os.arch")}. + * For unknown operating systems and architectures, {@code toString()} on + * the platforms's properties will return the the same value as these properties. + *

+ * NOTE: This method is intended for the SPI framework, and should not be + * invoked by client code. + * + * @param pPlatform the current platform + * @return a {@code String} containing the path to a classpath resource or + * {@code null} if no resource is available. + * + * @see com.twelvemonkeys.lang.Platform.OperatingSystem + * @see com.twelvemonkeys.lang.Platform.Architecture + * @see System#getProperties() + */ + public abstract String getClassPathResource(final Platform pPlatform); +} diff --git a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/DebugUtil.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/DebugUtil.java index 3d45d4e9..a3878c3c 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/DebugUtil.java +++ b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/DebugUtil.java @@ -1,1757 +1,1757 @@ -/**************************************************** - * * - * (c) 2000-2003 TwelveMonkeys * - * All rights reserved * - * http://www.twelvemonkeys.no * - * * - * $RCSfile: DebugUtil.java,v $ - * @version $Revision: #2 $ - * $Date: 2009/06/19 $ - * * - * @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.util; - - -import com.twelvemonkeys.lang.StringUtil; - -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.text.DateFormat; -import java.util.*; - - -/** - * A utility class to simplify debugging. - * This includes viewing generic data structures, printing timestamps, printing object info and more... - * NB! Only use this class for instrumentation purposes - *

- * @author Eirik Torske - */ -@Deprecated -public class DebugUtil { - - // Constants - - /** Field PRINTSTREAM_IS_NULL_ERROR_MESSAGE */ - public static final String PRINTSTREAM_IS_NULL_ERROR_MESSAGE = "PrintStream is null"; - - /** Field OBJECT_IS_NULL_ERROR_MESSAGE */ - public static final String OBJECT_IS_NULL_ERROR_MESSAGE = "Object is null"; - - /** Field INTARRAY_IS_NULL_ERROR_MESSAGE */ - public static final String INTARRAY_IS_NULL_ERROR_MESSAGE = "int array is null"; - - /** Field STRINGARRAY_IS_NULL_ERROR_MESSAGE */ - public static final String STRINGARRAY_IS_NULL_ERROR_MESSAGE = "String array is null"; - - /** Field ENUMERATION_IS_NULL_ERROR_MESSAGE */ - public static final String ENUMERATION_IS_NULL_ERROR_MESSAGE = "Enumeration is null"; - - /** Field COLLECTION_IS_NULL_ERROR_MESSAGE */ - public static final String COLLECTION_IS_NULL_ERROR_MESSAGE = "Collection is null"; - - /** Field COLLECTION_IS_EMPTY_ERROR_MESSAGE */ - public static final String COLLECTION_IS_EMPTY_ERROR_MESSAGE = "Collection contains no elements"; - - /** Field MAP_IS_NULL_ERROR_MESSAGE */ - public static final String MAP_IS_NULL_ERROR_MESSAGE = "Map is null"; - - /** Field MAP_IS_EMPTY_ERROR_MESSAGE */ - public static final String MAP_IS_EMPTY_ERROR_MESSAGE = "Map contains no elements"; - - /** Field PROPERTIES_IS_NULL_ERROR_MESSAGE */ - public static final String PROPERTIES_IS_NULL_ERROR_MESSAGE = "Properties is null"; - - /** Field PROPERTIES_IS_EMPTY_ERROR_MESSAGE */ - public static final String PROPERTIES_IS_EMPTY_ERROR_MESSAGE = "Properties contains no elements"; - - /** Field CALENDAR_IS_NULL_ERROR_MESSAGE */ - public static final String CALENDAR_IS_NULL_ERROR_MESSAGE = "Calendar is null"; - - /** Field CALENDAR_CAUSATION_ERROR_MESSAGE */ - public static final String CALENDAR_CAUSATION_ERROR_MESSAGE = "The causation of the calendars is wrong"; - - /** Field TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE */ - public static final String TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE = "Inner TimeDifference object is null"; - - /** Field TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE */ - public static final String TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE = - "Element in TimeDifference collection is not a TimeDifference object"; - - /** Field DEBUG */ - public static final String DEBUG = "**** external debug: "; - - /** Field INFO */ - public static final String INFO = "**** external info: "; - - /** Field WARNING */ - public static final String WARNING = "**** external warning: "; - - /** Field ERROR */ - public static final String ERROR = "**** external error: "; - - /** - * Builds a prefix message to be used in front of info messages for identification purposes. - * The message format is: - *

-   * **** external info: [timestamp] [class name]:
-   * 
- *

- * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. - * @return a prefix for an info message. - */ - public static String getPrefixInfoMessage(final Object pObject) { - - StringBuilder buffer = new StringBuilder(); - - buffer.append(INFO); - buffer.append(getTimestamp()); - buffer.append(" "); - if (pObject == null) { - buffer.append("[unknown class]"); - } else { - if (pObject instanceof String) { - buffer.append((String) pObject); - } else { - buffer.append(getClassName(pObject)); - } - } - buffer.append(": "); - return buffer.toString(); - } - - /** - * Builds a prefix message to be used in front of debug messages for identification purposes. - * The message format is: - *

-   * **** external debug: [timestamp] [class name]:
-   * 
- *

- * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. - * @return a prefix for a debug message. - */ - public static String getPrefixDebugMessage(final Object pObject) { - - StringBuilder buffer = new StringBuilder(); - - buffer.append(DEBUG); - buffer.append(getTimestamp()); - buffer.append(" "); - if (pObject == null) { - buffer.append("[unknown class]"); - } else { - if (pObject instanceof String) { - buffer.append((String) pObject); - } else { - buffer.append(getClassName(pObject)); - } - } - buffer.append(": "); - return buffer.toString(); - } - - /** - * Builds a prefix message to be used in front of warning messages for identification purposes. - * The message format is: - *

-   * **** external warning: [timestamp] [class name]:
-   * 
- *

- * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. - * @return a prefix for a warning message. - */ - public static String getPrefixWarningMessage(final Object pObject) { - - StringBuilder buffer = new StringBuilder(); - - buffer.append(WARNING); - buffer.append(getTimestamp()); - buffer.append(" "); - if (pObject == null) { - buffer.append("[unknown class]"); - } else { - if (pObject instanceof String) { - buffer.append((String) pObject); - } else { - buffer.append(getClassName(pObject)); - } - } - buffer.append(": "); - return buffer.toString(); - } - - /** - * Builds a prefix message to be used in front of error messages for identification purposes. - * The message format is: - *

-   * **** external error: [timestamp] [class name]:
-   * 
- *

- * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. - * @return a prefix for an error message. - */ - public static String getPrefixErrorMessage(final Object pObject) { - - StringBuilder buffer = new StringBuilder(); - - buffer.append(ERROR); - buffer.append(getTimestamp()); - buffer.append(" "); - if (pObject == null) { - buffer.append("[unknown class]"); - } else { - if (pObject instanceof String) { - buffer.append((String) pObject); - } else { - buffer.append(getClassName(pObject)); - } - } - buffer.append(": "); - return buffer.toString(); - } - - /** - * The "default" method that invokes a given method of an object and prints the results to a {@code java.io.PrintStream}.
- * The method for invocation must have no formal parameters. If the invoking method does not exist, the {@code toString()} method is called. - * The {@code toString()} method of the returning object is called. - *

- * @param pObject the {@code java.lang.Object} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printDebug(final Object pObject, final String pMethodName, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pObject == null) { - pPrintStream.println(OBJECT_IS_NULL_ERROR_MESSAGE); - return; - } - if (!StringUtil.isEmpty(pMethodName)) { - try { - Method objectMethod = pObject.getClass().getMethod(pMethodName, null); - Object retVal = objectMethod.invoke(pObject, null); - - if (retVal != null) { - printDebug(retVal, null, pPrintStream); - } else { - throw new Exception(); - } - } catch (Exception e) { - - // Default - pPrintStream.println(pObject.toString()); - } - } else { // Ultimate default - pPrintStream.println(pObject.toString()); - } - } - - /** - * Prints the object's {@code toString()} method to a {@code java.io.PrintStream}. - *

- * @param pObject the {@code java.lang.Object} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printDebug(final Object pObject, final PrintStream pPrintStream) { - printDebug(pObject, null, pPrintStream); - } - - /** - * Prints the object's {@code toString()} method to {@code System.out}. - *

- * @param pObject the {@code java.lang.Object} to be printed. - */ - public static void printDebug(final Object pObject) { - printDebug(pObject, System.out); - } - - /** - * Prints a line break. - */ - public static void printDebug() { - System.out.println(); - } - - /** - * Prints a primitive {@code boolean} to {@code System.out}. - *

- * @param pBoolean the {@code boolean} to be printed. - */ - public static void printDebug(final boolean pBoolean) { - printDebug(new Boolean(pBoolean).toString()); - } - - /** - * Prints a primitive {@code int} to {@code System.out}. - *

- * - * @param pInt - */ - public static void printDebug(final int pInt) { - printDebug(new Integer(pInt).toString()); - } - - /** - * Prints the content of a {@code int[]} to a {@code java.io.PrintStream}. - *

- * @param pIntArray the {@code int[]} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printDebug(final int[] pIntArray, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pIntArray == null) { - pPrintStream.println(INTARRAY_IS_NULL_ERROR_MESSAGE); - return; - } - for (int i = 0; i < pIntArray.length; i++) { - pPrintStream.println(pIntArray[i]); - } - } - - /** - * Prints the content of a {@code int[]} to {@code System.out}. - *

- * @param pIntArray the {@code int[]} to be printed. - */ - public static void printDebug(final int[] pIntArray) { - printDebug(pIntArray, System.out); - } - - /** - * Prints a number of character check methods from the {@code java.lang.Character} class to a {@code java.io.PrintStream}. - *

- * @param pChar the {@code java.lang.char} to be debugged. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printDebug(final char pChar, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println("Character.getNumericValue(pChar): " + Character.getNumericValue(pChar)); - pPrintStream.println("Character.getType(pChar): " + Character.getType(pChar)); - pPrintStream.println("pChar.hashCode(): " + new Character(pChar).hashCode()); - pPrintStream.println("Character.isDefined(pChar): " + Character.isDefined(pChar)); - pPrintStream.println("Character.isDigit(pChar): " + Character.isDigit(pChar)); - pPrintStream.println("Character.isIdentifierIgnorable(pChar): " + Character.isIdentifierIgnorable(pChar)); - pPrintStream.println("Character.isISOControl(pChar): " + Character.isISOControl(pChar)); - pPrintStream.println("Character.isJavaIdentifierPart(pChar): " + Character.isJavaIdentifierPart(pChar)); - pPrintStream.println("Character.isJavaIdentifierStart(pChar): " + Character.isJavaIdentifierStart(pChar)); - pPrintStream.println("Character.isLetter(pChar): " + Character.isLetter(pChar)); - pPrintStream.println("Character.isLetterOrDigit(pChar): " + Character.isLetterOrDigit(pChar)); - pPrintStream.println("Character.isLowerCase(pChar): " + Character.isLowerCase(pChar)); - pPrintStream.println("Character.isSpaceChar(pChar): " + Character.isSpaceChar(pChar)); - pPrintStream.println("Character.isTitleCase(pChar): " + Character.isTitleCase(pChar)); - pPrintStream.println("Character.isUnicodeIdentifierPart(pChar): " + Character.isUnicodeIdentifierPart(pChar)); - pPrintStream.println("Character.isUnicodeIdentifierStart(pChar): " + Character.isUnicodeIdentifierStart(pChar)); - pPrintStream.println("Character.isUpperCase(pChar): " + Character.isUpperCase(pChar)); - pPrintStream.println("Character.isWhitespace(pChar): " + Character.isWhitespace(pChar)); - pPrintStream.println("pChar.toString(): " + new Character(pChar).toString()); - } - - /** - * Prints a number of character check methods from the {@code java.lang.Character} class to {@code System.out}. - *

- * @param pChar the {@code java.lang.char} to be debugged. - */ - public static void printDebug(final char pChar) { - printDebug(pChar, System.out); - } - - /** - * Prints the content of a {@code java.lang.String[]} to a {@code java.io.PrintStream}. - *

- * @param pStringArray the {@code java.lang.String[]} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printDebug(final String[] pStringArray, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pStringArray == null) { - pPrintStream.println(STRINGARRAY_IS_NULL_ERROR_MESSAGE); - return; - } - for (int i = 0; i < pStringArray.length; i++) { - pPrintStream.println(pStringArray[i]); - } - } - - /** - * Prints the content of a {@code java.lang.String[]} to {@code System.out}. - *

- * @param pStringArray the {@code java.lang.String[]} to be printed. - */ - public static void printDebug(final String[] pStringArray) { - printDebug(pStringArray, System.out); - } - - /** - * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. - * The method to be invoked must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pEnumeration the {@code java.util.Enumeration} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Enumeration} - */ - public static void printDebug(final Enumeration pEnumeration, final String pMethodName, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pEnumeration == null) { - pPrintStream.println(ENUMERATION_IS_NULL_ERROR_MESSAGE); - return; - } - while (pEnumeration.hasMoreElements()) { - printDebug(pEnumeration.nextElement(), pMethodName, pPrintStream); - } - } - - /** - * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. - * The method to be invoked must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pEnumeration the {@code java.util.Enumeration} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. - * @see {@code java.util.Enumeration} - */ - public static void printDebug(final Enumeration pEnumeration, final String pMethodName) { - printDebug(pEnumeration, pMethodName, System.out); - } - - /** - * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. - * The method to be invoked must have no formal parameters. The default is calling an element's {@code toString()} method. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pEnumeration the {@code java.util.Enumeration} to be printed. - * @see {@code java.util.Enumeration} - */ - public static void printDebug(final Enumeration pEnumeration) { - printDebug(pEnumeration, null, System.out); - } - - /** - * Invokes a given method of every element in a {@code java.util.Collection} and prints the results to a {@code java.io.PrintStream}. - * The method to be invoked must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, - * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. - *

- * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. - *

- * @param pCollection the {@code java.util.Collection} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Collection} - */ - public static void printDebug(final Collection pCollection, final String pMethodName, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pCollection == null) { - pPrintStream.println(COLLECTION_IS_NULL_ERROR_MESSAGE); - return; - } else if (pCollection.isEmpty()) { - pPrintStream.println(COLLECTION_IS_EMPTY_ERROR_MESSAGE); - return; - } - for (Iterator i = pCollection.iterator(); i.hasNext(); ) { - printDebug(i.next(), pMethodName, pPrintStream); - } - } - - /** - * Invokes a given method of every element in a {@code java.util.Collection} and prints the results to {@code System.out}. - * The method to be invoked must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, - * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. - *

- * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. - *

- * @param pCollection the {@code java.util.Collection} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. - * @see {@code java.util.Collection} - */ - public static void printDebug(final Collection pCollection, final String pMethodName) { - printDebug(pCollection, pMethodName, System.out); - } - - /** - * Prints the content of a {@code java.util.Collection} to a {@code java.io.PrintStream}. - *

- * Not all data types are supported so far. The default is calling an element's {@code toString()} method. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, - * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. - *

- * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. - *

- * @param pCollection the {@code java.util.Collection} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Collection} - */ - public static void printDebug(final Collection pCollection, final PrintStream pPrintStream) { - printDebug(pCollection, null, pPrintStream); - } - - /** - * Prints the content of a {@code java.util.Collection} to {@code System.out}. - *

- * Not all data types are supported so far. The default is calling an element's {@code toString()} method. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, - * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. - *

- * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. - *

- * @param pCollection the {@code java.util.Collection} to be printed. - * @see {@code java.util.Collection} - */ - public static void printDebug(final Collection pCollection) { - printDebug(pCollection, System.out); - } - - /** - * Invokes a given method of every object in a {@code java.util.Map} and prints the results to a {@code java.io.PrintStream}. - * The method called must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. - * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pMap the {@code java.util.Map} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each mapped object. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Map} - */ - public static void printDebug(final Map pMap, final String pMethodName, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pMap == null) { - pPrintStream.println(MAP_IS_NULL_ERROR_MESSAGE); - return; - } else if (pMap.isEmpty()) { - pPrintStream.println(MAP_IS_EMPTY_ERROR_MESSAGE); - return; - } - Object mKeyObject; - Object mEntryObject; - - for (Iterator i = pMap.keySet().iterator(); i.hasNext(); ) { - mKeyObject = i.next(); - mEntryObject = pMap.get(mKeyObject); - if ((mKeyObject instanceof String) && (mEntryObject instanceof String)) { - pPrintStream.println((String) mKeyObject + ": " + mEntryObject); - } else if ((mKeyObject instanceof String) && (mEntryObject instanceof List)) { - printDebug((List) mEntryObject, pPrintStream); - } else if ((mKeyObject instanceof String) && (mEntryObject instanceof Set)) { - printDebug((Set) mEntryObject, pPrintStream); - } else if (mKeyObject instanceof String) { - if (!StringUtil.isEmpty(pMethodName)) { - try { - Method objectMethod = mEntryObject.getClass().getMethod(pMethodName, null); - Object retVal = objectMethod.invoke(mEntryObject, null); - - if (retVal != null) { - pPrintStream.println((String) mKeyObject + ": " + retVal.toString()); - } else { // Default execution - throw new Exception(); - } - } catch (Exception e) { - - // Default - pPrintStream.println((String) mKeyObject + ": " + mEntryObject.toString()); - } - } else { // Default - pPrintStream.println((String) mKeyObject + ": " + mEntryObject.toString()); - } - } else if ((mKeyObject instanceof Integer) && (mEntryObject instanceof String)) { - pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject); - } else if ((mKeyObject instanceof Integer) && (mEntryObject instanceof List)) { - printDebug((List) mEntryObject, pPrintStream); - } else if ((mKeyObject instanceof String) && (mEntryObject instanceof Set)) { - printDebug((Set) mEntryObject, pPrintStream); - } else if (mKeyObject instanceof Integer) { - if (!StringUtil.isEmpty(pMethodName)) { - try { - Method objectMethod = mEntryObject.getClass().getMethod(pMethodName, null); - Object retVal = objectMethod.invoke(mEntryObject, null); - - if (retVal != null) { - pPrintStream.println((Integer) mKeyObject + ": " + retVal.toString()); - } else { // Default execution - throw new Exception(); - } - } catch (Exception e) { - - // Default - pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject.toString()); - } - } else { // Default - pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject.toString()); - } - } - - // More.. - //else if - } - } - - /** - * Invokes a given method of every object in a {@code java.util.Map} to {@code System.out}. - * The method called must have no formal parameters. - *

- * If an exception is throwed during the method invocation, the element's {@code toString()} method is called.
- * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pMap the {@code java.util.Map} to be printed. - * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each mapped object. - * @see {@code java.util.Map} - */ - public static void printDebug(final Map pMap, final String pMethodName) { - printDebug(pMap, pMethodName, System.out); - } - - /** - * Prints the content of a {@code java.util.Map} to a {@code java.io.PrintStream}. - *

- * Not all data types are supported so far. The default is calling an element's {@code toString()} method.
- * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pMap the {@code java.util.Map} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Map} - */ - public static void printDebug(final Map pMap, final PrintStream pPrintStream) { - printDebug(pMap, null, pPrintStream); - } - - /** - * Prints the content of a {@code java.util.Map} to {@code System.out}. - *

- * Not all data types are supported so far. The default is calling an element's {@code toString()} method.
- * For bulk data types, recursive invocations and invocations of other methods in this class, are used. - *

- * @param pMap the {@code java.util.Map} to be printed. - * @see {@code java.util.Map} - */ - public static void printDebug(final Map pMap) { - printDebug(pMap, System.out); - } - - /** - * Prints the content of a {@code java.util.Properties} to a {@code java.io.PrintStream}. - *

- * @param pProperties the {@code java.util.Properties} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Properties} - */ - public static void printDebug(final Properties pProperties, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pProperties == null) { - pPrintStream.println(PROPERTIES_IS_NULL_ERROR_MESSAGE); - return; - } else if (pProperties.isEmpty()) { - pPrintStream.println(PROPERTIES_IS_EMPTY_ERROR_MESSAGE); - return; - } - for (Enumeration e = pProperties.propertyNames(); e.hasMoreElements(); ) { - String key = (String) e.nextElement(); - - pPrintStream.println(key + ": " + pProperties.getProperty(key)); - } - } - - /** - * Prints the content of a {@code java.util.Properties} to {@code System.out}. - *

- * @param pProperties the {@code java.util.Properties} to be printed. - * @see {@code java.util.Properties} - */ - public static void printDebug(final Properties pProperties) { - printDebug(pProperties, System.out); - } - - // Timestamp utilities - - /** - * Prints out the calendar time. - *

- * @param pCalendar the {@code java.util.Calendar} object from which to extract the date information. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Calendar} - */ - public static void printTimestamp(final Calendar pCalendar, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println(getTimestamp(pCalendar)); - } - - /** - * Prints out the system time. - *

- * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.GregorianCalendar} - */ - public static void printTimestamp(final PrintStream pPrintStream) { - - GregorianCalendar cal = new GregorianCalendar(); - - printTimestamp(cal, pPrintStream); - } - - /** - * Prints out the system time to {@code System.out}. - */ - public static void printTimestamp() { - printTimestamp(System.out); - } - - /** - * Returns a presentation of the date based on the given milliseconds. - *

- * @param pMilliseconds The specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. - * @return a presentation of the calendar time. - * @see {@code java.util.Calendar} - */ - public static String getTimestamp(final String pMilliseconds) { - return getTimestamp(Long.parseLong(pMilliseconds)); - } - - /** - * Returns a presentation of the date based on the given milliseconds. - *

- * @param pMilliseconds The specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. - * @return a presentation of the calendar time. - * @see {@code java.util.Calendar} - */ - public static String getTimestamp(final long pMilliseconds) { - - java.util.Date date = new java.util.Date(pMilliseconds); - java.util.Calendar calendar = new GregorianCalendar(); - - calendar.setTime(date); - return getTimestamp(calendar); - } - - /** - * Returns a presentation of the given calendar's time. - *

- * @param pCalendar the {@code java.util.Calendar} object from which to extract the date information. - * @return a presentation of the calendar time. - * @see {@code java.util.Calendar} - */ - public static String getTimestamp(final Calendar pCalendar) { - return buildTimestamp(pCalendar); - } - - /** - * @return a presentation of the system time. - */ - public static String getTimestamp() { - - GregorianCalendar cal = new GregorianCalendar(); - - return getTimestamp(cal); - } - - /** - * Builds a presentation of the given calendar's time. This method contains the common timestamp format used in this class. - * @return a presentation of the calendar time. - */ - protected static String buildTimestamp(final Calendar pCalendar) { - - if (pCalendar == null) { - return CALENDAR_IS_NULL_ERROR_MESSAGE; - } - - // The timestamp format - StringBuilder timestamp = new StringBuilder(); - - //timestamp.append(DateUtil.getMonthName(new Integer(pCalendar.get(Calendar.MONTH)).toString(), "0", "us", "MEDIUM", false) + " "); - timestamp.append(DateFormat.getDateInstance(DateFormat.MEDIUM).format(pCalendar.getTime())); - - //timestamp.append(pCalendar.get(Calendar.DAY_OF_MONTH) + " "); - timestamp.append(" "); - timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.HOUR_OF_DAY)).toString(), 2, "0", true) + ":"); - timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.MINUTE)).toString(), 2, "0", true) + ":"); - timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.SECOND)).toString(), 2, "0", true) + ":"); - timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.MILLISECOND)).toString(), 3, "0", true)); - return timestamp.toString(); - } - - /** - * Builds the time difference between two millisecond representations. - *

- * This method is to be used with small time intervals between 0 ms up to a couple of minutes. - *

- * @param pStartTime the start time. - * @param pEndTime the end time. - * @return the time difference in milliseconds. - */ - public static String buildTimeDifference(final long pStartTime, final long pEndTime) { - - //return pEndTime - pStartTime; - StringBuilder retVal = new StringBuilder(); - - // The time difference in milliseconds - long timeDifference = pEndTime - pStartTime; - - if (timeDifference < 1000) { - retVal.append(timeDifference); - retVal.append(" ms"); - } else { - long seconds = timeDifference / 1000; - - timeDifference = timeDifference % 1000; - retVal.append(seconds); - retVal.append("s "); - retVal.append(timeDifference); - retVal.append("ms"); - } - - //return retVal.toString() + " (original timeDifference: " + new String(new Long(pEndTime - pStartTime).toString()) + ")"; - return retVal.toString(); - } - - /** - * Builds the time difference between the given time and present time. - *

- * This method is to be used with small time intervals between 0 ms up to a couple of minutes. - *

- * @param pStartTime the start time. - * @return the time difference in milliseconds. - */ - public static String buildTimeDifference(final long pStartTime) { - - long presentTime = System.currentTimeMillis(); - - return buildTimeDifference(pStartTime, presentTime); - } - - /** - * Prints out the difference between two millisecond representations. - * The start time is subtracted from the end time. - *

- * @param pStartTime the start time. - * @param pEndTime the end time. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printTimeDifference(final long pStartTime, final long pEndTime, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println(buildTimeDifference(pStartTime, pEndTime)); - } - - /** - * Prints out the difference between two millisecond representations. - * The start time is subtracted from the end time. - *

- * @param pStartTime the start time. - * @param pEndTime the end time. - */ - public static void printTimeDifference(final long pStartTime, final long pEndTime) { - printTimeDifference(pStartTime, pEndTime, System.out); - } - - /** - * Prints out the difference between the given time and present time. - * The start time is subtracted from the present time. - *

- * @param pStartTime the start time. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printTimeDifference(final long pStartTime, final PrintStream pPrintStream) { - printTimeDifference(pStartTime, System.currentTimeMillis(), pPrintStream); - } - - /** - * Prints out the difference between the given time and present time to {@code System.out}. - * The start time is subtracted from the present time. - *

- * usage: - *

-   * long startTime = System.currentTimeMillis();
-   * ...
-   * com.iml.oslo.eito.util.DebugUtil.printTimeDifference(startTime);
-   * 
- *

- * @param pStartTime the start time. - */ - public static void printTimeDifference(final long pStartTime) { - printTimeDifference(pStartTime, System.out); - } - - /** - * Builds a string representing the difference between two calendar times. - * The first calendar object is subtracted from the second one. - *

- * This method is to be used with time intervals between 0 ms up to several hours. - *

- * @param pStartCalendar the first {@code java.util.Calendar}. - * @param pEndCalendar the second {@code java.util.Calendar}. - * @return a string representation of the time difference. - * @see {@code java.util.Calendar} - */ - public static String buildTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { - - if (pStartCalendar == null) { - return CALENDAR_IS_NULL_ERROR_MESSAGE; - } - if (pEndCalendar == null) { - return CALENDAR_IS_NULL_ERROR_MESSAGE; - } - if (pEndCalendar.before(pStartCalendar)) { - return CALENDAR_CAUSATION_ERROR_MESSAGE; - } - int dateDiff = pEndCalendar.get(Calendar.DATE) - pStartCalendar.get(Calendar.DATE); - int hourDiff = pEndCalendar.get(Calendar.HOUR_OF_DAY) - pStartCalendar.get(Calendar.HOUR_OF_DAY); - int minuteDiff = pEndCalendar.get(Calendar.MINUTE) - pStartCalendar.get(Calendar.MINUTE); - int secondDiff = pEndCalendar.get(Calendar.SECOND) - pStartCalendar.get(Calendar.SECOND); - int milliSecondDiff = pEndCalendar.get(Calendar.MILLISECOND) - pStartCalendar.get(Calendar.MILLISECOND); - - if (milliSecondDiff < 0) { - secondDiff--; - milliSecondDiff += 1000; - } - if (secondDiff < 0) { - minuteDiff--; - secondDiff += 60; - } - if (minuteDiff < 0) { - hourDiff--; - minuteDiff += 60; - } - while (dateDiff > 0) { - dateDiff--; - hourDiff += 24; - } - - // Time difference presentation format - StringBuilder buffer = new StringBuilder(); - - if ((hourDiff == 0) && (minuteDiff == 0) && (secondDiff == 0)) { - buffer.append(milliSecondDiff); - buffer.append("ms"); - } else if ((hourDiff == 0) && (minuteDiff == 0)) { - buffer.append(secondDiff); - buffer.append("s "); - buffer.append(milliSecondDiff); - buffer.append("ms"); - } else if (hourDiff == 0) { - buffer.append(minuteDiff); - buffer.append("m "); - buffer.append(secondDiff); - buffer.append(","); - buffer.append(milliSecondDiff); - buffer.append("s"); - } else { - buffer.append(hourDiff); - buffer.append("h "); - buffer.append(minuteDiff); - buffer.append("m "); - buffer.append(secondDiff); - buffer.append(","); - buffer.append(milliSecondDiff); - buffer.append("s"); - } - return buffer.toString(); - } - - /** - * Prints out the difference between to calendar times. - * The first calendar object is subtracted from the second one. - *

- * @param pStartCalendar the first {@code java.util.Calendar}. - * @param pEndCalendar the second {@code java.util.Calendar}. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Calendar} - */ - public static void printTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println(buildTimeDifference(pStartCalendar, pEndCalendar)); - } - - /** - * Prints out the difference between to calendar times two {@code System.out}. - * The first calendar object is subtracted from the second one. - *

- * @param pStartCalendar the first {@code java.util.Calendar}. - * @param pEndCalendar the second {@code java.util.Calendar}. - * @see {@code java.util.Calendar} - */ - public static void printTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { - printTimeDifference(pStartCalendar, pEndCalendar, System.out); - } - - /** - * Prints out the difference between the given calendar time and present time. - *

- * @param pStartCalendar the {@code java.util.Calendar} to compare with present time. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.util.Calendar} - */ - public static void printTimeDifference(final Calendar pStartCalendar, final PrintStream pPrintStream) { - - GregorianCalendar endCalendar = new GregorianCalendar(); - - printTimeDifference(pStartCalendar, endCalendar, pPrintStream); - } - - /** - * Prints out the difference between the given calendar time and present time to {@code System.out}. - *

- * usage: - *

-   * GregorianCalendar startTime = new GregorianCalendar();
-   * ...
-   * com.iml.oslo.eito.util.DebugUtil.printTimeDifference(startTime);
-   * 
- *

- * @param pStartCalendar the {@code java.util.Calendar} to compare with present time. - * @see {@code java.util.Calendar} - */ - public static void printTimeDifference(final Calendar pStartCalendar) { - - GregorianCalendar endCalendar = new GregorianCalendar(); - - printTimeDifference(pStartCalendar, endCalendar); - } - - /** - * Prints out a {@code com.iml.oslo.eito.util.DebugUtil.TimeDifference} object. - *

- * @param pTimeDifference the {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} to investigate. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printTimeDifference(final TimeDifference pTimeDifference, final PrintStream pPrintStream) { - printTimeDifference(pTimeDifference.getStartCalendar(), pTimeDifference.getEndCalendar(), pPrintStream); - } - - /** - * Prints out a {@code com.iml.oslo.eito.util.DebugUtil.TimeDifference} object to {@code System.out}. - *

- * @param pTimeDifference the {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} to investigate. - */ - public static void printTimeDifference(final TimeDifference pTimeDifference) { - printTimeDifference(pTimeDifference.getStartCalendar(), pTimeDifference.getEndCalendar(), System.out); - } - - /** - * A convenience class for embracing two {@code java.util.Calendar} objects. - * The class is used for building a collection of time differences according to the {@code printTimeAverage} method. - */ - public static class TimeDifference { - - Calendar mStartCalendar; - Calendar mEndCalendar; - - /** - * Constructor TimeDifference - * - * - */ - public TimeDifference() {} - - /** - * Constructor TimeDifference - * - * - * @param pStartCalendar - * @param pEndCalendar - * - */ - public TimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { - this.mStartCalendar = pStartCalendar; - this.mEndCalendar = pEndCalendar; - } - - /** - * Method setStartCalendar - * - * - * @param pStartCalendar - * - */ - public void setStartCalendar(Calendar pStartCalendar) { - this.mStartCalendar = pStartCalendar; - } - - /** - * Method getStartCalendar - * - * - * @return - * - */ - public Calendar getStartCalendar() { - return this.mStartCalendar; - } - - /** - * Method setEndCalendar - * - * - * @param pEndCalendar - * - */ - public void setEndCalendar(Calendar pEndCalendar) { - this.mEndCalendar = pEndCalendar; - } - - /** - * Method getEndCalendar - * - * - * @return - * - */ - public Calendar getEndCalendar() { - return this.mEndCalendar; - } - } - - /** - * Prints out the average time difference from a collection of {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} objects. - *

- * - * @param pTimeDifferences - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - */ - public static void printTimeAverage(final Collection pTimeDifferences, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - if (pTimeDifferences == null) { - pPrintStream.println(TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE); - return; - } - Object o; - TimeDifference timeDifference; - Calendar startCalendar = null; - Calendar endCalendar = null; - Calendar totalStartCalendar = null; - Calendar totalEndCalendar = null; - long startCalendarMilliSeconds, endCalendarMilliSeconds; - List timeDifferenceList = new Vector(); - Iterator i = pTimeDifferences.iterator(); - - if (i.hasNext()) { - o = i.next(); - if (!(o instanceof TimeDifference)) { - pPrintStream.println(TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE); - return; - } - timeDifference = (TimeDifference) o; - startCalendar = timeDifference.getStartCalendar(); - totalStartCalendar = startCalendar; - endCalendar = timeDifference.getEndCalendar(); - startCalendarMilliSeconds = startCalendar.getTime().getTime(); - endCalendarMilliSeconds = endCalendar.getTime().getTime(); - timeDifferenceList.add(new Long(endCalendarMilliSeconds - startCalendarMilliSeconds)); - } - while (i.hasNext()) { - o = i.next(); - if (!(o instanceof TimeDifference)) { - pPrintStream.println(TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE); - return; - } - timeDifference = (TimeDifference) o; - startCalendar = timeDifference.getStartCalendar(); - endCalendar = timeDifference.getEndCalendar(); - startCalendarMilliSeconds = startCalendar.getTime().getTime(); - endCalendarMilliSeconds = endCalendar.getTime().getTime(); - timeDifferenceList.add(new Long(endCalendarMilliSeconds - startCalendarMilliSeconds)); - } - totalEndCalendar = endCalendar; - int numberOfElements = timeDifferenceList.size(); - long timeDifferenceElement; - long timeDifferenceSum = 0; - - for (Iterator i2 = timeDifferenceList.iterator(); i2.hasNext(); ) { - timeDifferenceElement = ((Long) i2.next()).longValue(); - timeDifferenceSum += timeDifferenceElement; - } - - // Total elapsed time - String totalElapsedTime = buildTimeDifference(totalStartCalendar, totalEndCalendar); - - // Time average presentation format - pPrintStream.println("Average time difference: " + timeDifferenceSum / numberOfElements + "ms (" + numberOfElements - + " elements, total elapsed time: " + totalElapsedTime + ")"); - } - - /** - * Prints out the average time difference from a collection of {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} objects to {@code System.out}. - *

- * - * @param pTimeDifferences - */ - public static void printTimeAverage(final Collection pTimeDifferences) { - printTimeAverage(pTimeDifferences, System.out); - } - - // Reflective methods - - /** - * Prints the top-wrapped class name of a {@code java.lang.Object} to a {@code java.io.PrintStream}. - *

- * @param pObject the {@code java.lang.Object} to be printed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.lang.Class} - */ - public static void printClassName(final Object pObject, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println(getClassName(pObject)); - } - - /** - * Prints the top-wrapped class name of a {@code java.lang.Object} to {@code System.out}. - *

- * @param pObject the {@code java.lang.Object} to be printed. - * @see {@code java.lang.Class} - */ - public static void printClassName(final Object pObject) { - printClassName(pObject, System.out); - } - - /** - * Builds the top-wrapped class name of a {@code java.lang.Object}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @return the object's class name. - * @see {@code java.lang.Class} - */ - public static String getClassName(final Object pObject) { - - if (pObject == null) { - return OBJECT_IS_NULL_ERROR_MESSAGE; - } - return pObject.getClass().getName(); - } - - /** - * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to a {@code java.io.PrintStream}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @param pObjectName the name of the object instance, for identification purposes. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static void printClassDetails(final Object pObject, final String pObjectName, final PrintStream pPrintStream) { - - if (pPrintStream == null) { - System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); - return; - } - pPrintStream.println(getClassDetails(pObject, pObjectName)); - } - - /** - * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to {@code System.out}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @param pObjectName the name of the object instance, for identification purposes. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static void printClassDetails(final Object pObject, final String pObjectName) { - printClassDetails(pObject, pObjectName, System.out); - } - - /** - * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to {@code System.out}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static void printClassDetails(final Object pObject) { - printClassDetails(pObject, null, System.out); - } - - /** - * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to a {@code java.io.PrintStream}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static void printClassDetails(final Object pObject, final PrintStream pPrintStream) { - printClassDetails(pObject, null, pPrintStream); - } - - /** - * Builds a javadoc-like presentation of the top wrapped class fields and methods of a {@code java.lang.Object}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @return a listing of the object's class details. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static String getClassDetails(final Object pObject) { - return getClassDetails(pObject, null); - } - - /** - * Builds a javadoc-like presentation of the top wrapped class fields and methods of a {@code java.lang.Object}. - *

- * @param pObject the {@code java.lang.Object} to be analysed. - * @param pObjectName the name of the object instance, for identification purposes. - * @return a listing of the object's class details. - * @see {@code java.lang.Class} - * @see {@code java.lang.reflect.Modifier} - * @see {@code java.lang.reflect.Field} - * @see {@code java.lang.reflect.Constructor} - * @see {@code java.lang.reflect.Method} - */ - public static String getClassDetails(final Object pObject, final String pObjectName) { - - if (pObject == null) { - return OBJECT_IS_NULL_ERROR_MESSAGE; - } - final String endOfLine = System.getProperty("line.separator"); - final String dividerLine = "---------------------------------------------------------"; - Class c = pObject.getClass(); - StringTokenizer tokenizedString; - String str; - String className = new String(); - String superClassName = new String(); - StringBuilder buffer = new StringBuilder(); - - // Heading - buffer.append(endOfLine); - buffer.append("**** class details"); - if (!StringUtil.isEmpty(pObjectName)) { - buffer.append(" for \"" + pObjectName + "\""); - } - buffer.append(" ****"); - buffer.append(endOfLine); - - // Package - Package p = c.getPackage(); - - if (p != null) { - buffer.append(p.getName()); - } - buffer.append(endOfLine); - - // Class or Interface - if (c.isInterface()) { - buffer.append("I n t e r f a c e "); - } else { - buffer.append("C l a s s "); - } - str = c.getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - className = tokenizedString.nextToken().trim(); - } - str = new String(); - char[] charArray = className.toCharArray(); - - for (int i = 0; i < charArray.length; i++) { - str += charArray[i] + " "; - } - buffer.append(str); - buffer.append(endOfLine); - buffer.append(endOfLine); - - // Class Hierarch - List classNameList = new Vector(); - - classNameList.add(c.getName()); - Class superclass = c.getSuperclass(); - - while (superclass != null) { - classNameList.add(superclass.getName()); - superclass = superclass.getSuperclass(); - } - Object[] classNameArray = classNameList.toArray(); - int counter = 0; - - for (int i = classNameArray.length - 1; i >= 0; i--) { - for (int j = 0; j < counter; j++) { - buffer.append(" "); - } - if (counter > 0) { - buffer.append("|"); - buffer.append(endOfLine); - } - for (int j = 0; j < counter; j++) { - buffer.append(" "); - } - if (counter > 0) { - buffer.append("+-"); - } - buffer.append((String) classNameArray[i]); - buffer.append(endOfLine); - counter++; - } - - // Divider - buffer.append(endOfLine); - buffer.append(dividerLine); - buffer.append(endOfLine); - buffer.append(endOfLine); - - // Profile - int classModifier = c.getModifiers(); - - buffer.append(Modifier.toString(classModifier) + " "); - if (c.isInterface()) { - buffer.append("Interface "); - } else { - buffer.append("Class "); - } - buffer.append(className); - buffer.append(endOfLine); - if ((classNameArray != null) && (classNameArray[classNameArray.length - 2] != null)) { - str = (String) classNameArray[classNameArray.length - 2]; - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - superClassName = tokenizedString.nextToken().trim(); - } - buffer.append("extends " + superClassName); - buffer.append(endOfLine); - } - if (!c.isInterface()) { - Class[] interfaces = c.getInterfaces(); - - if ((interfaces != null) && (interfaces.length > 0)) { - buffer.append("implements "); - str = interfaces[0].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(str); - for (int i = 1; i < interfaces.length; i++) { - str = interfaces[i].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(", " + str); - } - buffer.append(endOfLine); - } - } - - // Divider - buffer.append(endOfLine); - buffer.append(dividerLine); - buffer.append(endOfLine); - buffer.append(endOfLine); - - // Fields - buffer.append("F I E L D S U M M A R Y"); - buffer.append(endOfLine); - Field[] fields = c.getFields(); - - if (fields != null) { - for (int i = 0; i < fields.length; i++) { - buffer.append(Modifier.toString(fields[i].getType().getModifiers()) + " "); - str = fields[i].getType().getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(str + " "); - buffer.append(fields[i].getName()); - buffer.append(endOfLine); - } - } - buffer.append(endOfLine); - - // Constructors - buffer.append("C O N S T R U C T O R S U M M A R Y"); - buffer.append(endOfLine); - Constructor[] constructors = c.getConstructors(); - - if (constructors != null) { - for (int i = 0; i < constructors.length; i++) { - buffer.append(className + "("); - Class[] parameterTypes = constructors[i].getParameterTypes(); - - if (parameterTypes != null) { - if (parameterTypes.length > 0) { - str = parameterTypes[0].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(str); - for (int j = 1; j < parameterTypes.length; j++) { - str = parameterTypes[j].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(", " + str); - } - } - } - buffer.append(")"); - buffer.append(endOfLine); - } - } - buffer.append(endOfLine); - - // Methods - buffer.append("M E T H O D S U M M A R Y"); - buffer.append(endOfLine); - Method[] methods = c.getMethods(); - - if (methods != null) { - for (int i = 0; i < methods.length; i++) { - buffer.append(Modifier.toString(methods[i].getModifiers()) + " "); - str = methods[i].getReturnType().getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(str + " "); - buffer.append(methods[i].getName() + "("); - Class[] parameterTypes = methods[i].getParameterTypes(); - - if ((parameterTypes != null) && (parameterTypes.length > 0)) { - if (parameterTypes[0] != null) { - str = parameterTypes[0].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - - // array bugfix - if (str.charAt(str.length() - 1) == ';') { - str = str.substring(0, str.length() - 1) + "[]"; - } - buffer.append(str); - for (int j = 1; j < parameterTypes.length; j++) { - str = parameterTypes[j].getName(); - tokenizedString = new StringTokenizer(str, "."); - while (tokenizedString.hasMoreTokens()) { - str = tokenizedString.nextToken().trim(); - } - buffer.append(", " + str); - } - } - } - buffer.append(")"); - buffer.append(endOfLine); - } - } - buffer.append(endOfLine); - - // Ending - buffer.append("**** class details"); - if (!StringUtil.isEmpty(pObjectName)) { - buffer.append(" for \"" + pObjectName + "\""); - } - buffer.append(" end ****"); - buffer.append(endOfLine); - return buffer.toString(); - } - - /** - * Prettyprints a large number. - *

- * - * @param pBigNumber - * @return prettyprinted number with dot-separation each 10e3. - */ - public static String getLargeNumber(final long pBigNumber) { - - StringBuilder buffer = new StringBuilder(new Long(pBigNumber).toString()); - char[] number = new Long(pBigNumber).toString().toCharArray(); - int reverseIndex = 0; - - for (int i = number.length; i >= 0; i--) { - reverseIndex++; - if ((reverseIndex % 3 == 0) && (i > 1)) { - buffer = buffer.insert(i - 1, '.'); - } - } - return buffer.toString(); - } - - /** - * Prettyprints milliseconds to ?day(s) ?h ?m ?s ?ms. - *

- * - * @param pMilliseconds - * @return prettyprinted time duration. - */ - public static String getTimeInterval(final long pMilliseconds) { - - long timeIntervalMilliseconds = pMilliseconds; - long timeIntervalSeconds = 0; - long timeIntervalMinutes = 0; - long timeIntervalHours = 0; - long timeIntervalDays = 0; - boolean printMilliseconds = true; - boolean printSeconds = false; - boolean printMinutes = false; - boolean printHours = false; - boolean printDays = false; - final long MILLISECONDS_IN_SECOND = 1000; - final long MILLISECONDS_IN_MINUTE = 60 * MILLISECONDS_IN_SECOND; // 60000 - final long MILLISECONDS_IN_HOUR = 60 * MILLISECONDS_IN_MINUTE; // 3600000 - final long MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR; // 86400000 - StringBuilder timeIntervalBuffer = new StringBuilder(); - - // Days - if (timeIntervalMilliseconds >= MILLISECONDS_IN_DAY) { - timeIntervalDays = timeIntervalMilliseconds / MILLISECONDS_IN_DAY; - timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_DAY; - printDays = true; - printHours = true; - printMinutes = true; - printSeconds = true; - } - - // Hours - if (timeIntervalMilliseconds >= MILLISECONDS_IN_HOUR) { - timeIntervalHours = timeIntervalMilliseconds / MILLISECONDS_IN_HOUR; - timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_HOUR; - printHours = true; - printMinutes = true; - printSeconds = true; - } - - // Minutes - if (timeIntervalMilliseconds >= MILLISECONDS_IN_MINUTE) { - timeIntervalMinutes = timeIntervalMilliseconds / MILLISECONDS_IN_MINUTE; - timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_MINUTE; - printMinutes = true; - printSeconds = true; - } - - // Seconds - if (timeIntervalMilliseconds >= MILLISECONDS_IN_SECOND) { - timeIntervalSeconds = timeIntervalMilliseconds / MILLISECONDS_IN_SECOND; - timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_SECOND; - printSeconds = true; - } - - // Prettyprint - if (printDays) { - timeIntervalBuffer.append(timeIntervalDays); - if (timeIntervalDays > 1) { - timeIntervalBuffer.append("days "); - } else { - timeIntervalBuffer.append("day "); - } - } - if (printHours) { - timeIntervalBuffer.append(timeIntervalHours); - timeIntervalBuffer.append("h "); - } - if (printMinutes) { - timeIntervalBuffer.append(timeIntervalMinutes); - timeIntervalBuffer.append("m "); - } - if (printSeconds) { - timeIntervalBuffer.append(timeIntervalSeconds); - timeIntervalBuffer.append("s "); - } - if (printMilliseconds) { - timeIntervalBuffer.append(timeIntervalMilliseconds); - timeIntervalBuffer.append("ms"); - } - return timeIntervalBuffer.toString(); - } -} - - -/*--- 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: DebugUtil.java,v $ + * @version $Revision: #2 $ + * $Date: 2009/06/19 $ + * * + * @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.util; + + +import com.twelvemonkeys.lang.StringUtil; + +import java.io.PrintStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.text.DateFormat; +import java.util.*; + + +/** + * A utility class to simplify debugging. + * This includes viewing generic data structures, printing timestamps, printing object info and more... + * NB! Only use this class for instrumentation purposes + *

+ * @author Eirik Torske + */ +@Deprecated +public class DebugUtil { + + // Constants + + /** Field PRINTSTREAM_IS_NULL_ERROR_MESSAGE */ + public static final String PRINTSTREAM_IS_NULL_ERROR_MESSAGE = "PrintStream is null"; + + /** Field OBJECT_IS_NULL_ERROR_MESSAGE */ + public static final String OBJECT_IS_NULL_ERROR_MESSAGE = "Object is null"; + + /** Field INTARRAY_IS_NULL_ERROR_MESSAGE */ + public static final String INTARRAY_IS_NULL_ERROR_MESSAGE = "int array is null"; + + /** Field STRINGARRAY_IS_NULL_ERROR_MESSAGE */ + public static final String STRINGARRAY_IS_NULL_ERROR_MESSAGE = "String array is null"; + + /** Field ENUMERATION_IS_NULL_ERROR_MESSAGE */ + public static final String ENUMERATION_IS_NULL_ERROR_MESSAGE = "Enumeration is null"; + + /** Field COLLECTION_IS_NULL_ERROR_MESSAGE */ + public static final String COLLECTION_IS_NULL_ERROR_MESSAGE = "Collection is null"; + + /** Field COLLECTION_IS_EMPTY_ERROR_MESSAGE */ + public static final String COLLECTION_IS_EMPTY_ERROR_MESSAGE = "Collection contains no elements"; + + /** Field MAP_IS_NULL_ERROR_MESSAGE */ + public static final String MAP_IS_NULL_ERROR_MESSAGE = "Map is null"; + + /** Field MAP_IS_EMPTY_ERROR_MESSAGE */ + public static final String MAP_IS_EMPTY_ERROR_MESSAGE = "Map contains no elements"; + + /** Field PROPERTIES_IS_NULL_ERROR_MESSAGE */ + public static final String PROPERTIES_IS_NULL_ERROR_MESSAGE = "Properties is null"; + + /** Field PROPERTIES_IS_EMPTY_ERROR_MESSAGE */ + public static final String PROPERTIES_IS_EMPTY_ERROR_MESSAGE = "Properties contains no elements"; + + /** Field CALENDAR_IS_NULL_ERROR_MESSAGE */ + public static final String CALENDAR_IS_NULL_ERROR_MESSAGE = "Calendar is null"; + + /** Field CALENDAR_CAUSATION_ERROR_MESSAGE */ + public static final String CALENDAR_CAUSATION_ERROR_MESSAGE = "The causation of the calendars is wrong"; + + /** Field TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE */ + public static final String TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE = "Inner TimeDifference object is null"; + + /** Field TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE */ + public static final String TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE = + "Element in TimeDifference collection is not a TimeDifference object"; + + /** Field DEBUG */ + public static final String DEBUG = "**** external debug: "; + + /** Field INFO */ + public static final String INFO = "**** external info: "; + + /** Field WARNING */ + public static final String WARNING = "**** external warning: "; + + /** Field ERROR */ + public static final String ERROR = "**** external error: "; + + /** + * Builds a prefix message to be used in front of info messages for identification purposes. + * The message format is: + *

+   * **** external info: [timestamp] [class name]:
+   * 
+ *

+ * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. + * @return a prefix for an info message. + */ + public static String getPrefixInfoMessage(final Object pObject) { + + StringBuilder buffer = new StringBuilder(); + + buffer.append(INFO); + buffer.append(getTimestamp()); + buffer.append(" "); + if (pObject == null) { + buffer.append("[unknown class]"); + } else { + if (pObject instanceof String) { + buffer.append((String) pObject); + } else { + buffer.append(getClassName(pObject)); + } + } + buffer.append(": "); + return buffer.toString(); + } + + /** + * Builds a prefix message to be used in front of debug messages for identification purposes. + * The message format is: + *

+   * **** external debug: [timestamp] [class name]:
+   * 
+ *

+ * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. + * @return a prefix for a debug message. + */ + public static String getPrefixDebugMessage(final Object pObject) { + + StringBuilder buffer = new StringBuilder(); + + buffer.append(DEBUG); + buffer.append(getTimestamp()); + buffer.append(" "); + if (pObject == null) { + buffer.append("[unknown class]"); + } else { + if (pObject instanceof String) { + buffer.append((String) pObject); + } else { + buffer.append(getClassName(pObject)); + } + } + buffer.append(": "); + return buffer.toString(); + } + + /** + * Builds a prefix message to be used in front of warning messages for identification purposes. + * The message format is: + *

+   * **** external warning: [timestamp] [class name]:
+   * 
+ *

+ * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. + * @return a prefix for a warning message. + */ + public static String getPrefixWarningMessage(final Object pObject) { + + StringBuilder buffer = new StringBuilder(); + + buffer.append(WARNING); + buffer.append(getTimestamp()); + buffer.append(" "); + if (pObject == null) { + buffer.append("[unknown class]"); + } else { + if (pObject instanceof String) { + buffer.append((String) pObject); + } else { + buffer.append(getClassName(pObject)); + } + } + buffer.append(": "); + return buffer.toString(); + } + + /** + * Builds a prefix message to be used in front of error messages for identification purposes. + * The message format is: + *

+   * **** external error: [timestamp] [class name]:
+   * 
+ *

+ * @param pObject the {@code java.lang.Object} to be debugged. If the object ia a {@code java.lang.String} object, it is assumed that it is the class name given directly. + * @return a prefix for an error message. + */ + public static String getPrefixErrorMessage(final Object pObject) { + + StringBuilder buffer = new StringBuilder(); + + buffer.append(ERROR); + buffer.append(getTimestamp()); + buffer.append(" "); + if (pObject == null) { + buffer.append("[unknown class]"); + } else { + if (pObject instanceof String) { + buffer.append((String) pObject); + } else { + buffer.append(getClassName(pObject)); + } + } + buffer.append(": "); + return buffer.toString(); + } + + /** + * The "default" method that invokes a given method of an object and prints the results to a {@code java.io.PrintStream}.
+ * The method for invocation must have no formal parameters. If the invoking method does not exist, the {@code toString()} method is called. + * The {@code toString()} method of the returning object is called. + *

+ * @param pObject the {@code java.lang.Object} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printDebug(final Object pObject, final String pMethodName, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pObject == null) { + pPrintStream.println(OBJECT_IS_NULL_ERROR_MESSAGE); + return; + } + if (!StringUtil.isEmpty(pMethodName)) { + try { + Method objectMethod = pObject.getClass().getMethod(pMethodName, null); + Object retVal = objectMethod.invoke(pObject, null); + + if (retVal != null) { + printDebug(retVal, null, pPrintStream); + } else { + throw new Exception(); + } + } catch (Exception e) { + + // Default + pPrintStream.println(pObject.toString()); + } + } else { // Ultimate default + pPrintStream.println(pObject.toString()); + } + } + + /** + * Prints the object's {@code toString()} method to a {@code java.io.PrintStream}. + *

+ * @param pObject the {@code java.lang.Object} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printDebug(final Object pObject, final PrintStream pPrintStream) { + printDebug(pObject, null, pPrintStream); + } + + /** + * Prints the object's {@code toString()} method to {@code System.out}. + *

+ * @param pObject the {@code java.lang.Object} to be printed. + */ + public static void printDebug(final Object pObject) { + printDebug(pObject, System.out); + } + + /** + * Prints a line break. + */ + public static void printDebug() { + System.out.println(); + } + + /** + * Prints a primitive {@code boolean} to {@code System.out}. + *

+ * @param pBoolean the {@code boolean} to be printed. + */ + public static void printDebug(final boolean pBoolean) { + printDebug(new Boolean(pBoolean).toString()); + } + + /** + * Prints a primitive {@code int} to {@code System.out}. + *

+ * + * @param pInt + */ + public static void printDebug(final int pInt) { + printDebug(new Integer(pInt).toString()); + } + + /** + * Prints the content of a {@code int[]} to a {@code java.io.PrintStream}. + *

+ * @param pIntArray the {@code int[]} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printDebug(final int[] pIntArray, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pIntArray == null) { + pPrintStream.println(INTARRAY_IS_NULL_ERROR_MESSAGE); + return; + } + for (int i = 0; i < pIntArray.length; i++) { + pPrintStream.println(pIntArray[i]); + } + } + + /** + * Prints the content of a {@code int[]} to {@code System.out}. + *

+ * @param pIntArray the {@code int[]} to be printed. + */ + public static void printDebug(final int[] pIntArray) { + printDebug(pIntArray, System.out); + } + + /** + * Prints a number of character check methods from the {@code java.lang.Character} class to a {@code java.io.PrintStream}. + *

+ * @param pChar the {@code java.lang.char} to be debugged. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printDebug(final char pChar, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println("Character.getNumericValue(pChar): " + Character.getNumericValue(pChar)); + pPrintStream.println("Character.getType(pChar): " + Character.getType(pChar)); + pPrintStream.println("pChar.hashCode(): " + new Character(pChar).hashCode()); + pPrintStream.println("Character.isDefined(pChar): " + Character.isDefined(pChar)); + pPrintStream.println("Character.isDigit(pChar): " + Character.isDigit(pChar)); + pPrintStream.println("Character.isIdentifierIgnorable(pChar): " + Character.isIdentifierIgnorable(pChar)); + pPrintStream.println("Character.isISOControl(pChar): " + Character.isISOControl(pChar)); + pPrintStream.println("Character.isJavaIdentifierPart(pChar): " + Character.isJavaIdentifierPart(pChar)); + pPrintStream.println("Character.isJavaIdentifierStart(pChar): " + Character.isJavaIdentifierStart(pChar)); + pPrintStream.println("Character.isLetter(pChar): " + Character.isLetter(pChar)); + pPrintStream.println("Character.isLetterOrDigit(pChar): " + Character.isLetterOrDigit(pChar)); + pPrintStream.println("Character.isLowerCase(pChar): " + Character.isLowerCase(pChar)); + pPrintStream.println("Character.isSpaceChar(pChar): " + Character.isSpaceChar(pChar)); + pPrintStream.println("Character.isTitleCase(pChar): " + Character.isTitleCase(pChar)); + pPrintStream.println("Character.isUnicodeIdentifierPart(pChar): " + Character.isUnicodeIdentifierPart(pChar)); + pPrintStream.println("Character.isUnicodeIdentifierStart(pChar): " + Character.isUnicodeIdentifierStart(pChar)); + pPrintStream.println("Character.isUpperCase(pChar): " + Character.isUpperCase(pChar)); + pPrintStream.println("Character.isWhitespace(pChar): " + Character.isWhitespace(pChar)); + pPrintStream.println("pChar.toString(): " + new Character(pChar).toString()); + } + + /** + * Prints a number of character check methods from the {@code java.lang.Character} class to {@code System.out}. + *

+ * @param pChar the {@code java.lang.char} to be debugged. + */ + public static void printDebug(final char pChar) { + printDebug(pChar, System.out); + } + + /** + * Prints the content of a {@code java.lang.String[]} to a {@code java.io.PrintStream}. + *

+ * @param pStringArray the {@code java.lang.String[]} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printDebug(final String[] pStringArray, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pStringArray == null) { + pPrintStream.println(STRINGARRAY_IS_NULL_ERROR_MESSAGE); + return; + } + for (int i = 0; i < pStringArray.length; i++) { + pPrintStream.println(pStringArray[i]); + } + } + + /** + * Prints the content of a {@code java.lang.String[]} to {@code System.out}. + *

+ * @param pStringArray the {@code java.lang.String[]} to be printed. + */ + public static void printDebug(final String[] pStringArray) { + printDebug(pStringArray, System.out); + } + + /** + * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. + * The method to be invoked must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pEnumeration the {@code java.util.Enumeration} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Enumeration} + */ + public static void printDebug(final Enumeration pEnumeration, final String pMethodName, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pEnumeration == null) { + pPrintStream.println(ENUMERATION_IS_NULL_ERROR_MESSAGE); + return; + } + while (pEnumeration.hasMoreElements()) { + printDebug(pEnumeration.nextElement(), pMethodName, pPrintStream); + } + } + + /** + * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. + * The method to be invoked must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pEnumeration the {@code java.util.Enumeration} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. + * @see {@code java.util.Enumeration} + */ + public static void printDebug(final Enumeration pEnumeration, final String pMethodName) { + printDebug(pEnumeration, pMethodName, System.out); + } + + /** + * Invokes a given method of every element in a {@code java.util.Enumeration} and prints the results to a {@code java.io.PrintStream}. + * The method to be invoked must have no formal parameters. The default is calling an element's {@code toString()} method. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pEnumeration the {@code java.util.Enumeration} to be printed. + * @see {@code java.util.Enumeration} + */ + public static void printDebug(final Enumeration pEnumeration) { + printDebug(pEnumeration, null, System.out); + } + + /** + * Invokes a given method of every element in a {@code java.util.Collection} and prints the results to a {@code java.io.PrintStream}. + * The method to be invoked must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, + * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. + *

+ * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. + *

+ * @param pCollection the {@code java.util.Collection} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Collection} + */ + public static void printDebug(final Collection pCollection, final String pMethodName, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pCollection == null) { + pPrintStream.println(COLLECTION_IS_NULL_ERROR_MESSAGE); + return; + } else if (pCollection.isEmpty()) { + pPrintStream.println(COLLECTION_IS_EMPTY_ERROR_MESSAGE); + return; + } + for (Iterator i = pCollection.iterator(); i.hasNext(); ) { + printDebug(i.next(), pMethodName, pPrintStream); + } + } + + /** + * Invokes a given method of every element in a {@code java.util.Collection} and prints the results to {@code System.out}. + * The method to be invoked must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, + * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. + *

+ * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. + *

+ * @param pCollection the {@code java.util.Collection} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each collection element. + * @see {@code java.util.Collection} + */ + public static void printDebug(final Collection pCollection, final String pMethodName) { + printDebug(pCollection, pMethodName, System.out); + } + + /** + * Prints the content of a {@code java.util.Collection} to a {@code java.io.PrintStream}. + *

+ * Not all data types are supported so far. The default is calling an element's {@code toString()} method. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, + * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. + *

+ * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. + *

+ * @param pCollection the {@code java.util.Collection} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Collection} + */ + public static void printDebug(final Collection pCollection, final PrintStream pPrintStream) { + printDebug(pCollection, null, pPrintStream); + } + + /** + * Prints the content of a {@code java.util.Collection} to {@code System.out}. + *

+ * Not all data types are supported so far. The default is calling an element's {@code toString()} method. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * Be aware that the {@code Collection} interface embraces a large portion of the bulk data types in the {@code java.util} package, + * e.g. {@code List}, {@code Set}, {@code Vector} and {@code HashSet}. + *

+ * For debugging of arrays, use the method {@code java.util.Arrays.asList(Object[])} method for converting the object array to a list before calling this method. + *

+ * @param pCollection the {@code java.util.Collection} to be printed. + * @see {@code java.util.Collection} + */ + public static void printDebug(final Collection pCollection) { + printDebug(pCollection, System.out); + } + + /** + * Invokes a given method of every object in a {@code java.util.Map} and prints the results to a {@code java.io.PrintStream}. + * The method called must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called. + * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pMap the {@code java.util.Map} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each mapped object. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Map} + */ + public static void printDebug(final Map pMap, final String pMethodName, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pMap == null) { + pPrintStream.println(MAP_IS_NULL_ERROR_MESSAGE); + return; + } else if (pMap.isEmpty()) { + pPrintStream.println(MAP_IS_EMPTY_ERROR_MESSAGE); + return; + } + Object mKeyObject; + Object mEntryObject; + + for (Iterator i = pMap.keySet().iterator(); i.hasNext(); ) { + mKeyObject = i.next(); + mEntryObject = pMap.get(mKeyObject); + if ((mKeyObject instanceof String) && (mEntryObject instanceof String)) { + pPrintStream.println((String) mKeyObject + ": " + mEntryObject); + } else if ((mKeyObject instanceof String) && (mEntryObject instanceof List)) { + printDebug((List) mEntryObject, pPrintStream); + } else if ((mKeyObject instanceof String) && (mEntryObject instanceof Set)) { + printDebug((Set) mEntryObject, pPrintStream); + } else if (mKeyObject instanceof String) { + if (!StringUtil.isEmpty(pMethodName)) { + try { + Method objectMethod = mEntryObject.getClass().getMethod(pMethodName, null); + Object retVal = objectMethod.invoke(mEntryObject, null); + + if (retVal != null) { + pPrintStream.println((String) mKeyObject + ": " + retVal.toString()); + } else { // Default execution + throw new Exception(); + } + } catch (Exception e) { + + // Default + pPrintStream.println((String) mKeyObject + ": " + mEntryObject.toString()); + } + } else { // Default + pPrintStream.println((String) mKeyObject + ": " + mEntryObject.toString()); + } + } else if ((mKeyObject instanceof Integer) && (mEntryObject instanceof String)) { + pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject); + } else if ((mKeyObject instanceof Integer) && (mEntryObject instanceof List)) { + printDebug((List) mEntryObject, pPrintStream); + } else if ((mKeyObject instanceof String) && (mEntryObject instanceof Set)) { + printDebug((Set) mEntryObject, pPrintStream); + } else if (mKeyObject instanceof Integer) { + if (!StringUtil.isEmpty(pMethodName)) { + try { + Method objectMethod = mEntryObject.getClass().getMethod(pMethodName, null); + Object retVal = objectMethod.invoke(mEntryObject, null); + + if (retVal != null) { + pPrintStream.println((Integer) mKeyObject + ": " + retVal.toString()); + } else { // Default execution + throw new Exception(); + } + } catch (Exception e) { + + // Default + pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject.toString()); + } + } else { // Default + pPrintStream.println((Integer) mKeyObject + ": " + mEntryObject.toString()); + } + } + + // More.. + //else if + } + } + + /** + * Invokes a given method of every object in a {@code java.util.Map} to {@code System.out}. + * The method called must have no formal parameters. + *

+ * If an exception is throwed during the method invocation, the element's {@code toString()} method is called.
+ * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pMap the {@code java.util.Map} to be printed. + * @param pMethodName a {@code java.lang.String} holding the name of the method to be invoked on each mapped object. + * @see {@code java.util.Map} + */ + public static void printDebug(final Map pMap, final String pMethodName) { + printDebug(pMap, pMethodName, System.out); + } + + /** + * Prints the content of a {@code java.util.Map} to a {@code java.io.PrintStream}. + *

+ * Not all data types are supported so far. The default is calling an element's {@code toString()} method.
+ * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pMap the {@code java.util.Map} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Map} + */ + public static void printDebug(final Map pMap, final PrintStream pPrintStream) { + printDebug(pMap, null, pPrintStream); + } + + /** + * Prints the content of a {@code java.util.Map} to {@code System.out}. + *

+ * Not all data types are supported so far. The default is calling an element's {@code toString()} method.
+ * For bulk data types, recursive invocations and invocations of other methods in this class, are used. + *

+ * @param pMap the {@code java.util.Map} to be printed. + * @see {@code java.util.Map} + */ + public static void printDebug(final Map pMap) { + printDebug(pMap, System.out); + } + + /** + * Prints the content of a {@code java.util.Properties} to a {@code java.io.PrintStream}. + *

+ * @param pProperties the {@code java.util.Properties} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Properties} + */ + public static void printDebug(final Properties pProperties, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pProperties == null) { + pPrintStream.println(PROPERTIES_IS_NULL_ERROR_MESSAGE); + return; + } else if (pProperties.isEmpty()) { + pPrintStream.println(PROPERTIES_IS_EMPTY_ERROR_MESSAGE); + return; + } + for (Enumeration e = pProperties.propertyNames(); e.hasMoreElements(); ) { + String key = (String) e.nextElement(); + + pPrintStream.println(key + ": " + pProperties.getProperty(key)); + } + } + + /** + * Prints the content of a {@code java.util.Properties} to {@code System.out}. + *

+ * @param pProperties the {@code java.util.Properties} to be printed. + * @see {@code java.util.Properties} + */ + public static void printDebug(final Properties pProperties) { + printDebug(pProperties, System.out); + } + + // Timestamp utilities + + /** + * Prints out the calendar time. + *

+ * @param pCalendar the {@code java.util.Calendar} object from which to extract the date information. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Calendar} + */ + public static void printTimestamp(final Calendar pCalendar, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println(getTimestamp(pCalendar)); + } + + /** + * Prints out the system time. + *

+ * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.GregorianCalendar} + */ + public static void printTimestamp(final PrintStream pPrintStream) { + + GregorianCalendar cal = new GregorianCalendar(); + + printTimestamp(cal, pPrintStream); + } + + /** + * Prints out the system time to {@code System.out}. + */ + public static void printTimestamp() { + printTimestamp(System.out); + } + + /** + * Returns a presentation of the date based on the given milliseconds. + *

+ * @param pMilliseconds The specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. + * @return a presentation of the calendar time. + * @see {@code java.util.Calendar} + */ + public static String getTimestamp(final String pMilliseconds) { + return getTimestamp(Long.parseLong(pMilliseconds)); + } + + /** + * Returns a presentation of the date based on the given milliseconds. + *

+ * @param pMilliseconds The specified number of milliseconds since the standard base time known as "the epoch", namely January 1, 1970, 00:00:00 GMT. + * @return a presentation of the calendar time. + * @see {@code java.util.Calendar} + */ + public static String getTimestamp(final long pMilliseconds) { + + java.util.Date date = new java.util.Date(pMilliseconds); + java.util.Calendar calendar = new GregorianCalendar(); + + calendar.setTime(date); + return getTimestamp(calendar); + } + + /** + * Returns a presentation of the given calendar's time. + *

+ * @param pCalendar the {@code java.util.Calendar} object from which to extract the date information. + * @return a presentation of the calendar time. + * @see {@code java.util.Calendar} + */ + public static String getTimestamp(final Calendar pCalendar) { + return buildTimestamp(pCalendar); + } + + /** + * @return a presentation of the system time. + */ + public static String getTimestamp() { + + GregorianCalendar cal = new GregorianCalendar(); + + return getTimestamp(cal); + } + + /** + * Builds a presentation of the given calendar's time. This method contains the common timestamp format used in this class. + * @return a presentation of the calendar time. + */ + protected static String buildTimestamp(final Calendar pCalendar) { + + if (pCalendar == null) { + return CALENDAR_IS_NULL_ERROR_MESSAGE; + } + + // The timestamp format + StringBuilder timestamp = new StringBuilder(); + + //timestamp.append(DateUtil.getMonthName(new Integer(pCalendar.get(Calendar.MONTH)).toString(), "0", "us", "MEDIUM", false) + " "); + timestamp.append(DateFormat.getDateInstance(DateFormat.MEDIUM).format(pCalendar.getTime())); + + //timestamp.append(pCalendar.get(Calendar.DAY_OF_MONTH) + " "); + timestamp.append(" "); + timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.HOUR_OF_DAY)).toString(), 2, "0", true) + ":"); + timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.MINUTE)).toString(), 2, "0", true) + ":"); + timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.SECOND)).toString(), 2, "0", true) + ":"); + timestamp.append(StringUtil.pad(new Integer(pCalendar.get(Calendar.MILLISECOND)).toString(), 3, "0", true)); + return timestamp.toString(); + } + + /** + * Builds the time difference between two millisecond representations. + *

+ * This method is to be used with small time intervals between 0 ms up to a couple of minutes. + *

+ * @param pStartTime the start time. + * @param pEndTime the end time. + * @return the time difference in milliseconds. + */ + public static String buildTimeDifference(final long pStartTime, final long pEndTime) { + + //return pEndTime - pStartTime; + StringBuilder retVal = new StringBuilder(); + + // The time difference in milliseconds + long timeDifference = pEndTime - pStartTime; + + if (timeDifference < 1000) { + retVal.append(timeDifference); + retVal.append(" ms"); + } else { + long seconds = timeDifference / 1000; + + timeDifference = timeDifference % 1000; + retVal.append(seconds); + retVal.append("s "); + retVal.append(timeDifference); + retVal.append("ms"); + } + + //return retVal.toString() + " (original timeDifference: " + new String(new Long(pEndTime - pStartTime).toString()) + ")"; + return retVal.toString(); + } + + /** + * Builds the time difference between the given time and present time. + *

+ * This method is to be used with small time intervals between 0 ms up to a couple of minutes. + *

+ * @param pStartTime the start time. + * @return the time difference in milliseconds. + */ + public static String buildTimeDifference(final long pStartTime) { + + long presentTime = System.currentTimeMillis(); + + return buildTimeDifference(pStartTime, presentTime); + } + + /** + * Prints out the difference between two millisecond representations. + * The start time is subtracted from the end time. + *

+ * @param pStartTime the start time. + * @param pEndTime the end time. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printTimeDifference(final long pStartTime, final long pEndTime, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println(buildTimeDifference(pStartTime, pEndTime)); + } + + /** + * Prints out the difference between two millisecond representations. + * The start time is subtracted from the end time. + *

+ * @param pStartTime the start time. + * @param pEndTime the end time. + */ + public static void printTimeDifference(final long pStartTime, final long pEndTime) { + printTimeDifference(pStartTime, pEndTime, System.out); + } + + /** + * Prints out the difference between the given time and present time. + * The start time is subtracted from the present time. + *

+ * @param pStartTime the start time. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printTimeDifference(final long pStartTime, final PrintStream pPrintStream) { + printTimeDifference(pStartTime, System.currentTimeMillis(), pPrintStream); + } + + /** + * Prints out the difference between the given time and present time to {@code System.out}. + * The start time is subtracted from the present time. + *

+ * usage: + *

+   * long startTime = System.currentTimeMillis();
+   * ...
+   * com.iml.oslo.eito.util.DebugUtil.printTimeDifference(startTime);
+   * 
+ *

+ * @param pStartTime the start time. + */ + public static void printTimeDifference(final long pStartTime) { + printTimeDifference(pStartTime, System.out); + } + + /** + * Builds a string representing the difference between two calendar times. + * The first calendar object is subtracted from the second one. + *

+ * This method is to be used with time intervals between 0 ms up to several hours. + *

+ * @param pStartCalendar the first {@code java.util.Calendar}. + * @param pEndCalendar the second {@code java.util.Calendar}. + * @return a string representation of the time difference. + * @see {@code java.util.Calendar} + */ + public static String buildTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { + + if (pStartCalendar == null) { + return CALENDAR_IS_NULL_ERROR_MESSAGE; + } + if (pEndCalendar == null) { + return CALENDAR_IS_NULL_ERROR_MESSAGE; + } + if (pEndCalendar.before(pStartCalendar)) { + return CALENDAR_CAUSATION_ERROR_MESSAGE; + } + int dateDiff = pEndCalendar.get(Calendar.DATE) - pStartCalendar.get(Calendar.DATE); + int hourDiff = pEndCalendar.get(Calendar.HOUR_OF_DAY) - pStartCalendar.get(Calendar.HOUR_OF_DAY); + int minuteDiff = pEndCalendar.get(Calendar.MINUTE) - pStartCalendar.get(Calendar.MINUTE); + int secondDiff = pEndCalendar.get(Calendar.SECOND) - pStartCalendar.get(Calendar.SECOND); + int milliSecondDiff = pEndCalendar.get(Calendar.MILLISECOND) - pStartCalendar.get(Calendar.MILLISECOND); + + if (milliSecondDiff < 0) { + secondDiff--; + milliSecondDiff += 1000; + } + if (secondDiff < 0) { + minuteDiff--; + secondDiff += 60; + } + if (minuteDiff < 0) { + hourDiff--; + minuteDiff += 60; + } + while (dateDiff > 0) { + dateDiff--; + hourDiff += 24; + } + + // Time difference presentation format + StringBuilder buffer = new StringBuilder(); + + if ((hourDiff == 0) && (minuteDiff == 0) && (secondDiff == 0)) { + buffer.append(milliSecondDiff); + buffer.append("ms"); + } else if ((hourDiff == 0) && (minuteDiff == 0)) { + buffer.append(secondDiff); + buffer.append("s "); + buffer.append(milliSecondDiff); + buffer.append("ms"); + } else if (hourDiff == 0) { + buffer.append(minuteDiff); + buffer.append("m "); + buffer.append(secondDiff); + buffer.append(","); + buffer.append(milliSecondDiff); + buffer.append("s"); + } else { + buffer.append(hourDiff); + buffer.append("h "); + buffer.append(minuteDiff); + buffer.append("m "); + buffer.append(secondDiff); + buffer.append(","); + buffer.append(milliSecondDiff); + buffer.append("s"); + } + return buffer.toString(); + } + + /** + * Prints out the difference between to calendar times. + * The first calendar object is subtracted from the second one. + *

+ * @param pStartCalendar the first {@code java.util.Calendar}. + * @param pEndCalendar the second {@code java.util.Calendar}. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Calendar} + */ + public static void printTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println(buildTimeDifference(pStartCalendar, pEndCalendar)); + } + + /** + * Prints out the difference between to calendar times two {@code System.out}. + * The first calendar object is subtracted from the second one. + *

+ * @param pStartCalendar the first {@code java.util.Calendar}. + * @param pEndCalendar the second {@code java.util.Calendar}. + * @see {@code java.util.Calendar} + */ + public static void printTimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { + printTimeDifference(pStartCalendar, pEndCalendar, System.out); + } + + /** + * Prints out the difference between the given calendar time and present time. + *

+ * @param pStartCalendar the {@code java.util.Calendar} to compare with present time. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.util.Calendar} + */ + public static void printTimeDifference(final Calendar pStartCalendar, final PrintStream pPrintStream) { + + GregorianCalendar endCalendar = new GregorianCalendar(); + + printTimeDifference(pStartCalendar, endCalendar, pPrintStream); + } + + /** + * Prints out the difference between the given calendar time and present time to {@code System.out}. + *

+ * usage: + *

+   * GregorianCalendar startTime = new GregorianCalendar();
+   * ...
+   * com.iml.oslo.eito.util.DebugUtil.printTimeDifference(startTime);
+   * 
+ *

+ * @param pStartCalendar the {@code java.util.Calendar} to compare with present time. + * @see {@code java.util.Calendar} + */ + public static void printTimeDifference(final Calendar pStartCalendar) { + + GregorianCalendar endCalendar = new GregorianCalendar(); + + printTimeDifference(pStartCalendar, endCalendar); + } + + /** + * Prints out a {@code com.iml.oslo.eito.util.DebugUtil.TimeDifference} object. + *

+ * @param pTimeDifference the {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} to investigate. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printTimeDifference(final TimeDifference pTimeDifference, final PrintStream pPrintStream) { + printTimeDifference(pTimeDifference.getStartCalendar(), pTimeDifference.getEndCalendar(), pPrintStream); + } + + /** + * Prints out a {@code com.iml.oslo.eito.util.DebugUtil.TimeDifference} object to {@code System.out}. + *

+ * @param pTimeDifference the {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} to investigate. + */ + public static void printTimeDifference(final TimeDifference pTimeDifference) { + printTimeDifference(pTimeDifference.getStartCalendar(), pTimeDifference.getEndCalendar(), System.out); + } + + /** + * A convenience class for embracing two {@code java.util.Calendar} objects. + * The class is used for building a collection of time differences according to the {@code printTimeAverage} method. + */ + public static class TimeDifference { + + Calendar mStartCalendar; + Calendar mEndCalendar; + + /** + * Constructor TimeDifference + * + * + */ + public TimeDifference() {} + + /** + * Constructor TimeDifference + * + * + * @param pStartCalendar + * @param pEndCalendar + * + */ + public TimeDifference(final Calendar pStartCalendar, final Calendar pEndCalendar) { + this.mStartCalendar = pStartCalendar; + this.mEndCalendar = pEndCalendar; + } + + /** + * Method setStartCalendar + * + * + * @param pStartCalendar + * + */ + public void setStartCalendar(Calendar pStartCalendar) { + this.mStartCalendar = pStartCalendar; + } + + /** + * Method getStartCalendar + * + * + * @return + * + */ + public Calendar getStartCalendar() { + return this.mStartCalendar; + } + + /** + * Method setEndCalendar + * + * + * @param pEndCalendar + * + */ + public void setEndCalendar(Calendar pEndCalendar) { + this.mEndCalendar = pEndCalendar; + } + + /** + * Method getEndCalendar + * + * + * @return + * + */ + public Calendar getEndCalendar() { + return this.mEndCalendar; + } + } + + /** + * Prints out the average time difference from a collection of {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} objects. + *

+ * + * @param pTimeDifferences + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + */ + public static void printTimeAverage(final Collection pTimeDifferences, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + if (pTimeDifferences == null) { + pPrintStream.println(TIMEDIFFERENCES_IS_NULL_ERROR_MESSAGE); + return; + } + Object o; + TimeDifference timeDifference; + Calendar startCalendar = null; + Calendar endCalendar = null; + Calendar totalStartCalendar = null; + Calendar totalEndCalendar = null; + long startCalendarMilliSeconds, endCalendarMilliSeconds; + List timeDifferenceList = new Vector(); + Iterator i = pTimeDifferences.iterator(); + + if (i.hasNext()) { + o = i.next(); + if (!(o instanceof TimeDifference)) { + pPrintStream.println(TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE); + return; + } + timeDifference = (TimeDifference) o; + startCalendar = timeDifference.getStartCalendar(); + totalStartCalendar = startCalendar; + endCalendar = timeDifference.getEndCalendar(); + startCalendarMilliSeconds = startCalendar.getTime().getTime(); + endCalendarMilliSeconds = endCalendar.getTime().getTime(); + timeDifferenceList.add(new Long(endCalendarMilliSeconds - startCalendarMilliSeconds)); + } + while (i.hasNext()) { + o = i.next(); + if (!(o instanceof TimeDifference)) { + pPrintStream.println(TIMEDIFFERENCES_WRONG_DATATYPE_ERROR_MESSAGE); + return; + } + timeDifference = (TimeDifference) o; + startCalendar = timeDifference.getStartCalendar(); + endCalendar = timeDifference.getEndCalendar(); + startCalendarMilliSeconds = startCalendar.getTime().getTime(); + endCalendarMilliSeconds = endCalendar.getTime().getTime(); + timeDifferenceList.add(new Long(endCalendarMilliSeconds - startCalendarMilliSeconds)); + } + totalEndCalendar = endCalendar; + int numberOfElements = timeDifferenceList.size(); + long timeDifferenceElement; + long timeDifferenceSum = 0; + + for (Iterator i2 = timeDifferenceList.iterator(); i2.hasNext(); ) { + timeDifferenceElement = ((Long) i2.next()).longValue(); + timeDifferenceSum += timeDifferenceElement; + } + + // Total elapsed time + String totalElapsedTime = buildTimeDifference(totalStartCalendar, totalEndCalendar); + + // Time average presentation format + pPrintStream.println("Average time difference: " + timeDifferenceSum / numberOfElements + "ms (" + numberOfElements + + " elements, total elapsed time: " + totalElapsedTime + ")"); + } + + /** + * Prints out the average time difference from a collection of {@code com.twelvemonkeys.util.DebugUtil.TimeDifference} objects to {@code System.out}. + *

+ * + * @param pTimeDifferences + */ + public static void printTimeAverage(final Collection pTimeDifferences) { + printTimeAverage(pTimeDifferences, System.out); + } + + // Reflective methods + + /** + * Prints the top-wrapped class name of a {@code java.lang.Object} to a {@code java.io.PrintStream}. + *

+ * @param pObject the {@code java.lang.Object} to be printed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.lang.Class} + */ + public static void printClassName(final Object pObject, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println(getClassName(pObject)); + } + + /** + * Prints the top-wrapped class name of a {@code java.lang.Object} to {@code System.out}. + *

+ * @param pObject the {@code java.lang.Object} to be printed. + * @see {@code java.lang.Class} + */ + public static void printClassName(final Object pObject) { + printClassName(pObject, System.out); + } + + /** + * Builds the top-wrapped class name of a {@code java.lang.Object}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @return the object's class name. + * @see {@code java.lang.Class} + */ + public static String getClassName(final Object pObject) { + + if (pObject == null) { + return OBJECT_IS_NULL_ERROR_MESSAGE; + } + return pObject.getClass().getName(); + } + + /** + * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to a {@code java.io.PrintStream}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @param pObjectName the name of the object instance, for identification purposes. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static void printClassDetails(final Object pObject, final String pObjectName, final PrintStream pPrintStream) { + + if (pPrintStream == null) { + System.err.println(PRINTSTREAM_IS_NULL_ERROR_MESSAGE); + return; + } + pPrintStream.println(getClassDetails(pObject, pObjectName)); + } + + /** + * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to {@code System.out}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @param pObjectName the name of the object instance, for identification purposes. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static void printClassDetails(final Object pObject, final String pObjectName) { + printClassDetails(pObject, pObjectName, System.out); + } + + /** + * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to {@code System.out}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static void printClassDetails(final Object pObject) { + printClassDetails(pObject, null, System.out); + } + + /** + * Prints javadoc-like, the top wrapped class fields and methods of a {@code java.lang.Object} to a {@code java.io.PrintStream}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the results. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static void printClassDetails(final Object pObject, final PrintStream pPrintStream) { + printClassDetails(pObject, null, pPrintStream); + } + + /** + * Builds a javadoc-like presentation of the top wrapped class fields and methods of a {@code java.lang.Object}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @return a listing of the object's class details. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static String getClassDetails(final Object pObject) { + return getClassDetails(pObject, null); + } + + /** + * Builds a javadoc-like presentation of the top wrapped class fields and methods of a {@code java.lang.Object}. + *

+ * @param pObject the {@code java.lang.Object} to be analysed. + * @param pObjectName the name of the object instance, for identification purposes. + * @return a listing of the object's class details. + * @see {@code java.lang.Class} + * @see {@code java.lang.reflect.Modifier} + * @see {@code java.lang.reflect.Field} + * @see {@code java.lang.reflect.Constructor} + * @see {@code java.lang.reflect.Method} + */ + public static String getClassDetails(final Object pObject, final String pObjectName) { + + if (pObject == null) { + return OBJECT_IS_NULL_ERROR_MESSAGE; + } + final String endOfLine = System.getProperty("line.separator"); + final String dividerLine = "---------------------------------------------------------"; + Class c = pObject.getClass(); + StringTokenizer tokenizedString; + String str; + String className = new String(); + String superClassName = new String(); + StringBuilder buffer = new StringBuilder(); + + // Heading + buffer.append(endOfLine); + buffer.append("**** class details"); + if (!StringUtil.isEmpty(pObjectName)) { + buffer.append(" for \"" + pObjectName + "\""); + } + buffer.append(" ****"); + buffer.append(endOfLine); + + // Package + Package p = c.getPackage(); + + if (p != null) { + buffer.append(p.getName()); + } + buffer.append(endOfLine); + + // Class or Interface + if (c.isInterface()) { + buffer.append("I n t e r f a c e "); + } else { + buffer.append("C l a s s "); + } + str = c.getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + className = tokenizedString.nextToken().trim(); + } + str = new String(); + char[] charArray = className.toCharArray(); + + for (int i = 0; i < charArray.length; i++) { + str += charArray[i] + " "; + } + buffer.append(str); + buffer.append(endOfLine); + buffer.append(endOfLine); + + // Class Hierarch + List classNameList = new Vector(); + + classNameList.add(c.getName()); + Class superclass = c.getSuperclass(); + + while (superclass != null) { + classNameList.add(superclass.getName()); + superclass = superclass.getSuperclass(); + } + Object[] classNameArray = classNameList.toArray(); + int counter = 0; + + for (int i = classNameArray.length - 1; i >= 0; i--) { + for (int j = 0; j < counter; j++) { + buffer.append(" "); + } + if (counter > 0) { + buffer.append("|"); + buffer.append(endOfLine); + } + for (int j = 0; j < counter; j++) { + buffer.append(" "); + } + if (counter > 0) { + buffer.append("+-"); + } + buffer.append((String) classNameArray[i]); + buffer.append(endOfLine); + counter++; + } + + // Divider + buffer.append(endOfLine); + buffer.append(dividerLine); + buffer.append(endOfLine); + buffer.append(endOfLine); + + // Profile + int classModifier = c.getModifiers(); + + buffer.append(Modifier.toString(classModifier) + " "); + if (c.isInterface()) { + buffer.append("Interface "); + } else { + buffer.append("Class "); + } + buffer.append(className); + buffer.append(endOfLine); + if ((classNameArray != null) && (classNameArray[classNameArray.length - 2] != null)) { + str = (String) classNameArray[classNameArray.length - 2]; + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + superClassName = tokenizedString.nextToken().trim(); + } + buffer.append("extends " + superClassName); + buffer.append(endOfLine); + } + if (!c.isInterface()) { + Class[] interfaces = c.getInterfaces(); + + if ((interfaces != null) && (interfaces.length > 0)) { + buffer.append("implements "); + str = interfaces[0].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(str); + for (int i = 1; i < interfaces.length; i++) { + str = interfaces[i].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(", " + str); + } + buffer.append(endOfLine); + } + } + + // Divider + buffer.append(endOfLine); + buffer.append(dividerLine); + buffer.append(endOfLine); + buffer.append(endOfLine); + + // Fields + buffer.append("F I E L D S U M M A R Y"); + buffer.append(endOfLine); + Field[] fields = c.getFields(); + + if (fields != null) { + for (int i = 0; i < fields.length; i++) { + buffer.append(Modifier.toString(fields[i].getType().getModifiers()) + " "); + str = fields[i].getType().getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(str + " "); + buffer.append(fields[i].getName()); + buffer.append(endOfLine); + } + } + buffer.append(endOfLine); + + // Constructors + buffer.append("C O N S T R U C T O R S U M M A R Y"); + buffer.append(endOfLine); + Constructor[] constructors = c.getConstructors(); + + if (constructors != null) { + for (int i = 0; i < constructors.length; i++) { + buffer.append(className + "("); + Class[] parameterTypes = constructors[i].getParameterTypes(); + + if (parameterTypes != null) { + if (parameterTypes.length > 0) { + str = parameterTypes[0].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(str); + for (int j = 1; j < parameterTypes.length; j++) { + str = parameterTypes[j].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(", " + str); + } + } + } + buffer.append(")"); + buffer.append(endOfLine); + } + } + buffer.append(endOfLine); + + // Methods + buffer.append("M E T H O D S U M M A R Y"); + buffer.append(endOfLine); + Method[] methods = c.getMethods(); + + if (methods != null) { + for (int i = 0; i < methods.length; i++) { + buffer.append(Modifier.toString(methods[i].getModifiers()) + " "); + str = methods[i].getReturnType().getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(str + " "); + buffer.append(methods[i].getName() + "("); + Class[] parameterTypes = methods[i].getParameterTypes(); + + if ((parameterTypes != null) && (parameterTypes.length > 0)) { + if (parameterTypes[0] != null) { + str = parameterTypes[0].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + + // array bugfix + if (str.charAt(str.length() - 1) == ';') { + str = str.substring(0, str.length() - 1) + "[]"; + } + buffer.append(str); + for (int j = 1; j < parameterTypes.length; j++) { + str = parameterTypes[j].getName(); + tokenizedString = new StringTokenizer(str, "."); + while (tokenizedString.hasMoreTokens()) { + str = tokenizedString.nextToken().trim(); + } + buffer.append(", " + str); + } + } + } + buffer.append(")"); + buffer.append(endOfLine); + } + } + buffer.append(endOfLine); + + // Ending + buffer.append("**** class details"); + if (!StringUtil.isEmpty(pObjectName)) { + buffer.append(" for \"" + pObjectName + "\""); + } + buffer.append(" end ****"); + buffer.append(endOfLine); + return buffer.toString(); + } + + /** + * Prettyprints a large number. + *

+ * + * @param pBigNumber + * @return prettyprinted number with dot-separation each 10e3. + */ + public static String getLargeNumber(final long pBigNumber) { + + StringBuilder buffer = new StringBuilder(new Long(pBigNumber).toString()); + char[] number = new Long(pBigNumber).toString().toCharArray(); + int reverseIndex = 0; + + for (int i = number.length; i >= 0; i--) { + reverseIndex++; + if ((reverseIndex % 3 == 0) && (i > 1)) { + buffer = buffer.insert(i - 1, '.'); + } + } + return buffer.toString(); + } + + /** + * Prettyprints milliseconds to ?day(s) ?h ?m ?s ?ms. + *

+ * + * @param pMilliseconds + * @return prettyprinted time duration. + */ + public static String getTimeInterval(final long pMilliseconds) { + + long timeIntervalMilliseconds = pMilliseconds; + long timeIntervalSeconds = 0; + long timeIntervalMinutes = 0; + long timeIntervalHours = 0; + long timeIntervalDays = 0; + boolean printMilliseconds = true; + boolean printSeconds = false; + boolean printMinutes = false; + boolean printHours = false; + boolean printDays = false; + final long MILLISECONDS_IN_SECOND = 1000; + final long MILLISECONDS_IN_MINUTE = 60 * MILLISECONDS_IN_SECOND; // 60000 + final long MILLISECONDS_IN_HOUR = 60 * MILLISECONDS_IN_MINUTE; // 3600000 + final long MILLISECONDS_IN_DAY = 24 * MILLISECONDS_IN_HOUR; // 86400000 + StringBuilder timeIntervalBuffer = new StringBuilder(); + + // Days + if (timeIntervalMilliseconds >= MILLISECONDS_IN_DAY) { + timeIntervalDays = timeIntervalMilliseconds / MILLISECONDS_IN_DAY; + timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_DAY; + printDays = true; + printHours = true; + printMinutes = true; + printSeconds = true; + } + + // Hours + if (timeIntervalMilliseconds >= MILLISECONDS_IN_HOUR) { + timeIntervalHours = timeIntervalMilliseconds / MILLISECONDS_IN_HOUR; + timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_HOUR; + printHours = true; + printMinutes = true; + printSeconds = true; + } + + // Minutes + if (timeIntervalMilliseconds >= MILLISECONDS_IN_MINUTE) { + timeIntervalMinutes = timeIntervalMilliseconds / MILLISECONDS_IN_MINUTE; + timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_MINUTE; + printMinutes = true; + printSeconds = true; + } + + // Seconds + if (timeIntervalMilliseconds >= MILLISECONDS_IN_SECOND) { + timeIntervalSeconds = timeIntervalMilliseconds / MILLISECONDS_IN_SECOND; + timeIntervalMilliseconds = timeIntervalMilliseconds % MILLISECONDS_IN_SECOND; + printSeconds = true; + } + + // Prettyprint + if (printDays) { + timeIntervalBuffer.append(timeIntervalDays); + if (timeIntervalDays > 1) { + timeIntervalBuffer.append("days "); + } else { + timeIntervalBuffer.append("day "); + } + } + if (printHours) { + timeIntervalBuffer.append(timeIntervalHours); + timeIntervalBuffer.append("h "); + } + if (printMinutes) { + timeIntervalBuffer.append(timeIntervalMinutes); + timeIntervalBuffer.append("m "); + } + if (printSeconds) { + timeIntervalBuffer.append(timeIntervalSeconds); + timeIntervalBuffer.append("s "); + } + if (printMilliseconds) { + timeIntervalBuffer.append(timeIntervalMilliseconds); + timeIntervalBuffer.append("ms"); + } + return timeIntervalBuffer.toString(); + } +} + + +/*--- 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/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java b/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java index 9a1c1b9d..d2bbb8de 100755 --- a/twelvemonkeys-sandbox/src/main/java/com/twelvemonkeys/util/regex/REWildcardStringParser.java +++ b/twelvemonkeys-sandbox/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', 'æ', - 'ø', 'å', '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', 'Æ', 'Ø', 'Å', '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', 'æ', + 'ø', 'å', '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', 'Æ', 'Ø', 'Å', '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/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java b/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java index d2e501ff..a1666ff4 100644 --- a/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java +++ b/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/DeflateEncoderTestCase.java @@ -1,18 +1,18 @@ -package com.twelvemonkeys.io.enc; - -/** - * DeflateEncoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/DeflateDecoderTestCase.java#1 $ - */ -public class DeflateEncoderTestCase extends EncoderAbstractTestCase { - protected Encoder createEncoder() { - return new DeflateEncoder(); - } - - protected Decoder createCompatibleDecoder() { - return new InflateDecoder(); - } -} +package com.twelvemonkeys.io.enc; + +/** + * DeflateEncoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/DeflateDecoderTestCase.java#1 $ + */ +public class DeflateEncoderTestCase extends EncoderAbstractTestCase { + protected Encoder createEncoder() { + return new DeflateEncoder(); + } + + protected Decoder createCompatibleDecoder() { + return new InflateDecoder(); + } +} diff --git a/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java b/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java index 70a6ad08..fcde90b3 100644 --- a/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java +++ b/twelvemonkeys-sandbox/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java @@ -1,18 +1,18 @@ -package com.twelvemonkeys.io.enc; - -/** - * InflateEncoderTest - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java#1 $ - */ -public class InflateDecoderTestCase extends DecoderAbstractTestCase { - public Decoder createDecoder() { - return new InflateDecoder(); - } - - public Encoder createCompatibleEncoder() { - return new DeflateEncoder(); - } -} +package com.twelvemonkeys.io.enc; + +/** + * InflateEncoderTest + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/test/java/com/twelvemonkeys/io/enc/InflateDecoderTestCase.java#1 $ + */ +public class InflateDecoderTestCase extends DecoderAbstractTestCase { + public Decoder createDecoder() { + return new InflateDecoder(); + } + + public Encoder createCompatibleEncoder() { + return new DeflateEncoder(); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java index 015e84e8..1796a5ef 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java#1 $ - */ -public class DebugServlet extends GenericServlet { - private long mDateModified; - - public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException { - service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse); - } - - public void init() throws ServletException { - super.init(); - mDateModified = 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", mDateModified); - 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/DebugServlet.java#1 $ + */ +public class DebugServlet extends GenericServlet { + private long mDateModified; + + public final void service(ServletRequest pRequest, ServletResponse pResponse) throws ServletException, IOException { + service((HttpServletRequest) pRequest, (HttpServletResponse) pResponse); + } + + public void init() throws ServletException { + super.init(); + mDateModified = 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", mDateModified); + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java index 0a98a8f3..64b314c5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java @@ -1,383 +1,383 @@ -/* - * 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 doFilterImpl} method. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java#1 $ - * - * @see Filter - * @see FilterConfig - */ -public abstract class GenericFilter implements Filter, FilterConfig, Serializable { - - /** - * The filter config. - */ - private transient FilterConfig mFilterConfig = null; - - /** - * Makes sure the filter runs once per request - *

- * see #isRunOnce - * - * @see #mOncePerRequest - * see #ATTRIB_RUN_ONCE_VALUE - */ - 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 FitlerConfig object. - *

- * see #isRunOnce - * - * @see #mOncePerRequest - * see #ATTRIB_RUN_ONCE_VALUE - */ - private String mAttribRunOnce = null; - - /** - * Makes sure the filter runs once per request - *

- * see #isRunOnce - * - * @see #mOncePerRequest - * see #ATTRIB_RUN_ONCE_EXT - */ - 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 mOncePerRequest = 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 - * @see #init() init - * @see BeanUtil#configure(Object, java.util.Map, boolean) - */ - public void init(FilterConfig pConfig) throws ServletException { - if (pConfig == null) { - throw new ServletConfigException("filterconfig == null"); - } - - // Store filterconfig - mFilterConfig = 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 - mAttribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT; - log("init (oncePerRequest=" + mOncePerRequest + ", attribRunOnce=" + mAttribRunOnce + ")"); - 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 doFilterImpl} method. - * - * @param pRequest the servlet request - * @param pResponse the servlet response - * @param pFilterChain the filter chain - * - * @throws IOException - * @throws ServletException - * - * @see Filter#doFilter Filter.doFilter - * @see #doFilterImpl doFilterImpl - */ - public final void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException { - // If request filter and allready run, continue chain and return fast - if (mOncePerRequest && 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 contine. - *

- * 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 allready filtered, otherwise - * {@code false}. - */ - private boolean isRunOnce(ServletRequest pRequest) { - // If request allready filtered, return true (skip) - if (pRequest.getAttribute(mAttribRunOnce) == ATTRIB_RUN_ONCE_VALUE) { - return true; - } - - // Set attribute and return false (continue) - pRequest.setAttribute(mAttribRunOnce, ATTRIB_RUN_ONCE_VALUE); - return false; - } - - /** - * Invoked once, or each time a request/response pair is passed through the - * chain, depending on the {@link #mOncePerRequest} 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 #mOncePerRequest - * @see #doFilter doFilter - * @see Filter#doFilter 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"); - mFilterConfig = 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 mFilterConfig.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() { - // TODO: Create a servlet context wrapper that lets you log to a log4j appender? - return mFilterConfig.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(String pKey) { - return mFilterConfig.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 mFilterConfig.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(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(String pMessage, 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. - */ - public void setFilterConfig(FilterConfig pFilterConfig) { - try { - init(pFilterConfig); - } - catch (ServletException e) { - log("Error in init(), see stacktrace for details.", e); - } - } - - /** - * Gets the {@code FilterConfig} for this filter. - * - * @return the {@code FilterConfig} for this filter - * @see FilterConfig - */ - public FilterConfig getFilterConfig() { - return mFilterConfig; - } - - /** - * 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 #mOncePerRequest - */ - @InitParam - public void setOncePerRequest(boolean pOncePerRequest) { - mOncePerRequest = 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 doFilterImpl} method. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericFilter.java#1 $ + * + * @see Filter + * @see FilterConfig + */ +public abstract class GenericFilter implements Filter, FilterConfig, Serializable { + + /** + * The filter config. + */ + private transient FilterConfig mFilterConfig = null; + + /** + * Makes sure the filter runs once per request + *

+ * see #isRunOnce + * + * @see #mOncePerRequest + * see #ATTRIB_RUN_ONCE_VALUE + */ + 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 FitlerConfig object. + *

+ * see #isRunOnce + * + * @see #mOncePerRequest + * see #ATTRIB_RUN_ONCE_VALUE + */ + private String mAttribRunOnce = null; + + /** + * Makes sure the filter runs once per request + *

+ * see #isRunOnce + * + * @see #mOncePerRequest + * see #ATTRIB_RUN_ONCE_EXT + */ + 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 mOncePerRequest = 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 + * @see #init() init + * @see BeanUtil#configure(Object, java.util.Map, boolean) + */ + public void init(FilterConfig pConfig) throws ServletException { + if (pConfig == null) { + throw new ServletConfigException("filterconfig == null"); + } + + // Store filterconfig + mFilterConfig = 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 + mAttribRunOnce = pConfig.getFilterName() + ATTRIB_RUN_ONCE_EXT; + log("init (oncePerRequest=" + mOncePerRequest + ", attribRunOnce=" + mAttribRunOnce + ")"); + 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 doFilterImpl} method. + * + * @param pRequest the servlet request + * @param pResponse the servlet response + * @param pFilterChain the filter chain + * + * @throws IOException + * @throws ServletException + * + * @see Filter#doFilter Filter.doFilter + * @see #doFilterImpl doFilterImpl + */ + public final void doFilter(ServletRequest pRequest, ServletResponse pResponse, FilterChain pFilterChain) throws IOException, ServletException { + // If request filter and allready run, continue chain and return fast + if (mOncePerRequest && 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 contine. + *

+ * 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 allready filtered, otherwise + * {@code false}. + */ + private boolean isRunOnce(ServletRequest pRequest) { + // If request allready filtered, return true (skip) + if (pRequest.getAttribute(mAttribRunOnce) == ATTRIB_RUN_ONCE_VALUE) { + return true; + } + + // Set attribute and return false (continue) + pRequest.setAttribute(mAttribRunOnce, ATTRIB_RUN_ONCE_VALUE); + return false; + } + + /** + * Invoked once, or each time a request/response pair is passed through the + * chain, depending on the {@link #mOncePerRequest} 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 #mOncePerRequest + * @see #doFilter doFilter + * @see Filter#doFilter 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"); + mFilterConfig = 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 mFilterConfig.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() { + // TODO: Create a servlet context wrapper that lets you log to a log4j appender? + return mFilterConfig.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(String pKey) { + return mFilterConfig.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 mFilterConfig.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(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(String pMessage, 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. + */ + public void setFilterConfig(FilterConfig pFilterConfig) { + try { + init(pFilterConfig); + } + catch (ServletException e) { + log("Error in init(), see stacktrace for details.", e); + } + } + + /** + * Gets the {@code FilterConfig} for this filter. + * + * @return the {@code FilterConfig} for this filter + * @see FilterConfig + */ + public FilterConfig getFilterConfig() { + return mFilterConfig; + } + + /** + * 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 #mOncePerRequest + */ + @InitParam + public void setOncePerRequest(boolean pOncePerRequest) { + mOncePerRequest = pOncePerRequest; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java index 6a120cc6..60603c2e 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java @@ -1,87 +1,87 @@ -/* - * 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 paramter - * naming is supported, lisp-style names will be converted to camelCase. - * Parameter values are automatically converted from string represenation to - * most basic types, if neccessary. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java#1 $ - */ -public abstract class GenericServlet extends javax.servlet.GenericServlet { - - /** - * 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(ServletConfig pConfig) throws ServletException { - if (pConfig == null) { - throw new ServletConfigException("servletconfig == 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 paramter + * naming is supported, lisp-style names will be converted to camelCase. + * Parameter values are automatically converted from string represenation to + * most basic types, if neccessary. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/GenericServlet.java#1 $ + */ +public abstract class GenericServlet extends javax.servlet.GenericServlet { + + /** + * 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(ServletConfig pConfig) throws ServletException { + if (pConfig == null) { + throw new ServletConfigException("servletconfig == 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java index 2b546317..f4307131 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java @@ -1,87 +1,87 @@ -/* - * 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 paramter - * naming is supported, lisp-style names will be converted to camelCase. - * Parameter values are automatically converted from string represenation to - * most basic types, if neccessary. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java#1 $ - */ -public abstract class HttpServlet extends javax.servlet.http.HttpServlet { - - /** - * 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 ouccured 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("servletconfig == 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 paramter + * naming is supported, lisp-style names will be converted to camelCase. + * Parameter values are automatically converted from string represenation to + * most basic types, if neccessary. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/HttpServlet.java#1 $ + */ +public abstract class HttpServlet extends javax.servlet.http.HttpServlet { + + /** + * 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 ouccured 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("servletconfig == 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java index 046b5fc6..5dc3e325 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java @@ -1,122 +1,122 @@ -/* - * 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java#1 $ - * - */ -public class OutputStreamAdapter extends ServletOutputStream { - - /** The wrapped {@code OutputStream}. */ - protected final OutputStream mOut; - - /** - * Creates an {@code OutputStreamAdapter}. - * - * @param pOut the wrapped {@code OutputStream} - * - * @throws IllegalArgumentException if {@code pOut} is {@code null}. - */ - public OutputStreamAdapter(OutputStream pOut) { - if (pOut == null) { - throw new IllegalArgumentException("out == null"); - } - mOut = pOut; - } - - /** - * Returns the wrapped {@code OutputStream}. - * - * @return the wrapped {@code OutputStream}. - */ - public OutputStream getOutputStream() { - return mOut; - } - - public String toString() { - return "ServletOutputStream adapted from " + mOut.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(int pByte) - throws IOException { - mOut.write(pByte); - } - - // Overide for efficiency - public void write(byte pBytes[]) - throws IOException { - mOut.write(pBytes); - } - - // Overide for efficiency - public void write(byte pBytes[], int pOff, int pLen) - throws IOException { - mOut.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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/OutputStreamAdapter.java#1 $ + * + */ +public class OutputStreamAdapter extends ServletOutputStream { + + /** The wrapped {@code OutputStream}. */ + protected final OutputStream mOut; + + /** + * Creates an {@code OutputStreamAdapter}. + * + * @param pOut the wrapped {@code OutputStream} + * + * @throws IllegalArgumentException if {@code pOut} is {@code null}. + */ + public OutputStreamAdapter(OutputStream pOut) { + if (pOut == null) { + throw new IllegalArgumentException("out == null"); + } + mOut = pOut; + } + + /** + * Returns the wrapped {@code OutputStream}. + * + * @return the wrapped {@code OutputStream}. + */ + public OutputStream getOutputStream() { + return mOut; + } + + public String toString() { + return "ServletOutputStream adapted from " + mOut.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(int pByte) + throws IOException { + mOut.write(pByte); + } + + // Overide for efficiency + public void write(byte pBytes[]) + throws IOException { + mOut.write(pBytes); + } + + // Overide for efficiency + public void write(byte pBytes[], int pOff, int pLen) + throws IOException { + mOut.write(pBytes, pOff, pLen); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java index 898e7222..6f47968e 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java#1 $ - */ -public class ProxyServlet extends GenericServlet { - - /** Remote server host name or IP address */ - protected String mRemoteServer = null; - /** Remote server port */ - protected int mRemotePort = 80; - /** Remote server "mount" path */ - protected String mRemotePath = ""; - - 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) { - mRemoteServer = 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 { - mRemotePort = 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; - } - - mRemotePath = 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 (mRemoteServer == 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(), mRemoteServer, mRemotePort, 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(mRemotePath); - 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ProxyServlet.java#1 $ + */ +public class ProxyServlet extends GenericServlet { + + /** Remote server host name or IP address */ + protected String mRemoteServer = null; + /** Remote server port */ + protected int mRemotePort = 80; + /** Remote server "mount" path */ + protected String mRemotePath = ""; + + 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) { + mRemoteServer = 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 { + mRemotePort = 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; + } + + mRemotePath = 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 (mRemoteServer == 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(), mRemoteServer, mRemotePort, 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(mRemotePath); + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java index 3d189490..29ea534b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java @@ -1,92 +1,92 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java#2 $ - */ -public class ServletConfigException extends ServletException { - - /** - * 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(String pMessage, Throwable pCause) { - super(pMessage, pCause); - if (getCause() == null) { - initCause(pCause); - } - } - - /** - * Creates a {@code ServletConfigException} with the cause. - * - * @param pCause the exception cause - */ - public ServletConfigException(Throwable pCause) { - super("Erorr in Servlet configuration: " + pCause.getMessage(), pCause); - if (getCause() == null) { - initCause(pCause); - } - } - - /** - * Gets the cause of this {@code ServletConfigException}. - * - * @return the cause, or {@code null} if unknown. - * @see #getRootCause() - */ -// public final Throwable getCause() { -// Throwable cause = super.getCause(); -// return cause != null ? cause : super.getRootCause(); -// } - - /** - * @deprecated Use {@link #getCause()} instead. - */ -// public final Throwable getRootCause() { -// return getCause(); -// } -} +/* + * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigException.java#2 $ + */ +public class ServletConfigException extends ServletException { + + /** + * 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(String pMessage, Throwable pCause) { + super(pMessage, pCause); + if (getCause() == null) { + initCause(pCause); + } + } + + /** + * Creates a {@code ServletConfigException} with the cause. + * + * @param pCause the exception cause + */ + public ServletConfigException(Throwable pCause) { + super("Erorr in Servlet configuration: " + pCause.getMessage(), pCause); + if (getCause() == null) { + initCause(pCause); + } + } + + /** + * Gets the cause of this {@code ServletConfigException}. + * + * @return the cause, or {@code null} if unknown. + * @see #getRootCause() + */ +// public final Throwable getCause() { +// Throwable cause = super.getCause(); +// return cause != null ? cause : super.getRootCause(); +// } + + /** + * @deprecated Use {@link #getCause()} instead. + */ +// public final Throwable getRootCause() { +// return getCause(); +// } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java index d01d611a..db93a47f 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java @@ -1,284 +1,284 @@ -/* - * 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java#2 $ - */ -class ServletConfigMapAdapter extends AbstractMap implements Map, Serializable, Cloneable { - - enum ConfigType { - ServletConfig, FilterConfig, ServletContext - } - -// private final boolean mIsServlet; - private final ConfigType mType; - - private final ServletConfig mServletConfig; - private final FilterConfig mFilterConfig; - private final ServletContext mServletContext; - - // Cache the entry set - private transient Set> mEntrySet; - - public ServletConfigMapAdapter(ServletConfig pConfig) { - this(pConfig, ConfigType.ServletConfig); - } - - public ServletConfigMapAdapter(FilterConfig pConfig) { - this(pConfig, ConfigType.FilterConfig); - } - - public ServletConfigMapAdapter(ServletContext pContext) { - this(pContext, ConfigType.ServletContext); - } - - private ServletConfigMapAdapter(Object pConfig, ConfigType pType) { - if (pConfig == null) { - // Could happen of client code invokes with null reference - throw new IllegalArgumentException("Config == null"); - } - - mType = pType; - - switch (mType) { - case ServletConfig: - mServletConfig = (ServletConfig) pConfig; - mFilterConfig = null; - mServletContext = null; - break; - case FilterConfig: - mServletConfig = null; - mFilterConfig = (FilterConfig) pConfig; - mServletContext = null; - break; - case ServletContext: - mServletConfig = null; - mFilterConfig = null; - mServletContext = (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 (mType) { - case ServletConfig: - return mServletConfig.getServletName(); - case FilterConfig: - return mFilterConfig.getFilterName(); - case ServletContext: - return mServletContext.getServletContextName(); - default: - throw new IllegalStateException(); - } - } - - /** - * Gets the servlet context from the config. - * - * @return the servlet context - */ - public final ServletContext getServletContext() { - switch (mType) { - case ServletConfig: - return mServletConfig.getServletContext(); - case FilterConfig: - return mFilterConfig.getServletContext(); - case ServletContext: - return mServletContext; - default: - throw new IllegalStateException(); - } - } - - public final Enumeration getInitParameterNames() { - switch (mType) { - case ServletConfig: - return mServletConfig.getInitParameterNames(); - case FilterConfig: - return mFilterConfig.getInitParameterNames(); - case ServletContext: - return mServletContext.getInitParameterNames(); - default: - throw new IllegalStateException(); - } - } - - public final String getInitParameter(final String pName) { - switch (mType) { - case ServletConfig: - return mServletConfig.getInitParameter(pName); - case FilterConfig: - return mFilterConfig.getInitParameter(pName); - case ServletContext: - return mServletContext.getInitParameter(pName); - default: - throw new IllegalStateException(); - } - } - - public Set> entrySet() { - if (mEntrySet == null) { - mEntrySet = createEntrySet(); - } - return mEntrySet; - } - - private Set> createEntrySet() { - return new AbstractSet>() { - // Cache size, if requested, -1 means not calculated - private int mSize = -1; - - public Iterator> iterator() { - return new Iterator>() { - // Iterator is backed by initParameterNames enumeration - final Enumeration mNames = getInitParameterNames(); - - public boolean hasNext() { - return mNames.hasMoreElements(); - } - - public Entry next() { - final String key = (String) mNames.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 (mSize < 0) { - mSize = calculateSize(); - } - - return mSize; - } - - 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletConfigMapAdapter.java#2 $ + */ +class ServletConfigMapAdapter extends AbstractMap implements Map, Serializable, Cloneable { + + enum ConfigType { + ServletConfig, FilterConfig, ServletContext + } + +// private final boolean mIsServlet; + private final ConfigType mType; + + private final ServletConfig mServletConfig; + private final FilterConfig mFilterConfig; + private final ServletContext mServletContext; + + // Cache the entry set + private transient Set> mEntrySet; + + public ServletConfigMapAdapter(ServletConfig pConfig) { + this(pConfig, ConfigType.ServletConfig); + } + + public ServletConfigMapAdapter(FilterConfig pConfig) { + this(pConfig, ConfigType.FilterConfig); + } + + public ServletConfigMapAdapter(ServletContext pContext) { + this(pContext, ConfigType.ServletContext); + } + + private ServletConfigMapAdapter(Object pConfig, ConfigType pType) { + if (pConfig == null) { + // Could happen of client code invokes with null reference + throw new IllegalArgumentException("Config == null"); + } + + mType = pType; + + switch (mType) { + case ServletConfig: + mServletConfig = (ServletConfig) pConfig; + mFilterConfig = null; + mServletContext = null; + break; + case FilterConfig: + mServletConfig = null; + mFilterConfig = (FilterConfig) pConfig; + mServletContext = null; + break; + case ServletContext: + mServletConfig = null; + mFilterConfig = null; + mServletContext = (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 (mType) { + case ServletConfig: + return mServletConfig.getServletName(); + case FilterConfig: + return mFilterConfig.getFilterName(); + case ServletContext: + return mServletContext.getServletContextName(); + default: + throw new IllegalStateException(); + } + } + + /** + * Gets the servlet context from the config. + * + * @return the servlet context + */ + public final ServletContext getServletContext() { + switch (mType) { + case ServletConfig: + return mServletConfig.getServletContext(); + case FilterConfig: + return mFilterConfig.getServletContext(); + case ServletContext: + return mServletContext; + default: + throw new IllegalStateException(); + } + } + + public final Enumeration getInitParameterNames() { + switch (mType) { + case ServletConfig: + return mServletConfig.getInitParameterNames(); + case FilterConfig: + return mFilterConfig.getInitParameterNames(); + case ServletContext: + return mServletContext.getInitParameterNames(); + default: + throw new IllegalStateException(); + } + } + + public final String getInitParameter(final String pName) { + switch (mType) { + case ServletConfig: + return mServletConfig.getInitParameter(pName); + case FilterConfig: + return mFilterConfig.getInitParameter(pName); + case ServletContext: + return mServletContext.getInitParameter(pName); + default: + throw new IllegalStateException(); + } + } + + public Set> entrySet() { + if (mEntrySet == null) { + mEntrySet = createEntrySet(); + } + return mEntrySet; + } + + private Set> createEntrySet() { + return new AbstractSet>() { + // Cache size, if requested, -1 means not calculated + private int mSize = -1; + + public Iterator> iterator() { + return new Iterator>() { + // Iterator is backed by initParameterNames enumeration + final Enumeration mNames = getInitParameterNames(); + + public boolean hasNext() { + return mNames.hasMoreElements(); + } + + public Entry next() { + final String key = (String) mNames.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 (mSize < 0) { + mSize = calculateSize(); + } + + return mSize; + } + + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java index 1c29a951..f9de3105 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java +++ b/twelvemonkeys-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.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.OutputStream; - -/** - * A delegate for handling stream support in wrapped servlet responses. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java#2 $ - */ -public class ServletResponseStreamDelegate { - private Object mOut = null; - protected final ServletResponse mResponse; - - public ServletResponseStreamDelegate(ServletResponse pResponse) { - if (pResponse == null) { - throw new IllegalArgumentException("response == null"); - } - mResponse = pResponse; - } - - // NOTE: Intentionally NOT threadsafe, as one request/response should be - // handled by one thread ONLY. - public final ServletOutputStream getOutputStream() throws IOException { - if (mOut == null) { - OutputStream out = createOutputStream(); - mOut = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out); - } - else if (mOut instanceof PrintWriter) { - throw new IllegalStateException("getWriter() allready called."); - } - - return (ServletOutputStream) mOut; - } - - // NOTE: Intentionally NOT threadsafe, as one request/response should be - // handled by one thread ONLY. - public final PrintWriter getWriter() throws IOException { - if (mOut == null) { - // NOTE: getCharacterEncoding may should not return null - OutputStream out = createOutputStream(); - String charEncoding = mResponse.getCharacterEncoding(); - mOut = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out)); - } - else if (mOut instanceof ServletOutputStream) { - throw new IllegalStateException("getOutputStream() allready called."); - } - - return (PrintWriter) mOut; - } - - /** - * Returns the {@code OutputStream}. - * Override this method to provide a decoreated outputstream. - * This method is guaranteed to be invoked only once for a request/response. - *

- * 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 mResponse.getOutputStream(); - } - - public void flushBuffer() throws IOException { - if (mOut instanceof ServletOutputStream) { - ((ServletOutputStream) mOut).flush(); - } - else if (mOut != null) { - ((PrintWriter) mOut).flush(); - } - } - - public void resetBuffer() { - // TODO: Is this okay? Probably not... :-) - mOut = 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.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.OutputStream; + +/** + * A delegate for handling stream support in wrapped servlet responses. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletResponseStreamDelegate.java#2 $ + */ +public class ServletResponseStreamDelegate { + private Object mOut = null; + protected final ServletResponse mResponse; + + public ServletResponseStreamDelegate(ServletResponse pResponse) { + if (pResponse == null) { + throw new IllegalArgumentException("response == null"); + } + mResponse = pResponse; + } + + // NOTE: Intentionally NOT threadsafe, as one request/response should be + // handled by one thread ONLY. + public final ServletOutputStream getOutputStream() throws IOException { + if (mOut == null) { + OutputStream out = createOutputStream(); + mOut = out instanceof ServletOutputStream ? out : new OutputStreamAdapter(out); + } + else if (mOut instanceof PrintWriter) { + throw new IllegalStateException("getWriter() allready called."); + } + + return (ServletOutputStream) mOut; + } + + // NOTE: Intentionally NOT threadsafe, as one request/response should be + // handled by one thread ONLY. + public final PrintWriter getWriter() throws IOException { + if (mOut == null) { + // NOTE: getCharacterEncoding may should not return null + OutputStream out = createOutputStream(); + String charEncoding = mResponse.getCharacterEncoding(); + mOut = new PrintWriter(charEncoding != null ? new OutputStreamWriter(out, charEncoding) : new OutputStreamWriter(out)); + } + else if (mOut instanceof ServletOutputStream) { + throw new IllegalStateException("getOutputStream() allready called."); + } + + return (PrintWriter) mOut; + } + + /** + * Returns the {@code OutputStream}. + * Override this method to provide a decoreated outputstream. + * This method is guaranteed to be invoked only once for a request/response. + *

+ * 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 mResponse.getOutputStream(); + } + + public void flushBuffer() throws IOException { + if (mOut instanceof ServletOutputStream) { + ((ServletOutputStream) mOut).flush(); + } + else if (mOut != null) { + ((PrintWriter) mOut).flush(); + } + } + + public void resetBuffer() { + // TODO: Is this okay? Probably not... :-) + mOut = null; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java index df937202..c93af83b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java @@ -1,1060 +1,1060 @@ -/* - * 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.io.PrintStream; -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.Date; -import java.util.Enumeration; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java#3 $ - */ -public final class ServletUtil { - - /** - * "javax.servlet.include.request_uri" - */ - private final static String ATTRIB_INC_REQUEST_URI = "javax.servlet.include.request_uri"; - - /** - * "javax.servlet.include.context_path" - */ - private final static String ATTRIB_INC_CONTEXT_PATH = "javax.servlet.include.context_path"; - - /** - * "javax.servlet.include.servlet_path" - */ - private final static String ATTRIB_INC_SERVLET_PATH = "javax.servlet.include.servlet_path"; - - /** - * "javax.servlet.include.path_info" - */ - private final static String ATTRIB_INC_PATH_INFO = "javax.servlet.include.path_info"; - - /** - * "javax.servlet.include.query_string" - */ - private final static String ATTRIB_INC_QUERY_STRING = "javax.servlet.include.query_string"; - - /** - * "javax.servlet.forward.request_uri" - */ - private final static String ATTRIB_FWD_REQUEST_URI = "javax.servlet.forward.request_uri"; - - /** - * "javax.servlet.forward.context_path" - */ - private final static String ATTRIB_FWD_CONTEXT_PATH = "javax.servlet.forward.context_path"; - - /** - * "javax.servlet.forward.servlet_path" - */ - private final static String ATTRIB_FWD_SERVLET_PATH = "javax.servlet.forward.servlet_path"; - - /** - * "javax.servlet.forward.path_info" - */ - private final static String ATTRIB_FWD_PATH_INFO = "javax.servlet.forward.path_info"; - - /** - * "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(ServletRequest pReq, String pName, 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... - * @see Converter#toObject - */ - - // public static T getParameter(ServletRequest pReq, String pName, - // String pFormat, T pDefault) { - static T getParameter(ServletRequest pReq, String pName, Class pType, String pFormat, 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 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 boolean, or the - * default value, if the parameter is not set. - */ - public static boolean getBooleanParameter(ServletRequest pReq, String pName, 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 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 int, or the default - * value, if the parameter is not set. - */ - public static int getIntParameter(ServletRequest pReq, String pName, 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 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 long, or the default - * value, if the parameter is not set. - */ - public static long getLongParameter(ServletRequest pReq, String pName, 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 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 float, or the default - * value, if the parameter is not set. - */ - public static float getFloatParameter(ServletRequest pReq, String pName, 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 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 double, or the default - * value, if the parameter is not set. - */ - public static double getDoubleParameter(ServletRequest pReq, String pName, 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 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 Date, or the - * default value, if the parameter is not set. - * @see com.twelvemonkeys.lang.StringUtil#toDate(String) - */ - public static long getDateParameter(ServletRequest pReq, String pName, 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(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(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(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(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(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(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(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(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(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(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(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(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(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 mehtod 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(ServletContext pContext, 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} (webapp). - * - * @param pContext the servlet context - * @return the temp directory - */ - public static File getTempDir(ServletContext pContext) { - return (File) pContext.getAttribute("javax.servlet.context.tempdir"); - } - - /** - * Gets the identificator string containing 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(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 serlvet configuration - * @return a {@code Map} view of the config - * @throws IllegalArgumentException if {@code pConfig} is {@code null} - */ - public static Map asMap(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(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 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 HttpServletRequest pRequest) { - return new SerlvetParametersMapAdapter(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 SerlvetHeadersMapAdapter(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; - } - - - /** - * Prints the init parameters in a {@code javax.servlet.ServletConfig} - * object to a {@code java.io.PrintStream}. - *

- * - * @param pServletConfig The Servlet Config object. - * @param pPrintStream The {@code java.io.PrintStream} for flushing - * the results. - */ - public static void printDebug(final ServletConfig pServletConfig, final PrintStream pPrintStream) { - Enumeration parameterNames = pServletConfig.getInitParameterNames(); - - while (parameterNames.hasMoreElements()) { - String initParameterName = (String) parameterNames.nextElement(); - - pPrintStream.println(initParameterName + ": " + pServletConfig.getInitParameter(initParameterName)); - } - } - - /** - * Prints the init parameters in a {@code javax.servlet.ServletConfig} - * object to {@code System.out}. - * - * @param pServletConfig the Servlet Config object. - */ - public static void printDebug(final ServletConfig pServletConfig) { - printDebug(pServletConfig, System.out); - } - - /** - * Prints the init parameters in a {@code javax.servlet.ServletContext} - * object to a {@code java.io.PrintStream}. - * - * @param pServletContext the Servlet Context object. - * @param pPrintStream the {@code java.io.PrintStream} for flushing the - * results. - */ - public static void printDebug(final ServletContext pServletContext, final PrintStream pPrintStream) { - Enumeration parameterNames = pServletContext.getInitParameterNames(); - - while (parameterNames.hasMoreElements()) { - String initParameterName = (String) parameterNames.nextElement(); - - pPrintStream.println(initParameterName + ": " + pServletContext.getInitParameter(initParameterName)); - } - } - - /** - * Prints the init parameters in a {@code javax.servlet.ServletContext} - * object to {@code System.out}. - * - * @param pServletContext The Servlet Context object. - */ - public static void printDebug(final ServletContext pServletContext) { - printDebug(pServletContext, System.out); - } - - /** - * Prints an excerpt of the residing information in a - * {@code javax.servlet.http.HttpServletRequest} object to a - * {@code java.io.PrintStream}. - * - * @param pRequest The HTTP servlet request object. - * @param pPrintStream The {@code java.io.PrintStream} for flushing - * the results. - */ - public static void printDebug(final HttpServletRequest pRequest, final PrintStream pPrintStream) { - String indentation = " "; - StringBuilder buffer = new StringBuilder(); - - // Returns the name of the authentication scheme used to protect the - // servlet, for example, "BASIC" or "SSL," or null if the servlet was - // not protected. - buffer.append(indentation); - buffer.append("Authentication scheme: "); - buffer.append(pRequest.getAuthType()); - buffer.append("\n"); - - // Returns the portion of the request URI that indicates the context - // of the request. - buffer.append(indentation); - buffer.append("Context path: "); - buffer.append(pRequest.getContextPath()); - buffer.append("\n"); - - // Returns an enumeration of all the header mNames this request contains. - buffer.append(indentation); - buffer.append("Header:"); - buffer.append("\n"); - Enumeration headerNames = pRequest.getHeaderNames(); - - while (headerNames.hasMoreElements()) { - String headerElement = (String) headerNames.nextElement(); - - buffer.append(indentation); - buffer.append(indentation); - buffer.append(headerElement); - buffer.append(": "); - buffer.append(pRequest.getHeader(headerElement)); - buffer.append("\n"); - } - - // Returns the name of the HTTP method with which this request was made, - // for example, GET, POST, or PUT. - buffer.append(indentation); - buffer.append("HTTP method: "); - buffer.append(pRequest.getMethod()); - buffer.append("\n"); - - // Returns any extra path information associated with the URL the client - // sent when it made this request. - buffer.append(indentation); - buffer.append("Extra path information from client: "); - buffer.append(pRequest.getPathInfo()); - buffer.append("\n"); - - // Returns any extra path information after the servlet name but before - // the query string, and translates it to a real path. - buffer.append(indentation); - buffer.append("Extra translated path information from client: "); - buffer.append(pRequest.getPathTranslated()); - buffer.append("\n"); - - // Returns the login of the user making this request, if the user has - // been authenticated, or null if the user has not been authenticated. - buffer.append(indentation); - String userInfo = pRequest.getRemoteUser(); - - if (StringUtil.isEmpty(userInfo)) { - buffer.append("User is not authenticated"); - } - else { - buffer.append("User logint: "); - buffer.append(userInfo); - } - buffer.append("\n"); - - // Returns the session ID specified by the client. - buffer.append(indentation); - buffer.append("Session ID from client: "); - buffer.append(pRequest.getRequestedSessionId()); - buffer.append("\n"); - - // Returns the server name. - buffer.append(indentation); - buffer.append("Server name: "); - buffer.append(pRequest.getServerName()); - buffer.append("\n"); - - // Returns the part of this request's URL from the protocol name up - // to the query string in the first line of the HTTP request. - buffer.append(indentation); - buffer.append("Request URI: ").append(pRequest.getRequestURI()); - buffer.append("\n"); - - // Returns the path info. - buffer.append(indentation); - buffer.append("Path information: ").append(pRequest.getPathInfo()); - buffer.append("\n"); - - // Returns the part of this request's URL that calls the servlet. - buffer.append(indentation); - buffer.append("Servlet path: ").append(pRequest.getServletPath()); - buffer.append("\n"); - - // Returns the query string that is contained in the request URL after - // the path. - buffer.append(indentation); - buffer.append("Query string: ").append(pRequest.getQueryString()); - buffer.append("\n"); - - // Returns an enumeration of all the parameters bound to this request. - buffer.append(indentation); - buffer.append("Parameters:"); - buffer.append("\n"); - Enumeration parameterNames = pRequest.getParameterNames(); - while (parameterNames.hasMoreElements()) { - String parameterName = (String) parameterNames.nextElement(); - - buffer.append(indentation); - buffer.append(indentation); - buffer.append(parameterName); - buffer.append(": "); - buffer.append(pRequest.getParameter(parameterName)); - buffer.append("\n"); - } - - // Returns an enumeration of all the attribute objects bound to this - // request. - buffer.append(indentation); - buffer.append("Attributes:"); - buffer.append("\n"); - Enumeration attributeNames = pRequest.getAttributeNames(); - while (attributeNames.hasMoreElements()) { - String attributeName = (String) attributeNames.nextElement(); - - buffer.append(indentation); - buffer.append(indentation); - buffer.append(attributeName); - buffer.append(": "); - buffer.append(pRequest.getAttribute(attributeName).toString()); - buffer.append("\n"); - } - pPrintStream.println(buffer.toString()); - } - - /** - * Prints an excerpt of the residing information in a - * {@code javax.servlet.http.HttpServletRequest} object to - * {@code System.out}. - * - * @param pRequest The HTTP servlet request object. - */ - public static void printDebug(final HttpServletRequest pRequest) { - printDebug(pRequest, System.out); - } - - /** - * Prints an excerpt of a {@code javax.servlet.http.HttpSession} object - * to a {@code java.io.PrintStream}. - * - * @param pHttpSession The HTTP Session object. - * @param pPrintStream The {@code java.io.PrintStream} for flushing - * the results. - */ - public static void printDebug(final HttpSession pHttpSession, final PrintStream pPrintStream) { - String indentation = " "; - StringBuilder buffer = new StringBuilder(); - - if (pHttpSession == null) { - buffer.append(indentation); - buffer.append("No session object available"); - buffer.append("\n"); - } - else { - - // Returns a string containing the unique identifier assigned to - //this session - buffer.append(indentation); - buffer.append("Session ID: ").append(pHttpSession.getId()); - buffer.append("\n"); - - // Returns the last time the client sent a request associated with - // this session, as the number of milliseconds since midnight - // January 1, 1970 GMT, and marked by the time the container - // recieved the request - buffer.append(indentation); - buffer.append("Last accessed time: "); - buffer.append(new Date(pHttpSession.getLastAccessedTime())); - buffer.append("\n"); - - // Returns the time when this session was created, measured in - // milliseconds since midnight January 1, 1970 GMT - buffer.append(indentation); - buffer.append("Creation time: "); - buffer.append(new Date(pHttpSession.getCreationTime())); - buffer.append("\n"); - - // Returns true if the client does not yet know about the session - // or if the client chooses not to join the session - buffer.append(indentation); - buffer.append("New session?: "); - buffer.append(pHttpSession.isNew()); - buffer.append("\n"); - - // Returns the maximum time interval, in seconds, that the servlet - // container will keep this session open between client accesses - buffer.append(indentation); - buffer.append("Max inactive interval: "); - buffer.append(pHttpSession.getMaxInactiveInterval()); - buffer.append("\n"); - - // Returns an enumeration of all the attribute objects bound to - // this session - buffer.append(indentation); - buffer.append("Attributes:"); - buffer.append("\n"); - Enumeration attributeNames = pHttpSession.getAttributeNames(); - - while (attributeNames.hasMoreElements()) { - String attributeName = (String) attributeNames.nextElement(); - - buffer.append(indentation); - buffer.append(indentation); - buffer.append(attributeName); - buffer.append(": "); - buffer.append(pHttpSession.getAttribute(attributeName).toString()); - buffer.append("\n"); - } - } - pPrintStream.println(buffer.toString()); - } - - /** - * Prints an excerpt of a {@code javax.servlet.http.HttpSession} - * object to {@code System.out}. - *

- * - * @param pHttpSession The HTTP Session object. - */ - public static void printDebug(final HttpSession pHttpSession) { - printDebug(pHttpSession, System.out); - } - - private static class HttpServletResponseHandler implements InvocationHandler { - private ServletResponse mResponse; - private HttpServletResponse mHttpResponse; - - HttpServletResponseHandler(ServletResponseWrapper pResponse) { - mResponse = pResponse; - mHttpResponse = (HttpServletResponse) pResponse.getResponse(); - } - - public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable { - try { - if (pMethod.getDeclaringClass().isInstance(mResponse)) { - //System.out.println("Invoking " + pMethod + " on wrapper"); - return pMethod.invoke(mResponse, pArgs); - } - // Method is not implemented in wrapper - //System.out.println("Invoking " + pMethod + " on wrapped object"); - return pMethod.invoke(mHttpResponse, pArgs); - } - catch (InvocationTargetException e) { - // Unwrap, to avoid UndeclaredThrowableException... - throw e.getTargetException(); - } - } - } - - private static class HttpServletRequestHandler implements InvocationHandler { - private ServletRequest mRequest; - private HttpServletRequest mHttpRequest; - - HttpServletRequestHandler(ServletRequestWrapper pRequest) { - mRequest = pRequest; - mHttpRequest = (HttpServletRequest) pRequest.getRequest(); - } - - public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable { - try { - if (pMethod.getDeclaringClass().isInstance(mRequest)) { - //System.out.println("Invoking " + pMethod + " on wrapper"); - return pMethod.invoke(mRequest, pArgs); - } - // Method is not implemented in wrapper - //System.out.println("Invoking " + pMethod + " on wrapped object"); - return pMethod.invoke(mHttpRequest, 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.io.PrintStream; +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.Date; +import java.util.Enumeration; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ServletUtil.java#3 $ + */ +public final class ServletUtil { + + /** + * "javax.servlet.include.request_uri" + */ + private final static String ATTRIB_INC_REQUEST_URI = "javax.servlet.include.request_uri"; + + /** + * "javax.servlet.include.context_path" + */ + private final static String ATTRIB_INC_CONTEXT_PATH = "javax.servlet.include.context_path"; + + /** + * "javax.servlet.include.servlet_path" + */ + private final static String ATTRIB_INC_SERVLET_PATH = "javax.servlet.include.servlet_path"; + + /** + * "javax.servlet.include.path_info" + */ + private final static String ATTRIB_INC_PATH_INFO = "javax.servlet.include.path_info"; + + /** + * "javax.servlet.include.query_string" + */ + private final static String ATTRIB_INC_QUERY_STRING = "javax.servlet.include.query_string"; + + /** + * "javax.servlet.forward.request_uri" + */ + private final static String ATTRIB_FWD_REQUEST_URI = "javax.servlet.forward.request_uri"; + + /** + * "javax.servlet.forward.context_path" + */ + private final static String ATTRIB_FWD_CONTEXT_PATH = "javax.servlet.forward.context_path"; + + /** + * "javax.servlet.forward.servlet_path" + */ + private final static String ATTRIB_FWD_SERVLET_PATH = "javax.servlet.forward.servlet_path"; + + /** + * "javax.servlet.forward.path_info" + */ + private final static String ATTRIB_FWD_PATH_INFO = "javax.servlet.forward.path_info"; + + /** + * "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(ServletRequest pReq, String pName, 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... + * @see Converter#toObject + */ + + // public static T getParameter(ServletRequest pReq, String pName, + // String pFormat, T pDefault) { + static T getParameter(ServletRequest pReq, String pName, Class pType, String pFormat, 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 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 boolean, or the + * default value, if the parameter is not set. + */ + public static boolean getBooleanParameter(ServletRequest pReq, String pName, 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 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 int, or the default + * value, if the parameter is not set. + */ + public static int getIntParameter(ServletRequest pReq, String pName, 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 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 long, or the default + * value, if the parameter is not set. + */ + public static long getLongParameter(ServletRequest pReq, String pName, 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 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 float, or the default + * value, if the parameter is not set. + */ + public static float getFloatParameter(ServletRequest pReq, String pName, 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 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 double, or the default + * value, if the parameter is not set. + */ + public static double getDoubleParameter(ServletRequest pReq, String pName, 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 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 Date, or the + * default value, if the parameter is not set. + * @see com.twelvemonkeys.lang.StringUtil#toDate(String) + */ + public static long getDateParameter(ServletRequest pReq, String pName, 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(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(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(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(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(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(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(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(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(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(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(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(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(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 mehtod 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(ServletContext pContext, 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} (webapp). + * + * @param pContext the servlet context + * @return the temp directory + */ + public static File getTempDir(ServletContext pContext) { + return (File) pContext.getAttribute("javax.servlet.context.tempdir"); + } + + /** + * Gets the identificator string containing 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(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 serlvet configuration + * @return a {@code Map} view of the config + * @throws IllegalArgumentException if {@code pConfig} is {@code null} + */ + public static Map asMap(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(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 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 HttpServletRequest pRequest) { + return new SerlvetParametersMapAdapter(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 SerlvetHeadersMapAdapter(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; + } + + + /** + * Prints the init parameters in a {@code javax.servlet.ServletConfig} + * object to a {@code java.io.PrintStream}. + *

+ * + * @param pServletConfig The Servlet Config object. + * @param pPrintStream The {@code java.io.PrintStream} for flushing + * the results. + */ + public static void printDebug(final ServletConfig pServletConfig, final PrintStream pPrintStream) { + Enumeration parameterNames = pServletConfig.getInitParameterNames(); + + while (parameterNames.hasMoreElements()) { + String initParameterName = (String) parameterNames.nextElement(); + + pPrintStream.println(initParameterName + ": " + pServletConfig.getInitParameter(initParameterName)); + } + } + + /** + * Prints the init parameters in a {@code javax.servlet.ServletConfig} + * object to {@code System.out}. + * + * @param pServletConfig the Servlet Config object. + */ + public static void printDebug(final ServletConfig pServletConfig) { + printDebug(pServletConfig, System.out); + } + + /** + * Prints the init parameters in a {@code javax.servlet.ServletContext} + * object to a {@code java.io.PrintStream}. + * + * @param pServletContext the Servlet Context object. + * @param pPrintStream the {@code java.io.PrintStream} for flushing the + * results. + */ + public static void printDebug(final ServletContext pServletContext, final PrintStream pPrintStream) { + Enumeration parameterNames = pServletContext.getInitParameterNames(); + + while (parameterNames.hasMoreElements()) { + String initParameterName = (String) parameterNames.nextElement(); + + pPrintStream.println(initParameterName + ": " + pServletContext.getInitParameter(initParameterName)); + } + } + + /** + * Prints the init parameters in a {@code javax.servlet.ServletContext} + * object to {@code System.out}. + * + * @param pServletContext The Servlet Context object. + */ + public static void printDebug(final ServletContext pServletContext) { + printDebug(pServletContext, System.out); + } + + /** + * Prints an excerpt of the residing information in a + * {@code javax.servlet.http.HttpServletRequest} object to a + * {@code java.io.PrintStream}. + * + * @param pRequest The HTTP servlet request object. + * @param pPrintStream The {@code java.io.PrintStream} for flushing + * the results. + */ + public static void printDebug(final HttpServletRequest pRequest, final PrintStream pPrintStream) { + String indentation = " "; + StringBuilder buffer = new StringBuilder(); + + // Returns the name of the authentication scheme used to protect the + // servlet, for example, "BASIC" or "SSL," or null if the servlet was + // not protected. + buffer.append(indentation); + buffer.append("Authentication scheme: "); + buffer.append(pRequest.getAuthType()); + buffer.append("\n"); + + // Returns the portion of the request URI that indicates the context + // of the request. + buffer.append(indentation); + buffer.append("Context path: "); + buffer.append(pRequest.getContextPath()); + buffer.append("\n"); + + // Returns an enumeration of all the header mNames this request contains. + buffer.append(indentation); + buffer.append("Header:"); + buffer.append("\n"); + Enumeration headerNames = pRequest.getHeaderNames(); + + while (headerNames.hasMoreElements()) { + String headerElement = (String) headerNames.nextElement(); + + buffer.append(indentation); + buffer.append(indentation); + buffer.append(headerElement); + buffer.append(": "); + buffer.append(pRequest.getHeader(headerElement)); + buffer.append("\n"); + } + + // Returns the name of the HTTP method with which this request was made, + // for example, GET, POST, or PUT. + buffer.append(indentation); + buffer.append("HTTP method: "); + buffer.append(pRequest.getMethod()); + buffer.append("\n"); + + // Returns any extra path information associated with the URL the client + // sent when it made this request. + buffer.append(indentation); + buffer.append("Extra path information from client: "); + buffer.append(pRequest.getPathInfo()); + buffer.append("\n"); + + // Returns any extra path information after the servlet name but before + // the query string, and translates it to a real path. + buffer.append(indentation); + buffer.append("Extra translated path information from client: "); + buffer.append(pRequest.getPathTranslated()); + buffer.append("\n"); + + // Returns the login of the user making this request, if the user has + // been authenticated, or null if the user has not been authenticated. + buffer.append(indentation); + String userInfo = pRequest.getRemoteUser(); + + if (StringUtil.isEmpty(userInfo)) { + buffer.append("User is not authenticated"); + } + else { + buffer.append("User logint: "); + buffer.append(userInfo); + } + buffer.append("\n"); + + // Returns the session ID specified by the client. + buffer.append(indentation); + buffer.append("Session ID from client: "); + buffer.append(pRequest.getRequestedSessionId()); + buffer.append("\n"); + + // Returns the server name. + buffer.append(indentation); + buffer.append("Server name: "); + buffer.append(pRequest.getServerName()); + buffer.append("\n"); + + // Returns the part of this request's URL from the protocol name up + // to the query string in the first line of the HTTP request. + buffer.append(indentation); + buffer.append("Request URI: ").append(pRequest.getRequestURI()); + buffer.append("\n"); + + // Returns the path info. + buffer.append(indentation); + buffer.append("Path information: ").append(pRequest.getPathInfo()); + buffer.append("\n"); + + // Returns the part of this request's URL that calls the servlet. + buffer.append(indentation); + buffer.append("Servlet path: ").append(pRequest.getServletPath()); + buffer.append("\n"); + + // Returns the query string that is contained in the request URL after + // the path. + buffer.append(indentation); + buffer.append("Query string: ").append(pRequest.getQueryString()); + buffer.append("\n"); + + // Returns an enumeration of all the parameters bound to this request. + buffer.append(indentation); + buffer.append("Parameters:"); + buffer.append("\n"); + Enumeration parameterNames = pRequest.getParameterNames(); + while (parameterNames.hasMoreElements()) { + String parameterName = (String) parameterNames.nextElement(); + + buffer.append(indentation); + buffer.append(indentation); + buffer.append(parameterName); + buffer.append(": "); + buffer.append(pRequest.getParameter(parameterName)); + buffer.append("\n"); + } + + // Returns an enumeration of all the attribute objects bound to this + // request. + buffer.append(indentation); + buffer.append("Attributes:"); + buffer.append("\n"); + Enumeration attributeNames = pRequest.getAttributeNames(); + while (attributeNames.hasMoreElements()) { + String attributeName = (String) attributeNames.nextElement(); + + buffer.append(indentation); + buffer.append(indentation); + buffer.append(attributeName); + buffer.append(": "); + buffer.append(pRequest.getAttribute(attributeName).toString()); + buffer.append("\n"); + } + pPrintStream.println(buffer.toString()); + } + + /** + * Prints an excerpt of the residing information in a + * {@code javax.servlet.http.HttpServletRequest} object to + * {@code System.out}. + * + * @param pRequest The HTTP servlet request object. + */ + public static void printDebug(final HttpServletRequest pRequest) { + printDebug(pRequest, System.out); + } + + /** + * Prints an excerpt of a {@code javax.servlet.http.HttpSession} object + * to a {@code java.io.PrintStream}. + * + * @param pHttpSession The HTTP Session object. + * @param pPrintStream The {@code java.io.PrintStream} for flushing + * the results. + */ + public static void printDebug(final HttpSession pHttpSession, final PrintStream pPrintStream) { + String indentation = " "; + StringBuilder buffer = new StringBuilder(); + + if (pHttpSession == null) { + buffer.append(indentation); + buffer.append("No session object available"); + buffer.append("\n"); + } + else { + + // Returns a string containing the unique identifier assigned to + //this session + buffer.append(indentation); + buffer.append("Session ID: ").append(pHttpSession.getId()); + buffer.append("\n"); + + // Returns the last time the client sent a request associated with + // this session, as the number of milliseconds since midnight + // January 1, 1970 GMT, and marked by the time the container + // recieved the request + buffer.append(indentation); + buffer.append("Last accessed time: "); + buffer.append(new Date(pHttpSession.getLastAccessedTime())); + buffer.append("\n"); + + // Returns the time when this session was created, measured in + // milliseconds since midnight January 1, 1970 GMT + buffer.append(indentation); + buffer.append("Creation time: "); + buffer.append(new Date(pHttpSession.getCreationTime())); + buffer.append("\n"); + + // Returns true if the client does not yet know about the session + // or if the client chooses not to join the session + buffer.append(indentation); + buffer.append("New session?: "); + buffer.append(pHttpSession.isNew()); + buffer.append("\n"); + + // Returns the maximum time interval, in seconds, that the servlet + // container will keep this session open between client accesses + buffer.append(indentation); + buffer.append("Max inactive interval: "); + buffer.append(pHttpSession.getMaxInactiveInterval()); + buffer.append("\n"); + + // Returns an enumeration of all the attribute objects bound to + // this session + buffer.append(indentation); + buffer.append("Attributes:"); + buffer.append("\n"); + Enumeration attributeNames = pHttpSession.getAttributeNames(); + + while (attributeNames.hasMoreElements()) { + String attributeName = (String) attributeNames.nextElement(); + + buffer.append(indentation); + buffer.append(indentation); + buffer.append(attributeName); + buffer.append(": "); + buffer.append(pHttpSession.getAttribute(attributeName).toString()); + buffer.append("\n"); + } + } + pPrintStream.println(buffer.toString()); + } + + /** + * Prints an excerpt of a {@code javax.servlet.http.HttpSession} + * object to {@code System.out}. + *

+ * + * @param pHttpSession The HTTP Session object. + */ + public static void printDebug(final HttpSession pHttpSession) { + printDebug(pHttpSession, System.out); + } + + private static class HttpServletResponseHandler implements InvocationHandler { + private ServletResponse mResponse; + private HttpServletResponse mHttpResponse; + + HttpServletResponseHandler(ServletResponseWrapper pResponse) { + mResponse = pResponse; + mHttpResponse = (HttpServletResponse) pResponse.getResponse(); + } + + public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable { + try { + if (pMethod.getDeclaringClass().isInstance(mResponse)) { + //System.out.println("Invoking " + pMethod + " on wrapper"); + return pMethod.invoke(mResponse, pArgs); + } + // Method is not implemented in wrapper + //System.out.println("Invoking " + pMethod + " on wrapped object"); + return pMethod.invoke(mHttpResponse, pArgs); + } + catch (InvocationTargetException e) { + // Unwrap, to avoid UndeclaredThrowableException... + throw e.getTargetException(); + } + } + } + + private static class HttpServletRequestHandler implements InvocationHandler { + private ServletRequest mRequest; + private HttpServletRequest mHttpRequest; + + HttpServletRequestHandler(ServletRequestWrapper pRequest) { + mRequest = pRequest; + mHttpRequest = (HttpServletRequest) pRequest.getRequest(); + } + + public Object invoke(Object pProxy, Method pMethod, Object[] pArgs) throws Throwable { + try { + if (pMethod.getDeclaringClass().isInstance(mRequest)) { + //System.out.println("Invoking " + pMethod + " on wrapper"); + return pMethod.invoke(mRequest, pArgs); + } + // Method is not implemented in wrapper + //System.out.println("Invoking " + pMethod + " on wrapped object"); + return pMethod.invoke(mHttpRequest, pArgs); + } + catch (InvocationTargetException e) { + // Unwrap, to avoid UndeclaredThrowableException... + throw e.getTargetException(); + } + } + } +} + diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java index 295a0b3a..52f2bb88 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java @@ -1,311 +1,311 @@ -/* - * 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java#1 $ - * @see #setMaxConcurrentThreadCount - * @see #setResponseMessages - */ -public class ThrottleFilter extends GenericFilter { - - /** - * Minimum free thread count, defaults to {@code 10} - */ - protected int mMaxConcurrentThreadCount = 10; - - /** - * The number of running request threads - */ - private int mRunningThreads = 0; - private final Object mRunningThreadsLock = 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 mResponseMessageNames = new HashMap(10); - - /** - * The reposne message sent to user agents, if the request is rejected - */ - private String[] mResponseMessageTypes = null; - - /** - * Cache for response messages - */ - private Map mResponseCache = new HashMap(10); - - - /** - * Sets the minimum free thread count. - * - * @param pMaxConcurrentThreadCount - */ - public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) { - if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) { - try { - mMaxConcurrentThreadCount = 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 (int i = 0; i < mappings.length; i++) { - // Split pairs on '=' - String[] mapping = StringUtil.toStringArray(mappings[i], "= "); - - // Test for wrong mapping - if ((mapping == null) || (mapping.length < 2)) { - log("Error in init param \"responseMessages\": " + pResponseMessages); - continue; - } - types.add(mapping[0]); - mResponseMessageNames.put(mapping[0], mapping[1]); - } - - // Create arrays - mResponseMessageTypes = (String[]) 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 true if the request should be handled. - */ - private boolean beginRequest() { - synchronized (mRunningThreadsLock) { - mRunningThreads++; - } - return (mRunningThreads <= mMaxConcurrentThreadCount); - } - - /** - * Marks the end of the request - */ - private void doneRequest() { - synchronized (mRunningThreadsLock) { - mRunningThreads--; - } - } - - /** - * 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 (mResponseMessageTypes != null) { - String accept = pRequest.getHeader("Accept"); - - for (int i = 0; i < mResponseMessageTypes.length; i++) { - String type = mResponseMessageTypes[i]; - - // 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 = (String) mResponseMessageNames.get(pContentType); - - // Get cached value - CacheEntry entry = (CacheEntry) mResponseCache.get(fileName); - - if ((entry == null) || entry.isExpired()) { - - // Create and add or replace cached value - entry = new CacheEntry(readMessage(fileName)); - mResponseCache.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 mValue; - private long mTimestamp = -1; - - CacheEntry(Object pValue) { - mValue = pValue; - mTimestamp = System.currentTimeMillis(); - } - - Object getValue() { - return mValue; - } - - boolean isExpired() { - return (System.currentTimeMillis() - mTimestamp) > 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/ThrottleFilter.java#1 $ + * @see #setMaxConcurrentThreadCount + * @see #setResponseMessages + */ +public class ThrottleFilter extends GenericFilter { + + /** + * Minimum free thread count, defaults to {@code 10} + */ + protected int mMaxConcurrentThreadCount = 10; + + /** + * The number of running request threads + */ + private int mRunningThreads = 0; + private final Object mRunningThreadsLock = 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 mResponseMessageNames = new HashMap(10); + + /** + * The reposne message sent to user agents, if the request is rejected + */ + private String[] mResponseMessageTypes = null; + + /** + * Cache for response messages + */ + private Map mResponseCache = new HashMap(10); + + + /** + * Sets the minimum free thread count. + * + * @param pMaxConcurrentThreadCount + */ + public void setMaxConcurrentThreadCount(String pMaxConcurrentThreadCount) { + if (!StringUtil.isEmpty(pMaxConcurrentThreadCount)) { + try { + mMaxConcurrentThreadCount = 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 (int i = 0; i < mappings.length; i++) { + // Split pairs on '=' + String[] mapping = StringUtil.toStringArray(mappings[i], "= "); + + // Test for wrong mapping + if ((mapping == null) || (mapping.length < 2)) { + log("Error in init param \"responseMessages\": " + pResponseMessages); + continue; + } + types.add(mapping[0]); + mResponseMessageNames.put(mapping[0], mapping[1]); + } + + // Create arrays + mResponseMessageTypes = (String[]) 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 true if the request should be handled. + */ + private boolean beginRequest() { + synchronized (mRunningThreadsLock) { + mRunningThreads++; + } + return (mRunningThreads <= mMaxConcurrentThreadCount); + } + + /** + * Marks the end of the request + */ + private void doneRequest() { + synchronized (mRunningThreadsLock) { + mRunningThreads--; + } + } + + /** + * 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 (mResponseMessageTypes != null) { + String accept = pRequest.getHeader("Accept"); + + for (int i = 0; i < mResponseMessageTypes.length; i++) { + String type = mResponseMessageTypes[i]; + + // 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 = (String) mResponseMessageNames.get(pContentType); + + // Get cached value + CacheEntry entry = (CacheEntry) mResponseCache.get(fileName); + + if ((entry == null) || entry.isExpired()) { + + // Create and add or replace cached value + entry = new CacheEntry(readMessage(fileName)); + mResponseCache.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 mValue; + private long mTimestamp = -1; + + CacheEntry(Object pValue) { + mValue = pValue; + mTimestamp = System.currentTimeMillis(); + } + + Object getValue() { + return mValue; + } + + boolean isExpired() { + return (System.currentTimeMillis() - mTimestamp) > 60000; // Cache 1 minute + } + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java index cad2bdf3..586b1f82 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java @@ -1,113 +1,113 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java#1 $ - */ -public class TimingFilter extends GenericFilter { - - private String mAttribUsage = null; - - /** - * Method init - * - * @throws ServletException - */ - public void init() throws ServletException { - mAttribUsage = 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(mAttribUsage); - long total = 0; - - if (usageAttrib instanceof Long) { - // If set, get value, and remove attribute for nested resources - total = ((Long) usageAttrib).longValue(); - pRequest.removeAttribute(mAttribUsage); - } - - // 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(mAttribUsage); - long usage = 0; - if (usageAttrib instanceof Long) { - usage = ((Long) usageAttrib).longValue(); - } - - // 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("Request processing time for resource \"" + resourceURI + "\": " + - (delta - usage) + " ms (accumulated: " + delta + " ms)."); - - // Store total usage - total += delta; - pRequest.setAttribute(mAttribUsage, new Long(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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TimingFilter.java#1 $ + */ +public class TimingFilter extends GenericFilter { + + private String mAttribUsage = null; + + /** + * Method init + * + * @throws ServletException + */ + public void init() throws ServletException { + mAttribUsage = 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(mAttribUsage); + long total = 0; + + if (usageAttrib instanceof Long) { + // If set, get value, and remove attribute for nested resources + total = ((Long) usageAttrib).longValue(); + pRequest.removeAttribute(mAttribUsage); + } + + // 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(mAttribUsage); + long usage = 0; + if (usageAttrib instanceof Long) { + usage = ((Long) usageAttrib).longValue(); + } + + // 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("Request processing time for resource \"" + resourceURI + "\": " + + (delta - usage) + " ms (accumulated: " + delta + " ms)."); + + // Store total usage + total += delta; + pRequest.setAttribute(mAttribUsage, new Long(total)); + } + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java index cc9782b0..935cd31b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java#2 $ - */ -public class TrimWhiteSpaceFilter extends GenericFilter { - - private boolean mAutoFlush = true; - - @InitParam - public void setAutoFlush(final boolean pAutoFlush) { - mAutoFlush = pAutoFlush; - } - - public void init() throws ServletException { - super.init(); - log("Automatic flushing is " + (mAutoFlush ? "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 (mAutoFlush) { - wrapped.flushBuffer(); - } - } - - static final class TrimWSFilterOutputStream extends FilterOutputStream { - boolean mLastWasWS = 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); - mLastWasWS = false; - } - else { - // TODO: Consider writing only 0x0a (LF) and 0x20 (space) - // Else, if char is WS, store first, skip the rest - if (!mLastWasWS) { - if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a - super.write(0x0a); - } - else { - super.write(pByte); - } - } - mLastWasWS = true; - } - } - } - - private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate { - public TrimWSStreamDelegate(ServletResponse pResponse) { - super(pResponse); - } - - protected OutputStream createOutputStream() throws IOException { - return new TrimWSFilterOutputStream(mResponse.getOutputStream()); - } - } - - static class TrimWSServletResponseWrapper extends ServletResponseWrapper { - private final ServletResponseStreamDelegate mStreamDelegate = new TrimWSStreamDelegate(getResponse()); - - public TrimWSServletResponseWrapper(ServletResponse pResponse) { - super(pResponse); - } - - public ServletOutputStream getOutputStream() throws IOException { - return mStreamDelegate.getOutputStream(); - } - - public PrintWriter getWriter() throws IOException { - return mStreamDelegate.getWriter(); - } - - public void setContentLength(int pLength) { - // Will be changed by filter, so don't set. - } - - @Override - public void flushBuffer() throws IOException { - mStreamDelegate.flushBuffer(); - } - - @Override - public void resetBuffer() { - mStreamDelegate.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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilter.java#2 $ + */ +public class TrimWhiteSpaceFilter extends GenericFilter { + + private boolean mAutoFlush = true; + + @InitParam + public void setAutoFlush(final boolean pAutoFlush) { + mAutoFlush = pAutoFlush; + } + + public void init() throws ServletException { + super.init(); + log("Automatic flushing is " + (mAutoFlush ? "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 (mAutoFlush) { + wrapped.flushBuffer(); + } + } + + static final class TrimWSFilterOutputStream extends FilterOutputStream { + boolean mLastWasWS = 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); + mLastWasWS = false; + } + else { + // TODO: Consider writing only 0x0a (LF) and 0x20 (space) + // Else, if char is WS, store first, skip the rest + if (!mLastWasWS) { + if (pByte == 0x0d) { // Convert all CR/LF's to 0x0a + super.write(0x0a); + } + else { + super.write(pByte); + } + } + mLastWasWS = true; + } + } + } + + private static class TrimWSStreamDelegate extends ServletResponseStreamDelegate { + public TrimWSStreamDelegate(ServletResponse pResponse) { + super(pResponse); + } + + protected OutputStream createOutputStream() throws IOException { + return new TrimWSFilterOutputStream(mResponse.getOutputStream()); + } + } + + static class TrimWSServletResponseWrapper extends ServletResponseWrapper { + private final ServletResponseStreamDelegate mStreamDelegate = new TrimWSStreamDelegate(getResponse()); + + public TrimWSServletResponseWrapper(ServletResponse pResponse) { + super(pResponse); + } + + public ServletOutputStream getOutputStream() throws IOException { + return mStreamDelegate.getOutputStream(); + } + + public PrintWriter getWriter() throws IOException { + return mStreamDelegate.getWriter(); + } + + public void setContentLength(int pLength) { + // Will be changed by filter, so don't set. + } + + @Override + public void flushBuffer() throws IOException { + mStreamDelegate.flushBuffer(); + } + + @Override + public void resetBuffer() { + mStreamDelegate.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java index 94eca85c..812bfd8a 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java @@ -1,199 +1,199 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java#4 $ - * - */ -public class CacheFilter extends GenericFilter { - - HTTPCache mCache; - - /** - * Initializes the filter - * - * @throws javax.servlet.ServletException - */ - public void init() throws ServletException { - FilterConfig config = getFilterConfig(); - - // Default don't delete cache files on exit (peristent 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 { - 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 { - mCache = 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: " + mCache); - } - 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: " + mCache); - mCache = 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 { - mCache.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 mContext; - - public ServletContextLoggerAdapter(String pName, ServletContext pContext) { - super(pName, null); - mContext = pContext; - } - - @Override - public void log(Level pLevel, String pMessage) { - mContext.log(pMessage); - } - - @Override - public void log(Level pLevel, String pMessage, Throwable pThrowable) { - mContext.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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheFilter.java#4 $ + * + */ +public class CacheFilter extends GenericFilter { + + HTTPCache mCache; + + /** + * Initializes the filter + * + * @throws javax.servlet.ServletException + */ + public void init() throws ServletException { + FilterConfig config = getFilterConfig(); + + // Default don't delete cache files on exit (peristent 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 { + 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 { + mCache = 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: " + mCache); + } + 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: " + mCache); + mCache = 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 { + mCache.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 mContext; + + public ServletContextLoggerAdapter(String pName, ServletContext pContext) { + super(pName, null); + mContext = pContext; + } + + @Override + public void log(Level pLevel, String pMessage) { + mContext.log(pMessage); + } + + @Override + public void log(Level pLevel, String pMessage, Throwable pThrowable) { + mContext.log(pMessage, pThrowable); + } + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java index bd2f051a..04eb74df 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java +++ b/twelvemonkeys-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.NetUtil; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java#3 $ - */ -class CacheResponseWrapper extends HttpServletResponseWrapper { - private ServletResponseStreamDelegate mStreamDelegate; - - private CacheResponse mResponse; - private CachedEntity mCached; - private WritableCachedResponse mCachedResponse; - - private Boolean mCachable; - private int mStatus; - - public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) { - super(pResponse.getResponse()); - mResponse = pResponse; - mCached = pCached; - init(); - } - - /* - NOTE: This class defers determining if a response is cachable until the - output stream is needed. - This it the reason for the somewhat complicated logic in the add/setHeader - methods below. - */ - private void init() { - mCachable = null; - mStatus = SC_OK; - mCachedResponse = mCached.createCachedResponse(); - mStreamDelegate = new ServletResponseStreamDelegate(this) { - protected OutputStream createOutputStream() throws IOException { - // Test if this request is really cachable, otherwise, - // just write through to underlying response, and don't cache - if (isCachable()) { - return mCachedResponse.getOutputStream(); - } - else { - mCachedResponse.setStatus(mStatus); - mCachedResponse.writeHeadersTo(CacheResponseWrapper.this.mResponse); - return super.getOutputStream(); - } - } - }; - } - - CachedResponse getCachedResponse() { - return mCachedResponse.getCachedResponse(); - } - - public boolean isCachable() { - // NOTE: Intentionally not synchronized - if (mCachable == null) { - mCachable = isCachableImpl(); - } - - return mCachable; - } - - private boolean isCachableImpl() { - if (mStatus != SC_OK) { - return false; - } - - // Vary: * - String[] values = mCachedResponse.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 = mCachedResponse.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 = mCachedResponse.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 { - mStreamDelegate.flushBuffer(); - } - - public void resetBuffer() { - // Servlet 2.3 - mStreamDelegate.resetBuffer(); - } - - public void reset() { - if (Boolean.FALSE.equals(mCachable)) { - super.reset(); - } - // No else, might be cachable after all.. - init(); - } - - public ServletOutputStream getOutputStream() throws IOException { - return mStreamDelegate.getOutputStream(); - } - - public PrintWriter getWriter() throws IOException { - return mStreamDelegate.getWriter(); - } - - public boolean containsHeader(String name) { - return mCachedResponse.getHeaderValues(name) != null; - } - - public void sendError(int pStatusCode, String msg) throws IOException { - // NOT cachable - mStatus = pStatusCode; - super.sendError(pStatusCode, msg); - } - - public void sendError(int pStatusCode) throws IOException { - // NOT cachable - mStatus = pStatusCode; - super.sendError(pStatusCode); - } - - public void setStatus(int pStatusCode, String sm) { - // NOTE: This method is deprecated - setStatus(pStatusCode); - } - - public void setStatus(int pStatusCode) { - // NOT cachable unless pStatusCode == 200 (or a FEW others?) - if (pStatusCode != SC_OK) { - mStatus = pStatusCode; - super.setStatus(pStatusCode); - } - } - - public void sendRedirect(String pLocation) throws IOException { - // NOT cachable - mStatus = 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(mCachable)) { - super.setDateHeader(pName, pValue); - } - mCachedResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue)); - } - - public void addDateHeader(String pName, long pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addDateHeader(pName, pValue); - } - mCachedResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue)); - } - - public void setHeader(String pName, String pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.setHeader(pName, pValue); - } - mCachedResponse.setHeader(pName, pValue); - } - - public void addHeader(String pName, String pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addHeader(pName, pValue); - } - mCachedResponse.addHeader(pName, pValue); - } - - public void setIntHeader(String pName, int pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.setIntHeader(pName, pValue); - } - mCachedResponse.setHeader(pName, String.valueOf(pValue)); - } - - public void addIntHeader(String pName, int pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addIntHeader(pName, pValue); - } - mCachedResponse.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.NetUtil; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CacheResponseWrapper.java#3 $ + */ +class CacheResponseWrapper extends HttpServletResponseWrapper { + private ServletResponseStreamDelegate mStreamDelegate; + + private CacheResponse mResponse; + private CachedEntity mCached; + private WritableCachedResponse mCachedResponse; + + private Boolean mCachable; + private int mStatus; + + public CacheResponseWrapper(final ServletCacheResponse pResponse, final CachedEntity pCached) { + super(pResponse.getResponse()); + mResponse = pResponse; + mCached = pCached; + init(); + } + + /* + NOTE: This class defers determining if a response is cachable until the + output stream is needed. + This it the reason for the somewhat complicated logic in the add/setHeader + methods below. + */ + private void init() { + mCachable = null; + mStatus = SC_OK; + mCachedResponse = mCached.createCachedResponse(); + mStreamDelegate = new ServletResponseStreamDelegate(this) { + protected OutputStream createOutputStream() throws IOException { + // Test if this request is really cachable, otherwise, + // just write through to underlying response, and don't cache + if (isCachable()) { + return mCachedResponse.getOutputStream(); + } + else { + mCachedResponse.setStatus(mStatus); + mCachedResponse.writeHeadersTo(CacheResponseWrapper.this.mResponse); + return super.getOutputStream(); + } + } + }; + } + + CachedResponse getCachedResponse() { + return mCachedResponse.getCachedResponse(); + } + + public boolean isCachable() { + // NOTE: Intentionally not synchronized + if (mCachable == null) { + mCachable = isCachableImpl(); + } + + return mCachable; + } + + private boolean isCachableImpl() { + if (mStatus != SC_OK) { + return false; + } + + // Vary: * + String[] values = mCachedResponse.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 = mCachedResponse.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 = mCachedResponse.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 { + mStreamDelegate.flushBuffer(); + } + + public void resetBuffer() { + // Servlet 2.3 + mStreamDelegate.resetBuffer(); + } + + public void reset() { + if (Boolean.FALSE.equals(mCachable)) { + super.reset(); + } + // No else, might be cachable after all.. + init(); + } + + public ServletOutputStream getOutputStream() throws IOException { + return mStreamDelegate.getOutputStream(); + } + + public PrintWriter getWriter() throws IOException { + return mStreamDelegate.getWriter(); + } + + public boolean containsHeader(String name) { + return mCachedResponse.getHeaderValues(name) != null; + } + + public void sendError(int pStatusCode, String msg) throws IOException { + // NOT cachable + mStatus = pStatusCode; + super.sendError(pStatusCode, msg); + } + + public void sendError(int pStatusCode) throws IOException { + // NOT cachable + mStatus = pStatusCode; + super.sendError(pStatusCode); + } + + public void setStatus(int pStatusCode, String sm) { + // NOTE: This method is deprecated + setStatus(pStatusCode); + } + + public void setStatus(int pStatusCode) { + // NOT cachable unless pStatusCode == 200 (or a FEW others?) + if (pStatusCode != SC_OK) { + mStatus = pStatusCode; + super.setStatus(pStatusCode); + } + } + + public void sendRedirect(String pLocation) throws IOException { + // NOT cachable + mStatus = 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(mCachable)) { + super.setDateHeader(pName, pValue); + } + mCachedResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue)); + } + + public void addDateHeader(String pName, long pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addDateHeader(pName, pValue); + } + mCachedResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue)); + } + + public void setHeader(String pName, String pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.setHeader(pName, pValue); + } + mCachedResponse.setHeader(pName, pValue); + } + + public void addHeader(String pName, String pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addHeader(pName, pValue); + } + mCachedResponse.addHeader(pName, pValue); + } + + public void setIntHeader(String pName, int pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.setIntHeader(pName, pValue); + } + mCachedResponse.setHeader(pName, String.valueOf(pValue)); + } + + public void addIntHeader(String pName, int pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addIntHeader(pName, pValue); + } + mCachedResponse.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java index 7e29bdc2..f39c4e14 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntity.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java index af0c37a7..d7719814 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java @@ -1,172 +1,172 @@ -/* - * 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 javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.List; - -/** - * CachedEntity - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java#3 $ - */ -class CachedEntityImpl implements CachedEntity { - private String mCacheURI; - private HTTPCache mCache; - - CachedEntityImpl(String pCacheURI, HTTPCache pCache) { - if (pCacheURI == null) { - throw new IllegalArgumentException("cacheURI == null"); - } - - mCacheURI = pCacheURI; - mCache = pCache; - } - - public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException { - // Get cached content - CachedResponse cached = mCache.getContent(mCacheURI, 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..?! - mCache.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.isCachable()) { - mCache.registerContent( - mCacheURI, - 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 mCache.isContentStale(mCacheURI, pRequest); - } - - public WritableCachedResponse createCachedResponse() { - return new WritableCachedResponseImpl(); - } - - public int hashCode() { - return (mCacheURI != null ? mCacheURI.hashCode() : 0) + 1397; - } - - public boolean equals(Object pOther) { - return pOther instanceof CachedEntityImpl && - ((mCacheURI == null && ((CachedEntityImpl) pOther).mCacheURI == null) || - mCacheURI != null && mCacheURI.equals(((CachedEntityImpl) pOther).mCacheURI)); - } - - public String toString() { - return "CachedEntity[URI=" + mCacheURI + "]"; - } +/* + * 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 javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * CachedEntity + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedEntityImpl.java#3 $ + */ +class CachedEntityImpl implements CachedEntity { + private String mCacheURI; + private HTTPCache mCache; + + CachedEntityImpl(String pCacheURI, HTTPCache pCache) { + if (pCacheURI == null) { + throw new IllegalArgumentException("cacheURI == null"); + } + + mCacheURI = pCacheURI; + mCache = pCache; + } + + public void render(CacheRequest pRequest, CacheResponse pResponse) throws IOException { + // Get cached content + CachedResponse cached = mCache.getContent(mCacheURI, 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..?! + mCache.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.isCachable()) { + mCache.registerContent( + mCacheURI, + 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 mCache.isContentStale(mCacheURI, pRequest); + } + + public WritableCachedResponse createCachedResponse() { + return new WritableCachedResponseImpl(); + } + + public int hashCode() { + return (mCacheURI != null ? mCacheURI.hashCode() : 0) + 1397; + } + + public boolean equals(Object pOther) { + return pOther instanceof CachedEntityImpl && + ((mCacheURI == null && ((CachedEntityImpl) pOther).mCacheURI == null) || + mCacheURI != null && mCacheURI.equals(((CachedEntityImpl) pOther).mCacheURI)); + } + + public String toString() { + return "CachedEntity[URI=" + mCacheURI + "]"; + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java index 14ba1626..933314f5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponse.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java index 1dcf8c46..f3489173 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java @@ -1,220 +1,220 @@ -/* - * 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.util.LinkedMap; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * CachedResponseImpl - * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java#4 $ - */ -class CachedResponseImpl implements CachedResponse { - final protected Map> mHeaders; - protected int mHeadersSize; - protected ByteArrayOutputStream mContent = null; - int mStatus; - - protected CachedResponseImpl() { - mHeaders = new LinkedMap>(); // Keep headers in insertion order - } - - // For use by HTTPCache, when recreating CachedResponses from disk cache - CachedResponseImpl(final int pStatus, final LinkedMap> pHeaders, final int pHeaderSize, final byte[] pContent) { - if (pHeaders == null) { - throw new IllegalArgumentException("headers == null"); - } - mStatus = pStatus; - mHeaders = pHeaders; - mHeadersSize = pHeaderSize; - mContent = new FastByteArrayOutputStream(pContent); - } - - public int getStatus() { - return mStatus; - } - - /** - * 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, line 50 - - 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 cahced content to the response - * - * @param pStream the response stream - * @throws java.io.IOException - */ - public void writeContentsTo(final OutputStream pStream) throws IOException { - if (mContent == null) { - throw new IOException("Cache is null, no content to write."); - } - - mContent.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 = mHeaders.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 = mHeaders.get(pHeaderName); - if (values == null) { - return null; - } - else { - return 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 = mHeaders.get(pHeaderName); - return (values != null && values.size() > 0) ? values.get(0) : null; - } - - public int size() { - // mContent.size() is exact size in bytes, mHeadersSize is an estimate - return (mContent != null ? mContent.size() : 0) + mHeadersSize; - } - - 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 mHeadersSize == pOther.mHeadersSize && - (mContent == null ? pOther.mContent == null : mContent.equals(pOther.mContent)) && - mHeaders.equals(pOther.mHeaders); - } - - 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 = mHeaders.hashCode(); - result = 29 * result + mHeadersSize; - result = 37 * result + (mContent != null ? mContent.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.util.LinkedMap; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * CachedResponseImpl + * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/CachedResponseImpl.java#4 $ + */ +class CachedResponseImpl implements CachedResponse { + final protected Map> mHeaders; + protected int mHeadersSize; + protected ByteArrayOutputStream mContent = null; + int mStatus; + + protected CachedResponseImpl() { + mHeaders = new LinkedMap>(); // Keep headers in insertion order + } + + // For use by HTTPCache, when recreating CachedResponses from disk cache + CachedResponseImpl(final int pStatus, final LinkedMap> pHeaders, final int pHeaderSize, final byte[] pContent) { + if (pHeaders == null) { + throw new IllegalArgumentException("headers == null"); + } + mStatus = pStatus; + mHeaders = pHeaders; + mHeadersSize = pHeaderSize; + mContent = new FastByteArrayOutputStream(pContent); + } + + public int getStatus() { + return mStatus; + } + + /** + * 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, line 50 + + 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 cahced content to the response + * + * @param pStream the response stream + * @throws java.io.IOException + */ + public void writeContentsTo(final OutputStream pStream) throws IOException { + if (mContent == null) { + throw new IOException("Cache is null, no content to write."); + } + + mContent.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 = mHeaders.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 = mHeaders.get(pHeaderName); + if (values == null) { + return null; + } + else { + return 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 = mHeaders.get(pHeaderName); + return (values != null && values.size() > 0) ? values.get(0) : null; + } + + public int size() { + // mContent.size() is exact size in bytes, mHeadersSize is an estimate + return (mContent != null ? mContent.size() : 0) + mHeadersSize; + } + + 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 mHeadersSize == pOther.mHeadersSize && + (mContent == null ? pOther.mContent == null : mContent.equals(pOther.mContent)) && + mHeaders.equals(pOther.mHeaders); + } + + 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 = mHeaders.hashCode(); + result = 29 * result + mHeadersSize; + result = 37 * result + (mContent != null ? mContent.hashCode() : 0); + return result; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java index 7dcf8f26..999537dd 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/HTTPCache.java @@ -1,1167 +1,1167 @@ -/* - * 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.net.MIMEUtil; -import com.twelvemonkeys.net.NetUtil; -import com.twelvemonkeys.util.LRUHashMap; -import com.twelvemonkeys.util.LinkedMap; -import com.twelvemonkeys.util.NullMap; - -import javax.servlet.ServletContext; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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"; - - protected static final int STATUS_OK = 200; - - /** - * The directory used for the disk-based cache - */ - private File mTempDir; - - /** - * Indicates wether the disk-based cache should be deleted when the - * container shuts down/VM exits - */ - private boolean mDeleteCacheOnExit; - - /** - * In-memory content cache - */ - private final Map mContentCache; - /** - * In-memory enity cache - */ - private final Map mEntityCache; - /** - * In-memory varyiation-info cache - */ - private final Map mVaryCache; - - private long mDefaultExpiryTime = -1; - - private final Logger mLogger; - - // 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 - ) { - if (pTempFolder == null) { - throw new IllegalArgumentException("temp folder == null"); - } - if (!pTempFolder.exists() && !pTempFolder.mkdirs()) { - throw new IllegalArgumentException("Could not create required temp directory: " + mTempDir.getAbsolutePath()); - } - if (!(pTempFolder.canRead() && pTempFolder.canWrite())) { - throw new IllegalArgumentException("Must have read/write access to temp folder: " + mTempDir.getAbsolutePath()); - } - if (pDefaultCacheExpiryTime < 0) { - throw new IllegalArgumentException("Negative expiry time"); - } - if (pMaxMemCacheSize < 0) { - throw new IllegalArgumentException("Negative maximum memory cache size"); - } - if (pMaxCachedEntites < 0) { - throw new IllegalArgumentException("Negative maximum number of cached entries"); - } - - mDefaultExpiryTime = pDefaultCacheExpiryTime; - - if (pMaxMemCacheSize > 0) { -// Map backing = new SizedLRUMap(pMaxMemCacheSize); // size in bytes -// mContentCache = new TimeoutMap(backing, null, pDefaultCacheExpiryTime); - mContentCache = new SizedLRUMap(pMaxMemCacheSize); // size in bytes - } - else { - mContentCache = new NullMap(); - } - - mEntityCache = new LRUHashMap(pMaxCachedEntites); - mVaryCache = new LRUHashMap(pMaxCachedEntites); - - mDeleteCacheOnExit = pDeleteCacheOnExit; - - mTempDir = pTempFolder; - - mLogger = 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) { - if (pName == null) { - throw new IllegalArgumentException("name == null"); - } - if (pName.trim().length() == 0) { - throw new IllegalArgumentException("Empty name"); - } - if (pContext == null) { - throw new IllegalArgumentException("servlet context == null"); - } - 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(mTempDir.getAbsolutePath()); - if (mDeleteCacheOnExit) { - buf.append(" (non-persistent)"); - } - else { - buf.append(" (persistent)"); - } - buf.append(", EntityCache: {"); - buf.append(mEntityCache.size()); - buf.append(" entries in a "); - buf.append(mEntityCache.getClass().getName()); - buf.append("}, VaryCache: {"); - buf.append(mVaryCache.size()); - buf.append(" entries in a "); - buf.append(mVaryCache.getClass().getName()); - buf.append("}, ContentCache: {"); - buf.append(mContentCache.size()); - buf.append(" entries in a "); - buf.append(mContentCache.getClass().getName()); - buf.append("}]"); - - return buf.toString(); - } - - void log(final String pMessage) { - mLogger.log(Level.INFO, pMessage); - } - - void log(final String pMessage, Throwable pException) { - mLogger.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 (mEntityCache) { - cached = mEntityCache.get(cacheURI); - if (cached == null) { - cached = new CachedEntityImpl(cacheURI, this); - mEntityCache.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 (mEntityCache) { - cached = mEntityCache.get(cacheURI); - if (cached != null) { - // TODO; Remove all variants - mEntityCache.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() != STATUS_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(mTempDir, "./" + 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() && mDeleteCacheOnExit) { - 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 (mVaryCache) { - variations = mVaryCache.get(pCacheURI); - if (variations == null) { - variations = loadVaryProperties(pCacheURI); - mVaryCache.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(mTempDir, "./" + 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 (mContentCache) { - mContentCache.put(pCacheURI + '.' + extension, pCachedResponse); - - // This will be the default version - if (!mContentCache.containsKey(pCacheURI)) { - mContentCache.put(pCacheURI, pCachedResponse); - } - } - - // Write the cached content to disk - File content = new File(mTempDir, "./" + pCacheURI + '.' + extension); - if (mDeleteCacheOnExit && !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 (mDeleteCacheOnExit && !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 (mContentCache) { -// System.out.println(" ## HTTPCache ## Looking up content with ext: \"" + extension + "\" from memory cache (" + mContentCache /*.size()*/ + " entries)..."); - if ("ANY".equals(extension)) { - response = mContentCache.get(pCacheURI); - } - else { - response = mContentCache.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)); - LinkedMap> headerMap = new LinkedMap>(); - 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(STATUS_OK, headerMap, headerSize, contents); - mContentCache.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 " + NetUtil.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 + mDefaultExpiryTime; - //// System.out.println(" ## HTTPCache ## Expires is " + NetUtil.formatHTTPDate(expires) + ", using lastModified + defaultExpiry"); - } - else { - expires = lastModified + (maxAge * 1000L); // max-age is seconds - //// System.out.println(" ## HTTPCache ## Expires is " + NetUtil.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: " - // + NetUtil.formatHTTPDate(expires) + " before " + NetUtil.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 " + NetUtil.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" - // + NetUtil.formatHTTPDate(lastModified) + " before " + NetUtil.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 = NetUtil.parseHTTPDate(pHeaderValue); - } - return date; - } - - // TODO: Extract and make public? - final static class SizedLRUMap extends LRUHashMap { - int mSize; - int mMaxSize; - - public SizedLRUMap(int pMaxSize) { - //super(true); - super(); // Note: super.mMaxSize doesn't count... - mMaxSize = 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) { - mSize += sizeOf(pValue); - - V old = super.put(pKey, pValue); - if (old != null) { - mSize -= sizeOf(old); - } - return old; - } - - @Override - public V remove(Object pKey) { - V old = super.remove(pKey); - if (old != null) { - mSize -= sizeOf(old); - } - return old; - } - - @Override - protected boolean removeEldestEntry(Map.Entry pEldest) { - if (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size - removeLRU(); - } - return false; - } - - @Override - public void removeLRU() { - while (mMaxSize <= mSize) { // NOTE: mMaxSize 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.net.MIMEUtil; +import com.twelvemonkeys.net.NetUtil; +import com.twelvemonkeys.util.LRUHashMap; +import com.twelvemonkeys.util.LinkedMap; +import com.twelvemonkeys.util.NullMap; + +import javax.servlet.ServletContext; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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"; + + protected static final int STATUS_OK = 200; + + /** + * The directory used for the disk-based cache + */ + private File mTempDir; + + /** + * Indicates wether the disk-based cache should be deleted when the + * container shuts down/VM exits + */ + private boolean mDeleteCacheOnExit; + + /** + * In-memory content cache + */ + private final Map mContentCache; + /** + * In-memory enity cache + */ + private final Map mEntityCache; + /** + * In-memory varyiation-info cache + */ + private final Map mVaryCache; + + private long mDefaultExpiryTime = -1; + + private final Logger mLogger; + + // 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 + ) { + if (pTempFolder == null) { + throw new IllegalArgumentException("temp folder == null"); + } + if (!pTempFolder.exists() && !pTempFolder.mkdirs()) { + throw new IllegalArgumentException("Could not create required temp directory: " + mTempDir.getAbsolutePath()); + } + if (!(pTempFolder.canRead() && pTempFolder.canWrite())) { + throw new IllegalArgumentException("Must have read/write access to temp folder: " + mTempDir.getAbsolutePath()); + } + if (pDefaultCacheExpiryTime < 0) { + throw new IllegalArgumentException("Negative expiry time"); + } + if (pMaxMemCacheSize < 0) { + throw new IllegalArgumentException("Negative maximum memory cache size"); + } + if (pMaxCachedEntites < 0) { + throw new IllegalArgumentException("Negative maximum number of cached entries"); + } + + mDefaultExpiryTime = pDefaultCacheExpiryTime; + + if (pMaxMemCacheSize > 0) { +// Map backing = new SizedLRUMap(pMaxMemCacheSize); // size in bytes +// mContentCache = new TimeoutMap(backing, null, pDefaultCacheExpiryTime); + mContentCache = new SizedLRUMap(pMaxMemCacheSize); // size in bytes + } + else { + mContentCache = new NullMap(); + } + + mEntityCache = new LRUHashMap(pMaxCachedEntites); + mVaryCache = new LRUHashMap(pMaxCachedEntites); + + mDeleteCacheOnExit = pDeleteCacheOnExit; + + mTempDir = pTempFolder; + + mLogger = 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) { + if (pName == null) { + throw new IllegalArgumentException("name == null"); + } + if (pName.trim().length() == 0) { + throw new IllegalArgumentException("Empty name"); + } + if (pContext == null) { + throw new IllegalArgumentException("servlet context == null"); + } + 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(mTempDir.getAbsolutePath()); + if (mDeleteCacheOnExit) { + buf.append(" (non-persistent)"); + } + else { + buf.append(" (persistent)"); + } + buf.append(", EntityCache: {"); + buf.append(mEntityCache.size()); + buf.append(" entries in a "); + buf.append(mEntityCache.getClass().getName()); + buf.append("}, VaryCache: {"); + buf.append(mVaryCache.size()); + buf.append(" entries in a "); + buf.append(mVaryCache.getClass().getName()); + buf.append("}, ContentCache: {"); + buf.append(mContentCache.size()); + buf.append(" entries in a "); + buf.append(mContentCache.getClass().getName()); + buf.append("}]"); + + return buf.toString(); + } + + void log(final String pMessage) { + mLogger.log(Level.INFO, pMessage); + } + + void log(final String pMessage, Throwable pException) { + mLogger.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 (mEntityCache) { + cached = mEntityCache.get(cacheURI); + if (cached == null) { + cached = new CachedEntityImpl(cacheURI, this); + mEntityCache.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 (mEntityCache) { + cached = mEntityCache.get(cacheURI); + if (cached != null) { + // TODO; Remove all variants + mEntityCache.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() != STATUS_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(mTempDir, "./" + 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() && mDeleteCacheOnExit) { + 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 (mVaryCache) { + variations = mVaryCache.get(pCacheURI); + if (variations == null) { + variations = loadVaryProperties(pCacheURI); + mVaryCache.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(mTempDir, "./" + 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 (mContentCache) { + mContentCache.put(pCacheURI + '.' + extension, pCachedResponse); + + // This will be the default version + if (!mContentCache.containsKey(pCacheURI)) { + mContentCache.put(pCacheURI, pCachedResponse); + } + } + + // Write the cached content to disk + File content = new File(mTempDir, "./" + pCacheURI + '.' + extension); + if (mDeleteCacheOnExit && !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 (mDeleteCacheOnExit && !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 (mContentCache) { +// System.out.println(" ## HTTPCache ## Looking up content with ext: \"" + extension + "\" from memory cache (" + mContentCache /*.size()*/ + " entries)..."); + if ("ANY".equals(extension)) { + response = mContentCache.get(pCacheURI); + } + else { + response = mContentCache.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)); + LinkedMap> headerMap = new LinkedMap>(); + 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(STATUS_OK, headerMap, headerSize, contents); + mContentCache.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 " + NetUtil.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 + mDefaultExpiryTime; + //// System.out.println(" ## HTTPCache ## Expires is " + NetUtil.formatHTTPDate(expires) + ", using lastModified + defaultExpiry"); + } + else { + expires = lastModified + (maxAge * 1000L); // max-age is seconds + //// System.out.println(" ## HTTPCache ## Expires is " + NetUtil.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: " + // + NetUtil.formatHTTPDate(expires) + " before " + NetUtil.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 " + NetUtil.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" + // + NetUtil.formatHTTPDate(lastModified) + " before " + NetUtil.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 = NetUtil.parseHTTPDate(pHeaderValue); + } + return date; + } + + // TODO: Extract and make public? + final static class SizedLRUMap extends LRUHashMap { + int mSize; + int mMaxSize; + + public SizedLRUMap(int pMaxSize) { + //super(true); + super(); // Note: super.mMaxSize doesn't count... + mMaxSize = 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) { + mSize += sizeOf(pValue); + + V old = super.put(pKey, pValue); + if (old != null) { + mSize -= sizeOf(old); + } + return old; + } + + @Override + public V remove(Object pKey) { + V old = super.remove(pKey); + if (old != null) { + mSize -= sizeOf(old); + } + return old; + } + + @Override + protected boolean removeEldestEntry(Map.Entry pEldest) { + if (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size + removeLRU(); + } + return false; + } + + @Override + public void removeLRU() { + while (mMaxSize <= mSize) { // NOTE: mMaxSize here is mem size + super.removeLRU(); + } + } + } + } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java index 22b91de4..44ec91f5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java +++ b/twelvemonkeys-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.NetUtil; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java#2 $ - */ -class SerlvetCacheResponseWrapper extends HttpServletResponseWrapper { - private ServletResponseStreamDelegate mStreamDelegate; - - private CacheResponse mCacheResponse; - - private Boolean mCachable; - private int mStatus; - - public SerlvetCacheResponseWrapper(final HttpServletResponse pServletResponse, final CacheResponse pResponse) { - super(pServletResponse); - mCacheResponse = pResponse; - init(); - } - - - /* - NOTE: This class defers determining if a response is cachable until the - output stream is needed. - This it the reason for the somewhat complicated logic in the add/setHeader - methods below. - */ - private void init() { - mCachable = null; - mStatus = SC_OK; - mStreamDelegate = new ServletResponseStreamDelegate(this) { - protected OutputStream createOutputStream() throws IOException { - // Test if this request is really cachable, otherwise, - // just write through to underlying response, and don't cache - if (isCachable()) { - return mCacheResponse.getOutputStream(); - } - else { - // TODO: We need to tell the cache about this, somehow... - writeHeaders(mCacheResponse, (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 isCachable() { - // NOTE: Intentionally not synchronized - if (mCachable == null) { - mCachable = isCachableImpl(); - } - - return mCachable; - } - - private boolean isCachableImpl() { - // TODO: This code is duped in the cache... - if (mStatus != SC_OK) { - return false; - } - - // Vary: * - List values = mCacheResponse.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 = mCacheResponse.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 = mCacheResponse.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 { - mStreamDelegate.flushBuffer(); - } - - public void resetBuffer() { - // Servlet 2.3 - mStreamDelegate.resetBuffer(); - } - - public void reset() { - if (Boolean.FALSE.equals(mCachable)) { - super.reset(); - } - // No else, might be cachable after all.. - init(); - } - - public ServletOutputStream getOutputStream() throws IOException { - return mStreamDelegate.getOutputStream(); - } - - public PrintWriter getWriter() throws IOException { - return mStreamDelegate.getWriter(); - } - - public boolean containsHeader(String name) { - return mCacheResponse.getHeaders().get(name) != null; - } - - public void sendError(int pStatusCode, String msg) throws IOException { - // NOT cachable - mStatus = pStatusCode; - super.sendError(pStatusCode, msg); - } - - public void sendError(int pStatusCode) throws IOException { - // NOT cachable - mStatus = pStatusCode; - super.sendError(pStatusCode); - } - - public void setStatus(int pStatusCode, String sm) { - // NOTE: This method is deprecated - setStatus(pStatusCode); - } - - public void setStatus(int pStatusCode) { - // NOT cachable unless pStatusCode == 200 (or a FEW others?) - if (pStatusCode != SC_OK) { - mStatus = pStatusCode; - super.setStatus(pStatusCode); - } - } - - public void sendRedirect(String pLocation) throws IOException { - // NOT cachable - mStatus = 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(mCachable)) { - super.setDateHeader(pName, pValue); - } - mCacheResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue)); - } - - public void addDateHeader(String pName, long pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addDateHeader(pName, pValue); - } - mCacheResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue)); - } - - public void setHeader(String pName, String pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.setHeader(pName, pValue); - } - mCacheResponse.setHeader(pName, pValue); - } - - public void addHeader(String pName, String pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addHeader(pName, pValue); - } - mCacheResponse.addHeader(pName, pValue); - } - - public void setIntHeader(String pName, int pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.setIntHeader(pName, pValue); - } - mCacheResponse.setHeader(pName, String.valueOf(pValue)); - } - - public void addIntHeader(String pName, int pValue) { - // If in write-trough-mode, set headers directly - if (Boolean.FALSE.equals(mCachable)) { - super.addIntHeader(pName, pValue); - } - mCacheResponse.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.NetUtil; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/SerlvetCacheResponseWrapper.java#2 $ + */ +class SerlvetCacheResponseWrapper extends HttpServletResponseWrapper { + private ServletResponseStreamDelegate mStreamDelegate; + + private CacheResponse mCacheResponse; + + private Boolean mCachable; + private int mStatus; + + public SerlvetCacheResponseWrapper(final HttpServletResponse pServletResponse, final CacheResponse pResponse) { + super(pServletResponse); + mCacheResponse = pResponse; + init(); + } + + + /* + NOTE: This class defers determining if a response is cachable until the + output stream is needed. + This it the reason for the somewhat complicated logic in the add/setHeader + methods below. + */ + private void init() { + mCachable = null; + mStatus = SC_OK; + mStreamDelegate = new ServletResponseStreamDelegate(this) { + protected OutputStream createOutputStream() throws IOException { + // Test if this request is really cachable, otherwise, + // just write through to underlying response, and don't cache + if (isCachable()) { + return mCacheResponse.getOutputStream(); + } + else { + // TODO: We need to tell the cache about this, somehow... + writeHeaders(mCacheResponse, (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 isCachable() { + // NOTE: Intentionally not synchronized + if (mCachable == null) { + mCachable = isCachableImpl(); + } + + return mCachable; + } + + private boolean isCachableImpl() { + // TODO: This code is duped in the cache... + if (mStatus != SC_OK) { + return false; + } + + // Vary: * + List values = mCacheResponse.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 = mCacheResponse.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 = mCacheResponse.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 { + mStreamDelegate.flushBuffer(); + } + + public void resetBuffer() { + // Servlet 2.3 + mStreamDelegate.resetBuffer(); + } + + public void reset() { + if (Boolean.FALSE.equals(mCachable)) { + super.reset(); + } + // No else, might be cachable after all.. + init(); + } + + public ServletOutputStream getOutputStream() throws IOException { + return mStreamDelegate.getOutputStream(); + } + + public PrintWriter getWriter() throws IOException { + return mStreamDelegate.getWriter(); + } + + public boolean containsHeader(String name) { + return mCacheResponse.getHeaders().get(name) != null; + } + + public void sendError(int pStatusCode, String msg) throws IOException { + // NOT cachable + mStatus = pStatusCode; + super.sendError(pStatusCode, msg); + } + + public void sendError(int pStatusCode) throws IOException { + // NOT cachable + mStatus = pStatusCode; + super.sendError(pStatusCode); + } + + public void setStatus(int pStatusCode, String sm) { + // NOTE: This method is deprecated + setStatus(pStatusCode); + } + + public void setStatus(int pStatusCode) { + // NOT cachable unless pStatusCode == 200 (or a FEW others?) + if (pStatusCode != SC_OK) { + mStatus = pStatusCode; + super.setStatus(pStatusCode); + } + } + + public void sendRedirect(String pLocation) throws IOException { + // NOT cachable + mStatus = 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(mCachable)) { + super.setDateHeader(pName, pValue); + } + mCacheResponse.setHeader(pName, NetUtil.formatHTTPDate(pValue)); + } + + public void addDateHeader(String pName, long pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addDateHeader(pName, pValue); + } + mCacheResponse.addHeader(pName, NetUtil.formatHTTPDate(pValue)); + } + + public void setHeader(String pName, String pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.setHeader(pName, pValue); + } + mCacheResponse.setHeader(pName, pValue); + } + + public void addHeader(String pName, String pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addHeader(pName, pValue); + } + mCacheResponse.addHeader(pName, pValue); + } + + public void setIntHeader(String pName, int pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.setIntHeader(pName, pValue); + } + mCacheResponse.setHeader(pName, String.valueOf(pValue)); + } + + public void addIntHeader(String pName, int pValue) { + // If in write-trough-mode, set headers directly + if (Boolean.FALSE.equals(mCachable)) { + super.addIntHeader(pName, pValue); + } + mCacheResponse.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java index c6ce7e6a..eecee196 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponse.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java index ffeb54b5..e350568e 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java @@ -1,188 +1,188 @@ -/* - * 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.NetUtil; - -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java#3 $ - */ -class WritableCachedResponseImpl implements WritableCachedResponse { - private final CachedResponseImpl mCachedResponse; - - /** - * Creates a {@code WritableCachedResponseImpl}. - */ - protected WritableCachedResponseImpl() { - mCachedResponse = new CachedResponseImpl(); - // Hmmm.. - setHeader(HTTPCache.HEADER_CACHED_TIME, NetUtil.formatHTTPDate(System.currentTimeMillis())); - } - - public CachedResponse getCachedResponse() { - return mCachedResponse; - } - - 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 mCachedResponse.mHeaders; - } - - /** - * - * @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 = null; - if (pAdd) { - values = mCachedResponse.mHeaders.get(pName); - } - - if (values == null) { - values = new ArrayList(); - - if (pAdd) { - // Add length of pName - mCachedResponse.mHeadersSize += (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) { - mCachedResponse.mHeadersSize -= oldValue.length(); - } - } - else { - mCachedResponse.mHeadersSize += (pName != null ? pName.length() : 0); - } - } - } - - // Add value, if not null - if (pValue != null) { - values.add(pValue); - - // Add length of pValue - mCachedResponse.mHeadersSize += pValue.length(); - } - - // Always add to headers - mCachedResponse.mHeaders.put(pName, values); - } - - public OutputStream getOutputStream() { - // TODO: Hmm.. Smells like DCL..? - if (mCachedResponse.mContent == null) { - createOutputStream(); - } - return mCachedResponse.mContent; - } - - public void setStatus(int pStatusCode) { - mCachedResponse.mStatus = pStatusCode; - } - - public int getStatus() { - return mCachedResponse.getStatus(); - } - - private synchronized void createOutputStream() { - ByteArrayOutputStream cache = mCachedResponse.mContent; - 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); - } - mCachedResponse.mContent = cache; - } - } - - public void writeHeadersTo(CacheResponse pResponse) { - mCachedResponse.writeHeadersTo(pResponse); - } - - public void writeContentsTo(OutputStream pStream) throws IOException { - mCachedResponse.writeContentsTo(pStream); - } - - public String[] getHeaderNames() { - return mCachedResponse.getHeaderNames(); - } - - public String[] getHeaderValues(String pHeaderName) { - return mCachedResponse.getHeaderValues(pHeaderName); - } - - public String getHeaderValue(String pHeaderName) { - return mCachedResponse.getHeaderValue(pHeaderName); - } - - public int size() { - return mCachedResponse.size(); - } - - public boolean equals(Object pOther) { - if (pOther instanceof WritableCachedResponse) { - // Take advantage of faster implementation - return mCachedResponse.equals(((WritableCachedResponse) pOther).getCachedResponse()); - } - return mCachedResponse.equals(pOther); - } - - public int hashCode() { - return mCachedResponse.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.NetUtil; + +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/cache/WritableCachedResponseImpl.java#3 $ + */ +class WritableCachedResponseImpl implements WritableCachedResponse { + private final CachedResponseImpl mCachedResponse; + + /** + * Creates a {@code WritableCachedResponseImpl}. + */ + protected WritableCachedResponseImpl() { + mCachedResponse = new CachedResponseImpl(); + // Hmmm.. + setHeader(HTTPCache.HEADER_CACHED_TIME, NetUtil.formatHTTPDate(System.currentTimeMillis())); + } + + public CachedResponse getCachedResponse() { + return mCachedResponse; + } + + 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 mCachedResponse.mHeaders; + } + + /** + * + * @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 = null; + if (pAdd) { + values = mCachedResponse.mHeaders.get(pName); + } + + if (values == null) { + values = new ArrayList(); + + if (pAdd) { + // Add length of pName + mCachedResponse.mHeadersSize += (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) { + mCachedResponse.mHeadersSize -= oldValue.length(); + } + } + else { + mCachedResponse.mHeadersSize += (pName != null ? pName.length() : 0); + } + } + } + + // Add value, if not null + if (pValue != null) { + values.add(pValue); + + // Add length of pValue + mCachedResponse.mHeadersSize += pValue.length(); + } + + // Always add to headers + mCachedResponse.mHeaders.put(pName, values); + } + + public OutputStream getOutputStream() { + // TODO: Hmm.. Smells like DCL..? + if (mCachedResponse.mContent == null) { + createOutputStream(); + } + return mCachedResponse.mContent; + } + + public void setStatus(int pStatusCode) { + mCachedResponse.mStatus = pStatusCode; + } + + public int getStatus() { + return mCachedResponse.getStatus(); + } + + private synchronized void createOutputStream() { + ByteArrayOutputStream cache = mCachedResponse.mContent; + 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); + } + mCachedResponse.mContent = cache; + } + } + + public void writeHeadersTo(CacheResponse pResponse) { + mCachedResponse.writeHeadersTo(pResponse); + } + + public void writeContentsTo(OutputStream pStream) throws IOException { + mCachedResponse.writeContentsTo(pStream); + } + + public String[] getHeaderNames() { + return mCachedResponse.getHeaderNames(); + } + + public String[] getHeaderValues(String pHeaderName) { + return mCachedResponse.getHeaderValues(pHeaderName); + } + + public String getHeaderValue(String pHeaderName) { + return mCachedResponse.getHeaderValue(pHeaderName); + } + + public int size() { + return mCachedResponse.size(); + } + + public boolean equals(Object pOther) { + if (pOther instanceof WritableCachedResponse) { + // Take advantage of faster implementation + return mCachedResponse.equals(((WritableCachedResponse) pOther).getCachedResponse()); + } + return mCachedResponse.equals(pOther); + } + + public int hashCode() { + return mCachedResponse.hashCode(); + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java index c55090cc..09a9d35f 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileSizeExceededException.java#1 $ + */ +public class FileSizeExceededException extends FileUploadException { + public FileSizeExceededException(Throwable pCause) { + super(pCause.getMessage(), pCause); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java index fd8175e8..a02bd7b0 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadException.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java index c4176d0d..05118a8d 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java @@ -1,137 +1,137 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java#1 $ - */ -public class FileUploadFilter extends GenericFilter { - private File mUploadDir; - private long mMaxFileSize = 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); - mUploadDir = FileUtil.toFile(uploadDirURL); - } - catch (MalformedURLException e) { - throw new ServletException(e.getMessage(), e); - } - } - if (mUploadDir == null) { - mUploadDir = ServletUtil.getTempDir(getServletContext()); - } - } - - /** - * Sets max filesize allowed for upload. - * - * - * @param pMaxSize - */ -// public void setMaxFileSize(String pMaxSize) { -// try { -// setMaxFileSize(Long.parseLong(pMaxSize)); -// } -// catch (NumberFormatException e) { -// log("Error setting maxFileSize, using default: " + mMaxFileSize, e); -// } -// } - - /** - * Sets max filesize allowed for upload. - * - * @param pMaxSize - */ - public void setMaxFileSize(long pMaxSize) { - log("maxFileSize=" + pMaxSize); - mMaxFileSize = 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, mUploadDir, mMaxFileSize); - } - - 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/FileUploadFilter.java#1 $ + */ +public class FileUploadFilter extends GenericFilter { + private File mUploadDir; + private long mMaxFileSize = 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); + mUploadDir = FileUtil.toFile(uploadDirURL); + } + catch (MalformedURLException e) { + throw new ServletException(e.getMessage(), e); + } + } + if (mUploadDir == null) { + mUploadDir = ServletUtil.getTempDir(getServletContext()); + } + } + + /** + * Sets max filesize allowed for upload. + * + * + * @param pMaxSize + */ +// public void setMaxFileSize(String pMaxSize) { +// try { +// setMaxFileSize(Long.parseLong(pMaxSize)); +// } +// catch (NumberFormatException e) { +// log("Error setting maxFileSize, using default: " + mMaxFileSize, e); +// } +// } + + /** + * Sets max filesize allowed for upload. + * + * @param pMaxSize + */ + public void setMaxFileSize(long pMaxSize) { + log("maxFileSize=" + pMaxSize); + mMaxFileSize = 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, mUploadDir, mMaxFileSize); + } + + pChain.doFilter(pRequest, pResponse); + } + + private boolean isMultipartFileUpload(String pContentType) { + return pContentType != null && pContentType.startsWith("multipart/"); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java index 92a3e990..6d40a323 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequest.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java index 1a371ceb..8f4b3447 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java#1 $ - */ -class HttpFileUploadRequestWrapper extends HttpServletRequestWrapper implements HttpFileUploadRequest { - - private final Map mParameters = new HashMap(); - private final Map mFiles = 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 = mFiles.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}; - } - - mFiles.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 = mParameters.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}; - } - - mParameters.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(mParameters); - } - - public Enumeration getParameterNames() { - return Collections.enumeration(mParameters.keySet()); - } - - public String getParameter(String pString) { - String[] values = getParameterValues(pString); - return values != null ? values[0] : null; - } - - public String[] getParameterValues(String pString) { - // TODO: Optimize? - return mParameters.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 mFiles.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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/HttpFileUploadRequestWrapper.java#1 $ + */ +class HttpFileUploadRequestWrapper extends HttpServletRequestWrapper implements HttpFileUploadRequest { + + private final Map mParameters = new HashMap(); + private final Map mFiles = 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 = mFiles.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}; + } + + mFiles.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 = mParameters.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}; + } + + mParameters.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(mParameters); + } + + public Enumeration getParameterNames() { + return Collections.enumeration(mParameters.keySet()); + } + + public String getParameter(String pString) { + String[] values = getParameterValues(pString); + return values != null ? values[0] : null; + } + + public String[] getParameterValues(String pString) { + // TODO: Optimize? + return mParameters.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 mFiles.get(pName).clone(); + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java index f4c1e6df..4a11f5c3 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFile.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java index 5c19cd98..bcb2eda5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java#1 $ - */ -class UploadedFileImpl implements UploadedFile { - private final FileItem mItem; - - public UploadedFileImpl(FileItem pItem) { - if (pItem == null) { - throw new IllegalArgumentException("fileitem == null"); - } - - mItem = pItem; - } - - public String getContentType() { - return mItem.getContentType(); - } - - public InputStream getInputStream() throws IOException { - return mItem.getInputStream(); - } - - public String getName() { - return mItem.getName(); - } - - public long length() { - return mItem.getSize(); - } - - public void writeTo(File pFile) throws IOException { - try { - mItem.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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/fileupload/UploadedFileImpl.java#1 $ + */ +class UploadedFileImpl implements UploadedFile { + private final FileItem mItem; + + public UploadedFileImpl(FileItem pItem) { + if (pItem == null) { + throw new IllegalArgumentException("fileitem == null"); + } + + mItem = pItem; + } + + public String getContentType() { + return mItem.getContentType(); + } + + public InputStream getInputStream() throws IOException { + return mItem.getInputStream(); + } + + public String getName() { + return mItem.getName(); + } + + public long length() { + return mItem.getSize(); + } + + public void writeTo(File pFile) throws IOException { + try { + mItem.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java index 1fbe64ff..949e8657 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java#1 $ - */ -public class GZIPFilter extends GenericFilter { - - { - mOncePerRequest = 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.indexOf("gzip") != -1) { - //System.out.println("GZIP supported, compressing."); - // TODO: Set Vary: Accept-Encoding ?! - - GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response); - try { - pChain.doFilter(pRequest, wrapped); - } - finally { - wrapped.flushResponse(); - } - return; - } - } - - // Else, contiue 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPFilter.java#1 $ + */ +public class GZIPFilter extends GenericFilter { + + { + mOncePerRequest = 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.indexOf("gzip") != -1) { + //System.out.println("GZIP supported, compressing."); + // TODO: Set Vary: Accept-Encoding ?! + + GZIPResponseWrapper wrapped = new GZIPResponseWrapper(response); + try { + pChain.doFilter(pRequest, wrapped); + } + finally { + wrapped.flushResponse(); + } + return; + } + } + + // Else, contiue chain + pChain.doFilter(pRequest, pResponse); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java index 60579c9f..4555d6bd 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java @@ -1,146 +1,146 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java#1 $ - */ -public class GZIPResponseWrapper extends HttpServletResponseWrapper { - protected ServletOutputStream mOut = null; - protected PrintWriter mWriter = null; - protected GZIPOutputStream mGZIPOut = null; - protected int mContentLength = -1; - - public GZIPResponseWrapper(HttpServletResponse response) { - super(response); - response.addHeader("Content-Encoding", "gzip"); - } - - 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 (mContentLength >= 0) { - mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream(), mContentLength); - } - else { - mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream()); - } - - // Wrap in ServletOutputStream and return - return new OutputStreamAdapter(mGZIPOut); - } - - // TODO: Move this to flushbuffer or something? Hmmm.. - public void flushResponse() { - try { - try { - // Finish GZIP encodig - if (mGZIPOut != null) { - mGZIPOut.finish(); - } - - flushBuffer(); - } - finally { - // Close stream - if (mWriter != null) { - mWriter.close(); - } - else { - if (mOut != null) { - mOut.close(); - } - } - } - } - catch (IOException e) { - // TODO: Fix this one... - e.printStackTrace(); - } - } - - public void flushBuffer() throws IOException { - if (mWriter != null) { - mWriter.flush(); - } - else if (mOut != null) { - mOut.flush(); - } - } - - public ServletOutputStream getOutputStream() throws IOException { - if (mWriter != null) { - throw new IllegalStateException("getWriter() has already been called!"); - } - - if (mOut == null) { - mOut = createOutputStream(); - } - return (mOut); - } - - public PrintWriter getWriter() throws IOException { - if (mWriter != null) { - return (mWriter); - } - - if (mOut != null) { - throw new IllegalStateException("getOutputStream() has already been called!"); - } - - mOut = createOutputStream(); - // TODO: This is wrong. Should use getCharacterEncoding() or "ISO-8859-1" if gCE returns null. - mWriter = new PrintWriter(new OutputStreamWriter(mOut, "UTF-8")); - return (mWriter); - } - - public void setContentLength(int pLength) { - // NOTE: Do not call super, as we will shrink the size. - mContentLength = 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/gzip/GZIPResponseWrapper.java#1 $ + */ +public class GZIPResponseWrapper extends HttpServletResponseWrapper { + protected ServletOutputStream mOut = null; + protected PrintWriter mWriter = null; + protected GZIPOutputStream mGZIPOut = null; + protected int mContentLength = -1; + + public GZIPResponseWrapper(HttpServletResponse response) { + super(response); + response.addHeader("Content-Encoding", "gzip"); + } + + 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 (mContentLength >= 0) { + mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream(), mContentLength); + } + else { + mGZIPOut = new GZIPOutputStream(getResponse().getOutputStream()); + } + + // Wrap in ServletOutputStream and return + return new OutputStreamAdapter(mGZIPOut); + } + + // TODO: Move this to flushbuffer or something? Hmmm.. + public void flushResponse() { + try { + try { + // Finish GZIP encodig + if (mGZIPOut != null) { + mGZIPOut.finish(); + } + + flushBuffer(); + } + finally { + // Close stream + if (mWriter != null) { + mWriter.close(); + } + else { + if (mOut != null) { + mOut.close(); + } + } + } + } + catch (IOException e) { + // TODO: Fix this one... + e.printStackTrace(); + } + } + + public void flushBuffer() throws IOException { + if (mWriter != null) { + mWriter.flush(); + } + else if (mOut != null) { + mOut.flush(); + } + } + + public ServletOutputStream getOutputStream() throws IOException { + if (mWriter != null) { + throw new IllegalStateException("getWriter() has already been called!"); + } + + if (mOut == null) { + mOut = createOutputStream(); + } + return (mOut); + } + + public PrintWriter getWriter() throws IOException { + if (mWriter != null) { + return (mWriter); + } + + if (mOut != null) { + throw new IllegalStateException("getOutputStream() has already been called!"); + } + + mOut = createOutputStream(); + // TODO: This is wrong. Should use getCharacterEncoding() or "ISO-8859-1" if gCE returns null. + mWriter = new PrintWriter(new OutputStreamWriter(mOut, "UTF-8")); + return (mWriter); + } + + public void setContentLength(int pLength) { + // NOTE: Do not call super, as we will shrink the size. + mContentLength = pLength; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java index 10164832..045fedcf 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java +++ b/twelvemonkeys-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 $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java#1 $ - * - */ -public class AWTImageFilterAdapter extends ImageFilter { - - private java.awt.image.ImageFilter mFilter = null; - - public void setImageFilter(String pFilterClass) { - try { - Class filterClass = Class.forName(pFilterClass); - mFilter = (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, mFilter); - - // Create BufferedImage & return - return ImageUtil.toBuffered(img, BufferedImage.TYPE_INT_RGB); // TODO: This is 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 $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AWTImageFilterAdapter.java#1 $ + * + */ +public class AWTImageFilterAdapter extends ImageFilter { + + private java.awt.image.ImageFilter mFilter = null; + + public void setImageFilter(String pFilterClass) { + try { + Class filterClass = Class.forName(pFilterClass); + mFilter = (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, mFilter); + + // Create BufferedImage & return + return ImageUtil.toBuffered(img, BufferedImage.TYPE_INT_RGB); // TODO: This is for JPEG only... + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java index 68d4ea50..999f17f5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java +++ b/twelvemonkeys-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 $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java#1 $ - * - */ -public class BufferedImageOpAdapter extends ImageFilter { - - private BufferedImageOp mFilter = null; - - public void setImageFilter(String pFilterClass) { - try { - Class filterClass = Class.forName(pFilterClass); - mFilter = (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 mFilter.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 $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/BufferedImageOpAdapter.java#1 $ + * + */ +public class BufferedImageOpAdapter extends ImageFilter { + + private BufferedImageOp mFilter = null; + + public void setImageFilter(String pFilterClass) { + try { + Class filterClass = Class.forName(pFilterClass); + mFilter = (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 mFilter.filter(pImage, null); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java index e97833b4..539aca8d 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ColorServlet.java @@ -1,212 +1,212 @@ -/* - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 mCRC = 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 (mCRC) { - mCRC.reset(); - mCRC.update(pBytes, pOff, pLen); - value = (int) mCRC.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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 mCRC = 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 (mCRC) { + mCRC.reset(); + mCRC.update(pBytes, pOff, pLen); + value = (int) mCRC.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java index 0952c366..fc9a67e8 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ComposeFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java index 4d2996ee..37fea59f 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ContentNegotiationFilter.java @@ -1,436 +1,436 @@ -/* - * 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). - */ -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[] mKnownFormatQuality = new float[] { - 1f, 1f, 0.99f, 0.5f - }; - - private HashMap mFormatQuality; // HashMap, as I need to clone this for each request - private final Object mLock = new Object(); - - /* - private Pattern[] mKnownAgentPatterns; - private String[] mKnownAgentAccpets; - */ - { - // Hack: Make sure the filter don't trigger all the time - // See: super.trigger(ServletRequest) - mTriggerParams = 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(mLock) { - if (mFormatQuality == null) { - mFormatQuality = 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) { - mFormatQuality.put(format, getKnownFormatQuality(format)); - } - } - } - //noinspection unchecked - return (Map) mFormatQuality.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 mKnownFormatQuality[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). + */ +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[] mKnownFormatQuality = new float[] { + 1f, 1f, 0.99f, 0.5f + }; + + private HashMap mFormatQuality; // HashMap, as I need to clone this for each request + private final Object mLock = new Object(); + + /* + private Pattern[] mKnownAgentPatterns; + private String[] mKnownAgentAccpets; + */ + { + // Hack: Make sure the filter don't trigger all the time + // See: super.trigger(ServletRequest) + mTriggerParams = 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(mLock) { + if (mFormatQuality == null) { + mFormatQuality = 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) { + mFormatQuality.put(format, getKnownFormatQuality(format)); + } + } + } + //noinspection unchecked + return (Map) mFormatQuality.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 mKnownFormatQuality[i]; + } + } + return 0.1f; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java index e3a7bdbf..13edab1b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/CropFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java index 04978301..7b716c9e 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java @@ -1,199 +1,199 @@ -/* - * 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java#2 $ - * - */ -public abstract class ImageFilter extends GenericFilter { - - protected String[] mTriggerParams = null; - - /** - * The {@code doFilterImpl} method is called once, or each time a - * request/response pair is passed through the chain, depending on the - * {@link #mOncePerRequest} member variable. - * - * @see #mOncePerRequest - * @see com.twelvemonkeys.servlet.GenericFilter#doFilterImpl doFilter - * @see Filter#doFilter Filter.doFilter - * - * @param pRequest the servlet request - * @param pResponse the servlet response - * @param pChain the filter chain - * - * @throws IOException - * @throws ServletException - */ - protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, 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 { - // For images, we do post filtering only and need to wrap the response - ImageServletResponse imageResponse; - boolean encode; - if (pResponse instanceof ImageServletResponse) { - //System.out.println("Allready ImageServletResponse"); - imageResponse = (ImageServletResponse) pResponse; - encode = false; // Allready wrapped, will be encoded later in the chain - } - else { - //System.out.println("Wrapping in ImageServletResponse"); - imageResponse = new ImageServletResponseImpl(pRequest, pResponse, getServletContext()); - encode = true; // This is first filter in chain, must encode when done - } - - //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 repsonse - 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()) + "\""; - ((ImageServletResponseImpl) imageResponse).setHeader("ETag", etag); - ((ImageServletResponseImpl) imageResponse).setDateHeader("Last-Modified", (System.currentTimeMillis() / 1000) * 1000); - imageResponse.flush(); - } - //System.out.println("Done encoding."); - } - } - } - //System.out.println("Filtering done."); - } - - /** - * Tests if the filter should do image filtering/processing. - *

- * This default implementation uses {@link #mTriggerParams} 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(ServletRequest pRequest) { - // If triggerParams not set, assume always trigger - if (mTriggerParams == null) { - return true; - } - - // Trigger only for certain request parameters - for (String triggerParam : mTriggerParams) { - 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. - */ - public void setTriggerParams(String pTriggerParams) { - mTriggerParams = 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 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageFilter.java#2 $ + * + */ +public abstract class ImageFilter extends GenericFilter { + + protected String[] mTriggerParams = null; + + /** + * The {@code doFilterImpl} method is called once, or each time a + * request/response pair is passed through the chain, depending on the + * {@link #mOncePerRequest} member variable. + * + * @see #mOncePerRequest + * @see com.twelvemonkeys.servlet.GenericFilter#doFilterImpl doFilter + * @see Filter#doFilter Filter.doFilter + * + * @param pRequest the servlet request + * @param pResponse the servlet response + * @param pChain the filter chain + * + * @throws IOException + * @throws ServletException + */ + protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, 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 { + // For images, we do post filtering only and need to wrap the response + ImageServletResponse imageResponse; + boolean encode; + if (pResponse instanceof ImageServletResponse) { + //System.out.println("Allready ImageServletResponse"); + imageResponse = (ImageServletResponse) pResponse; + encode = false; // Allready wrapped, will be encoded later in the chain + } + else { + //System.out.println("Wrapping in ImageServletResponse"); + imageResponse = new ImageServletResponseImpl(pRequest, pResponse, getServletContext()); + encode = true; // This is first filter in chain, must encode when done + } + + //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 repsonse + 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()) + "\""; + ((ImageServletResponseImpl) imageResponse).setHeader("ETag", etag); + ((ImageServletResponseImpl) imageResponse).setDateHeader("Last-Modified", (System.currentTimeMillis() / 1000) * 1000); + imageResponse.flush(); + } + //System.out.println("Done encoding."); + } + } + } + //System.out.println("Filtering done."); + } + + /** + * Tests if the filter should do image filtering/processing. + *

+ * This default implementation uses {@link #mTriggerParams} 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(ServletRequest pRequest) { + // If triggerParams not set, assume always trigger + if (mTriggerParams == null) { + return true; + } + + // Trigger only for certain request parameters + for (String triggerParam : mTriggerParams) { + 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. + */ + public void setTriggerParams(String pTriggerParams) { + mTriggerParams = 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java index 6e4de253..6587c8cb 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletException.java +++ b/twelvemonkeys-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 excpetion is a subclass of ServletException, and acts just as a marker - * for excpetions thrown by the ImageServlet API. - * - * @author Harald Kuhr - * @author last modified by $Author: haku $ - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 excpetion is a subclass of ServletException, and acts just as a marker + * for excpetions thrown by the ImageServlet API. + * + * @author Harald Kuhr + * @author last modified by $Author: haku $ + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java index 772bd548..557c07cd 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponse.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java index 3a023428..bc3b3f04 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java @@ -1,735 +1,735 @@ -/* - * 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 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 automtically handles writing the image back to the underlying response stream - * in the preferred format, when the response is flushed. - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java#10 $ - * - */ -// TODO: Refactor out HTTP specifcs (if possible). -// TODO: Is it a good ide to throw IIOException? -class ImageServletResponseImpl extends HttpServletResponseWrapper implements ImageServletResponse { - - private final ServletRequest mOriginalRequest; - private final ServletContext mContext; - private final ServletResponseStreamDelegate mStreamDelegate; - - private FastByteArrayOutputStream mBufferedOut; - - private RenderedImage mImage; - private String mOutputContentType; - - private String mOriginalContentType; - private int mOriginalContentLength = -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); - mOriginalRequest = pRequest; - mStreamDelegate = new ServletResponseStreamDelegate(pResponse) { - @Override - protected OutputStream createOutputStream() throws IOException { - if (mOriginalContentLength >= 0) { - mBufferedOut = new FastByteArrayOutputStream(mOriginalContentLength); - } - else { - mBufferedOut = new FastByteArrayOutputStream(0); - } - - return mBufferedOut; - } - }; - mContext = 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 (mOriginalContentType != null) { - throw new IllegalStateException("ContentType already set."); - } - - mOriginalContentType = pMimeType; - } - - /** - * Called by the container. Do not invoke. - * - * @return the response's {@code OutputStream} - * @throws IOException - */ - public ServletOutputStream getOutputStream() throws IOException { - return mStreamDelegate.getOutputStream(); - } - - /** - * Called by the container. Do not invoke. - * - * @return the response's {@code PrintWriter} - * @throws IOException - */ - public PrintWriter getWriter() throws IOException { - return mStreamDelegate.getWriter(); - } - - /** - * Called by the container. Do not invoke. - * - * @param pLength the content length - */ - public void setContentLength(final int pLength) { - if (mOriginalContentLength != -1) { - throw new IllegalStateException("ContentLength already set."); - } - - mOriginalContentLength = pLength; - } - - /** - * 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.equals(mOriginalContentType)) { - getImage(); - } - - // For known formats that don't support transparency, convert to opaque - if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType) - || "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) && - mImage.getColorModel().getTransparency() != Transparency.OPAQUE) { - mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB); - } - - if (mImage != null) { - Iterator writers = ImageIO.getImageWritersByMIMEType(outputType); - if (writers.hasNext()) { - super.setContentType(outputType); - OutputStream out = super.getOutputStream(); - - ImageWriter writer = (ImageWriter) writers.next(); - try { - ImageWriteParam param = writer.getDefaultWriteParam(); - - Float requestQuality = (Float) mOriginalRequest.getAttribute(ImageServletResponse.ATTRIB_OUTPUT_QUALITY); - - // The default JPEG quality is not good enough, so always apply compression - if ((requestQuality != null || "jpeg".equalsIgnoreCase(getFormatNameSafe(writer))) && param.canWriteCompressed()) { - param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - param.setCompressionQuality(requestQuality != null ? requestQuality : 0.8f); - } - - ImageOutputStream stream = ImageIO.createImageOutputStream(out); - - writer.setOutput(stream); - try { - writer.write(null, new IIOImage(mImage, null, null), param); - } - finally { - stream.close(); - } - } - finally { - writer.dispose(); - out.flush(); - } - } - else { - mContext.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(mOriginalContentType); - ServletOutputStream out = super.getOutputStream(); - try { - mBufferedOut.writeTo(out); - } - finally { - out.flush(); - } - } - } - - private String getFormatNameSafe(final ImageWriter pWriter) { - try { - return pWriter.getOriginatingProvider().getFormatNames()[0]; - } - catch (RuntimeException e) { - // NPE, AIOOBE, etc.. - return null; - } - } - - public String getOutputContentType() { - return mOutputContentType != null ? mOutputContentType : mOriginalContentType; - } - - public void setOutputContentType(final String pImageFormat) { - mOutputContentType = 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) { - mImage = 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 (mImage == null) { - // No content, no image - if (mBufferedOut == null) { - return null; - } - - // Read from the byte buffer - InputStream byteStream = mBufferedOut.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); - - // Extract AOI from request - Rectangle aoi = extractAOIFromRequest(originalWidth, originalHeight); - if (aoi != null) { - param.setSourceRegion(aoi); - originalWidth = aoi.width; - originalHeight = aoi.height; - } - - // If possible, extract size from request - Dimension size = extractSizeFromRequest(originalWidth, originalHeight); - double readSubSamplingFactor = getReadSubsampleFactorFromRequest(); - if (size != null) { - //System.out.println("Size: " + size); - if (param.canSetSourceRenderSize()) { - param.setSourceRenderSize(size); - } - else { - int subX = (int) Math.max(originalWidth / (double) (size.width * readSubSamplingFactor), 1.0); - int subY = (int) Math.max(originalHeight / (double) (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); - - // If reader doesn't support dynamic sizing, scale now - if (image != null && size != null - && (image.getWidth() != size.width || image.getHeight() != size.height)) { - - int resampleAlgorithm = getResampleAlgorithmFromRequest(); - // NOTE: Only use createScaled if IndexColorModel, - // as it's more expensive due to color conversion - if (image.getColorModel() instanceof IndexColorModel) { - image = ImageUtil.createScaled(image, size.width, size.height, resampleAlgorithm); - } - else { - image = ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm); - } - } - - // Fill bgcolor behind image, if transparent - extractAndSetBackgroundColor(image); - - // Set image - mImage = image; - } - finally { - reader.dispose(); - } - } - else { - mContext.log("ERROR: No suitable image reader found (content-type: " + mOriginalContentType + ")."); - mContext.log("ERROR: Available formats: " + getFormatsString()); - - throw new IIOException("Unable to transcode image: No suitable image reader found (content-type: " + mOriginalContentType + ")."); - } - - // Free resources, as the image is now either read, or unreadable - mBufferedOut = null; - } - finally { - if (input != null) { - input.close(); - } - } - } - - // Image is usually a BufferedImage, but may also be a RenderedImage - return mImage != null ? ImageUtil.toBuffered(mImage) : null; - } - - private int getResampleAlgorithmFromRequest() { - int resampleAlgoithm; - - Object algorithm = mOriginalRequest.getAttribute(ATTRIB_IMAGE_RESAMPLE_ALGORITHM); - if (algorithm instanceof Integer && ((Integer) algorithm == Image.SCALE_SMOOTH || (Integer) algorithm == Image.SCALE_FAST || (Integer) algorithm == Image.SCALE_DEFAULT)) { - resampleAlgoithm = (Integer) algorithm; - } - else { - if (algorithm != null) { - mContext.log("WARN: Illegal image resampling algorithm: " + algorithm); - } - resampleAlgoithm = BufferedImage.SCALE_DEFAULT; - } - - return resampleAlgoithm; - } - - private double getReadSubsampleFactorFromRequest() { - double subsampleFactor; - - Object factor = mOriginalRequest.getAttribute(ATTRIB_READ_SUBSAMPLING_FACTOR); - if (factor instanceof Number && ((Number) factor).doubleValue() >= 1.0) { - subsampleFactor = ((Number) factor).doubleValue(); - } - else { - if (factor != null) { - mContext.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 = mOriginalRequest.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 (mOriginalRequest 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) mOriginalRequest); - - URL resourceURL = mContext.getResource(baseURI); - if (resourceURL == null) { - resourceURL = ServletUtil.getRealURL(mContext, baseURI); - } - - if (resourceURL != null) { - setBaseURI.invoke(pParam, resourceURL.toExternalForm()); - } - else { - mContext.log("WARN: Resource URL not found for URI: " + baseURI); - } - } - catch (Exception e) { - mContext.log("WARN: Could not set base URI: ", e); - } - } - } - - private Dimension extractSizeFromRequest(final int pDefaultWidth, final int pDefaultHeight) { - // TODO: Allow extraction from request parameters - /* - int sizeW = ServletUtil.getIntParameter(mOriginalRequest, "size.w", -1); - int sizeH = ServletUtil.getIntParameter(mOriginalRequest, "size.h", -1); - boolean sizePercent = ServletUtil.getBooleanParameter(mOriginalRequest, "size.percent", false); - boolean sizeUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "size.uniform", true); - */ - Dimension size = (Dimension) mOriginalRequest.getAttribute(ATTRIB_SIZE); - int sizeW = size != null ? size.width : -1; - int sizeH = size != null ? size.height : -1; - - Boolean b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_SIZE_PERCENT); - boolean sizePercent = b != null && b; // default: false - - b = (Boolean) mOriginalRequest.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) { - // TODO: Allow extraction from request parameters - /* - int aoiX = ServletUtil.getIntParameter(mOriginalRequest, "aoi.x", -1); - int aoiY = ServletUtil.getIntParameter(mOriginalRequest, "aoi.y", -1); - int aoiW = ServletUtil.getIntParameter(mOriginalRequest, "aoi.w", -1); - int aoiH = ServletUtil.getIntParameter(mOriginalRequest, "aoi.h", -1); - boolean aoiPercent = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.percent", false); - boolean aoiUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.uniform", false); - */ - Rectangle aoi = (Rectangle) mOriginalRequest.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) mOriginalRequest.getAttribute(ATTRIB_AOI_PERCENT); - boolean aoiPercent = b != null && b; // default: false - - b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_AOI_UNIFORM); - boolean aoiUniform = b != null && b; // default: false - - if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) { - aoi = getAOI(pDefaultWidth, pDefaultHeight, aoiX, aoiY, aoiW, aoiH, aoiPercent, aoiUniform); - 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 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 static Dimension getSize(int pOriginalWidth, int pOriginalHeight, - int pWidth, int pHeight, - boolean pPercent, 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 - - 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 (pUniformScale) { - 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); - } - - protected static Rectangle getAOI(int pOriginalWidth, int pOriginalHeight, - int pX, int pY, int pWidth, int pHeight, - boolean pPercent, boolean pUniform) { - // 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 - // - - 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 (pUniform) { - 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 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 automtically handles writing the image back to the underlying response stream + * in the preferred format, when the response is flushed. + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java#10 $ + * + */ +// TODO: Refactor out HTTP specifcs (if possible). +// TODO: Is it a good ide to throw IIOException? +class ImageServletResponseImpl extends HttpServletResponseWrapper implements ImageServletResponse { + + private final ServletRequest mOriginalRequest; + private final ServletContext mContext; + private final ServletResponseStreamDelegate mStreamDelegate; + + private FastByteArrayOutputStream mBufferedOut; + + private RenderedImage mImage; + private String mOutputContentType; + + private String mOriginalContentType; + private int mOriginalContentLength = -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); + mOriginalRequest = pRequest; + mStreamDelegate = new ServletResponseStreamDelegate(pResponse) { + @Override + protected OutputStream createOutputStream() throws IOException { + if (mOriginalContentLength >= 0) { + mBufferedOut = new FastByteArrayOutputStream(mOriginalContentLength); + } + else { + mBufferedOut = new FastByteArrayOutputStream(0); + } + + return mBufferedOut; + } + }; + mContext = 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 (mOriginalContentType != null) { + throw new IllegalStateException("ContentType already set."); + } + + mOriginalContentType = pMimeType; + } + + /** + * Called by the container. Do not invoke. + * + * @return the response's {@code OutputStream} + * @throws IOException + */ + public ServletOutputStream getOutputStream() throws IOException { + return mStreamDelegate.getOutputStream(); + } + + /** + * Called by the container. Do not invoke. + * + * @return the response's {@code PrintWriter} + * @throws IOException + */ + public PrintWriter getWriter() throws IOException { + return mStreamDelegate.getWriter(); + } + + /** + * Called by the container. Do not invoke. + * + * @param pLength the content length + */ + public void setContentLength(final int pLength) { + if (mOriginalContentLength != -1) { + throw new IllegalStateException("ContentLength already set."); + } + + mOriginalContentLength = pLength; + } + + /** + * 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.equals(mOriginalContentType)) { + getImage(); + } + + // For known formats that don't support transparency, convert to opaque + if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType) + || "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) && + mImage.getColorModel().getTransparency() != Transparency.OPAQUE) { + mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB); + } + + if (mImage != null) { + Iterator writers = ImageIO.getImageWritersByMIMEType(outputType); + if (writers.hasNext()) { + super.setContentType(outputType); + OutputStream out = super.getOutputStream(); + + ImageWriter writer = (ImageWriter) writers.next(); + try { + ImageWriteParam param = writer.getDefaultWriteParam(); + + Float requestQuality = (Float) mOriginalRequest.getAttribute(ImageServletResponse.ATTRIB_OUTPUT_QUALITY); + + // The default JPEG quality is not good enough, so always apply compression + if ((requestQuality != null || "jpeg".equalsIgnoreCase(getFormatNameSafe(writer))) && param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(requestQuality != null ? requestQuality : 0.8f); + } + + ImageOutputStream stream = ImageIO.createImageOutputStream(out); + + writer.setOutput(stream); + try { + writer.write(null, new IIOImage(mImage, null, null), param); + } + finally { + stream.close(); + } + } + finally { + writer.dispose(); + out.flush(); + } + } + else { + mContext.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(mOriginalContentType); + ServletOutputStream out = super.getOutputStream(); + try { + mBufferedOut.writeTo(out); + } + finally { + out.flush(); + } + } + } + + private String getFormatNameSafe(final ImageWriter pWriter) { + try { + return pWriter.getOriginatingProvider().getFormatNames()[0]; + } + catch (RuntimeException e) { + // NPE, AIOOBE, etc.. + return null; + } + } + + public String getOutputContentType() { + return mOutputContentType != null ? mOutputContentType : mOriginalContentType; + } + + public void setOutputContentType(final String pImageFormat) { + mOutputContentType = 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) { + mImage = 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 (mImage == null) { + // No content, no image + if (mBufferedOut == null) { + return null; + } + + // Read from the byte buffer + InputStream byteStream = mBufferedOut.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); + + // Extract AOI from request + Rectangle aoi = extractAOIFromRequest(originalWidth, originalHeight); + if (aoi != null) { + param.setSourceRegion(aoi); + originalWidth = aoi.width; + originalHeight = aoi.height; + } + + // If possible, extract size from request + Dimension size = extractSizeFromRequest(originalWidth, originalHeight); + double readSubSamplingFactor = getReadSubsampleFactorFromRequest(); + if (size != null) { + //System.out.println("Size: " + size); + if (param.canSetSourceRenderSize()) { + param.setSourceRenderSize(size); + } + else { + int subX = (int) Math.max(originalWidth / (double) (size.width * readSubSamplingFactor), 1.0); + int subY = (int) Math.max(originalHeight / (double) (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); + + // If reader doesn't support dynamic sizing, scale now + if (image != null && size != null + && (image.getWidth() != size.width || image.getHeight() != size.height)) { + + int resampleAlgorithm = getResampleAlgorithmFromRequest(); + // NOTE: Only use createScaled if IndexColorModel, + // as it's more expensive due to color conversion + if (image.getColorModel() instanceof IndexColorModel) { + image = ImageUtil.createScaled(image, size.width, size.height, resampleAlgorithm); + } + else { + image = ImageUtil.createResampled(image, size.width, size.height, resampleAlgorithm); + } + } + + // Fill bgcolor behind image, if transparent + extractAndSetBackgroundColor(image); + + // Set image + mImage = image; + } + finally { + reader.dispose(); + } + } + else { + mContext.log("ERROR: No suitable image reader found (content-type: " + mOriginalContentType + ")."); + mContext.log("ERROR: Available formats: " + getFormatsString()); + + throw new IIOException("Unable to transcode image: No suitable image reader found (content-type: " + mOriginalContentType + ")."); + } + + // Free resources, as the image is now either read, or unreadable + mBufferedOut = null; + } + finally { + if (input != null) { + input.close(); + } + } + } + + // Image is usually a BufferedImage, but may also be a RenderedImage + return mImage != null ? ImageUtil.toBuffered(mImage) : null; + } + + private int getResampleAlgorithmFromRequest() { + int resampleAlgoithm; + + Object algorithm = mOriginalRequest.getAttribute(ATTRIB_IMAGE_RESAMPLE_ALGORITHM); + if (algorithm instanceof Integer && ((Integer) algorithm == Image.SCALE_SMOOTH || (Integer) algorithm == Image.SCALE_FAST || (Integer) algorithm == Image.SCALE_DEFAULT)) { + resampleAlgoithm = (Integer) algorithm; + } + else { + if (algorithm != null) { + mContext.log("WARN: Illegal image resampling algorithm: " + algorithm); + } + resampleAlgoithm = BufferedImage.SCALE_DEFAULT; + } + + return resampleAlgoithm; + } + + private double getReadSubsampleFactorFromRequest() { + double subsampleFactor; + + Object factor = mOriginalRequest.getAttribute(ATTRIB_READ_SUBSAMPLING_FACTOR); + if (factor instanceof Number && ((Number) factor).doubleValue() >= 1.0) { + subsampleFactor = ((Number) factor).doubleValue(); + } + else { + if (factor != null) { + mContext.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 = mOriginalRequest.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 (mOriginalRequest 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) mOriginalRequest); + + URL resourceURL = mContext.getResource(baseURI); + if (resourceURL == null) { + resourceURL = ServletUtil.getRealURL(mContext, baseURI); + } + + if (resourceURL != null) { + setBaseURI.invoke(pParam, resourceURL.toExternalForm()); + } + else { + mContext.log("WARN: Resource URL not found for URI: " + baseURI); + } + } + catch (Exception e) { + mContext.log("WARN: Could not set base URI: ", e); + } + } + } + + private Dimension extractSizeFromRequest(final int pDefaultWidth, final int pDefaultHeight) { + // TODO: Allow extraction from request parameters + /* + int sizeW = ServletUtil.getIntParameter(mOriginalRequest, "size.w", -1); + int sizeH = ServletUtil.getIntParameter(mOriginalRequest, "size.h", -1); + boolean sizePercent = ServletUtil.getBooleanParameter(mOriginalRequest, "size.percent", false); + boolean sizeUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "size.uniform", true); + */ + Dimension size = (Dimension) mOriginalRequest.getAttribute(ATTRIB_SIZE); + int sizeW = size != null ? size.width : -1; + int sizeH = size != null ? size.height : -1; + + Boolean b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_SIZE_PERCENT); + boolean sizePercent = b != null && b; // default: false + + b = (Boolean) mOriginalRequest.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) { + // TODO: Allow extraction from request parameters + /* + int aoiX = ServletUtil.getIntParameter(mOriginalRequest, "aoi.x", -1); + int aoiY = ServletUtil.getIntParameter(mOriginalRequest, "aoi.y", -1); + int aoiW = ServletUtil.getIntParameter(mOriginalRequest, "aoi.w", -1); + int aoiH = ServletUtil.getIntParameter(mOriginalRequest, "aoi.h", -1); + boolean aoiPercent = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.percent", false); + boolean aoiUniform = ServletUtil.getBooleanParameter(mOriginalRequest, "aoi.uniform", false); + */ + Rectangle aoi = (Rectangle) mOriginalRequest.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) mOriginalRequest.getAttribute(ATTRIB_AOI_PERCENT); + boolean aoiPercent = b != null && b; // default: false + + b = (Boolean) mOriginalRequest.getAttribute(ATTRIB_AOI_UNIFORM); + boolean aoiUniform = b != null && b; // default: false + + if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) { + aoi = getAOI(pDefaultWidth, pDefaultHeight, aoiX, aoiY, aoiW, aoiH, aoiPercent, aoiUniform); + 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 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 static Dimension getSize(int pOriginalWidth, int pOriginalHeight, + int pWidth, int pHeight, + boolean pPercent, 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 + + 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 (pUniformScale) { + 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); + } + + protected static Rectangle getAOI(int pOriginalWidth, int pOriginalHeight, + int pX, int pY, int pWidth, int pHeight, + boolean pPercent, boolean pUniform) { + // 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 + // + + 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 (pUniform) { + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java index aa6e570d..66e2a293 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java +++ b/twelvemonkeys-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 $Author: haku $ - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java#2 $ - * - */ -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 $Author: haku $ + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/NullImageFilter.java#2 $ + * + */ +public final class NullImageFilter extends ImageFilter { + protected RenderedImage doFilter(BufferedImage pImage, ServletRequest pRequest, ImageServletResponse pResponse) { + return pImage; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java index 49cfc89b..37af808e 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/RotateFilter.java @@ -1,203 +1,203 @@ -/* - * 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.MathUtil; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 = MathUtil.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.MathUtil; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 = MathUtil.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java index 1d7a11fa..8bd9ca07 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ScaleFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 mDefaultScaleQuality = 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 mDefaultScaleQuality; - } - - public void setDefaultScaleQuality(String pDefaultScaleQuality) { - mDefaultScaleQuality = 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/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 mDefaultScaleQuality = 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 mDefaultScaleQuality; + } + + public void setDefaultScaleQuality(String pDefaultScaleQuality) { + mDefaultScaleQuality = 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java index 37873803..1fb6d497 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java +++ b/twelvemonkeys-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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java#1 $ - */ -public class SourceRenderFilter extends ImageFilter { - private String mSizeWidthParam = "size.w"; - private String mSizeHeightParam = "size.h"; - private String mSizePercentParam = "size.percent"; - private String mSizeUniformParam = "size.uniform"; - - private String mRegionWidthParam = "aoi.w"; - private String mRegionHeightParam = "aoi.h"; - private String mRegionLeftParam = "aoi.x"; - private String mRegionTopParam = "aoi.y"; - private String mRegionPercentParam = "aoi.percent"; - private String mRegionUniformParam = "aoi.uniform"; - - public void setRegionHeightParam(String pRegionHeightParam) { - mRegionHeightParam = pRegionHeightParam; - } - - public void setRegionWidthParam(String pRegionWidthParam) { - mRegionWidthParam = pRegionWidthParam; - } - - public void setRegionLeftParam(String pRegionLeftParam) { - mRegionLeftParam = pRegionLeftParam; - } - - public void setRegionTopParam(String pRegionTopParam) { - mRegionTopParam = pRegionTopParam; - } - - public void setSizeHeightParam(String pSizeHeightParam) { - mSizeHeightParam = pSizeHeightParam; - } - - public void setSizeWidthParam(String pSizeWidthParam) { - mSizeWidthParam = pSizeWidthParam; - } - - public void setRegionPercentParam(String pRegionPercentParam) { - mRegionPercentParam = pRegionPercentParam; - } - - public void setRegionUniformParam(String pRegionUniformParam) { - mRegionUniformParam = pRegionUniformParam; - } - - public void setSizePercentParam(String pSizePercentParam) { - mSizePercentParam = pSizePercentParam; - } - - public void setSizeUniformParam(String pSizeUniformParam) { - mSizeUniformParam = pSizeUniformParam; - } - - public void init() throws ServletException { - if (mTriggerParams == null) { - // Add all params as triggers - mTriggerParams = new String[]{mSizeWidthParam, mSizeHeightParam, - mSizeUniformParam, mSizePercentParam, - mRegionLeftParam, mRegionTopParam, - mRegionWidthParam, mRegionHeightParam, - mRegionUniformParam, mRegionPercentParam}; - } - } - - /** - * 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, mSizeWidthParam, -1); - int height = ServletUtil.getIntParameter(pRequest, mSizeHeightParam, -1); - if (width > 0 || height > 0) { - pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE, new Dimension(width, height)); - } - - // Size uniform/percent - boolean uniform = ServletUtil.getBooleanParameter(pRequest, mSizeUniformParam, true); - if (!uniform) { - pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.FALSE); - } - boolean percent = ServletUtil.getBooleanParameter(pRequest, mSizePercentParam, false); - if (percent) { - pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE); - } - - // Area of interest parameters - int x = ServletUtil.getIntParameter(pRequest, mRegionLeftParam, -1); // Default is center - int y = ServletUtil.getIntParameter(pRequest, mRegionTopParam, -1); // Default is center - width = ServletUtil.getIntParameter(pRequest, mRegionWidthParam, -1); - height = ServletUtil.getIntParameter(pRequest, mRegionHeightParam, -1); - if (width > 0 || height > 0) { - pRequest.setAttribute(ImageServletResponse.ATTRIB_AOI, new Rectangle(x, y, width, height)); - } - - // AOI uniform/percent - uniform = ServletUtil.getBooleanParameter(pRequest, mRegionUniformParam, false); - if (uniform) { - pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.TRUE); - } - percent = ServletUtil.getBooleanParameter(pRequest, mRegionPercentParam, 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/SourceRenderFilter.java#1 $ + */ +public class SourceRenderFilter extends ImageFilter { + private String mSizeWidthParam = "size.w"; + private String mSizeHeightParam = "size.h"; + private String mSizePercentParam = "size.percent"; + private String mSizeUniformParam = "size.uniform"; + + private String mRegionWidthParam = "aoi.w"; + private String mRegionHeightParam = "aoi.h"; + private String mRegionLeftParam = "aoi.x"; + private String mRegionTopParam = "aoi.y"; + private String mRegionPercentParam = "aoi.percent"; + private String mRegionUniformParam = "aoi.uniform"; + + public void setRegionHeightParam(String pRegionHeightParam) { + mRegionHeightParam = pRegionHeightParam; + } + + public void setRegionWidthParam(String pRegionWidthParam) { + mRegionWidthParam = pRegionWidthParam; + } + + public void setRegionLeftParam(String pRegionLeftParam) { + mRegionLeftParam = pRegionLeftParam; + } + + public void setRegionTopParam(String pRegionTopParam) { + mRegionTopParam = pRegionTopParam; + } + + public void setSizeHeightParam(String pSizeHeightParam) { + mSizeHeightParam = pSizeHeightParam; + } + + public void setSizeWidthParam(String pSizeWidthParam) { + mSizeWidthParam = pSizeWidthParam; + } + + public void setRegionPercentParam(String pRegionPercentParam) { + mRegionPercentParam = pRegionPercentParam; + } + + public void setRegionUniformParam(String pRegionUniformParam) { + mRegionUniformParam = pRegionUniformParam; + } + + public void setSizePercentParam(String pSizePercentParam) { + mSizePercentParam = pSizePercentParam; + } + + public void setSizeUniformParam(String pSizeUniformParam) { + mSizeUniformParam = pSizeUniformParam; + } + + public void init() throws ServletException { + if (mTriggerParams == null) { + // Add all params as triggers + mTriggerParams = new String[]{mSizeWidthParam, mSizeHeightParam, + mSizeUniformParam, mSizePercentParam, + mRegionLeftParam, mRegionTopParam, + mRegionWidthParam, mRegionHeightParam, + mRegionUniformParam, mRegionPercentParam}; + } + } + + /** + * 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, mSizeWidthParam, -1); + int height = ServletUtil.getIntParameter(pRequest, mSizeHeightParam, -1); + if (width > 0 || height > 0) { + pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE, new Dimension(width, height)); + } + + // Size uniform/percent + boolean uniform = ServletUtil.getBooleanParameter(pRequest, mSizeUniformParam, true); + if (!uniform) { + pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.FALSE); + } + boolean percent = ServletUtil.getBooleanParameter(pRequest, mSizePercentParam, false); + if (percent) { + pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_PERCENT, Boolean.TRUE); + } + + // Area of interest parameters + int x = ServletUtil.getIntParameter(pRequest, mRegionLeftParam, -1); // Default is center + int y = ServletUtil.getIntParameter(pRequest, mRegionTopParam, -1); // Default is center + width = ServletUtil.getIntParameter(pRequest, mRegionWidthParam, -1); + height = ServletUtil.getIntParameter(pRequest, mRegionHeightParam, -1); + if (width > 0 || height > 0) { + pRequest.setAttribute(ImageServletResponse.ATTRIB_AOI, new Rectangle(x, y, width, height)); + } + + // AOI uniform/percent + uniform = ServletUtil.getBooleanParameter(pRequest, mRegionUniformParam, false); + if (uniform) { + pRequest.setAttribute(ImageServletResponse.ATTRIB_SIZE_UNIFORM, Boolean.TRUE); + } + percent = ServletUtil.getBooleanParameter(pRequest, mRegionPercentParam, 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java index b4380bf3..ebe41d97 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java @@ -1,348 +1,348 @@ -/* - * 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.MathUtil; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java#2 $ - */ - -class TextRenderer /*extends ImageServlet implements ImagePainterServlet*/ { - // TODO: Create something useable 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 = MathUtil.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.MathUtil; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/TextRenderer.java#2 $ + */ + +class TextRenderer /*extends ImageServlet implements ImagePainterServlet*/ { + // TODO: Create something useable 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 = MathUtil.toRadians(angle); + } + + return angle; + } + +} + + diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java index 006395c7..e12ed172 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/package_info.java @@ -1,34 +1,34 @@ -/** - * Contains various image-outputting servlets, that should run under any servlet engine. To create your own image servlet, simply subclass the servlet - * {@code ImageServlet}. Optionally implement the interface - * {@code ImagePainterServlet}, if you want to do painting. - *

- * 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 for any reason, or do not want to use the X - * libraries, a possibilty 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 servlets, that should run under any servlet engine. To create your own image servlet, simply subclass the servlet + * {@code ImageServlet}. Optionally implement the interface + * {@code ImagePainterServlet}, if you want to do painting. + *

+ * 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 for any reason, or do not want to use the X + * libraries, a possibilty 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java index 9a3d2c1a..5f30f57f 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Droplet.java @@ -1,80 +1,80 @@ -/* - * 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 java.io.*; - -import javax.servlet.*; -import javax.servlet.http.*; -import javax.servlet.jsp.*; - -import com.twelvemonkeys.servlet.jsp.droplet.taglib.*; - -/** - * 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 { - - // Copy doc - 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 java.io.*; + +import javax.servlet.*; +import javax.servlet.http.*; +import javax.servlet.jsp.*; + +import com.twelvemonkeys.servlet.jsp.droplet.taglib.*; + +/** + * 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 { + + // Copy doc + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java index 8d6e2c84..acdf86e7 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/JspFragment.java @@ -1,44 +1,44 @@ -/* - * 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 java.io.*; - -import javax.servlet.*; -import javax.servlet.jsp.*; - -/** - * 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 java.io.*; + +import javax.servlet.*; +import javax.servlet.jsp.*; + +/** + * 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java index 46e4a8f0..e87f79ea 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Oparam.java @@ -1,29 +1,29 @@ - -package com.twelvemonkeys.servlet.jsp.droplet; - -import java.io.*; - -import javax.servlet.*; -import javax.servlet.jsp.*; - -/** - * 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(mValue)); - - pContext.include(mValue); - } -} - + +package com.twelvemonkeys.servlet.jsp.droplet; + +import java.io.*; + +import javax.servlet.*; +import javax.servlet.jsp.*; + +/** + * 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(mValue)); + + pContext.include(mValue); + } +} + diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java index 3e86ce75..6517de11 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/Param.java @@ -1,42 +1,42 @@ - -package com.twelvemonkeys.servlet.jsp.droplet; - -import java.io.*; - -import javax.servlet.*; -import javax.servlet.jsp.*; - -/** - * Param - */ -public class Param implements JspFragment { - - /** The value member field. */ - protected String mValue = null; - - /** - * Creates a Param. - * - * @param pValue the value of the parameter - */ - public Param(String pValue) { - mValue = pValue; - } - - /** - * Gets the value of the parameter. - */ - public String getValue() { - return mValue; - } - - /** - * 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(mValue); - } -} + +package com.twelvemonkeys.servlet.jsp.droplet; + +import java.io.*; + +import javax.servlet.*; +import javax.servlet.jsp.*; + +/** + * Param + */ +public class Param implements JspFragment { + + /** The value member field. */ + protected String mValue = null; + + /** + * Creates a Param. + * + * @param pValue the value of the parameter + */ + public Param(String pValue) { + mValue = pValue; + } + + /** + * Gets the value of the parameter. + */ + public String getValue() { + return mValue; + } + + /** + * 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(mValue); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/package_info.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/package_info.java index 19cab518..69dfcbc8 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/package_info.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/package_info.java @@ -1,10 +1,10 @@ -/** - * Dynamo Droplet-like functionality for JSP. - * - * This package is early beta, not for commercial use! :-) - * Read: The interfaces and classes in this package (and subpackages) will be - * developed and modified for a while. - * - * TODO: Insert taglib-descriptor here? - */ +/** + * Dynamo Droplet-like functionality for JSP. + * + * This package is early beta, not for commercial use! :-) + * Read: The interfaces and classes in this package (and subpackages) will be + * developed and modified for a while. + * + * TODO: Insert taglib-descriptor here? + */ package com.twelvemonkeys.servlet.jsp.droplet; \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java index b109afb0..5fce6abf 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/IncludeTag.java +++ b/twelvemonkeys-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 mParameterNames = 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 mOldParameters = null; - - /** - * This is the URL for the JSP page that the parameters contained in this - * tag are to be inserted into. - */ - private String mPage; - - /** - * 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) { - mPage = 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 (!mParameterNames.contains(pName)) { - mParameterNames.add(pName); - - // Now check if this parameter already exists in the page. - Object obj = getRequest().getAttribute(pName); - if (obj != null) { - mOldParameters.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 { - mOldParameters = new HashMap(); - mParameterNames = 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(mPage); - - // Remove all the parameters that were added to the request scope - // for this insert tag. - iterator = mParameterNames.iterator(); - - while (iterator.hasNext()) { - parameterName = (String) iterator.next(); - - getRequest().removeAttribute(parameterName); - } - - iterator = mOldParameters.keySet().iterator(); - - // Restore the parameters we temporarily replaced (if any). - while (iterator.hasNext()) { - parameterName = (String) iterator.next(); - - getRequest().setAttribute(parameterName, mOldParameters.get(parameterName)); - } - - return super.doEndTag(); - } - catch (IOException ioe) { - msg = "Caught an IOException while including " + mPage - + "\n" + ioe.toString(); - log(msg, ioe); - throw new JspException(msg); - } - catch (ServletException se) { - msg = "Caught a ServletException while including " + mPage - + "\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() { - mOldParameters = null; - mParameterNames = 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(mPage); - } - */ - - /** - * 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 mParameterNames = 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 mOldParameters = null; + + /** + * This is the URL for the JSP page that the parameters contained in this + * tag are to be inserted into. + */ + private String mPage; + + /** + * 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) { + mPage = 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 (!mParameterNames.contains(pName)) { + mParameterNames.add(pName); + + // Now check if this parameter already exists in the page. + Object obj = getRequest().getAttribute(pName); + if (obj != null) { + mOldParameters.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 { + mOldParameters = new HashMap(); + mParameterNames = 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(mPage); + + // Remove all the parameters that were added to the request scope + // for this insert tag. + iterator = mParameterNames.iterator(); + + while (iterator.hasNext()) { + parameterName = (String) iterator.next(); + + getRequest().removeAttribute(parameterName); + } + + iterator = mOldParameters.keySet().iterator(); + + // Restore the parameters we temporarily replaced (if any). + while (iterator.hasNext()) { + parameterName = (String) iterator.next(); + + getRequest().setAttribute(parameterName, mOldParameters.get(parameterName)); + } + + return super.doEndTag(); + } + catch (IOException ioe) { + msg = "Caught an IOException while including " + mPage + + "\n" + ioe.toString(); + log(msg, ioe); + throw new JspException(msg); + } + catch (ServletException se) { + msg = "Caught a ServletException while including " + mPage + + "\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() { + mOldParameters = null; + mParameterNames = 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(mPage); + } + */ + + /** + * 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java index ce1dff21..ef472a9b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingHandler.java @@ -1,184 +1,184 @@ -/* - * 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 mIncludeTagName = "include"; - private String mParamTagName = "param"; - private String mOpenParamTagName = "oparam"; - - //private Stack mParents = new Stack(); - - private boolean mInIncludeTag = false; - - private String mNamespacePrefix = null; - private String mNamespaceURI = null; - - private NestingValidator mValidator = null; - - public NestingHandler(String pNamespacePrefix, String pNameSpaceURI, - NestingValidator pValidator) { - mNamespacePrefix = pNamespacePrefix; - mNamespaceURI = pNameSpaceURI; - - mValidator = 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(mNamespacePrefix)) { - System.out.println("startElement:\nnamespaceURI=" + pNamespaceURI - + " namespacePrefix=" + namespacePrefix - + " localName=" + localName - + " qName=" + pQualifiedName - + " attributes=" + pAttributes); - } - */ - if (localName.equals(mIncludeTagName)) { - // include - //System.out.println("<" + mNamespacePrefix + ":" - // + mIncludeTagName + ">"); - if (mInIncludeTag) { - mValidator.reportError("Cannot nest " + namespacePrefix + ":" - + mIncludeTagName); - } - mInIncludeTag = true; - } - else if (localName.equals(mParamTagName)) { - // param - //System.out.println("<" + mNamespacePrefix + ":" - // + mParamTagName + "/>"); - if (!mInIncludeTag) { - mValidator.reportError(mNamespacePrefix + ":" - + mParamTagName - + " can only appear within " - + mNamespacePrefix + ":" - + mIncludeTagName); - } - } - else if (localName.equals(mOpenParamTagName)) { - // oparam - //System.out.println("<" + mNamespacePrefix + ":" - // + mOpenParamTagName + ">"); - if (!mInIncludeTag) { - mValidator.reportError(mNamespacePrefix + ":" - + mOpenParamTagName - + " can only appear within " - + mNamespacePrefix + ":" - + mIncludeTagName); - } - mInIncludeTag = false; - } - else { - // Only jsp:text allowed inside include! - if (mInIncludeTag && !localName.equals("text")) { - mValidator.reportError(namespacePrefix + ":" + localName - + " can not appear within " - + mNamespacePrefix + ":" - + mIncludeTagName); - } - } - } - - 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(mNamespacePrefix)) { - System.out.println("endElement:\nnamespaceURI=" + pNamespaceURI - + " namespacePrefix=" + namespacePrefix - + " localName=" + localName - + " qName=" + pQualifiedName); - } - */ - if (namespacePrefix.equals(mNamespacePrefix) - && localName.equals(mIncludeTagName)) { - - //System.out.println(""); - - mInIncludeTag = false; - } - else if (namespacePrefix.equals(mNamespacePrefix) - && localName.equals(mOpenParamTagName)) { - - //System.out.println(""); - - mInIncludeTag = true; // assuming no errors before this... - } - } - - /** - * Stupid broken namespace-support "fix".. - */ - - private String getNSPrefixFromURI(String pNamespaceURI) { - return (pNamespaceURI.equals(mNamespaceURI) - ? mNamespacePrefix : ""); - } - - 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 mIncludeTagName = "include"; + private String mParamTagName = "param"; + private String mOpenParamTagName = "oparam"; + + //private Stack mParents = new Stack(); + + private boolean mInIncludeTag = false; + + private String mNamespacePrefix = null; + private String mNamespaceURI = null; + + private NestingValidator mValidator = null; + + public NestingHandler(String pNamespacePrefix, String pNameSpaceURI, + NestingValidator pValidator) { + mNamespacePrefix = pNamespacePrefix; + mNamespaceURI = pNameSpaceURI; + + mValidator = 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(mNamespacePrefix)) { + System.out.println("startElement:\nnamespaceURI=" + pNamespaceURI + + " namespacePrefix=" + namespacePrefix + + " localName=" + localName + + " qName=" + pQualifiedName + + " attributes=" + pAttributes); + } + */ + if (localName.equals(mIncludeTagName)) { + // include + //System.out.println("<" + mNamespacePrefix + ":" + // + mIncludeTagName + ">"); + if (mInIncludeTag) { + mValidator.reportError("Cannot nest " + namespacePrefix + ":" + + mIncludeTagName); + } + mInIncludeTag = true; + } + else if (localName.equals(mParamTagName)) { + // param + //System.out.println("<" + mNamespacePrefix + ":" + // + mParamTagName + "/>"); + if (!mInIncludeTag) { + mValidator.reportError(mNamespacePrefix + ":" + + mParamTagName + + " can only appear within " + + mNamespacePrefix + ":" + + mIncludeTagName); + } + } + else if (localName.equals(mOpenParamTagName)) { + // oparam + //System.out.println("<" + mNamespacePrefix + ":" + // + mOpenParamTagName + ">"); + if (!mInIncludeTag) { + mValidator.reportError(mNamespacePrefix + ":" + + mOpenParamTagName + + " can only appear within " + + mNamespacePrefix + ":" + + mIncludeTagName); + } + mInIncludeTag = false; + } + else { + // Only jsp:text allowed inside include! + if (mInIncludeTag && !localName.equals("text")) { + mValidator.reportError(namespacePrefix + ":" + localName + + " can not appear within " + + mNamespacePrefix + ":" + + mIncludeTagName); + } + } + } + + 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(mNamespacePrefix)) { + System.out.println("endElement:\nnamespaceURI=" + pNamespaceURI + + " namespacePrefix=" + namespacePrefix + + " localName=" + localName + + " qName=" + pQualifiedName); + } + */ + if (namespacePrefix.equals(mNamespacePrefix) + && localName.equals(mIncludeTagName)) { + + //System.out.println(""); + + mInIncludeTag = false; + } + else if (namespacePrefix.equals(mNamespacePrefix) + && localName.equals(mOpenParamTagName)) { + + //System.out.println(""); + + mInIncludeTag = true; // assuming no errors before this... + } + } + + /** + * Stupid broken namespace-support "fix".. + */ + + private String getNSPrefixFromURI(String pNamespaceURI) { + return (pNamespaceURI.equals(mNamespaceURI) + ? mNamespacePrefix : ""); + } + + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java index 1d05a09c..37080286 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/NestingValidator.java @@ -1,108 +1,108 @@ -/* - * 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 java.util.*; - -import javax.servlet.jsp.tagext.*; -import javax.xml.parsers.*; - -import org.xml.sax.*; -import org.xml.sax.helpers.*; - -import com.twelvemonkeys.util.*; - -/** - * 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 Vector errors = new Vector(); - - /** - * - */ - - 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 (ValidationMessage[]) - 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 java.util.*; + +import javax.servlet.jsp.tagext.*; +import javax.xml.parsers.*; + +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +import com.twelvemonkeys.util.*; + +/** + * 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 Vector errors = new Vector(); + + /** + * + */ + + 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 (ValidationMessage[]) + 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java index 9ef4c596..98ca02db 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/OparamTag.java @@ -1,238 +1,238 @@ -/* - * 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 javax.servlet.jsp.tagext.BodyTag; -import javax.servlet.jsp.tagext.Tag; -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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 mSubpage = null; - - /** - * This is the name of the parameter to be inserted into the {@code - * PageContext.REQUEST_SCOPE} scope. - */ - - private String mParameterName = null; - - private String mLanguage = null; - - private String mPrefix = 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) { - mParameterName = pName; - } - - public void setLanguage(String pLanguage) { - //System.out.println("setLanguage:"+pLanguage); - mLanguage = pLanguage; - } - - public void setPrefix(String pPrefix) { - //System.out.println("setPrefix:"+pPrefix); - mPrefix = 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. - * @exception JspException - */ - - public int doStartTag() throws JspException { - //checkEnclosedInIncludeTag(); // Moved to TagLibValidator - - // Get request - HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); - - // Get filename - mSubpage = createFileNameFromRequest(request); - - // Get include tag, and add to parameters - IncludeTag includeTag = (IncludeTag) getParent(); - includeTag.addParameter(mParameterName, new Oparam(mSubpage.getName())); - - // if ! subpage.exist || jsp newer than subpage, write new - File jsp = new File(pageContext.getServletContext() - .getRealPath(request.getServletPath())); - - if (!mSubpage.exists() || jsp.lastModified() > mSubpage.lastModified()) { - return BodyTag.EVAL_BODY_BUFFERED; - } - - // No need to evaluate body again! - return Tag.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() { - mParameterName = 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. - * - * @exception 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(mLanguage)) { - content = "<%@page language=\"" + mLanguage + "\" %>" + content; - } - - if (!StringUtil.isEmpty(mPrefix)) { - content = "<%@taglib uri=\"/twelvemonkeys-common\" prefix=\"" + mPrefix + "\" %>" + content; - } - - // Write the content of the oparam to disk - try { - log("Processing subpage " + mSubpage.getPath()); - FileUtil.write(mSubpage, 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 = mParameterName.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 javax.servlet.jsp.tagext.BodyTag; +import javax.servlet.jsp.tagext.Tag; +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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 mSubpage = null; + + /** + * This is the name of the parameter to be inserted into the {@code + * PageContext.REQUEST_SCOPE} scope. + */ + + private String mParameterName = null; + + private String mLanguage = null; + + private String mPrefix = 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) { + mParameterName = pName; + } + + public void setLanguage(String pLanguage) { + //System.out.println("setLanguage:"+pLanguage); + mLanguage = pLanguage; + } + + public void setPrefix(String pPrefix) { + //System.out.println("setPrefix:"+pPrefix); + mPrefix = 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. + * @exception JspException + */ + + public int doStartTag() throws JspException { + //checkEnclosedInIncludeTag(); // Moved to TagLibValidator + + // Get request + HttpServletRequest request = (HttpServletRequest) pageContext.getRequest(); + + // Get filename + mSubpage = createFileNameFromRequest(request); + + // Get include tag, and add to parameters + IncludeTag includeTag = (IncludeTag) getParent(); + includeTag.addParameter(mParameterName, new Oparam(mSubpage.getName())); + + // if ! subpage.exist || jsp newer than subpage, write new + File jsp = new File(pageContext.getServletContext() + .getRealPath(request.getServletPath())); + + if (!mSubpage.exists() || jsp.lastModified() > mSubpage.lastModified()) { + return BodyTag.EVAL_BODY_BUFFERED; + } + + // No need to evaluate body again! + return Tag.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() { + mParameterName = 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. + * + * @exception 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(mLanguage)) { + content = "<%@page language=\"" + mLanguage + "\" %>" + content; + } + + if (!StringUtil.isEmpty(mPrefix)) { + content = "<%@taglib uri=\"/twelvemonkeys-common\" prefix=\"" + mPrefix + "\" %>" + content; + } + + // Write the content of the oparam to disk + try { + log("Processing subpage " + mSubpage.getPath()); + FileUtil.write(mSubpage, 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 = mParameterName.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java index 3f24a48a..047ab084 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ParamTag.java @@ -1,141 +1,141 @@ -/* - * 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 java.io.IOException; - -import javax.servlet.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -import com.twelvemonkeys.servlet.jsp.droplet.*; -import com.twelvemonkeys.servlet.jsp.taglib.*; - -/** - * 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 mParameterName; - - /** - * This is the value for the parameter to be inserted into the {@code - * PageContext.REQUEST_SCOPE} scope. - */ - - private Object mParameterValue; - - /** - * 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) { - mParameterName = 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) { - mParameterValue = 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(mParameterName, mParameterValue); - } - - /** - * 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() { - mParameterName = null; - mParameterValue = 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 java.io.IOException; + +import javax.servlet.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +import com.twelvemonkeys.servlet.jsp.droplet.*; +import com.twelvemonkeys.servlet.jsp.taglib.*; + +/** + * 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 mParameterName; + + /** + * This is the value for the parameter to be inserted into the {@code + * PageContext.REQUEST_SCOPE} scope. + */ + + private Object mParameterValue; + + /** + * 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) { + mParameterName = 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) { + mParameterValue = 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(mParameterName, mParameterValue); + } + + /** + * 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() { + mParameterName = null; + mParameterValue = null; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java index 25aae40d..9ecafd8d 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTEI.java @@ -1,50 +1,50 @@ -/* - * 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 java.io.IOException; - -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -/** - * 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 java.io.IOException; + +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +/** + * 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java index b3171222..180abe75 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/ValueOfTag.java @@ -1,147 +1,147 @@ -/* - * 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 java.io.*; - -import javax.servlet.*; -import javax.servlet.jsp.*; - -import com.twelvemonkeys.servlet.jsp.droplet.*; -import com.twelvemonkeys.servlet.jsp.taglib.*; - -/** - * 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 mParameterName; - - /** - * 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 mParameterValue; - - /** - * 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) { - mParameterName = 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) { - mParameterName = 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 (mParameterValue instanceof JspFragment) { - // OPARAM or PARAM - ((JspFragment) mParameterValue).service(pageContext); - /* - log("Service subpage " + pageContext.getServletContext().getRealPath(((Oparam) mParameterValue).getName())); - - pageContext.include(((Oparam) mParameterValue).getName()); - */ - } - else { - // Normal JSP parameter value - JspWriter writer = pageContext.getOut(); - writer.print(mParameterValue); - } - - 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() { - mParameterValue = pageContext.getAttribute(mParameterName, PageContext.REQUEST_SCOPE); - - // -- Harald K 20020726 - if (mParameterValue == null) { - mParameterValue = pageContext.getRequest().getParameter(mParameterName); - } - - return (mParameterValue != 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 java.io.*; + +import javax.servlet.*; +import javax.servlet.jsp.*; + +import com.twelvemonkeys.servlet.jsp.droplet.*; +import com.twelvemonkeys.servlet.jsp.taglib.*; + +/** + * 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 mParameterName; + + /** + * 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 mParameterValue; + + /** + * 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) { + mParameterName = 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) { + mParameterName = 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 (mParameterValue instanceof JspFragment) { + // OPARAM or PARAM + ((JspFragment) mParameterValue).service(pageContext); + /* + log("Service subpage " + pageContext.getServletContext().getRealPath(((Oparam) mParameterValue).getName())); + + pageContext.include(((Oparam) mParameterValue).getName()); + */ + } + else { + // Normal JSP parameter value + JspWriter writer = pageContext.getOut(); + writer.print(mParameterValue); + } + + 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() { + mParameterValue = pageContext.getAttribute(mParameterName, PageContext.REQUEST_SCOPE); + + // -- Harald K 20020726 + if (mParameterValue == null) { + mParameterValue = pageContext.getRequest().getParameter(mParameterName); + } + + return (mParameterValue != null); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html index 8aa9a145..2df9add1 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/droplet/taglib/package.html @@ -1,10 +1,10 @@ - - - -The TwelveMonkeys droplet TagLib. - -TODO: Insert taglib-descriptor here? - - - - + + + +The TwelveMonkeys droplet TagLib. + +TODO: Insert taglib-descriptor here? + + + + diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/package_info.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/package_info.java index b4d9a8dc..6681cfbc 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/package_info.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/package_info.java @@ -1,4 +1,4 @@ -/** - * JSP support. - */ +/** + * JSP support. + */ package com.twelvemonkeys.servlet.jsp; \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java index a7b2ae27..36d1f926 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/BodyReaderTag.java @@ -1,43 +1,43 @@ - -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 JscException - */ - - 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 JscException + */ + + protected abstract void processBody(String pContent) throws JspException; +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java index cd27b166..bbc0003a 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java @@ -1,240 +1,240 @@ -/* - * 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 java.util.*; -import java.io.*; - -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -/** - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java#1 $ - */ - -public class CSVToTableTag extends ExBodyTagSupport { - - public final static String TAB = "\t"; - - protected String mDelimiter = null; - protected boolean mFirstRowIsHeader = false; - protected boolean mFirstColIsHeader = false; - - public void setDelimiter(String pDelimiter) { - mDelimiter = pDelimiter; - } - - public String getDelimiter() { - return mDelimiter != null ? mDelimiter : TAB; - } - - public void setFirstRowIsHeader(String pBoolean) { - mFirstRowIsHeader = Boolean.valueOf(pBoolean).booleanValue(); - } - - public void setFirstColIsHeader(String pBoolean) { - mFirstColIsHeader = Boolean.valueOf(pBoolean).booleanValue(); - } - - - 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 (mFirstRowIsHeader && row == 0 - || mFirstColIsHeader && col == 0) { - out.println(""); - } - else { - out.println(""); - } - } - - out.println(""); - - } - out.println("
" + table.get(row, col) - + " " + table.get(row, col) - + "
"); - } - } - catch (IOException ioe) { - throw new JspException(ioe); - } - - return super.doEndTag(); - } - - static class Table { - List mRows = null; - int mCols = 0; - - private Table(List pRows, int pCols) { - mRows = pRows; - mCols = pCols; - } - - int getRows() { - return mRows != null ? mRows.size() : 0; - } - - int getCols() { - return mCols; - } - - List getTableRows() { - return mRows; - } - - List getTableRow(int pRow) { - return mRows != null - ? (List) mRows.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 { - ArrayList tableRows = new ArrayList(); - int tdsPerTR = 0; - - // Loop through TRs - BufferedReader reader = new BufferedReader(pContent); - String tr = null; - while ((tr = reader.readLine()) != null) { - // Discard blank lines - if (tr != null - && tr.trim().length() <= 0 && tr.indexOf(pDelim) < 0) { - continue; - } - - //System.out.println("CSVToTable: read LINE=\"" + tr + "\""); - - ArrayList 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 java.util.*; +import java.io.*; + +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +/** + * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/CSVToTableTag.java#1 $ + */ + +public class CSVToTableTag extends ExBodyTagSupport { + + public final static String TAB = "\t"; + + protected String mDelimiter = null; + protected boolean mFirstRowIsHeader = false; + protected boolean mFirstColIsHeader = false; + + public void setDelimiter(String pDelimiter) { + mDelimiter = pDelimiter; + } + + public String getDelimiter() { + return mDelimiter != null ? mDelimiter : TAB; + } + + public void setFirstRowIsHeader(String pBoolean) { + mFirstRowIsHeader = Boolean.valueOf(pBoolean).booleanValue(); + } + + public void setFirstColIsHeader(String pBoolean) { + mFirstColIsHeader = Boolean.valueOf(pBoolean).booleanValue(); + } + + + 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 (mFirstRowIsHeader && row == 0 + || mFirstColIsHeader && col == 0) { + out.println(""); + } + else { + out.println(""); + } + } + + out.println(""); + + } + out.println("
" + table.get(row, col) + + " " + table.get(row, col) + + "
"); + } + } + catch (IOException ioe) { + throw new JspException(ioe); + } + + return super.doEndTag(); + } + + static class Table { + List mRows = null; + int mCols = 0; + + private Table(List pRows, int pCols) { + mRows = pRows; + mCols = pCols; + } + + int getRows() { + return mRows != null ? mRows.size() : 0; + } + + int getCols() { + return mCols; + } + + List getTableRows() { + return mRows; + } + + List getTableRow(int pRow) { + return mRows != null + ? (List) mRows.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 { + ArrayList tableRows = new ArrayList(); + int tdsPerTR = 0; + + // Loop through TRs + BufferedReader reader = new BufferedReader(pContent); + String tr = null; + while ((tr = reader.readLine()) != null) { + // Discard blank lines + if (tr != null + && tr.trim().length() <= 0 && tr.indexOf(pDelim) < 0) { + continue; + } + + //System.out.println("CSVToTable: read LINE=\"" + tr + "\""); + + ArrayList 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java index c24ab33a..45e215c5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExBodyTagSupport.java @@ -1,286 +1,286 @@ -/* - * 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 java.io.*; -import java.net.*; -import java.util.*; - -import javax.servlet.*; -import javax.servlet.http.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -/** - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 java.io.*; +import java.net.*; +import java.util.*; + +import javax.servlet.*; +import javax.servlet.http.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +/** + * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java index 58e21648..690819ec 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTag.java @@ -1,162 +1,162 @@ -/* - * 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 java.io.*; -import java.util.*; - -import javax.servlet.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -/** - * This interface contains a lot of helper methods for simplifying common - * taglib related tasks. - * - * @author Harald Kuhr - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 java.io.*; +import java.util.*; + +import javax.servlet.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +/** + * This interface contains a lot of helper methods for simplifying common + * taglib related tasks. + * + * @author Harald Kuhr + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java index 4cd688ce..0798976b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/ExTagSupport.java @@ -1,289 +1,289 @@ -/* - * 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 java.io.*; -import java.net.*; -import java.util.*; - -import javax.servlet.*; -import javax.servlet.http.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -/** - * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 java.io.*; +import java.net.*; +import java.util.*; + +import javax.servlet.*; +import javax.servlet.http.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +/** + * 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: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java index e4d3deb0..8dc90dcc 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTEI.java @@ -1,21 +1,21 @@ - -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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTag.java index 8f823389..e5daa6d5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/LastModifiedTag.java @@ -1,54 +1,54 @@ - -package com.twelvemonkeys.servlet.jsp.taglib; - -import java.io.File; -import java.util.Date; - -import javax.servlet.http.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; - -import com.twelvemonkeys.util.convert.*; - -/** - * Prints the last modified - */ - -public class LastModifiedTag extends TagSupport { - private String mFileName = null; - private String mFormat = null; - - public void setFile(String pFileName) { - mFileName = pFileName; - } - - public void setFormat(String pFormat) { - mFormat = pFormat; - } - - public int doStartTag() throws JspException { - File file = null; - - if (mFileName != null) { - file = new File(pageContext.getServletContext() - .getRealPath(mFileName)); - } - else { - HttpServletRequest request = - (HttpServletRequest) pageContext.getRequest(); - - // Get the file containing the servlet - file = new File(pageContext.getServletContext() - .getRealPath(request.getServletPath())); - } - - Date lastModified = new Date(file.lastModified()); - Converter conv = Converter.getInstance(); - - // Set the last modified value back - pageContext.setAttribute("lastModified", - conv.toString(lastModified, mFormat)); - - return Tag.EVAL_BODY_INCLUDE; - } -} + +package com.twelvemonkeys.servlet.jsp.taglib; + +import java.io.File; +import java.util.Date; + +import javax.servlet.http.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; + +import com.twelvemonkeys.util.convert.*; + +/** + * Prints the last modified + */ + +public class LastModifiedTag extends TagSupport { + private String mFileName = null; + private String mFormat = null; + + public void setFile(String pFileName) { + mFileName = pFileName; + } + + public void setFormat(String pFormat) { + mFormat = pFormat; + } + + public int doStartTag() throws JspException { + File file = null; + + if (mFileName != null) { + file = new File(pageContext.getServletContext() + .getRealPath(mFileName)); + } + else { + HttpServletRequest request = + (HttpServletRequest) pageContext.getRequest(); + + // Get the file containing the servlet + file = new File(pageContext.getServletContext() + .getRealPath(request.getServletPath())); + } + + Date lastModified = new Date(file.lastModified()); + Converter conv = Converter.getInstance(); + + // Set the last modified value back + pageContext.setAttribute("lastModified", + conv.toString(lastModified, mFormat)); + + return Tag.EVAL_BODY_INCLUDE; + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java index 6ce1fcbb..8b376c00 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/TrimWhiteSpaceTag.java @@ -1,89 +1,89 @@ - -package com.twelvemonkeys.servlet.jsp.taglib; - -import java.io.IOException; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.BodyTag; - -/** - * 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 BodyTag.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; +import javax.servlet.jsp.tagext.BodyTag; + +/** + * 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 BodyTag.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java index 4e92a463..66c2d612 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/XMLTransformTag.java @@ -1,162 +1,162 @@ -/* - * 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.*; -import javax.servlet.jsp.*; -import javax.servlet.jsp.tagext.*; -import javax.xml.transform.*; -import javax.xml.transform.stream.*; - -import com.twelvemonkeys.servlet.jsp.*; - -/** - * This tag performs XSL Transformations (XSLT) on a given XML document or its - * body content. - * - * @author Harald Kuhr - * - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 Tag.SKIP_BODY; - } - - // ...else process the body - return BodyTag.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.*; +import javax.servlet.jsp.*; +import javax.servlet.jsp.tagext.*; +import javax.xml.transform.*; +import javax.xml.transform.stream.*; + +import com.twelvemonkeys.servlet.jsp.*; + +/** + * This tag performs XSL Transformations (XSLT) on a given XML document or its + * body content. + * + * @author Harald Kuhr + * + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/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 Tag.SKIP_BODY; + } + + // ...else process the body + return BodyTag.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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java index 1ecc0ed7..65e97915 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/ConditionalTagBase.java @@ -1,140 +1,140 @@ -/**************************************************** - * * - * (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 java.lang.*; - -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 mObjectName; - protected String mObjectValue; - - // Properties - - /** - * Method getName - * - * - * @return - * - */ - public String getName() { - return mObjectName; - } - - /** - * Method setName - * - * - * @param pObjectName - * - */ - public void setName(String pObjectName) { - this.mObjectName = pObjectName; - } - - /** - * Method getValue - * - * - * @return - * - */ - public String getValue() { - return mObjectValue; - } - - /** - * Method setValue - * - * - * @param pObjectValue - * - */ - public void setValue(String pObjectValue) { - this.mObjectValue = 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(); - mObjectName = null; - mObjectValue = 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 java.lang.*; + +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 mObjectName; + protected String mObjectValue; + + // Properties + + /** + * Method getName + * + * + * @return + * + */ + public String getName() { + return mObjectName; + } + + /** + * Method setName + * + * + * @param pObjectName + * + */ + public void setName(String pObjectName) { + this.mObjectName = pObjectName; + } + + /** + * Method getValue + * + * + * @return + * + */ + public String getValue() { + return mObjectValue; + } + + /** + * Method setValue + * + * + * @param pObjectValue + * + */ + public void setValue(String pObjectValue) { + this.mObjectValue = 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(); + mObjectName = null; + mObjectValue = 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java index e0dafbb3..7e21111b 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/EqualTag.java @@ -1,170 +1,170 @@ -/* - * 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 java.lang.*; - -import javax.servlet.http.Cookie; -import javax.servlet.jsp.JspException; - -import com.twelvemonkeys.lang.StringUtil; - - -/** - *

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

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
equalAvailability: 1.0

Tag for testing if an attribute is equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: - *
    - *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. - *
  2. The attribute must exist. - *
  3. The attribute must be an instance of one of the supported classes: - *
      - *
    • {@code java.lang.String} - *
    • {@code javax.servlet.http.Cookie} - *
    - *
  4. 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(mObjectName)) { - return false; - } - - if (StringUtil.isEmpty(mObjectValue)) { - return true; - } - - Object pageScopedAttribute = pageContext.getAttribute(mObjectName); - 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(mObjectValue)); - } - -} +/* + * 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 java.lang.*; + +import javax.servlet.http.Cookie; +import javax.servlet.jsp.JspException; + +import com.twelvemonkeys.lang.StringUtil; + + +/** + *

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

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
equalAvailability: 1.0

Tag for testing if an attribute is equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: + *
    + *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. + *
  2. The attribute must exist. + *
  3. The attribute must be an instance of one of the supported classes: + *
      + *
    • {@code java.lang.String} + *
    • {@code javax.servlet.http.Cookie} + *
    + *
  4. 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(mObjectName)) { + return false; + } + + if (StringUtil.isEmpty(mObjectValue)) { + return true; + } + + Object pageScopedAttribute = pageContext.getAttribute(mObjectName); + 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(mObjectValue)); + } + +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java index e0610cec..91ed1fb5 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTEI.java @@ -1,41 +1,41 @@ - -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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java index f4ae2b1c..7c3e1da6 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/IteratorProviderTag.java @@ -1,87 +1,87 @@ - -package com.twelvemonkeys.servlet.jsp.taglib.logic; - -import java.util.Iterator; - -import javax.servlet.jsp.JspException; -import javax.servlet.jsp.tagext.*; - -/** - * 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 mType = null; - - /** - * Gets the type. - * - * @return the type (class name) - */ - public String getType() { - return mType; - } - - /** - * Sets the type. - * - * @param pType - */ - - public void setType(String pType) { - mType = 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 java.util.Iterator; + +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.*; + +/** + * 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 mType = null; + + /** + * Gets the type. + * + * @return the type (class name) + */ + public String getType() { + return mType; + } + + /** + * Sets the type. + * + * @param pType + */ + + public void setType(String pType) { + mType = 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/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java index 47f59cee..05cf228f 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/logic/NotEqualTag.java +++ b/twelvemonkeys-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

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
notEqualAvailability: 1.0

Tag for testing if an attribute is NOT equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: - *
    - *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. - *
  2. The attribute must exist. - *
  3. The attribute must be an instance of one of the supported classes: - *
      - *
    • {@code java.lang.String} - *
    • {@code javax.servlet.http.Cookie} - *
    - *
  4. 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(mObjectName)) { - return false; - } - - if (StringUtil.isEmpty(mObjectValue)) { - return true; - } - - Object pageScopedAttribute = pageContext.getAttribute(mObjectName); - 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(mObjectValue))); - } - -} +/* + * 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

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
notEqualAvailability: 1.0

Tag for testing if an attribute is NOT equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: + *
    + *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. + *
  2. The attribute must exist. + *
  3. The attribute must be an instance of one of the supported classes: + *
      + *
    • {@code java.lang.String} + *
    • {@code javax.servlet.http.Cookie} + *
    + *
  4. 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(mObjectName)) { + return false; + } + + if (StringUtil.isEmpty(mObjectValue)) { + return true; + } + + Object pageScopedAttribute = pageContext.getAttribute(mObjectName); + 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(mObjectValue))); + } + +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/package_info.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/package_info.java index 266046fd..750d7abf 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/package_info.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/jsp/taglib/package_info.java @@ -1,4 +1,4 @@ -/** - * The TwelveMonkeys common TagLib. - */ -package com.twelvemonkeys.servlet.jsp.taglib; +/** + * The TwelveMonkeys common TagLib. + */ +package com.twelvemonkeys.servlet.jsp.taglib; diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java index a629c438..49e7eb79 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java +++ b/twelvemonkeys-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 java.util.Enumeration; -import java.util.Set; -import java.net.URL; -import java.net.MalformedURLException; -import java.io.InputStream; -import java.lang.reflect.Proxy; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.Method; - -import javax.servlet.ServletContext; -import javax.servlet.RequestDispatcher; -import javax.servlet.Servlet; -import javax.servlet.ServletException; - -/** - * Log4JContextWrapper - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java#1 $ - */ -final class Log4JContextWrapper implements ServletContext { - - // 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 bolierplate 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 mContext; - - private final Logger mLogger; - - Log4JContextWrapper(ServletContext pContext) { - mContext = pContext; - - // TODO: We want a logger per servlet, not per servlet context, right? - mLogger = 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 - mLogger.info(pMessage); - } - - public void log(String pMessage, Throwable pCause) { - // TODO: Get logger for caller.. - - mLogger.error(pMessage, pCause); - } - - public Object getAttribute(String pMessage) { - return mContext.getAttribute(pMessage); - } - - public Enumeration getAttributeNames() { - return mContext.getAttributeNames(); - } - - public ServletContext getContext(String pMessage) { - return mContext.getContext(pMessage); - } - - public String getInitParameter(String pMessage) { - return mContext.getInitParameter(pMessage); - } - - public Enumeration getInitParameterNames() { - return mContext.getInitParameterNames(); - } - - public int getMajorVersion() { - return mContext.getMajorVersion(); - } - - public String getMimeType(String pMessage) { - return mContext.getMimeType(pMessage); - } - - public int getMinorVersion() { - return mContext.getMinorVersion(); - } - - public RequestDispatcher getNamedDispatcher(String pMessage) { - return mContext.getNamedDispatcher(pMessage); - } - - public String getRealPath(String pMessage) { - return mContext.getRealPath(pMessage); - } - - public RequestDispatcher getRequestDispatcher(String pMessage) { - return mContext.getRequestDispatcher(pMessage); - } - - public URL getResource(String pMessage) throws MalformedURLException { - return mContext.getResource(pMessage); - } - - public InputStream getResourceAsStream(String pMessage) { - return mContext.getResourceAsStream(pMessage); - } - - public Set getResourcePaths(String pMessage) { - return mContext.getResourcePaths(pMessage); - } - - public String getServerInfo() { - return mContext.getServerInfo(); - } - - public Servlet getServlet(String pMessage) throws ServletException { - //noinspection deprecation - return mContext.getServlet(pMessage); - } - - public String getServletContextName() { - return mContext.getServletContextName(); - } - - public Enumeration getServletNames() { - //noinspection deprecation - return mContext.getServletNames(); - } - - public Enumeration getServlets() { - //noinspection deprecation - return mContext.getServlets(); - } - - public void removeAttribute(String pMessage) { - mContext.removeAttribute(pMessage); - } - - public void setAttribute(String pMessage, Object pExtension) { - mContext.setAttribute(pMessage, pExtension); - } -} +package com.twelvemonkeys.servlet.log4j; + +import org.apache.log4j.Logger; + +import java.util.Enumeration; +import java.util.Set; +import java.net.URL; +import java.net.MalformedURLException; +import java.io.InputStream; +import java.lang.reflect.Proxy; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +import javax.servlet.ServletContext; +import javax.servlet.RequestDispatcher; +import javax.servlet.Servlet; +import javax.servlet.ServletException; + +/** + * Log4JContextWrapper + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/log4j/Log4JContextWrapper.java#1 $ + */ +final class Log4JContextWrapper implements ServletContext { + + // 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 bolierplate 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 mContext; + + private final Logger mLogger; + + Log4JContextWrapper(ServletContext pContext) { + mContext = pContext; + + // TODO: We want a logger per servlet, not per servlet context, right? + mLogger = 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 + mLogger.info(pMessage); + } + + public void log(String pMessage, Throwable pCause) { + // TODO: Get logger for caller.. + + mLogger.error(pMessage, pCause); + } + + public Object getAttribute(String pMessage) { + return mContext.getAttribute(pMessage); + } + + public Enumeration getAttributeNames() { + return mContext.getAttributeNames(); + } + + public ServletContext getContext(String pMessage) { + return mContext.getContext(pMessage); + } + + public String getInitParameter(String pMessage) { + return mContext.getInitParameter(pMessage); + } + + public Enumeration getInitParameterNames() { + return mContext.getInitParameterNames(); + } + + public int getMajorVersion() { + return mContext.getMajorVersion(); + } + + public String getMimeType(String pMessage) { + return mContext.getMimeType(pMessage); + } + + public int getMinorVersion() { + return mContext.getMinorVersion(); + } + + public RequestDispatcher getNamedDispatcher(String pMessage) { + return mContext.getNamedDispatcher(pMessage); + } + + public String getRealPath(String pMessage) { + return mContext.getRealPath(pMessage); + } + + public RequestDispatcher getRequestDispatcher(String pMessage) { + return mContext.getRequestDispatcher(pMessage); + } + + public URL getResource(String pMessage) throws MalformedURLException { + return mContext.getResource(pMessage); + } + + public InputStream getResourceAsStream(String pMessage) { + return mContext.getResourceAsStream(pMessage); + } + + public Set getResourcePaths(String pMessage) { + return mContext.getResourcePaths(pMessage); + } + + public String getServerInfo() { + return mContext.getServerInfo(); + } + + public Servlet getServlet(String pMessage) throws ServletException { + //noinspection deprecation + return mContext.getServlet(pMessage); + } + + public String getServletContextName() { + return mContext.getServletContextName(); + } + + public Enumeration getServletNames() { + //noinspection deprecation + return mContext.getServletNames(); + } + + public Enumeration getServlets() { + //noinspection deprecation + return mContext.getServlets(); + } + + public void removeAttribute(String pMessage) { + mContext.removeAttribute(pMessage); + } + + public void setAttribute(String pMessage, Object pExtension) { + mContext.setAttribute(pMessage, pExtension); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java index 09d358cb..c14599d4 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/package_info.java +++ b/twelvemonkeys-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/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java index 45a4ea6c..30dd6711 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/FilterAbstractTestCase.java @@ -1,438 +1,438 @@ -package com.twelvemonkeys.servlet; - -import com.twelvemonkeys.lang.ObjectAbstractTestCase; - -import java.util.*; -import java.net.URL; -import java.net.MalformedURLException; -import java.io.*; - -import javax.servlet.*; - -/** - * 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(); - } - - 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. - } - } - - public void testInit() { - Filter filter = makeFilter(); - - try { - filter.init(makeFilterConfig()); - } - catch (ServletException e) { - assertNotNull(e.getMessage()); - } - finally { - filter.destroy(); - } - } - - public void testLifeCycle() throws ServletException { - Filter filter = makeFilter(); - - try { - filter.init(makeFilterConfig()); - } - finally { - filter.destroy(); - } - } - - public void testFilterBasic() throws ServletException, IOException { - Filter filter = makeFilter(); - - try { - filter.init(makeFilterConfig()); - - filter.doFilter(makeRequest(), makeResponse(), makeFilterChain()); - } - finally { - filter.destroy(); - } - } - - public void testDestroy() { - // TODO: Implement - } - - static class MockFilterConfig implements FilterConfig { - private final Map mParams; - - MockFilterConfig() { - this(new HashMap()); - } - - MockFilterConfig(Map pParams) { - if (pParams == null) { - throw new IllegalArgumentException("params == null"); - } - mParams = pParams; - } - - public String getFilterName() { - return "mock-filter"; - } - - public String getInitParameter(String pName) { - return (String) mParams.get(pName); - } - - public Enumeration getInitParameterNames() { - return Collections.enumeration(mParams.keySet()); - } - - public ServletContext getServletContext() { - return new MockServletContext(); - } - - private static class MockServletContext implements ServletContext { - private final Map mAttributes; - private final Map mParams; - - MockServletContext() { - mAttributes = new HashMap(); - mParams = new HashMap(); - } - - public Object getAttribute(String s) { - return mAttributes.get(s); - } - - public Enumeration getAttributeNames() { - return Collections.enumeration(mAttributes.keySet()); - } - - public ServletContext getContext(String s) { - return null; // TODO: Implement - } - - public String getInitParameter(String s) { - return (String) mParams.get(s); - } - - public Enumeration getInitParameterNames() { - return Collections.enumeration(mParams.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) { - // TODO: Implement - } - - public void log(String s) { - // TODO: Implement - } - - public void log(String s, Throwable throwable) { - // TODO: Implement - } - - public void removeAttribute(String s) { - mAttributes.remove(s); - } - - public void setAttribute(String s, Object obj) { - mAttributes.put(s, obj); - } - } - } - - static class MockServletRequest implements ServletRequest { - final private Map mAttributes; - - public MockServletRequest() { - mAttributes = new HashMap(); - } - - public Object getAttribute(String pKey) { - return mAttributes.get(pKey); - } - - public Enumeration getAttributeNames() { - return Collections.enumeration(mAttributes.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) { - mAttributes.put(pKey, pValue); - } - - public void removeAttribute(String pKey) { - mAttributes.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 java.util.*; +import java.net.URL; +import java.net.MalformedURLException; +import java.io.*; + +import javax.servlet.*; + +/** + * 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(); + } + + 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. + } + } + + public void testInit() { + Filter filter = makeFilter(); + + try { + filter.init(makeFilterConfig()); + } + catch (ServletException e) { + assertNotNull(e.getMessage()); + } + finally { + filter.destroy(); + } + } + + public void testLifeCycle() throws ServletException { + Filter filter = makeFilter(); + + try { + filter.init(makeFilterConfig()); + } + finally { + filter.destroy(); + } + } + + public void testFilterBasic() throws ServletException, IOException { + Filter filter = makeFilter(); + + try { + filter.init(makeFilterConfig()); + + filter.doFilter(makeRequest(), makeResponse(), makeFilterChain()); + } + finally { + filter.destroy(); + } + } + + public void testDestroy() { + // TODO: Implement + } + + static class MockFilterConfig implements FilterConfig { + private final Map mParams; + + MockFilterConfig() { + this(new HashMap()); + } + + MockFilterConfig(Map pParams) { + if (pParams == null) { + throw new IllegalArgumentException("params == null"); + } + mParams = pParams; + } + + public String getFilterName() { + return "mock-filter"; + } + + public String getInitParameter(String pName) { + return (String) mParams.get(pName); + } + + public Enumeration getInitParameterNames() { + return Collections.enumeration(mParams.keySet()); + } + + public ServletContext getServletContext() { + return new MockServletContext(); + } + + private static class MockServletContext implements ServletContext { + private final Map mAttributes; + private final Map mParams; + + MockServletContext() { + mAttributes = new HashMap(); + mParams = new HashMap(); + } + + public Object getAttribute(String s) { + return mAttributes.get(s); + } + + public Enumeration getAttributeNames() { + return Collections.enumeration(mAttributes.keySet()); + } + + public ServletContext getContext(String s) { + return null; // TODO: Implement + } + + public String getInitParameter(String s) { + return (String) mParams.get(s); + } + + public Enumeration getInitParameterNames() { + return Collections.enumeration(mParams.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) { + // TODO: Implement + } + + public void log(String s) { + // TODO: Implement + } + + public void log(String s, Throwable throwable) { + // TODO: Implement + } + + public void removeAttribute(String s) { + mAttributes.remove(s); + } + + public void setAttribute(String s, Object obj) { + mAttributes.put(s, obj); + } + } + } + + static class MockServletRequest implements ServletRequest { + final private Map mAttributes; + + public MockServletRequest() { + mAttributes = new HashMap(); + } + + public Object getAttribute(String pKey) { + return mAttributes.get(pKey); + } + + public Enumeration getAttributeNames() { + return Collections.enumeration(mAttributes.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) { + mAttributes.put(pKey, pValue); + } + + public void removeAttribute(String pKey) { + mAttributes.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/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java index 75170b8d..fd81745f 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/GenericFilterTestCase.java @@ -1,151 +1,151 @@ -package com.twelvemonkeys.servlet; - -import java.io.IOException; -import java.util.Map; -import java.util.HashMap; - -import javax.servlet.*; - -/** - * 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(); - } - - 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.mOncePerRequest); - 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.mOncePerRequest); - 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.mOncePerRequest); - filter.destroy(); - } - - 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(); - } - - 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 java.io.IOException; +import java.util.Map; +import java.util.HashMap; + +import javax.servlet.*; + +/** + * 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(); + } + + 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.mOncePerRequest); + 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.mOncePerRequest); + 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.mOncePerRequest); + filter.destroy(); + } + + 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(); + } + + 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/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java index 7ba56d50..976e506f 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java @@ -1,192 +1,192 @@ -package com.twelvemonkeys.servlet; - -import com.twelvemonkeys.util.MapAbstractTestCase; - -import javax.servlet.*; -import java.util.*; -import java.io.Serializable; -import java.io.InputStream; -import java.net.URL; -import java.net.MalformedURLException; - -/** - * ServletConfigMapAdapterTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $ - */ -public abstract class ServletConfigMapAdapterTestCase 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 mMap = 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) mMap.get(s); - } - - public Enumeration getInitParameterNames() { - //noinspection unchecked - return Collections.enumeration(mMap.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 ServletConfigMapTestCase extends ServletConfigMapAdapterTestCase { - - public Map makeEmptyMap() { - ServletConfig config = new TestConfig(); - return new ServletConfigMapAdapter(config); - } - - public Map makeFullMap() { - ServletConfig config = new TestConfig(); - addSampleMappings(((TestConfig) config).mMap); - return new ServletConfigMapAdapter(config); - } - } - - public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTestCase { - - public Map makeEmptyMap() { - FilterConfig config = new TestConfig(); - return new ServletConfigMapAdapter(config); - } - - public Map makeFullMap() { - FilterConfig config = new TestConfig(); - addSampleMappings(((TestConfig) config).mMap); - return new ServletConfigMapAdapter(config); - } - } - - public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTestCase { - - public Map makeEmptyMap() { - ServletContext config = new TestConfig(); - return new ServletConfigMapAdapter(config); - } - - public Map makeFullMap() { - FilterConfig config = new TestConfig(); - addSampleMappings(((TestConfig) config).mMap); - return new ServletConfigMapAdapter(config); - } - } -} +package com.twelvemonkeys.servlet; + +import com.twelvemonkeys.util.MapAbstractTestCase; + +import javax.servlet.*; +import java.util.*; +import java.io.Serializable; +import java.io.InputStream; +import java.net.URL; +import java.net.MalformedURLException; + +/** + * ServletConfigMapAdapterTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletConfigMapAdapterTestCase.java#3 $ + */ +public abstract class ServletConfigMapAdapterTestCase 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 mMap = 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) mMap.get(s); + } + + public Enumeration getInitParameterNames() { + //noinspection unchecked + return Collections.enumeration(mMap.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 ServletConfigMapTestCase extends ServletConfigMapAdapterTestCase { + + public Map makeEmptyMap() { + ServletConfig config = new TestConfig(); + return new ServletConfigMapAdapter(config); + } + + public Map makeFullMap() { + ServletConfig config = new TestConfig(); + addSampleMappings(((TestConfig) config).mMap); + return new ServletConfigMapAdapter(config); + } + } + + public static final class FilterConfigMapTestCase extends ServletConfigMapAdapterTestCase { + + public Map makeEmptyMap() { + FilterConfig config = new TestConfig(); + return new ServletConfigMapAdapter(config); + } + + public Map makeFullMap() { + FilterConfig config = new TestConfig(); + addSampleMappings(((TestConfig) config).mMap); + return new ServletConfigMapAdapter(config); + } + } + + public static final class ServletContextMapTestCase extends ServletConfigMapAdapterTestCase { + + public Map makeEmptyMap() { + ServletContext config = new TestConfig(); + return new ServletConfigMapAdapter(config); + } + + public Map makeFullMap() { + FilterConfig config = new TestConfig(); + addSampleMappings(((TestConfig) config).mMap); + return new ServletConfigMapAdapter(config); + } + } +} diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java index cd078d9c..6f0b180a 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java @@ -1,103 +1,103 @@ -package com.twelvemonkeys.servlet; - -import com.twelvemonkeys.util.MapAbstractTestCase; -import org.jmock.Mock; -import org.jmock.core.Invocation; -import org.jmock.core.Stub; -import org.jmock.core.stub.CustomStub; - -import javax.servlet.http.HttpServletRequest; -import java.util.*; - -/** - * ServletConfigMapAdapterTestCase - *

- * - * @author Harald Kuhr - * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java#1 $ - */ -public class ServletHeadersMapAdapterTestCase 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() { - Mock mockRequest = mock(HttpServletRequest.class); - mockRequest.stubs().method("getHeaderNames").will(returnValue(Collections.enumeration(Collections.emptyList()))); - mockRequest.stubs().method("getHeaders").will(returnValue(null)); - - return new SerlvetHeadersMapAdapter((HttpServletRequest) mockRequest.proxy()); - } - - @Override - public Map makeFullMap() { - Mock mockRequest = mock(HttpServletRequest.class); - - mockRequest.stubs().method("getHeaderNames").will(returnEnumeration("ETag", "Date", "X-Foo")); - mockRequest.stubs().method("getHeaders").with(eq("Date")).will(returnEnumeration(HEADER_VALUE_DATE)); - mockRequest.stubs().method("getHeaders").with(eq("ETag")).will(returnEnumeration(HEADER_VALUE_ETAG)); - mockRequest.stubs().method("getHeaders").with(eq("X-Foo")).will(returnEnumeration(HEADER_VALUE_FOO)); - mockRequest.stubs().method("getHeaders").with(not(or(eq("Date"), or(eq("ETag"), eq("X-Foo"))))).will(returnValue(null)); - - return new SerlvetHeadersMapAdapter((HttpServletRequest) mockRequest.proxy()); - } - - @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 Stub returnEnumeration(final Object... pValues) { - return new EnumerationStub(Arrays.asList(pValues)); - } - - protected Stub returnEnumeration(final List pValues) { - return new EnumerationStub(pValues); - } - - private static class EnumerationStub extends CustomStub { - private List mValues; - - public EnumerationStub(final List pValues) { - super("Returns a new enumeration"); - mValues = pValues; - } - - public Object invoke(Invocation invocation) throws Throwable { - return Collections.enumeration(mValues); - } - } +package com.twelvemonkeys.servlet; + +import com.twelvemonkeys.util.MapAbstractTestCase; +import org.jmock.Mock; +import org.jmock.core.Invocation; +import org.jmock.core.Stub; +import org.jmock.core.stub.CustomStub; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +/** + * ServletConfigMapAdapterTestCase + *

+ * + * @author Harald Kuhr + * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletHeadersMapAdapterTestCase.java#1 $ + */ +public class ServletHeadersMapAdapterTestCase 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() { + Mock mockRequest = mock(HttpServletRequest.class); + mockRequest.stubs().method("getHeaderNames").will(returnValue(Collections.enumeration(Collections.emptyList()))); + mockRequest.stubs().method("getHeaders").will(returnValue(null)); + + return new SerlvetHeadersMapAdapter((HttpServletRequest) mockRequest.proxy()); + } + + @Override + public Map makeFullMap() { + Mock mockRequest = mock(HttpServletRequest.class); + + mockRequest.stubs().method("getHeaderNames").will(returnEnumeration("ETag", "Date", "X-Foo")); + mockRequest.stubs().method("getHeaders").with(eq("Date")).will(returnEnumeration(HEADER_VALUE_DATE)); + mockRequest.stubs().method("getHeaders").with(eq("ETag")).will(returnEnumeration(HEADER_VALUE_ETAG)); + mockRequest.stubs().method("getHeaders").with(eq("X-Foo")).will(returnEnumeration(HEADER_VALUE_FOO)); + mockRequest.stubs().method("getHeaders").with(not(or(eq("Date"), or(eq("ETag"), eq("X-Foo"))))).will(returnValue(null)); + + return new SerlvetHeadersMapAdapter((HttpServletRequest) mockRequest.proxy()); + } + + @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 Stub returnEnumeration(final Object... pValues) { + return new EnumerationStub(Arrays.asList(pValues)); + } + + protected Stub returnEnumeration(final List pValues) { + return new EnumerationStub(pValues); + } + + private static class EnumerationStub extends CustomStub { + private List mValues; + + public EnumerationStub(final List pValues) { + super("Returns a new enumeration"); + mValues = pValues; + } + + public Object invoke(Invocation invocation) throws Throwable { + return Collections.enumeration(mValues); + } + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java index af0f606e..9c4cc437 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletParametersMapAdapterTestCase.java @@ -1,102 +1,102 @@ -package com.twelvemonkeys.servlet; - -import com.twelvemonkeys.util.MapAbstractTestCase; -import org.jmock.Mock; -import org.jmock.core.Invocation; -import org.jmock.core.Stub; -import org.jmock.core.stub.CustomStub; - -import javax.servlet.http.HttpServletRequest; -import java.util.*; - -/** - * 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 ServletParametersMapAdapterTestCase 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() { - Mock mockRequest = mock(HttpServletRequest.class); - mockRequest.stubs().method("getParameterNames").will(returnValue(Collections.enumeration(Collections.emptyList()))); - mockRequest.stubs().method("getParameterValues").will(returnValue(null)); - - return new SerlvetParametersMapAdapter((HttpServletRequest) mockRequest.proxy()); - } - - @Override - public Map makeFullMap() { - Mock mockRequest = mock(HttpServletRequest.class); - - mockRequest.stubs().method("getParameterNames").will(returnEnumeration("tag", "date", "foo")); - mockRequest.stubs().method("getParameterValues").with(eq("date")).will(returnValue(PARAM_VALUE_DATE.toArray(new String[PARAM_VALUE_DATE.size()]))); - mockRequest.stubs().method("getParameterValues").with(eq("tag")).will(returnValue(PARAM_VALUE_ETAG.toArray(new String[PARAM_VALUE_ETAG.size()]))); - mockRequest.stubs().method("getParameterValues").with(eq("foo")).will(returnValue(PARAM_VALUE_FOO.toArray(new String[PARAM_VALUE_FOO.size()]))); - mockRequest.stubs().method("getParameterValues").with(not(or(eq("date"), or(eq("tag"), eq("foo"))))).will(returnValue(null)); - - return new SerlvetParametersMapAdapter((HttpServletRequest) mockRequest.proxy()); - } - - @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 Stub returnEnumeration(final Object... pValues) { - return new EnumerationStub(Arrays.asList(pValues)); - } - - protected Stub returnEnumeration(final List pValues) { - return new EnumerationStub(pValues); - } - - private static class EnumerationStub extends CustomStub { - private List mValues; - - public EnumerationStub(final List pValues) { - super("Returns a new enumeration"); - mValues = pValues; - } - - public Object invoke(Invocation invocation) throws Throwable { - return Collections.enumeration(mValues); - } - } +package com.twelvemonkeys.servlet; + +import com.twelvemonkeys.util.MapAbstractTestCase; +import org.jmock.Mock; +import org.jmock.core.Invocation; +import org.jmock.core.Stub; +import org.jmock.core.stub.CustomStub; + +import javax.servlet.http.HttpServletRequest; +import java.util.*; + +/** + * 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 ServletParametersMapAdapterTestCase 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() { + Mock mockRequest = mock(HttpServletRequest.class); + mockRequest.stubs().method("getParameterNames").will(returnValue(Collections.enumeration(Collections.emptyList()))); + mockRequest.stubs().method("getParameterValues").will(returnValue(null)); + + return new SerlvetParametersMapAdapter((HttpServletRequest) mockRequest.proxy()); + } + + @Override + public Map makeFullMap() { + Mock mockRequest = mock(HttpServletRequest.class); + + mockRequest.stubs().method("getParameterNames").will(returnEnumeration("tag", "date", "foo")); + mockRequest.stubs().method("getParameterValues").with(eq("date")).will(returnValue(PARAM_VALUE_DATE.toArray(new String[PARAM_VALUE_DATE.size()]))); + mockRequest.stubs().method("getParameterValues").with(eq("tag")).will(returnValue(PARAM_VALUE_ETAG.toArray(new String[PARAM_VALUE_ETAG.size()]))); + mockRequest.stubs().method("getParameterValues").with(eq("foo")).will(returnValue(PARAM_VALUE_FOO.toArray(new String[PARAM_VALUE_FOO.size()]))); + mockRequest.stubs().method("getParameterValues").with(not(or(eq("date"), or(eq("tag"), eq("foo"))))).will(returnValue(null)); + + return new SerlvetParametersMapAdapter((HttpServletRequest) mockRequest.proxy()); + } + + @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 Stub returnEnumeration(final Object... pValues) { + return new EnumerationStub(Arrays.asList(pValues)); + } + + protected Stub returnEnumeration(final List pValues) { + return new EnumerationStub(pValues); + } + + private static class EnumerationStub extends CustomStub { + private List mValues; + + public EnumerationStub(final List pValues) { + super("Returns a new enumeration"); + mValues = pValues; + } + + public Object invoke(Invocation invocation) throws Throwable { + return Collections.enumeration(mValues); + } + } } \ No newline at end of file diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java index 222b5ea4..a648c78b 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/ServletResponseAbsrtactTestCase.java +++ b/twelvemonkeys-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/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java index dc3f283f..2da59456 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/TrimWhiteSpaceFilterTestCase.java @@ -1,111 +1,111 @@ -package com.twelvemonkeys.servlet; - -import com.twelvemonkeys.io.OutputStreamAbstractTestCase; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import javax.servlet.Filter; -import javax.servlet.ServletResponse; - -/** - * 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); - } - - 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()) + '"'); - } - - 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()) + '"'); - } - - 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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import javax.servlet.Filter; +import javax.servlet.ServletResponse; + +/** + * 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); + } + + 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()) + '"'); + } + + 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()) + '"'); + } + + 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()); + } + } +} From be959ce3f3a6e0cc983f7829b602aad14422dcc5 Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Tue, 20 Apr 2010 12:51:06 +0200 Subject: [PATCH 03/17] Rule Of thirds: - enable with system property - extracted AreaOfInterest into a separate class. Conflicts: servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java --- .../image/ImageServletResponseImpl.java | 91 +--- .../ImageServletResponseImplTestCase.java | 333 --------------- .../servlet/image/AreaOfInterest.java | 126 ++++++ .../servlet/image/AreaOfInterestTestCase.java | 404 ++++++++++++++++++ 4 files changed, 532 insertions(+), 422 deletions(-) create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java create mode 100644 twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java 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 c4c013b5..946e31b1 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java @@ -582,7 +582,8 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima boolean aoiUniform = b != null && b; // default: false if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) { - aoi = getAOI(pDefaultWidth, pDefaultHeight, aoiX, aoiY, aoiW, aoiH, aoiPercent, aoiUniform); + + aoi = new AreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform).getAOI(aoiX, aoiY, aoiW, aoiH); return aoi; } @@ -693,92 +694,4 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima // 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/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java index 5210151d..fc3a7803 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java @@ -1078,337 +1078,4 @@ public class ImageServletResponseImplTestCase { verify(response).getOutputStream(); verify(response).setContentType(CONTENT_TYPE_PNG); } - - // ----------------------------------------------------------------------------------------------------------------- - // Absolute AOI - // ----------------------------------------------------------------------------------------------------------------- - - @Test - public void testGetAOIAbsolute() { - assertEquals(new Rectangle(10, 10, 100, 100), ImageServletResponseImpl.getAOI(200, 200, 10, 10, 100, 100, false, false)); - } - - @Test - public void testGetAOIAbsoluteOverflowX() { - assertEquals(new Rectangle(10, 10, 90, 100), ImageServletResponseImpl.getAOI(100, 200, 10, 10, 100, 100, false, false)); - } - - @Test - public void testGetAOIAbsoluteOverflowW() { - assertEquals(new Rectangle(0, 10, 100, 100), ImageServletResponseImpl.getAOI(100, 200, 0, 10, 110, 100, false, false)); - } - - @Test - public void testGetAOIAbsoluteOverflowY() { - assertEquals(new Rectangle(10, 10, 100, 90), ImageServletResponseImpl.getAOI(200, 100, 10, 10, 100, 100, false, false)); - } - - @Test - public void testGetAOIAbsoluteOverflowH() { - assertEquals(new Rectangle(10, 0, 100, 100), ImageServletResponseImpl.getAOI(200, 100, 10, 0, 100, 110, false, false)); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Uniform AOI centered - // ----------------------------------------------------------------------------------------------------------------- - - @Test - public void testGetAOIUniformCenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 333, 333, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2SDown() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 33, 33, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2SNormalized() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2W() { - assertEquals(new Rectangle(0, 25, 100, 50), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 200, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2WNormalized() { - assertEquals(new Rectangle(0, 25, 100, 50), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 50, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2N() { - assertEquals(new Rectangle(25, 0, 50, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 200, false, true)); - } - - @Test - public void testGetAOIUniformCenteredS2NNormalized() { - assertEquals(new Rectangle(25, 0, 50, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 50, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2S() { - assertEquals(new Rectangle(50, 0, 100, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 333, 333, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2SNormalized() { - assertEquals(new Rectangle(50, 0, 100, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2W() { - assertEquals(new Rectangle(0, 0, 200, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 50, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2WW() { - assertEquals(new Rectangle(0, 25, 200, 50), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 200, 50, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2WN() { - assertEquals(new Rectangle(25, 0, 150, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 75, 50, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2WNNormalized() { - assertEquals(new Rectangle(25, 0, 150, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 150, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2WNormalized() { - assertEquals(new Rectangle(0, 0, 200, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 200, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2N() { - assertEquals(new Rectangle(75, 0, 50, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 200, false, true)); - } - - @Test - public void testGetAOIUniformCenteredW2NNormalized() { - assertEquals(new Rectangle(75, 0, 50, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 50, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2S() { - assertEquals(new Rectangle(0, 50, 100, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 333, 333, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2SNormalized() { - assertEquals(new Rectangle(0, 50, 100, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2W() { - assertEquals(new Rectangle(0, 75, 100, 50), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 200, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2WNormalized() { - assertEquals(new Rectangle(0, 75, 100, 50), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 50, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2N() { - assertEquals(new Rectangle(0, 0, 100, 200), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 50, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2NN() { - assertEquals(new Rectangle(25, 0, 50, 200), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 25, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2NW() { - assertEquals(new Rectangle(0, 33, 100, 133), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 75, 100, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2NWNormalized() { - assertEquals(new Rectangle(0, 37, 100, 125), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 125, false, true)); - } - - @Test - public void testGetAOIUniformCenteredN2NNormalized() { - assertEquals(new Rectangle(0, 0, 100, 200), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 200, false, true)); - } - - // ----------------------------------------------------------------------------------------------------------------- - // Absolute AOI centered - // ----------------------------------------------------------------------------------------------------------------- - - @Test - public void testGetAOICenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 333, 333, false, false)); - } - - @Test - public void testGetAOICenteredS2SDown() { - assertEquals(new Rectangle(33, 33, 33, 33), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 33, 33, false, false)); - } - - @Test - public void testGetAOICenteredS2SSame() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 100, false, false)); - } - - @Test - public void testGetAOICenteredS2WOverflow() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 200, 100, false, false)); - } - - @Test - public void testGetAOICenteredS2W() { - assertEquals(new Rectangle(40, 45, 20, 10), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 20, 10, false, false)); - } - - @Test - public void testGetAOICenteredS2WMax() { - assertEquals(new Rectangle(0, 25, 100, 50), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 50, false, false)); - } - - @Test - public void testGetAOICenteredS2NOverflow() { - assertEquals(new Rectangle(0, 0, 100, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 100, 200, false, false)); - } - - @Test - public void testGetAOICenteredS2N() { - assertEquals(new Rectangle(45, 40, 10, 20), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 10, 20, false, false)); - } - - @Test - public void testGetAOICenteredS2NMax() { - assertEquals(new Rectangle(25, 0, 50, 100), ImageServletResponseImpl.getAOI(100, 100, -1, -1, 50, 100, false, false)); - } - - @Test - public void testGetAOICenteredW2SOverflow() { - assertEquals(new Rectangle(0, 0, 200, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 333, 333, false, false)); - } - - @Test - public void testGetAOICenteredW2S() { - assertEquals(new Rectangle(75, 25, 50, 50), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 50, 50, false, false)); - } - - @Test - public void testGetAOICenteredW2SMax() { - assertEquals(new Rectangle(50, 0, 100, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 100, false, false)); - } - - @Test - public void testGetAOICenteredW2WOverflow() { - assertEquals(new Rectangle(0, 0, 200, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 300, 200, false, false)); - } - - @Test - public void testGetAOICenteredW2W() { - assertEquals(new Rectangle(50, 25, 100, 50), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 50, false, false)); - } - - @Test - public void testGetAOICenteredW2WW() { - assertEquals(new Rectangle(10, 40, 180, 20), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 180, 20, false, false)); - } - - @Test - public void testGetAOICenteredW2WN() { - assertEquals(new Rectangle(62, 25, 75, 50), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 75, 50, false, false)); - } - - @Test - public void testGetAOICenteredW2WSame() { - assertEquals(new Rectangle(0, 0, 200, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 200, 100, false, false)); - } - - @Test - public void testGetAOICenteredW2NOverflow() { - assertEquals(new Rectangle(50, 0, 100, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 100, 200, false, false)); - } - - @Test - public void testGetAOICenteredW2N() { - assertEquals(new Rectangle(83, 25, 33, 50), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 33, 50, false, false)); - } - - @Test - public void testGetAOICenteredW2NMax() { - assertEquals(new Rectangle(75, 0, 50, 100), ImageServletResponseImpl.getAOI(200, 100, -1, -1, 50, 100, false, false)); - } - - @Test - public void testGetAOICenteredN2S() { - assertEquals(new Rectangle(33, 83, 33, 33), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 33, 33, false, false)); - } - - @Test - public void testGetAOICenteredN2SMax() { - assertEquals(new Rectangle(0, 50, 100, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 100, false, false)); - } - - @Test - public void testGetAOICenteredN2WOverflow() { - assertEquals(new Rectangle(0, 50, 100, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 200, 100, false, false)); - } - - @Test - public void testGetAOICenteredN2W() { - assertEquals(new Rectangle(40, 95, 20, 10), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 20, 10, false, false)); - } - - @Test - public void testGetAOICenteredN2WMax() { - assertEquals(new Rectangle(0, 75, 100, 50), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 50, false, false)); - } - - @Test - public void testGetAOICenteredN2N() { - assertEquals(new Rectangle(45, 90, 10, 20), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 10, 20, false, false)); - } - - @Test - public void testGetAOICenteredN2NSame() { - assertEquals(new Rectangle(0, 0, 100, 200), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 200, false, false)); - } - - @Test - public void testGetAOICenteredN2NN() { - assertEquals(new Rectangle(37, 50, 25, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 25, 100, false, false)); - } - - @Test - public void testGetAOICenteredN2NW() { - assertEquals(new Rectangle(12, 50, 75, 100), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 75, 100, false, false)); - } - - @Test - public void testGetAOICenteredN2NWMax() { - assertEquals(new Rectangle(0, 37, 100, 125), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 125, false, false)); - } - - @Test - public void testGetAOICenteredN2NMax() { - assertEquals(new Rectangle(0, 0, 100, 200), ImageServletResponseImpl.getAOI(100, 200, -1, -1, 100, 200, false, false)); - } - - // TODO: Test percent - - // TODO: Test getSize()... - - private static class BlackLabel extends JLabel { - public BlackLabel(final String text, final BufferedImage outImage) { - super(text, new BufferedImageIcon(outImage), JLabel.CENTER); - setOpaque(true); - setBackground(Color.BLACK); - setForeground(Color.WHITE); - setVerticalAlignment(JLabel.CENTER); - setVerticalTextPosition(JLabel.BOTTOM); - setHorizontalTextPosition(JLabel.CENTER); - } - } } diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java new file mode 100644 index 00000000..f0305110 --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java @@ -0,0 +1,126 @@ +package com.twelvemonkeys.servlet.image; + +import java.awt.*; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +class AreaOfInterest { + private final int mOriginalWidth; + private final int mOriginalHeight; + private final boolean mPercent; + private final boolean pUniform; + + AreaOfInterest(int pOriginalWidth, int pOriginalHeight, boolean pPercent, boolean pUniform) { + this.mOriginalWidth = pOriginalWidth; + this.mOriginalHeight = pOriginalHeight; + this.mPercent = pPercent; + this.pUniform = pUniform; + } + + Rectangle getAOI(int pX, int pY, int pWidth, int pHeight) { + // 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 + // + + float ratio; + + if (mPercent) { + if (pWidth >= 0 && pHeight >= 0) { + // Non-uniform + pWidth = Math.round((float) mOriginalWidth * (float) pWidth / 100f); + pHeight = Math.round((float) mOriginalHeight * (float) pHeight / 100f); + } + else if (pWidth >= 0) { + // Find ratio from pWidth + ratio = (float) pWidth / 100f; + pWidth = Math.round((float) mOriginalWidth * ratio); + pHeight = Math.round((float) mOriginalHeight * ratio); + + } + else if (pHeight >= 0) { + // Find ratio from pHeight + ratio = (float) pHeight / 100f; + pWidth = Math.round((float) mOriginalWidth * ratio); + pHeight = Math.round((float) mOriginalHeight * ratio); + } + // Else: No crop + } + else { + // Uniform + if (pUniform) { + if (pWidth >= 0 && pHeight >= 0) { + // Compute both ratios + ratio = (float) pWidth / (float) pHeight; + float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; + if (ratio > originalRatio) { + pWidth = mOriginalWidth; + pHeight = Math.round((float) mOriginalWidth / ratio); + } + else { + pHeight = mOriginalHeight; + pWidth = Math.round((float) mOriginalHeight * ratio); + } + } + else if (pWidth >= 0) { + // Find ratio from pWidth + ratio = (float) pWidth / (float) mOriginalWidth; + pHeight = Math.round((float) mOriginalHeight * ratio); + } + else if (pHeight >= 0) { + // Find ratio from pHeight + ratio = (float) pHeight / (float) mOriginalHeight; + pWidth = Math.round((float) mOriginalWidth * ratio); + } + // Else: No crop + } + } + + // Not specified, or outside bounds: Use original dimensions + if (pWidth < 0 || (pX < 0 && pWidth > mOriginalWidth) + || (pX >= 0 && (pX + pWidth) > mOriginalWidth)) { + pWidth = (pX >= 0 ? mOriginalWidth - pX : mOriginalWidth); + } + if (pHeight < 0 || (pY < 0 && pHeight > mOriginalHeight) + || (pY >= 0 && (pY + pHeight) > mOriginalHeight)) { + pHeight = (pY >= 0 ? mOriginalHeight - pY : mOriginalHeight); + } + if (Boolean.getBoolean("rule-of-thirds")) { + pY = calculateRuleOfThirds(pY, pWidth, pHeight); + } + // Center + if (pY < 0) { + pY = (mOriginalHeight - pHeight) / 2; + } + + if (pX < 0) { + pX = (mOriginalWidth - pWidth) / 2; + } + +// System.out.println("x: " + pX + " y: " + pY +// + " w: " + pWidth + " h " + pHeight); + + return new Rectangle(pX, pY, pWidth, pHeight); + } + + private int calculateRuleOfThirds(final int pY, final int pWidth, final int pHeight) { + int y = pY; + if (y < 0) { + float origRatio = (float) mOriginalWidth / (float) mOriginalHeight; + float cropRatio = (float) pWidth / (float) pHeight; + if (cropRatio > origRatio && origRatio < 1) { + y = (int) ((mOriginalHeight * 0.33f) - (pHeight / 2)); + if (y < 0) { + y = 0; + } + } + } + return y; + } +} diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java new file mode 100644 index 00000000..a0f3480d --- /dev/null +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java @@ -0,0 +1,404 @@ +package com.twelvemonkeys.servlet.image; + +import org.junit.Test; + +import java.awt.*; + +import static org.junit.Assert.assertEquals; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class AreaOfInterestTestCase { + // ----------------------------------------------------------------------------------------------------------------- + // Absolute AOI + // ----------------------------------------------------------------------------------------------------------------- + + @Test + public void testGetAOIAbsolute() { + assertEquals(new Rectangle(10, 10, 100, 100), new AreaOfInterest(200, 200, false, false).getAOI(10, 10, 100, 100)); + } + + @Test + public void testGetAOIAbsoluteOverflowX() { + + assertEquals(new Rectangle(10, 10, 90, 100), new AreaOfInterest(100, 200, false, false).getAOI(10, 10, 100, 100)); + } + + @Test + public void testGetAOIAbsoluteOverflowW() { + + assertEquals(new Rectangle(0, 10, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(0, 10, 110, 100)); + } + + @Test + public void testGetAOIAbsoluteOverflowY() { + + assertEquals(new Rectangle(10, 10, 100, 90), new AreaOfInterest(200, 100, false, false).getAOI(10, 10, 100, 100)); + } + + @Test + public void testGetAOIAbsoluteOverflowH() { + + assertEquals(new Rectangle(10, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(10, 0, 100, 110)); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Uniform AOI centered + // ----------------------------------------------------------------------------------------------------------------- + + @Test + public void testGetAOIUniformCenteredS2SUp() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOIUniformCenteredS2SDown() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 33, 33)); + } + + @Test + public void testGetAOIUniformCenteredS2SNormalized() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOIUniformCenteredS2W() { + assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOIUniformCenteredS2WNormalized() { + assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOIUniformCenteredS2N() { + assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOIUniformCenteredS2NNormalized() { + assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 50, 100)); + } + + @Test + public void testGetAOIUniformCenteredW2S() { + assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOIUniformCenteredW2SNormalized() { + assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOIUniformCenteredW2W() { + assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOIUniformCenteredW2WW() { + assertEquals(new Rectangle(0, 25, 200, 50), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 200, 50)); + } + + @Test + public void testGetAOIUniformCenteredW2WN() { + assertEquals(new Rectangle(25, 0, 150, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 75, 50)); + } + + @Test + public void testGetAOIUniformCenteredW2WNNormalized() { + assertEquals(new Rectangle(25, 0, 150, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 150, 100)); + } + + @Test + public void testGetAOIUniformCenteredW2WNormalized() { + assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOIUniformCenteredW2N() { + assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOIUniformCenteredW2NNormalized() { + assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 50, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2S() { + assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOIUniformCenteredN2SNormalized() { + assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2W() { + assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2WNormalized() { + assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOIUniformCenteredN2N() { + assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 50, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2NN() { + assertEquals(new Rectangle(25, 0, 50, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 25, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2NW() { + assertEquals(new Rectangle(0, 33, 100, 133), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 75, 100)); + } + + @Test + public void testGetAOIUniformCenteredN2NWNormalized() { + assertEquals(new Rectangle(0, 37, 100, 125), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 125)); + } + + @Test + public void testGetAOIUniformCenteredN2NNormalized() { + assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 200)); + } + + // ----------------------------------------------------------------------------------------------------------------- + // Absolute AOI centered + // ----------------------------------------------------------------------------------------------------------------- + + @Test + public void testGetAOICenteredS2SUp() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOICenteredS2SDown() { + assertEquals(new Rectangle(33, 33, 33, 33), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 33, 33)); + } + + @Test + public void testGetAOICenteredS2SSame() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOICenteredS2WOverflow() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOICenteredS2W() { + assertEquals(new Rectangle(40, 45, 20, 10), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 20, 10)); + } + + @Test + public void testGetAOICenteredS2WMax() { + assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOICenteredS2NOverflow() { + assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOICenteredS2N() { + assertEquals(new Rectangle(45, 40, 10, 20), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 10, 20)); + } + + @Test + public void testGetAOICenteredS2NMax() { + assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 50, 100)); + } + + @Test + public void testGetAOICenteredW2SOverflow() { + assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOICenteredW2S() { + assertEquals(new Rectangle(75, 25, 50, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 50, 50)); + } + + @Test + public void testGetAOICenteredW2SMax() { + assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOICenteredW2WOverflow() { + assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 300, 200)); + } + + @Test + public void testGetAOICenteredW2W() { + assertEquals(new Rectangle(50, 25, 100, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOICenteredW2WW() { + assertEquals(new Rectangle(10, 40, 180, 20), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 180, 20)); + } + + @Test + public void testGetAOICenteredW2WN() { + assertEquals(new Rectangle(62, 25, 75, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 75, 50)); + } + + @Test + public void testGetAOICenteredW2WSame() { + assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOICenteredW2NOverflow() { + assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOICenteredW2N() { + assertEquals(new Rectangle(83, 25, 33, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 33, 50)); + } + + @Test + public void testGetAOICenteredW2NMax() { + assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 50, 100)); + } + + @Test + public void testGetAOICenteredN2S() { + assertEquals(new Rectangle(33, 83, 33, 33), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 33, 33)); + } + + @Test + public void testGetAOICenteredN2SMax() { + assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOICenteredN2WOverflow() { + assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOICenteredN2W() { + assertEquals(new Rectangle(40, 95, 20, 10), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 20, 10)); + } + + @Test + public void testGetAOICenteredN2WMax() { + assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOICenteredN2N() { + assertEquals(new Rectangle(45, 90, 10, 20), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 10, 20)); + } + + @Test + public void testGetAOICenteredN2NSame() { + assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOICenteredN2NN() { + assertEquals(new Rectangle(37, 50, 25, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 25, 100)); + } + + @Test + public void testGetAOICenteredN2NW() { + assertEquals(new Rectangle(12, 50, 75, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 75, 100)); + } + + @Test + public void testGetAOICenteredN2NWMax() { + assertEquals(new Rectangle(0, 37, 100, 125), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 125)); + } + + @Test + public void testGetAOICenteredN2NMax() { + assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOIRuleOfThirdsN2N() { + enableRuleOfThirds(); + assertEquals(new Rectangle(45, 90, 10, 20), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 10, 20)); + } + + @Test + public void testGetAOIRuleOfThirdsN2NMax() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); + } + + @Test + public void testGetAOIUniformRuleOfThirdsN2S() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 333, 333)); + } + + @Test + public void testGetAOIUniformRuleOfThirdsN2SNormalized() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOIUniformRuleOfThirdsN2W() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOIUniformRuleOfThirdsN2WNormalized() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 50)); + } + + @Test + public void testGetAOIRuleOfThirdsN2S() { + enableRuleOfThirds(); + assertEquals(new Rectangle(33, 50, 33, 33), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 33, 33)); + } + + @Test + public void testGetAOIRuleOfThirdsN2SMax() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 100)); + } + + @Test + public void testGetAOIRuleOfThirdsN2WOverflow() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 200, 100)); + } + + @Test + public void testGetAOIRuleOfThirdsN2W() { + enableRuleOfThirds(); + assertEquals(new Rectangle(40, 61, 20, 10), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 20, 10)); + } + + @Test + public void testGetAOIRuleOfThirdsN2WMax() { + enableRuleOfThirds(); + assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 50)); + } + + private void enableRuleOfThirds() { + System.setProperty("rule-of-thirds", "true"); + } +} From 3628f3b3922a044f565c27c97d123d8a9ff9af6f Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Tue, 20 Apr 2010 16:09:26 +0200 Subject: [PATCH 04/17] Cleanup of AreaOfInterest --- .../servlet/image/AreaOfInterest.java | 221 ++++++++++-------- .../servlet/image/AreaOfInterestTestCase.java | 5 +- 2 files changed, 125 insertions(+), 101 deletions(-) diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java index f0305110..dd3b372f 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java @@ -3,119 +3,69 @@ package com.twelvemonkeys.servlet.image; import java.awt.*; /** - * @author Erlend Hamnaberg + * @author Harald Kuhr + * @author Erlend Hamnaberg * @version $Revision: $ */ -class AreaOfInterest { - private final int mOriginalWidth; - private final int mOriginalHeight; - private final boolean mPercent; - private final boolean pUniform; +public class AreaOfInterest { + protected final int mOriginalWidth; + protected final int mOriginalHeight; + protected final boolean mPercent; + protected final boolean pUniform; - AreaOfInterest(int pOriginalWidth, int pOriginalHeight, boolean pPercent, boolean pUniform) { + public AreaOfInterest(int pOriginalWidth, int pOriginalHeight, boolean pPercent, boolean pUniform) { this.mOriginalWidth = pOriginalWidth; this.mOriginalHeight = pOriginalHeight; this.mPercent = pPercent; this.pUniform = pUniform; } - Rectangle getAOI(int pX, int pY, int pWidth, int pHeight) { - // 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 - // - - float ratio; - - if (mPercent) { - if (pWidth >= 0 && pHeight >= 0) { - // Non-uniform - pWidth = Math.round((float) mOriginalWidth * (float) pWidth / 100f); - pHeight = Math.round((float) mOriginalHeight * (float) pHeight / 100f); - } - else if (pWidth >= 0) { - // Find ratio from pWidth - ratio = (float) pWidth / 100f; - pWidth = Math.round((float) mOriginalWidth * ratio); - pHeight = Math.round((float) mOriginalHeight * ratio); - - } - else if (pHeight >= 0) { - // Find ratio from pHeight - ratio = (float) pHeight / 100f; - pWidth = Math.round((float) mOriginalWidth * ratio); - pHeight = Math.round((float) mOriginalHeight * ratio); - } - // Else: No crop - } - else { - // Uniform - if (pUniform) { - if (pWidth >= 0 && pHeight >= 0) { - // Compute both ratios - ratio = (float) pWidth / (float) pHeight; - float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; - if (ratio > originalRatio) { - pWidth = mOriginalWidth; - pHeight = Math.round((float) mOriginalWidth / ratio); - } - else { - pHeight = mOriginalHeight; - pWidth = Math.round((float) mOriginalHeight * ratio); - } - } - else if (pWidth >= 0) { - // Find ratio from pWidth - ratio = (float) pWidth / (float) mOriginalWidth; - pHeight = Math.round((float) mOriginalHeight * ratio); - } - else if (pHeight >= 0) { - // Find ratio from pHeight - ratio = (float) pHeight / (float) mOriginalHeight; - pWidth = Math.round((float) mOriginalWidth * ratio); - } - // Else: No crop - } - } - - // Not specified, or outside bounds: Use original dimensions - if (pWidth < 0 || (pX < 0 && pWidth > mOriginalWidth) - || (pX >= 0 && (pX + pWidth) > mOriginalWidth)) { - pWidth = (pX >= 0 ? mOriginalWidth - pX : mOriginalWidth); - } - if (pHeight < 0 || (pY < 0 && pHeight > mOriginalHeight) - || (pY >= 0 && (pY + pHeight) > mOriginalHeight)) { - pHeight = (pY >= 0 ? mOriginalHeight - pY : mOriginalHeight); - } - if (Boolean.getBoolean("rule-of-thirds")) { - pY = calculateRuleOfThirds(pY, pWidth, pHeight); - } - // Center - if (pY < 0) { - pY = (mOriginalHeight - pHeight) / 2; - } - - if (pX < 0) { - pX = (mOriginalWidth - pWidth) / 2; - } - -// System.out.println("x: " + pX + " y: " + pY -// + " w: " + pWidth + " h " + pHeight); - - return new Rectangle(pX, pY, pWidth, pHeight); + public Rectangle getAOI(final int pX, final int pY, final int pWidth, final int pHeight) { + return getAOI(new Rectangle(pX, pY, pWidth, pHeight)); } - private int calculateRuleOfThirds(final int pY, final int pWidth, final int pHeight) { + public Rectangle getAOI(final Rectangle pCrop) { + int y = pCrop.y; + int x = pCrop.x; + + Dimension crop; + if (mPercent) { + crop = getPercentCrop(pCrop); + } + else if (pUniform) { + crop = getAOIUniform(pCrop); + } + else { + crop = getOriginalDimension(pCrop); + } + + // Center + if (y < 0) { + y = calculateY(crop.height); + } + + if (x < 0) { + x = calculateX(crop.width); + } + return new Rectangle(x, y, crop.width, crop.height); + } + + protected int calculateX(int pWidth) { + return (mOriginalWidth - pWidth) / 2; + } + + + protected int calculateY(int pHeight) { + return (mOriginalHeight - pHeight) / 2; + } + + private int calculateRuleOfThirds(final int pY, final int pCropWidth, final int pCropHeight) { int y = pY; if (y < 0) { float origRatio = (float) mOriginalWidth / (float) mOriginalHeight; - float cropRatio = (float) pWidth / (float) pHeight; + float cropRatio = (float) pCropWidth / (float) pCropHeight; if (cropRatio > origRatio && origRatio < 1) { - y = (int) ((mOriginalHeight * 0.33f) - (pHeight / 2)); + y = (int) ((mOriginalHeight * 0.33f) - (pCropHeight / 2)); if (y < 0) { y = 0; } @@ -123,4 +73,79 @@ class AreaOfInterest { } return y; } + + private Dimension getAOIUniform(final Rectangle pCrop) { + float ratio; + + if (pCrop.width >= 0 && pCrop.height >= 0) { + // Compute both ratios + ratio = (float) pCrop.width / (float) pCrop.height; + float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; + if (ratio > originalRatio) { + pCrop.width = mOriginalWidth; + pCrop.height = Math.round((float) mOriginalWidth / ratio); + } + else { + pCrop.height = mOriginalHeight; + pCrop.width = Math.round((float) mOriginalHeight * ratio); + } + } + else if (pCrop.width >= 0) { + // Find ratio from pWidth + ratio = (float) pCrop.width / (float) mOriginalWidth; + pCrop.height = Math.round((float) mOriginalHeight * ratio); + } + else if (pCrop.height >= 0) { + // Find ratio from pHeight + ratio = (float) pCrop.height / (float) mOriginalHeight; + pCrop.width = Math.round((float) mOriginalWidth * ratio); + } + // Else: No crop + return new Dimension(pCrop.width, pCrop.height); + } + + private Dimension getPercentCrop(final Rectangle pCrop) { + int cropWidth = pCrop.width; + int cropHeight = pCrop.height; + float ratio; + + if (cropWidth >= 0 && cropHeight >= 0) { + // Non-uniform + cropWidth = Math.round((float) mOriginalWidth * (float) pCrop.width / 100f); + cropHeight = Math.round((float) mOriginalHeight * (float) pCrop.height / 100f); + } + else if (cropWidth >= 0) { + // Find ratio from pWidth + ratio = (float) cropWidth / 100f; + cropWidth = Math.round((float) mOriginalWidth * ratio); + cropHeight = Math.round((float) mOriginalHeight * ratio); + + } + else if (cropHeight >= 0) { + // Find ratio from pHeight + ratio = (float) cropHeight / 100f; + cropWidth = Math.round((float) mOriginalWidth * ratio); + cropHeight = Math.round((float) mOriginalHeight * ratio); + } + // Else: No crop + + return new Dimension(cropWidth, cropHeight); + } + + private Dimension getOriginalDimension(Rectangle pCrop) { + int x = pCrop.x; + int y = pCrop.y; + int cropWidth = pCrop.width; + int cropHeight = pCrop.height; + + if (cropWidth < 0 || (x < 0 && cropWidth > mOriginalWidth) + || (x >= 0 && (x + cropWidth) > mOriginalWidth)) { + cropWidth = (x >= 0 ? mOriginalWidth - x : mOriginalWidth); + } + if (cropHeight < 0 || (y < 0 && cropHeight > mOriginalHeight) + || (y >= 0 && (y + cropHeight) > mOriginalHeight)) { + cropHeight = (y >= 0 ? mOriginalHeight - y : mOriginalHeight); + } + return new Dimension(cropWidth, cropHeight); + } } diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java index a0f3480d..28950eb0 100644 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java @@ -22,7 +22,6 @@ public class AreaOfInterestTestCase { @Test public void testGetAOIAbsoluteOverflowX() { - assertEquals(new Rectangle(10, 10, 90, 100), new AreaOfInterest(100, 200, false, false).getAOI(10, 10, 100, 100)); } @@ -332,7 +331,7 @@ public class AreaOfInterestTestCase { assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); } - @Test + /* @Test public void testGetAOIRuleOfThirdsN2N() { enableRuleOfThirds(); assertEquals(new Rectangle(45, 90, 10, 20), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 10, 20)); @@ -400,5 +399,5 @@ public class AreaOfInterestTestCase { private void enableRuleOfThirds() { System.setProperty("rule-of-thirds", "true"); - } + }*/ } From 8137165bace5664b730d3e2500963d66e75cf4df Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Tue, 20 Apr 2010 17:04:48 +0200 Subject: [PATCH 05/17] Cleanup of AreaOfInterest - Extracted AreaOfInterest into an interface - Added an AreaOfInterestFactory. - Use AreaOfInterestFactory in ImageServletResponseImpl - fixed version Conflicts: servlet/pom.xml servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java --- .../image/ImageServletResponseImpl.java | 6 +- .../ImageServletResponseImplTestCase.java | 2 +- .../servlet/image/AreaOfInterest.java | 151 ------------------ .../servlet/image/aoi/AreaOfInterest.java | 13 ++ .../image/aoi/AreaOfInterestFactory.java | 33 ++++ .../image/aoi/AreaOfInterestWrapper.java | 25 +++ .../image/aoi/DefaultAreaOfInterest.java | 84 ++++++++++ .../image/aoi/PercentAreaOfInterest.java | 43 +++++ .../image/aoi/UniformAreaOfInterest.java | 45 ++++++ .../servlet/image/AreaOfInterestTestCase.java | 124 +++++++------- 10 files changed, 312 insertions(+), 214 deletions(-) delete mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java create mode 100644 twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java 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 946e31b1..b605cfa4 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java @@ -33,6 +33,8 @@ 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; @@ -583,7 +585,9 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima if (aoiX >= 0 || aoiY >= 0 || aoiW >= 0 || aoiH >= 0) { - aoi = new AreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform).getAOI(aoiX, aoiY, aoiW, aoiH); + AreaOfInterest areaOfInterest = AreaOfInterestFactory.getDefault(). + createAreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform); + aoi = areaOfInterest.getAOI(aoiX, aoiY, aoiW, aoiH); return aoi; } diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java index fc3a7803..d2673958 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java @@ -66,7 +66,7 @@ public class ImageServletResponseImplTestCase { when(context.getMimeType("file.txt")).thenReturn(CONTENT_TYPE_TEXT); } - private void fakeResponse(HttpServletRequest pRequest, ImageServletResponseImpl pImageResponse) throws IOException { + private void fakeResponse(HttpServletRequest pRequest, DefaultImageServletResponse pImageResponse) throws IOException { String uri = pRequest.getRequestURI(); int index = uri.lastIndexOf('/'); assertTrue(uri, index >= 0); diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java deleted file mode 100644 index dd3b372f..00000000 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/AreaOfInterest.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.twelvemonkeys.servlet.image; - -import java.awt.*; - -/** - * @author Harald Kuhr - * @author Erlend Hamnaberg - * @version $Revision: $ - */ -public class AreaOfInterest { - protected final int mOriginalWidth; - protected final int mOriginalHeight; - protected final boolean mPercent; - protected final boolean pUniform; - - public AreaOfInterest(int pOriginalWidth, int pOriginalHeight, boolean pPercent, boolean pUniform) { - this.mOriginalWidth = pOriginalWidth; - this.mOriginalHeight = pOriginalHeight; - this.mPercent = pPercent; - this.pUniform = pUniform; - } - - public Rectangle getAOI(final int pX, final int pY, final int pWidth, final int pHeight) { - return getAOI(new Rectangle(pX, pY, pWidth, pHeight)); - } - - public Rectangle getAOI(final Rectangle pCrop) { - int y = pCrop.y; - int x = pCrop.x; - - Dimension crop; - if (mPercent) { - crop = getPercentCrop(pCrop); - } - else if (pUniform) { - crop = getAOIUniform(pCrop); - } - else { - crop = getOriginalDimension(pCrop); - } - - // Center - if (y < 0) { - y = calculateY(crop.height); - } - - if (x < 0) { - x = calculateX(crop.width); - } - return new Rectangle(x, y, crop.width, crop.height); - } - - protected int calculateX(int pWidth) { - return (mOriginalWidth - pWidth) / 2; - } - - - protected int calculateY(int pHeight) { - return (mOriginalHeight - pHeight) / 2; - } - - private int calculateRuleOfThirds(final int pY, final int pCropWidth, final int pCropHeight) { - int y = pY; - if (y < 0) { - float origRatio = (float) mOriginalWidth / (float) mOriginalHeight; - float cropRatio = (float) pCropWidth / (float) pCropHeight; - if (cropRatio > origRatio && origRatio < 1) { - y = (int) ((mOriginalHeight * 0.33f) - (pCropHeight / 2)); - if (y < 0) { - y = 0; - } - } - } - return y; - } - - private Dimension getAOIUniform(final Rectangle pCrop) { - float ratio; - - if (pCrop.width >= 0 && pCrop.height >= 0) { - // Compute both ratios - ratio = (float) pCrop.width / (float) pCrop.height; - float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; - if (ratio > originalRatio) { - pCrop.width = mOriginalWidth; - pCrop.height = Math.round((float) mOriginalWidth / ratio); - } - else { - pCrop.height = mOriginalHeight; - pCrop.width = Math.round((float) mOriginalHeight * ratio); - } - } - else if (pCrop.width >= 0) { - // Find ratio from pWidth - ratio = (float) pCrop.width / (float) mOriginalWidth; - pCrop.height = Math.round((float) mOriginalHeight * ratio); - } - else if (pCrop.height >= 0) { - // Find ratio from pHeight - ratio = (float) pCrop.height / (float) mOriginalHeight; - pCrop.width = Math.round((float) mOriginalWidth * ratio); - } - // Else: No crop - return new Dimension(pCrop.width, pCrop.height); - } - - private Dimension getPercentCrop(final Rectangle pCrop) { - int cropWidth = pCrop.width; - int cropHeight = pCrop.height; - float ratio; - - if (cropWidth >= 0 && cropHeight >= 0) { - // Non-uniform - cropWidth = Math.round((float) mOriginalWidth * (float) pCrop.width / 100f); - cropHeight = Math.round((float) mOriginalHeight * (float) pCrop.height / 100f); - } - else if (cropWidth >= 0) { - // Find ratio from pWidth - ratio = (float) cropWidth / 100f; - cropWidth = Math.round((float) mOriginalWidth * ratio); - cropHeight = Math.round((float) mOriginalHeight * ratio); - - } - else if (cropHeight >= 0) { - // Find ratio from pHeight - ratio = (float) cropHeight / 100f; - cropWidth = Math.round((float) mOriginalWidth * ratio); - cropHeight = Math.round((float) mOriginalHeight * ratio); - } - // Else: No crop - - return new Dimension(cropWidth, cropHeight); - } - - private Dimension getOriginalDimension(Rectangle pCrop) { - int x = pCrop.x; - int y = pCrop.y; - int cropWidth = pCrop.width; - int cropHeight = pCrop.height; - - if (cropWidth < 0 || (x < 0 && cropWidth > mOriginalWidth) - || (x >= 0 && (x + cropWidth) > mOriginalWidth)) { - cropWidth = (x >= 0 ? mOriginalWidth - x : mOriginalWidth); - } - if (cropHeight < 0 || (y < 0 && cropHeight > mOriginalHeight) - || (y >= 0 && (y + cropHeight) > mOriginalHeight)) { - cropHeight = (y >= 0 ? mOriginalHeight - y : mOriginalHeight); - } - return new Dimension(cropWidth, cropHeight); - } -} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java new file mode 100644 index 00000000..63ba79db --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java @@ -0,0 +1,13 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import java.awt.*; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public interface AreaOfInterest { + Rectangle getAOI(int pX, int pY, int pWidth, int pHeight); + + Rectangle getAOI(Rectangle pCrop); +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java new file mode 100644 index 00000000..fd059682 --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java @@ -0,0 +1,33 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class AreaOfInterestFactory { + private final static AtomicReference DEFAULT = + new AtomicReference(new AreaOfInterestFactory()); + + public static void setDefault(AreaOfInterestFactory factory) { + DEFAULT.set(factory); + } + + public static AreaOfInterestFactory getDefault() { + return DEFAULT.get(); + } + + public AreaOfInterest createAreaOfInterest(int pDefaultWidth, int pDefaultHeight, boolean aoiPercent, boolean aoiUniform) { + if (aoiPercent && aoiUniform) { + throw new IllegalArgumentException("Cannot be both uniform and percent Area of Interest"); + } + if (aoiPercent) { + return new PercentAreaOfInterest(pDefaultWidth, pDefaultHeight); + } + else if (aoiUniform) { + return new UniformAreaOfInterest(pDefaultWidth, pDefaultHeight); + } + return new DefaultAreaOfInterest(pDefaultWidth, pDefaultHeight); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java new file mode 100644 index 00000000..a5564efc --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java @@ -0,0 +1,25 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import com.twelvemonkeys.lang.Validate; + +import java.awt.*; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class AreaOfInterestWrapper implements AreaOfInterest { + private AreaOfInterest mDelegate; + + public AreaOfInterestWrapper(AreaOfInterest mDelegate) { + this.mDelegate = Validate.notNull(mDelegate); + } + + public Rectangle getAOI(int pX, int pY, int pWidth, int pHeight) { + return mDelegate.getAOI(pX, pY, pWidth, pHeight); + } + + public Rectangle getAOI(Rectangle pCrop) { + return mDelegate.getAOI(pCrop); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java new file mode 100644 index 00000000..56c06709 --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java @@ -0,0 +1,84 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import java.awt.*; + +/** + * @author Harald Kuhr + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class DefaultAreaOfInterest implements AreaOfInterest { + protected final int mOriginalWidth; + protected final int mOriginalHeight; + + public DefaultAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { + this.mOriginalWidth = pOriginalWidth; + this.mOriginalHeight = pOriginalHeight; + } + + public Rectangle getAOI(final int pX, final int pY, final int pWidth, final int pHeight) { + return getAOI(new Rectangle(pX, pY, pWidth, pHeight)); + } + + public Rectangle getAOI(final Rectangle pCrop) { + int y = pCrop.y; + int x = pCrop.x; + + Dimension crop = getCrop(pCrop); + + // Center + if (y < 0) { + y = calculateY(crop.height); + } + + if (x < 0) { + x = calculateX(crop.width); + } + return new Rectangle(x, y, crop.width, crop.height); + } + + protected int calculateX(int pWidth) { + return (mOriginalWidth - pWidth) / 2; + } + + + protected int calculateY(int pHeight) { + return (mOriginalHeight - pHeight) / 2; + } + + private int calculateRuleOfThirds(final int pY, final int pCropWidth, final int pCropHeight) { + int y = pY; + if (y < 0) { + float origRatio = (float) mOriginalWidth / (float) mOriginalHeight; + float cropRatio = (float) pCropWidth / (float) pCropHeight; + if (cropRatio > origRatio && origRatio < 1) { + y = (int) ((mOriginalHeight * 0.33f) - (pCropHeight / 2)); + if (y < 0) { + y = 0; + } + } + } + return y; + } + + protected Dimension getCrop(final Rectangle pCrop) { + return getOriginalDimension(pCrop); + } + + private Dimension getOriginalDimension(Rectangle pCrop) { + int x = pCrop.x; + int y = pCrop.y; + int cropWidth = pCrop.width; + int cropHeight = pCrop.height; + + if (cropWidth < 0 || (x < 0 && cropWidth > mOriginalWidth) + || (x >= 0 && (x + cropWidth) > mOriginalWidth)) { + cropWidth = (x >= 0 ? mOriginalWidth - x : mOriginalWidth); + } + if (cropHeight < 0 || (y < 0 && cropHeight > mOriginalHeight) + || (y >= 0 && (y + cropHeight) > mOriginalHeight)) { + cropHeight = (y >= 0 ? mOriginalHeight - y : mOriginalHeight); + } + return new Dimension(cropWidth, cropHeight); + } +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java new file mode 100644 index 00000000..e23bd7d9 --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java @@ -0,0 +1,43 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import java.awt.*; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class PercentAreaOfInterest extends DefaultAreaOfInterest { + + public PercentAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { + super(pOriginalWidth, pOriginalHeight); + } + + protected Dimension getCrop(final Rectangle pCrop) { + int cropWidth = pCrop.width; + int cropHeight = pCrop.height; + float ratio; + + if (cropWidth >= 0 && cropHeight >= 0) { + // Non-uniform + cropWidth = Math.round((float) mOriginalWidth * (float) pCrop.width / 100f); + cropHeight = Math.round((float) mOriginalHeight * (float) pCrop.height / 100f); + } + else if (cropWidth >= 0) { + // Find ratio from pWidth + ratio = (float) cropWidth / 100f; + cropWidth = Math.round((float) mOriginalWidth * ratio); + cropHeight = Math.round((float) mOriginalHeight * ratio); + + } + else if (cropHeight >= 0) { + // Find ratio from pHeight + ratio = (float) cropHeight / 100f; + cropWidth = Math.round((float) mOriginalWidth * ratio); + cropHeight = Math.round((float) mOriginalHeight * ratio); + } + // Else: No crop + + return new Dimension(cropWidth, cropHeight); + } + +} diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java new file mode 100644 index 00000000..3623d4c8 --- /dev/null +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java @@ -0,0 +1,45 @@ +package com.twelvemonkeys.servlet.image.aoi; + +import java.awt.*; + +/** + * @author Erlend Hamnaberg + * @version $Revision: $ + */ +public class UniformAreaOfInterest extends DefaultAreaOfInterest { + + public UniformAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { + super(pOriginalWidth, pOriginalHeight); + } + + protected Dimension getCrop(final Rectangle pCrop) { + float ratio; + + if (pCrop.width >= 0 && pCrop.height >= 0) { + // Compute both ratios + ratio = (float) pCrop.width / (float) pCrop.height; + float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; + if (ratio > originalRatio) { + pCrop.width = mOriginalWidth; + pCrop.height = Math.round((float) mOriginalWidth / ratio); + } + else { + pCrop.height = mOriginalHeight; + pCrop.width = Math.round((float) mOriginalHeight * ratio); + } + } + else if (pCrop.width >= 0) { + // Find ratio from pWidth + ratio = (float) pCrop.width / (float) mOriginalWidth; + pCrop.height = Math.round((float) mOriginalHeight * ratio); + } + else if (pCrop.height >= 0) { + // Find ratio from pHeight + ratio = (float) pCrop.height / (float) mOriginalHeight; + pCrop.width = Math.round((float) mOriginalWidth * ratio); + } + // Else: No crop + return new Dimension(pCrop.width, pCrop.height); + } + +} diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java index 28950eb0..feab7f5c 100644 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java @@ -1,5 +1,7 @@ package com.twelvemonkeys.servlet.image; +import com.twelvemonkeys.servlet.image.aoi.DefaultAreaOfInterest; +import com.twelvemonkeys.servlet.image.aoi.UniformAreaOfInterest; import org.junit.Test; import java.awt.*; @@ -17,30 +19,30 @@ public class AreaOfInterestTestCase { @Test public void testGetAOIAbsolute() { - assertEquals(new Rectangle(10, 10, 100, 100), new AreaOfInterest(200, 200, false, false).getAOI(10, 10, 100, 100)); + assertEquals(new Rectangle(10, 10, 100, 100), new DefaultAreaOfInterest(200, 200).getAOI(10, 10, 100, 100)); } @Test public void testGetAOIAbsoluteOverflowX() { - assertEquals(new Rectangle(10, 10, 90, 100), new AreaOfInterest(100, 200, false, false).getAOI(10, 10, 100, 100)); + assertEquals(new Rectangle(10, 10, 90, 100), new DefaultAreaOfInterest(100, 200).getAOI(10, 10, 100, 100)); } @Test public void testGetAOIAbsoluteOverflowW() { - assertEquals(new Rectangle(0, 10, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(0, 10, 110, 100)); + assertEquals(new Rectangle(0, 10, 100, 100), new DefaultAreaOfInterest(100, 200).getAOI(0, 10, 110, 100)); } @Test public void testGetAOIAbsoluteOverflowY() { - assertEquals(new Rectangle(10, 10, 100, 90), new AreaOfInterest(200, 100, false, false).getAOI(10, 10, 100, 100)); + assertEquals(new Rectangle(10, 10, 100, 90), new DefaultAreaOfInterest(200, 100).getAOI(10, 10, 100, 100)); } @Test public void testGetAOIAbsoluteOverflowH() { - assertEquals(new Rectangle(10, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(10, 0, 100, 110)); + assertEquals(new Rectangle(10, 0, 100, 100), new DefaultAreaOfInterest(200, 100).getAOI(10, 0, 100, 110)); } // ----------------------------------------------------------------------------------------------------------------- @@ -49,127 +51,127 @@ public class AreaOfInterestTestCase { @Test public void testGetAOIUniformCenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 333, 333)); + assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 333, 333)); } @Test public void testGetAOIUniformCenteredS2SDown() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 33, 33)); + assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 33, 33)); } @Test public void testGetAOIUniformCenteredS2SNormalized() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOIUniformCenteredS2W() { - assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 25, 100, 50), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOIUniformCenteredS2WNormalized() { - assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(0, 25, 100, 50), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOIUniformCenteredS2N() { - assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(25, 0, 50, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 100, 200)); } @Test public void testGetAOIUniformCenteredS2NNormalized() { - assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, true).getAOI(-1, -1, 50, 100)); + assertEquals(new Rectangle(25, 0, 50, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 50, 100)); } @Test public void testGetAOIUniformCenteredW2S() { - assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 333, 333)); + assertEquals(new Rectangle(50, 0, 100, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 333, 333)); } @Test public void testGetAOIUniformCenteredW2SNormalized() { - assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(50, 0, 100, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOIUniformCenteredW2W() { - assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(0, 0, 200, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOIUniformCenteredW2WW() { - assertEquals(new Rectangle(0, 25, 200, 50), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 200, 50)); + assertEquals(new Rectangle(0, 25, 200, 50), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 200, 50)); } @Test public void testGetAOIUniformCenteredW2WN() { - assertEquals(new Rectangle(25, 0, 150, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 75, 50)); + assertEquals(new Rectangle(25, 0, 150, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 75, 50)); } @Test public void testGetAOIUniformCenteredW2WNNormalized() { - assertEquals(new Rectangle(25, 0, 150, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 150, 100)); + assertEquals(new Rectangle(25, 0, 150, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 150, 100)); } @Test public void testGetAOIUniformCenteredW2WNormalized() { - assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 0, 200, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOIUniformCenteredW2N() { - assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(75, 0, 50, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 100, 200)); } @Test public void testGetAOIUniformCenteredW2NNormalized() { - assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, true).getAOI(-1, -1, 50, 100)); + assertEquals(new Rectangle(75, 0, 50, 100), new UniformAreaOfInterest(200, 100).getAOI(-1, -1, 50, 100)); } @Test public void testGetAOIUniformCenteredN2S() { - assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 333, 333)); + assertEquals(new Rectangle(0, 50, 100, 100), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 333, 333)); } @Test public void testGetAOIUniformCenteredN2SNormalized() { - assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(0, 50, 100, 100), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOIUniformCenteredN2W() { - assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 75, 100, 50), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOIUniformCenteredN2WNormalized() { - assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(0, 75, 100, 50), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOIUniformCenteredN2N() { - assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 50, 100)); + assertEquals(new Rectangle(0, 0, 100, 200), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 50, 100)); } @Test public void testGetAOIUniformCenteredN2NN() { - assertEquals(new Rectangle(25, 0, 50, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 25, 100)); + assertEquals(new Rectangle(25, 0, 50, 200), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 25, 100)); } @Test public void testGetAOIUniformCenteredN2NW() { - assertEquals(new Rectangle(0, 33, 100, 133), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 75, 100)); + assertEquals(new Rectangle(0, 33, 100, 133), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 75, 100)); } @Test public void testGetAOIUniformCenteredN2NWNormalized() { - assertEquals(new Rectangle(0, 37, 100, 125), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 125)); + assertEquals(new Rectangle(0, 37, 100, 125), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 100, 125)); } @Test public void testGetAOIUniformCenteredN2NNormalized() { - assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 200), new UniformAreaOfInterest(100, 200).getAOI(-1, -1, 100, 200)); } // ----------------------------------------------------------------------------------------------------------------- @@ -178,157 +180,157 @@ public class AreaOfInterestTestCase { @Test public void testGetAOICenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 333, 333)); + assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 333, 333)); } @Test public void testGetAOICenteredS2SDown() { - assertEquals(new Rectangle(33, 33, 33, 33), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 33, 33)); + assertEquals(new Rectangle(33, 33, 33, 33), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 33, 33)); } @Test public void testGetAOICenteredS2SSame() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOICenteredS2WOverflow() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOICenteredS2W() { - assertEquals(new Rectangle(40, 45, 20, 10), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 20, 10)); + assertEquals(new Rectangle(40, 45, 20, 10), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 20, 10)); } @Test public void testGetAOICenteredS2WMax() { - assertEquals(new Rectangle(0, 25, 100, 50), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(0, 25, 100, 50), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOICenteredS2NOverflow() { - assertEquals(new Rectangle(0, 0, 100, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 100, 200)); } @Test public void testGetAOICenteredS2N() { - assertEquals(new Rectangle(45, 40, 10, 20), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 10, 20)); + assertEquals(new Rectangle(45, 40, 10, 20), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 10, 20)); } @Test public void testGetAOICenteredS2NMax() { - assertEquals(new Rectangle(25, 0, 50, 100), new AreaOfInterest(100, 100, false, false).getAOI(-1, -1, 50, 100)); + assertEquals(new Rectangle(25, 0, 50, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 50, 100)); } @Test public void testGetAOICenteredW2SOverflow() { - assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 333, 333)); + assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 333, 333)); } @Test public void testGetAOICenteredW2S() { - assertEquals(new Rectangle(75, 25, 50, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 50, 50)); + assertEquals(new Rectangle(75, 25, 50, 50), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 50, 50)); } @Test public void testGetAOICenteredW2SMax() { - assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(50, 0, 100, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOICenteredW2WOverflow() { - assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 300, 200)); + assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 300, 200)); } @Test public void testGetAOICenteredW2W() { - assertEquals(new Rectangle(50, 25, 100, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(50, 25, 100, 50), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOICenteredW2WW() { - assertEquals(new Rectangle(10, 40, 180, 20), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 180, 20)); + assertEquals(new Rectangle(10, 40, 180, 20), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 180, 20)); } @Test public void testGetAOICenteredW2WN() { - assertEquals(new Rectangle(62, 25, 75, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 75, 50)); + assertEquals(new Rectangle(62, 25, 75, 50), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 75, 50)); } @Test public void testGetAOICenteredW2WSame() { - assertEquals(new Rectangle(0, 0, 200, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 0, 200, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOICenteredW2NOverflow() { - assertEquals(new Rectangle(50, 0, 100, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(50, 0, 100, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 100, 200)); } @Test public void testGetAOICenteredW2N() { - assertEquals(new Rectangle(83, 25, 33, 50), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 33, 50)); + assertEquals(new Rectangle(83, 25, 33, 50), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 33, 50)); } @Test public void testGetAOICenteredW2NMax() { - assertEquals(new Rectangle(75, 0, 50, 100), new AreaOfInterest(200, 100, false, false).getAOI(-1, -1, 50, 100)); + assertEquals(new Rectangle(75, 0, 50, 100), new DefaultAreaOfInterest(200, 100).getAOI(-1, -1, 50, 100)); } @Test public void testGetAOICenteredN2S() { - assertEquals(new Rectangle(33, 83, 33, 33), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 33, 33)); + assertEquals(new Rectangle(33, 83, 33, 33), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 33, 33)); } @Test public void testGetAOICenteredN2SMax() { - assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 100)); + assertEquals(new Rectangle(0, 50, 100, 100), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 100, 100)); } @Test public void testGetAOICenteredN2WOverflow() { - assertEquals(new Rectangle(0, 50, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 200, 100)); + assertEquals(new Rectangle(0, 50, 100, 100), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 200, 100)); } @Test public void testGetAOICenteredN2W() { - assertEquals(new Rectangle(40, 95, 20, 10), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 20, 10)); + assertEquals(new Rectangle(40, 95, 20, 10), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 20, 10)); } @Test public void testGetAOICenteredN2WMax() { - assertEquals(new Rectangle(0, 75, 100, 50), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 50)); + assertEquals(new Rectangle(0, 75, 100, 50), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 100, 50)); } @Test public void testGetAOICenteredN2N() { - assertEquals(new Rectangle(45, 90, 10, 20), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 10, 20)); + assertEquals(new Rectangle(45, 90, 10, 20), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 10, 20)); } @Test public void testGetAOICenteredN2NSame() { - assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 200), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 100, 200)); } @Test public void testGetAOICenteredN2NN() { - assertEquals(new Rectangle(37, 50, 25, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 25, 100)); + assertEquals(new Rectangle(37, 50, 25, 100), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 25, 100)); } @Test public void testGetAOICenteredN2NW() { - assertEquals(new Rectangle(12, 50, 75, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 75, 100)); + assertEquals(new Rectangle(12, 50, 75, 100), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 75, 100)); } @Test public void testGetAOICenteredN2NWMax() { - assertEquals(new Rectangle(0, 37, 100, 125), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 125)); + assertEquals(new Rectangle(0, 37, 100, 125), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 100, 125)); } @Test public void testGetAOICenteredN2NMax() { - assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 200), new DefaultAreaOfInterest(100, 200).getAOI(-1, -1, 100, 200)); } /* @Test From 2f06f2de6d428546f01d810261bc8f581cd27116 Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Wed, 21 Apr 2010 12:45:08 +0200 Subject: [PATCH 06/17] Cleanup of AreaOfInterest Conflicts: servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java --- .../ImageServletResponseImplTestCase.java | 2 +- .../servlet/image/aoi/AreaOfInterest.java | 11 +- .../image/aoi/AreaOfInterestWrapper.java | 20 +- .../image/aoi/DefaultAreaOfInterest.java | 61 +++--- .../image/aoi/PercentAreaOfInterest.java | 18 +- .../image/aoi/UniformAreaOfInterest.java | 28 +-- .../{ => aoi}/AreaOfInterestTestCase.java | 196 ++++++------------ 7 files changed, 144 insertions(+), 192 deletions(-) rename twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/{ => aoi}/AreaOfInterestTestCase.java (61%) diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java index d2673958..fc3a7803 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java @@ -66,7 +66,7 @@ public class ImageServletResponseImplTestCase { when(context.getMimeType("file.txt")).thenReturn(CONTENT_TYPE_TEXT); } - private void fakeResponse(HttpServletRequest pRequest, DefaultImageServletResponse pImageResponse) throws IOException { + private void fakeResponse(HttpServletRequest pRequest, ImageServletResponseImpl pImageResponse) throws IOException { String uri = pRequest.getRequestURI(); int index = uri.lastIndexOf('/'); assertTrue(uri, index >= 0); diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java index 63ba79db..f095536e 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java @@ -7,7 +7,14 @@ import java.awt.*; * @version $Revision: $ */ public interface AreaOfInterest { - Rectangle getAOI(int pX, int pY, int pWidth, int pHeight); - + Rectangle getAOI(Rectangle pCrop); + + Dimension getOriginalDimension(); + + int calculateX(Dimension pOriginalDimension, Rectangle pCrop); + + int calculateY(Dimension pOriginalDimension, Rectangle pCrop); + + Dimension getCrop(Dimension pOriginalDimension, Rectangle pCrop); } diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java index a5564efc..af405eeb 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java @@ -15,11 +15,23 @@ public class AreaOfInterestWrapper implements AreaOfInterest { this.mDelegate = Validate.notNull(mDelegate); } - public Rectangle getAOI(int pX, int pY, int pWidth, int pHeight) { - return mDelegate.getAOI(pX, pY, pWidth, pHeight); - } - public Rectangle getAOI(Rectangle pCrop) { return mDelegate.getAOI(pCrop); } + + public Dimension getOriginalDimension() { + return mDelegate.getOriginalDimension(); + } + + public int calculateX(Dimension pOriginalDimension, Rectangle pCrop) { + return mDelegate.calculateX(pOriginalDimension, pCrop); + } + + public int calculateY(Dimension pOriginalDimension, Rectangle pCrop) { + return mDelegate.calculateY(pOriginalDimension, pCrop); + } + + public Dimension getCrop(Dimension pOriginalDimension, Rectangle pCrop) { + return mDelegate.getCrop(pOriginalDimension, pCrop); + } } diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java index 56c06709..ac8e8142 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java @@ -8,76 +8,67 @@ import java.awt.*; * @version $Revision: $ */ public class DefaultAreaOfInterest implements AreaOfInterest { - protected final int mOriginalWidth; - protected final int mOriginalHeight; + private final int mOriginalWidth; + private final int mOriginalHeight; public DefaultAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { this.mOriginalWidth = pOriginalWidth; this.mOriginalHeight = pOriginalHeight; } - public Rectangle getAOI(final int pX, final int pY, final int pWidth, final int pHeight) { + public DefaultAreaOfInterest(Dimension pOriginalDimension) { + this(pOriginalDimension.width, pOriginalDimension.height); + } + + Rectangle getAOI(final int pX, final int pY, final int pWidth, final int pHeight) { return getAOI(new Rectangle(pX, pY, pWidth, pHeight)); } public Rectangle getAOI(final Rectangle pCrop) { int y = pCrop.y; int x = pCrop.x; + Dimension dimension = getOriginalDimension(); - Dimension crop = getCrop(pCrop); + Dimension crop = getCrop(dimension, pCrop); // Center if (y < 0) { - y = calculateY(crop.height); + y = calculateY(dimension, new Rectangle(x, y, crop.width, crop.height)); } if (x < 0) { - x = calculateX(crop.width); + x = calculateX(dimension, new Rectangle(x, y, crop.width, crop.height)); } return new Rectangle(x, y, crop.width, crop.height); } - protected int calculateX(int pWidth) { - return (mOriginalWidth - pWidth) / 2; + public Dimension getOriginalDimension() { + return new Dimension(mOriginalWidth, mOriginalHeight); } - - protected int calculateY(int pHeight) { - return (mOriginalHeight - pHeight) / 2; + public int calculateX(Dimension pOriginalDimension, Rectangle pCrop) { + return (pOriginalDimension.width - pCrop.width) / 2; } - private int calculateRuleOfThirds(final int pY, final int pCropWidth, final int pCropHeight) { - int y = pY; - if (y < 0) { - float origRatio = (float) mOriginalWidth / (float) mOriginalHeight; - float cropRatio = (float) pCropWidth / (float) pCropHeight; - if (cropRatio > origRatio && origRatio < 1) { - y = (int) ((mOriginalHeight * 0.33f) - (pCropHeight / 2)); - if (y < 0) { - y = 0; - } - } - } - return y; + public int calculateY(Dimension pOriginalDimension, Rectangle pCrop) { + return (pOriginalDimension.height - pCrop.height) / 2; } - protected Dimension getCrop(final Rectangle pCrop) { - return getOriginalDimension(pCrop); - } - - private Dimension getOriginalDimension(Rectangle pCrop) { + public Dimension getCrop(Dimension pOriginalDimension, final Rectangle pCrop) { + int mOriginalWidth1 = pOriginalDimension.width; + int mOriginalHeight1 = pOriginalDimension.height; int x = pCrop.x; int y = pCrop.y; int cropWidth = pCrop.width; int cropHeight = pCrop.height; - if (cropWidth < 0 || (x < 0 && cropWidth > mOriginalWidth) - || (x >= 0 && (x + cropWidth) > mOriginalWidth)) { - cropWidth = (x >= 0 ? mOriginalWidth - x : mOriginalWidth); + if (cropWidth < 0 || (x < 0 && cropWidth > mOriginalWidth1) + || (x >= 0 && (x + cropWidth) > mOriginalWidth1)) { + cropWidth = (x >= 0 ? mOriginalWidth1 - x : mOriginalWidth1); } - if (cropHeight < 0 || (y < 0 && cropHeight > mOriginalHeight) - || (y >= 0 && (y + cropHeight) > mOriginalHeight)) { - cropHeight = (y >= 0 ? mOriginalHeight - y : mOriginalHeight); + if (cropHeight < 0 || (y < 0 && cropHeight > mOriginalHeight1) + || (y >= 0 && (y + cropHeight) > mOriginalHeight1)) { + cropHeight = (y >= 0 ? mOriginalHeight1 - y : mOriginalHeight1); } return new Dimension(cropWidth, cropHeight); } diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java index e23bd7d9..3b53b9d8 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java @@ -8,32 +8,36 @@ import java.awt.*; */ public class PercentAreaOfInterest extends DefaultAreaOfInterest { + public PercentAreaOfInterest(Dimension pOriginalDimension) { + super(pOriginalDimension); + } + public PercentAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { super(pOriginalWidth, pOriginalHeight); } - protected Dimension getCrop(final Rectangle pCrop) { + public Dimension getCrop(Dimension pOriginalDimension, final Rectangle pCrop) { int cropWidth = pCrop.width; int cropHeight = pCrop.height; float ratio; if (cropWidth >= 0 && cropHeight >= 0) { // Non-uniform - cropWidth = Math.round((float) mOriginalWidth * (float) pCrop.width / 100f); - cropHeight = Math.round((float) mOriginalHeight * (float) pCrop.height / 100f); + cropWidth = Math.round((float) pOriginalDimension.width * (float) pCrop.width / 100f); + cropHeight = Math.round((float) pOriginalDimension.height * (float) pCrop.height / 100f); } else if (cropWidth >= 0) { // Find ratio from pWidth ratio = (float) cropWidth / 100f; - cropWidth = Math.round((float) mOriginalWidth * ratio); - cropHeight = Math.round((float) mOriginalHeight * ratio); + cropWidth = Math.round((float) pOriginalDimension.width * ratio); + cropHeight = Math.round((float) pOriginalDimension.height * ratio); } else if (cropHeight >= 0) { // Find ratio from pHeight ratio = (float) cropHeight / 100f; - cropWidth = Math.round((float) mOriginalWidth * ratio); - cropHeight = Math.round((float) mOriginalHeight * ratio); + cropWidth = Math.round((float) pOriginalDimension.width * ratio); + cropHeight = Math.round((float) pOriginalDimension.height * ratio); } // Else: No crop diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java index 3623d4c8..7d785307 100644 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java @@ -7,39 +7,41 @@ import java.awt.*; * @version $Revision: $ */ public class UniformAreaOfInterest extends DefaultAreaOfInterest { - + + public UniformAreaOfInterest(Dimension pOriginalDimension) { + super(pOriginalDimension); + } + public UniformAreaOfInterest(int pOriginalWidth, int pOriginalHeight) { super(pOriginalWidth, pOriginalHeight); } - protected Dimension getCrop(final Rectangle pCrop) { + public Dimension getCrop(Dimension pOriginalDimension, final Rectangle pCrop) { float ratio; - if (pCrop.width >= 0 && pCrop.height >= 0) { // Compute both ratios ratio = (float) pCrop.width / (float) pCrop.height; - float originalRatio = (float) mOriginalWidth / (float) mOriginalHeight; + float originalRatio = (float) pOriginalDimension.width / (float) pOriginalDimension.height; if (ratio > originalRatio) { - pCrop.width = mOriginalWidth; - pCrop.height = Math.round((float) mOriginalWidth / ratio); + pCrop.width = pOriginalDimension.width; + pCrop.height = Math.round((float) pOriginalDimension.width / ratio); } else { - pCrop.height = mOriginalHeight; - pCrop.width = Math.round((float) mOriginalHeight * ratio); + pCrop.height = pOriginalDimension.height; + pCrop.width = Math.round((float) pOriginalDimension.height * ratio); } } else if (pCrop.width >= 0) { // Find ratio from pWidth - ratio = (float) pCrop.width / (float) mOriginalWidth; - pCrop.height = Math.round((float) mOriginalHeight * ratio); + ratio = (float) pCrop.width / (float) pOriginalDimension.width; + pCrop.height = Math.round((float) pOriginalDimension.height * ratio); } else if (pCrop.height >= 0) { // Find ratio from pHeight - ratio = (float) pCrop.height / (float) mOriginalHeight; - pCrop.width = Math.round((float) mOriginalWidth * ratio); + ratio = (float) pCrop.height / (float) pOriginalDimension.height; + pCrop.width = Math.round((float) pOriginalDimension.width * ratio); } // Else: No crop return new Dimension(pCrop.width, pCrop.height); } - } diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java similarity index 61% rename from twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java rename to twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java index feab7f5c..e8d94133 100644 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/AreaOfInterestTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java @@ -1,4 +1,4 @@ -package com.twelvemonkeys.servlet.image; +package com.twelvemonkeys.servlet.image.aoi; import com.twelvemonkeys.servlet.image.aoi.DefaultAreaOfInterest; import com.twelvemonkeys.servlet.image.aoi.UniformAreaOfInterest; @@ -13,36 +13,40 @@ import static org.junit.Assert.assertEquals; * @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(200, 200).getAOI(10, 10, 100, 100)); + 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(100, 200).getAOI(10, 10, 100, 100)); + 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(100, 200).getAOI(0, 10, 110, 100)); + 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(200, 100).getAOI(10, 10, 100, 100)); + 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(200, 100).getAOI(10, 0, 100, 110)); + assertEquals(new Rectangle(10, 0, 100, 100), new DefaultAreaOfInterest(LANDSCAPE_200_100).getAOI(10, 0, 100, 110)); } // ----------------------------------------------------------------------------------------------------------------- @@ -51,127 +55,127 @@ public class AreaOfInterestTestCase { @Test public void testGetAOIUniformCenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), new UniformAreaOfInterest(100, 100).getAOI(-1, -1, 333, 333)); + 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(100, 100).getAOI(-1, -1, 33, 33)); + 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(100, 100).getAOI(-1, -1, 100, 100)); + 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(100, 100).getAOI(-1, -1, 200, 100)); + 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(100, 100).getAOI(-1, -1, 100, 50)); + 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(100, 100).getAOI(-1, -1, 100, 200)); + 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(100, 100).getAOI(-1, -1, 50, 100)); + 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(200, 100).getAOI(-1, -1, 333, 333)); + 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(200, 100).getAOI(-1, -1, 100, 100)); + 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(200, 100).getAOI(-1, -1, 100, 50)); + 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(200, 100).getAOI(-1, -1, 200, 50)); + 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(200, 100).getAOI(-1, -1, 75, 50)); + 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(200, 100).getAOI(-1, -1, 150, 100)); + 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(200, 100).getAOI(-1, -1, 200, 100)); + 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(200, 100).getAOI(-1, -1, 100, 200)); + 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(200, 100).getAOI(-1, -1, 50, 100)); + 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(100, 200).getAOI(-1, -1, 333, 333)); + 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(100, 200).getAOI(-1, -1, 100, 100)); + 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(100, 200).getAOI(-1, -1, 200, 100)); + 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(100, 200).getAOI(-1, -1, 100, 50)); + 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(100, 200).getAOI(-1, -1, 50, 100)); + 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(100, 200).getAOI(-1, -1, 25, 100)); + 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(100, 200).getAOI(-1, -1, 75, 100)); + 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(100, 200).getAOI(-1, -1, 100, 125)); + 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(100, 200).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 200), new UniformAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 200)); } // ----------------------------------------------------------------------------------------------------------------- @@ -180,226 +184,158 @@ public class AreaOfInterestTestCase { @Test public void testGetAOICenteredS2SUp() { - assertEquals(new Rectangle(0, 0, 100, 100), new DefaultAreaOfInterest(100, 100).getAOI(-1, -1, 333, 333)); + 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(100, 100).getAOI(-1, -1, 33, 33)); + 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(100, 100).getAOI(-1, -1, 100, 100)); + 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(100, 100).getAOI(-1, -1, 200, 100)); + 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(100, 100).getAOI(-1, -1, 20, 10)); + 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(100, 100).getAOI(-1, -1, 100, 50)); + 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(100, 100).getAOI(-1, -1, 100, 200)); + 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(100, 100).getAOI(-1, -1, 10, 20)); + 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(100, 100).getAOI(-1, -1, 50, 100)); + 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(200, 100).getAOI(-1, -1, 333, 333)); + 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(200, 100).getAOI(-1, -1, 50, 50)); + 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(200, 100).getAOI(-1, -1, 100, 100)); + 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(200, 100).getAOI(-1, -1, 300, 200)); + 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(200, 100).getAOI(-1, -1, 100, 50)); + 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(200, 100).getAOI(-1, -1, 180, 20)); + 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(200, 100).getAOI(-1, -1, 75, 50)); + 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(200, 100).getAOI(-1, -1, 200, 100)); + 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(200, 100).getAOI(-1, -1, 100, 200)); + 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(200, 100).getAOI(-1, -1, 33, 50)); + 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(200, 100).getAOI(-1, -1, 50, 100)); + 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(100, 200).getAOI(-1, -1, 33, 33)); + 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(100, 200).getAOI(-1, -1, 100, 100)); + 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(100, 200).getAOI(-1, -1, 200, 100)); + 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(100, 200).getAOI(-1, -1, 20, 10)); + 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(100, 200).getAOI(-1, -1, 100, 50)); + 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(100, 200).getAOI(-1, -1, 10, 20)); + 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(100, 200).getAOI(-1, -1, 100, 200)); + 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(100, 200).getAOI(-1, -1, 25, 100)); + 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(100, 200).getAOI(-1, -1, 75, 100)); + 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(100, 200).getAOI(-1, -1, 100, 125)); + 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(100, 200).getAOI(-1, -1, 100, 200)); + assertEquals(new Rectangle(0, 0, 100, 200), new DefaultAreaOfInterest(PORTRAIT_100_200).getAOI(-1, -1, 100, 200)); } - /* @Test - public void testGetAOIRuleOfThirdsN2N() { - enableRuleOfThirds(); - assertEquals(new Rectangle(45, 90, 10, 20), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 10, 20)); - } - @Test - public void testGetAOIRuleOfThirdsN2NMax() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 0, 100, 200), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 200)); - } - - @Test - public void testGetAOIUniformRuleOfThirdsN2S() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 333, 333)); - } - - @Test - public void testGetAOIUniformRuleOfThirdsN2SNormalized() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 100)); - } - - @Test - public void testGetAOIUniformRuleOfThirdsN2W() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 200, 100)); - } - - @Test - public void testGetAOIUniformRuleOfThirdsN2WNormalized() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, true).getAOI(-1, -1, 100, 50)); - } - - @Test - public void testGetAOIRuleOfThirdsN2S() { - enableRuleOfThirds(); - assertEquals(new Rectangle(33, 50, 33, 33), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 33, 33)); - } - - @Test - public void testGetAOIRuleOfThirdsN2SMax() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 100)); - } - - @Test - public void testGetAOIRuleOfThirdsN2WOverflow() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 16, 100, 100), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 200, 100)); - } - - @Test - public void testGetAOIRuleOfThirdsN2W() { - enableRuleOfThirds(); - assertEquals(new Rectangle(40, 61, 20, 10), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 20, 10)); - } - - @Test - public void testGetAOIRuleOfThirdsN2WMax() { - enableRuleOfThirds(); - assertEquals(new Rectangle(0, 41, 100, 50), new AreaOfInterest(100, 200, false, false).getAOI(-1, -1, 100, 50)); - } - - private void enableRuleOfThirds() { - System.setProperty("rule-of-thirds", "true"); - }*/ } From e72700b0329dfe7539a987a6bf16c04e4ad93886 Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Wed, 21 Apr 2010 13:00:19 +0200 Subject: [PATCH 07/17] Use rectangle instead of package protected method. --- .../twelvemonkeys/servlet/image/ImageServletResponseImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b605cfa4..3edb7d62 100755 --- a/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java +++ b/servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java @@ -587,7 +587,7 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima AreaOfInterest areaOfInterest = AreaOfInterestFactory.getDefault(). createAreaOfInterest(pDefaultWidth, pDefaultHeight, aoiPercent, aoiUniform); - aoi = areaOfInterest.getAOI(aoiX, aoiY, aoiW, aoiH); + aoi = areaOfInterest.getAOI(new Rectangle(aoiX, aoiY, aoiW, aoiH)); return aoi; } From 5bd8c37c2d00bf0494762bd688f5695ecc266fb4 Mon Sep 17 00:00:00 2001 From: Shihab Uddin Date: Wed, 26 Sep 2012 11:51:25 +0200 Subject: [PATCH 08/17] fixed merge conflicts --- .../servlet/image/aoi/AreaOfInterest.java | 0 .../image/aoi/AreaOfInterestFactory.java | 0 .../image/aoi/AreaOfInterestWrapper.java | 0 .../image/aoi/DefaultAreaOfInterest.java | 0 .../image/aoi/PercentAreaOfInterest.java | 0 .../image/aoi/UniformAreaOfInterest.java | 0 .../ImageServletResponseImplTestCase.java | 25 ------------------- .../image/aoi/AreaOfInterestTestCase.java | 0 8 files changed, 25 deletions(-) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java (100%) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java (100%) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java (100%) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java (100%) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java (100%) rename {twelvemonkeys-servlet => servlet}/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java (100%) rename {twelvemonkeys-servlet => servlet}/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java (100%) diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterest.java diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestFactory.java diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestWrapper.java diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/DefaultAreaOfInterest.java diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/PercentAreaOfInterest.java diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java b/servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java similarity index 100% rename from twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java rename to servlet/src/main/java/com/twelvemonkeys/servlet/image/aoi/UniformAreaOfInterest.java diff --git a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java index fc3a7803..df1a9408 100755 --- a/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java +++ b/servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java @@ -1,6 +1,5 @@ package com.twelvemonkeys.servlet.image; -import com.twelvemonkeys.image.BufferedImageIcon; import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.io.FileUtil; import com.twelvemonkeys.servlet.OutputStreamAdapter; @@ -12,14 +11,12 @@ import javax.servlet.ServletContext; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.swing.*; import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import static org.junit.Assert.*; @@ -274,28 +271,6 @@ public class ImageServletResponseImplTestCase { verify(response).getOutputStream(); } - private static void showIt(final BufferedImage expected, final BufferedImage actual, final BufferedImage diff) { - try { - SwingUtilities.invokeAndWait(new Runnable() { - public void run() { - JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); - panel.add(new BlackLabel("expected", expected)); - panel.add(new BlackLabel("actual", actual)); - if (diff != null) { - panel.add(new BlackLabel("diff", diff)); - } - JScrollPane scroll = new JScrollPane(panel); - scroll.setBorder(BorderFactory.createEmptyBorder()); - JOptionPane.showMessageDialog(null, scroll); - } - }); - } - catch (InterruptedException ignore) { - } - catch (InvocationTargetException ignore) { - } - } - @Test public void testTranscodeResponseIndexedCM() throws IOException { // Custom setup diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java b/servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java similarity index 100% rename from twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java rename to servlet/src/test/java/com/twelvemonkeys/servlet/image/aoi/AreaOfInterestTestCase.java From a8e5906569a2db72abcd167000efd3b20f9ad524 Mon Sep 17 00:00:00 2001 From: Shihab Uddin Date: Wed, 26 Sep 2012 13:39:46 +0200 Subject: [PATCH 09/17] use escenic repo --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 7a805421..73833911 100755 --- a/pom.xml +++ b/pom.xml @@ -190,8 +190,8 @@ - java.net-m2-repository - java-net:/maven2-repository/trunk/repository/ + central + http://repo.dev.escenic.com/content/repositories/thirdparty From f28ad2d396fa983068accae3f1472634beeb82ed Mon Sep 17 00:00:00 2001 From: Shihab Uddin Date: Wed, 26 Sep 2012 13:42:04 +0200 Subject: [PATCH 10/17] building 3.0-ece-1 --- common/common-image/pom.xml | 2 +- common/common-io/pom.xml | 2 +- common/common-lang/pom.xml | 8 ++++---- common/pom.xml | 2 +- imageio/imageio-batik/pom.xml | 8 ++++---- imageio/imageio-core/pom.xml | 6 +++--- imageio/imageio-icns/pom.xml | 6 +++--- imageio/imageio-ico/pom.xml | 6 +++--- imageio/imageio-iff/pom.xml | 2 +- imageio/imageio-jmagick/pom.xml | 2 +- imageio/imageio-jpeg/pom.xml | 20 ++++++++++---------- imageio/imageio-metadata/pom.xml | 2 +- imageio/imageio-pdf/pom.xml | 10 +++++----- imageio/imageio-pict/pom.xml | 6 +++--- imageio/imageio-psd/pom.xml | 20 ++++++++++---------- imageio/imageio-reference/pom.xml | 2 +- imageio/imageio-thumbsdb/pom.xml | 2 +- imageio/imageio-tiff/pom.xml | 2 +- imageio/pom.xml | 6 +++--- pom.xml | 2 +- sandbox/pom.xml | 4 ++-- sandbox/sandbox-common/pom.xml | 2 +- sandbox/sandbox-imageio/pom.xml | 2 +- sandbox/sandbox-servlet/pom.xml | 2 +- sandbox/sandbox-swing/pom.xml | 2 +- servlet/pom.xml | 2 +- 26 files changed, 65 insertions(+), 65 deletions(-) diff --git a/common/common-image/pom.xml b/common/common-image/pom.xml index df8b1320..b89fa4d2 100644 --- a/common/common-image/pom.xml +++ b/common/common-image/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.common common - 3.0-SNAPSHOT + 3.0-ece-1 common-image jar diff --git a/common/common-io/pom.xml b/common/common-io/pom.xml index bef72a90..e50e74b6 100644 --- a/common/common-io/pom.xml +++ b/common/common-io/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.common common - 3.0-SNAPSHOT + 3.0-ece-1 common-io jar diff --git a/common/common-lang/pom.xml b/common/common-lang/pom.xml index f143ecb9..38f38f03 100644 --- a/common/common-lang/pom.xml +++ b/common/common-lang/pom.xml @@ -2,17 +2,17 @@ - 4.0.0 + 4.0.0 com.twelvemonkeys.common common - 3.0-SNAPSHOT + 3.0-ece-1 common-lang - jar + jar TwelveMonkeys :: Common :: Language support The TwelveMonkeys Common Language support - + diff --git a/common/pom.xml b/common/pom.xml index f537ad61..0b2c474c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys twelvemonkeys - 3.0-SNAPSHOT + 3.0-ece-1 com.twelvemonkeys.common common diff --git a/imageio/imageio-batik/pom.xml b/imageio/imageio-batik/pom.xml index 7a067bee..de92b4ce 100644 --- a/imageio/imageio-batik/pom.xml +++ b/imageio/imageio-batik/pom.xml @@ -3,10 +3,10 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - - com.twelvemonkeys.imageio + + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-batik TwelveMonkeys :: ImageIO :: Batik Plugin @@ -61,4 +61,4 @@ - + diff --git a/imageio/imageio-core/pom.xml b/imageio/imageio-core/pom.xml index a84569c5..6864c39f 100644 --- a/imageio/imageio-core/pom.xml +++ b/imageio/imageio-core/pom.xml @@ -4,11 +4,11 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-core TwelveMonkeys :: ImageIO :: Core - + diff --git a/imageio/imageio-icns/pom.xml b/imageio/imageio-icns/pom.xml index 6fd2a3af..fb820cfc 100644 --- a/imageio/imageio-icns/pom.xml +++ b/imageio/imageio-icns/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-icns TwelveMonkeys :: ImageIO :: ICNS plugin @@ -23,4 +23,4 @@ tests - + diff --git a/imageio/imageio-ico/pom.xml b/imageio/imageio-ico/pom.xml index ed812e8a..d4147544 100644 --- a/imageio/imageio-ico/pom.xml +++ b/imageio/imageio-ico/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-ico TwelveMonkeys :: ImageIO :: ICO plugin @@ -23,4 +23,4 @@ tests - + diff --git a/imageio/imageio-iff/pom.xml b/imageio/imageio-iff/pom.xml index b0f66ce4..450d012f 100644 --- a/imageio/imageio-iff/pom.xml +++ b/imageio/imageio-iff/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-iff TwelveMonkeys :: ImageIO :: IFF plugin diff --git a/imageio/imageio-jmagick/pom.xml b/imageio/imageio-jmagick/pom.xml index ced0f234..b2a2e5eb 100644 --- a/imageio/imageio-jmagick/pom.xml +++ b/imageio/imageio-jmagick/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-jmagick TwelveMonkeys :: ImageIO :: JMagick Plugin diff --git a/imageio/imageio-jpeg/pom.xml b/imageio/imageio-jpeg/pom.xml index 7e6a16e3..62fbb494 100644 --- a/imageio/imageio-jpeg/pom.xml +++ b/imageio/imageio-jpeg/pom.xml @@ -4,10 +4,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT - + 3.0-ece-1 + imageio-jpeg TwelveMonkeys :: ImageIO :: JPEG plugin @@ -23,10 +23,10 @@ com.twelvemonkeys.imageio imageio-core tests - - - com.twelvemonkeys.imageio - imageio-metadata - - - + + + com.twelvemonkeys.imageio + imageio-metadata + + + diff --git a/imageio/imageio-metadata/pom.xml b/imageio/imageio-metadata/pom.xml index 6b681a1e..f2a1c969 100644 --- a/imageio/imageio-metadata/pom.xml +++ b/imageio/imageio-metadata/pom.xml @@ -5,7 +5,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 4.0.0 imageio-metadata diff --git a/imageio/imageio-pdf/pom.xml b/imageio/imageio-pdf/pom.xml index b30ee526..582a56b4 100644 --- a/imageio/imageio-pdf/pom.xml +++ b/imageio/imageio-pdf/pom.xml @@ -3,11 +3,11 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - - com.twelvemonkeys.imageio + + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT - + 3.0-ece-1 + imageio-pdf TwelveMonkeys :: ImageIO :: PDF plugin @@ -26,4 +26,4 @@ tests - + diff --git a/imageio/imageio-pict/pom.xml b/imageio/imageio-pict/pom.xml index 63eebcae..1380f5e3 100644 --- a/imageio/imageio-pict/pom.xml +++ b/imageio/imageio-pict/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-pict TwelveMonkeys :: ImageIO :: PICT plugin @@ -24,4 +24,4 @@ - + diff --git a/imageio/imageio-psd/pom.xml b/imageio/imageio-psd/pom.xml index f03b7fdb..1320e73b 100644 --- a/imageio/imageio-psd/pom.xml +++ b/imageio/imageio-psd/pom.xml @@ -4,10 +4,10 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 4.0.0 - com.twelvemonkeys.imageio + com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT - + 3.0-ece-1 + imageio-psd TwelveMonkeys :: ImageIO :: PSD plugin @@ -23,10 +23,10 @@ com.twelvemonkeys.imageio imageio-core tests - - - com.twelvemonkeys.imageio - imageio-metadata - - - + + + com.twelvemonkeys.imageio + imageio-metadata + + + diff --git a/imageio/imageio-reference/pom.xml b/imageio/imageio-reference/pom.xml index db316d00..97456218 100644 --- a/imageio/imageio-reference/pom.xml +++ b/imageio/imageio-reference/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-reference TwelveMonkeys :: ImageIO :: reference test cases diff --git a/imageio/imageio-thumbsdb/pom.xml b/imageio/imageio-thumbsdb/pom.xml index b91550ed..05216d88 100644 --- a/imageio/imageio-thumbsdb/pom.xml +++ b/imageio/imageio-thumbsdb/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-thumbsdb TwelveMonkeys :: ImageIO :: Thumbs.db plugin diff --git a/imageio/imageio-tiff/pom.xml b/imageio/imageio-tiff/pom.xml index 9084a3fd..71bffffd 100644 --- a/imageio/imageio-tiff/pom.xml +++ b/imageio/imageio-tiff/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.imageio imageio - 3.0-SNAPSHOT + 3.0-ece-1 imageio-tiff TwelveMonkeys :: ImageIO :: TIFF plugin diff --git a/imageio/pom.xml b/imageio/pom.xml index f91ba124..6a9ef1d1 100644 --- a/imageio/pom.xml +++ b/imageio/pom.xml @@ -5,7 +5,7 @@ com.twelvemonkeys twelvemonkeys - 3.0-SNAPSHOT + 3.0-ece-1 4.0.0 com.twelvemonkeys.imageio @@ -49,8 +49,8 @@ - 3.0-SNAPSHOT - 3.0-SNAPSHOT + 3.0-ece-1 + 3.0-ece-1 diff --git a/pom.xml b/pom.xml index 73833911..aa25e926 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.twelvemonkeys twelvemonkeys - 3.0-SNAPSHOT + 3.0-ece-1 pom Twelvemonkeys diff --git a/sandbox/pom.xml b/sandbox/pom.xml index 20decb8b..2b0200c1 100644 --- a/sandbox/pom.xml +++ b/sandbox/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.twelvemonkeys.sandbox sandbox - 3.0-SNAPSHOT + 3.0-ece-1 TwelveMonkeys :: Sandbox pom @@ -16,7 +16,7 @@ com.twelvemonkeys twelvemonkeys - 3.0-SNAPSHOT + 3.0-ece-1 diff --git a/sandbox/sandbox-common/pom.xml b/sandbox/sandbox-common/pom.xml index 4579dfb4..329df6bd 100644 --- a/sandbox/sandbox-common/pom.xml +++ b/sandbox/sandbox-common/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.sandbox sandbox - 3.0-SNAPSHOT + 3.0-ece-1 sandbox-common jar diff --git a/sandbox/sandbox-imageio/pom.xml b/sandbox/sandbox-imageio/pom.xml index c574bd69..fba8c15e 100644 --- a/sandbox/sandbox-imageio/pom.xml +++ b/sandbox/sandbox-imageio/pom.xml @@ -34,7 +34,7 @@ com.twelvemonkeys.sandbox sandbox - 3.0-SNAPSHOT + 3.0-ece-1 sandbox-imageio jar diff --git a/sandbox/sandbox-servlet/pom.xml b/sandbox/sandbox-servlet/pom.xml index 87077d1c..3aba1862 100644 --- a/sandbox/sandbox-servlet/pom.xml +++ b/sandbox/sandbox-servlet/pom.xml @@ -34,7 +34,7 @@ com.twelvemonkeys.sandbox sandbox - 3.0-SNAPSHOT + 3.0-ece-1 sandbox-servlet jar diff --git a/sandbox/sandbox-swing/pom.xml b/sandbox/sandbox-swing/pom.xml index 16936b35..6d3520d3 100644 --- a/sandbox/sandbox-swing/pom.xml +++ b/sandbox/sandbox-swing/pom.xml @@ -6,7 +6,7 @@ com.twelvemonkeys.sandbox sandbox - 3.0-SNAPSHOT + 3.0-ece-1 sandbox-swing jar diff --git a/servlet/pom.xml b/servlet/pom.xml index 28bb1982..977a9f7e 100644 --- a/servlet/pom.xml +++ b/servlet/pom.xml @@ -5,7 +5,7 @@ com.twelvemonkeys twelvemonkeys - 3.0-SNAPSHOT + 3.0-ece-1 4.0.0 From 1e038509606dab198a77be773728cb137f1a22ea Mon Sep 17 00:00:00 2001 From: Rune Bremnes Date: Mon, 24 Feb 2014 12:42:31 +0100 Subject: [PATCH 11/17] Added failing testcase for JPEGImageReader. --- .../plugins/jpeg/JPEGImageReaderTest.java | 50 ++++++++++++++++++ .../test/resources/jpeg/read-error1024.jpg | Bin 0 -> 18528 bytes .../test/resources/jpeg/read-error1025.jpg | Bin 0 -> 18813 bytes .../test/resources/jpeg/read-error1026.jpg | Bin 0 -> 18813 bytes .../test/resources/jpeg/read-error1027.jpg | Bin 0 -> 18813 bytes .../test/resources/jpeg/read-error1028.jpg | Bin 0 -> 18813 bytes 6 files changed, 50 insertions(+) create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/read-error1024.jpg create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/read-error1025.jpg create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/read-error1026.jpg create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/read-error1027.jpg create mode 100644 imageio/imageio-jpeg/src/test/resources/jpeg/read-error1028.jpg 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 aa5042ee..db590485 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 @@ -962,4 +962,54 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase30lr+Ax%&OUF47=^3y|FaBNPiaLN(s{SKBj zn(mn|fr|`F0ny0(o zTQ%PIeo1dsUZkTD*~w*JU0)X~xFUc60tg_000IagfB*srAb30lr+Ax%&OUF47=^3y|FaBNPiaLN(s{SKBj zn(LW|=_DfOw2&q!f-X2v1W9^G3&!TOf)mF`?{~1Q z(TvA>@;U!W|GN8p|B#)O==>{hVsuSCO^I!6jcb~x#=8)_^FDM#7uvq{ewmj25Mzp= zTaBwYmV2y^s5)t^D}%B1ZK*z(e3Z#qz1Uc_6`hnx%5Ei}WGzb7zs}-Id5X8y+C1I$ zUaRT8_eXlG$|94j$g}KU^xg42A%9;0t2Q8F literal 0 HcmV?d00001 diff --git a/imageio/imageio-jpeg/src/test/resources/jpeg/read-error1027.jpg b/imageio/imageio-jpeg/src/test/resources/jpeg/read-error1027.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7acc733f9c2d9eb1892c10a54fc8824b1ff4fa4b GIT binary patch literal 18813 zcmeIuv5f*T5QX9K+U(ihW!Hu!M>GP6P7uV`!Zkq=baBBIaX=5WU~D1;5h2q50?QiB zc&sn|r>k`9-S>7cJISfiMK+?Vl5*-CQ=1Z7*Q455@11qdw|(oIu5oT0$6X&n48EOA zCt;fJp**6>q&7AWhQ>9y`exFv4A$tcxls$LqS8^Bg`_{3iBjdS4{^CXgy(8$9$tFy z)%ecWNJk^Gl*6hz{yePUiU0x#Ab!!wXW$JSGTpbi?C=r?}K+u zKlFY`cb^|oc~YAy4f@*Fsrt{vvy5qa{%O=wilB5*W-0L^AEH$L>m)XnNB_5)o5z>l z-)ea6y-RCUTBL&!S;^PB_&!Zma76$C1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0 z009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{1Q0*~0R#|0009ILKmY**5I_I{ O1Q0*~fwvdv;_U}K`ygZh literal 0 HcmV?d00001 From 0a7f2505667795e5dd40729828be0efc33ceca17 Mon Sep 17 00:00:00 2001 From: Rune Bremnes Date: Mon, 24 Feb 2014 13:27:16 +0100 Subject: [PATCH 12/17] Fix reading jpeg images where last scanline is higher than the y source subsampling offset. --- .../twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index e8be131d..0a4b2879 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -445,6 +445,10 @@ public class JPEGImageReader extends ImageReaderBase { for (int y = srcRegion.y; y < srcMaxY; y += step) { int scan = Math.min(step, srcMaxY - y); + if(scan <= param.getSubsamplingYOffset()) { + param.setSourceSubsampling(param.getSourceXSubsampling(),param.getSourceYSubsampling(),param.getSubsamplingXOffset(),0); + } + // Let the progress delegator handle progress, using corrected range progressDelegator.updateProgressRange(100f * (y + scan) / srcRegion.height); From 1402e78144570c8157f52d66d1cd4d2d9b76bb7b Mon Sep 17 00:00:00 2001 From: Rune Bremnes Date: Mon, 3 Mar 2014 16:09:01 +0100 Subject: [PATCH 13/17] Clean up after merge. --- .../imageio/plugins/jpeg/JPEGImageReader.java | 4 -- .../plugins/jpeg/JPEGImageReaderTest.java | 50 ------------------- 2 files changed, 54 deletions(-) diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index 0b39d33f..ba8837ea 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -445,10 +445,6 @@ public class JPEGImageReader extends ImageReaderBase { for (int y = srcRegion.y; y < srcMaxY; y += step) { int scan = Math.min(step, srcMaxY - y); - if(scan <= param.getSubsamplingYOffset()) { - param.setSourceSubsampling(param.getSourceXSubsampling(),param.getSourceYSubsampling(),param.getSubsamplingXOffset(),0); - } - // Let the progress delegator handle progress, using corrected range progressDelegator.updateProgressRange(100f * (y + scan) / srcRegion.height); 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 7a901fc9..7a12da6f 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 @@ -1105,54 +1105,4 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase Date: Mon, 3 Mar 2014 16:14:22 +0100 Subject: [PATCH 14/17] Ignoring some more tests. --- .../imageio/plugins/jpeg/JPEGImageReaderTest.java | 3 +++ 1 file changed, 3 insertions(+) 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 7a12da6f..4c964556 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 @@ -720,6 +720,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase Date: Tue, 4 Mar 2014 14:43:41 +0100 Subject: [PATCH 15/17] Reenabling tests after sync from upstream. --- .../imageio/plugins/jpeg/JPEGImageReaderTest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 954750c5..c0e81586 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 @@ -705,8 +705,8 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTestCase Date: Tue, 4 Mar 2014 15:04:01 +0100 Subject: [PATCH 16/17] Added distributionManagement. --- pom.xml | 439 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 223 insertions(+), 216 deletions(-) diff --git a/pom.xml b/pom.xml index ad07784b..8e0c4f20 100755 --- a/pom.xml +++ b/pom.xml @@ -1,216 +1,223 @@ - - - 4.0.0 - - org.sonatype.oss - oss-parent - 7 - - com.twelvemonkeys - twelvemonkeys - 3.0-SNAPSHOT - pom - Twelvemonkeys - - - common - servlet - imageio - - - - - - - harald.kuhr - Harald Kuhr - harald.kuhr@gmail.com - Twelvemonkeys - - project-owner - developer - - - - - - - Erlend Hamnaberg - erlend@hamnaberg.net - - maven-guru - - - - - - scm:git:https://github.com/haraldk/TwelveMonkeys - scm:git:https://github.com/haraldk/TwelveMonkeys - https://github.com/haraldk/TwelveMonkeys - HEAD - - - - UTF-8 - - - - twelvemonkeys-${project.artifactId}-${project.version} - - - org.apache.maven.plugins - maven-jar-plugin - - - - twelvemonkeys-${project.artifactId} - TwelveMonkeys - ${project.version} - ${project.scm.url} - - - - - - - - - - org.apache.maven.plugins - maven-resources-plugin - 2.5 - - UTF-8 - - - - org.apache.maven.plugins - maven-jar-plugin - 2.4 - true - - - package - attach-tests - - test-jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - true - - - - package - attach-sources - - jar - test-jar - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 - true - - 1.5 - 1.5 - false - -g:lines - - iso-8859-1 - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.10 - - - - java.awt.headless - true - - - - - - - org.apache.maven.plugins - maven-idea-plugin - true - 2.2 - - 1.5 - 1.5 - true - - - - - org.apache.maven.plugins - maven-release-plugin - 2.4.2 - - - org.apache.maven.scm - maven-scm-provider-gitexe - 1.9 - - - - - - - - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - org.apache.maven.plugins - maven-surefire-report-plugin - 2.16 - - - org.codehaus.mojo - cobertura-maven-plugin - 2.6 - - - org.apache.maven.plugins - maven-pmd-plugin - 3.0.1 - - 1.5 - - - - org.apache.maven.plugins - maven-checkstyle-plugin - 2.10 - - - - - + + + 4.0.0 + + org.sonatype.oss + oss-parent + 7 + + com.twelvemonkeys + twelvemonkeys + 3.0-SNAPSHOT + pom + Twelvemonkeys + + + common + servlet + imageio + + + + + + + harald.kuhr + Harald Kuhr + harald.kuhr@gmail.com + Twelvemonkeys + + project-owner + developer + + + + + + + Erlend Hamnaberg + erlend@hamnaberg.net + + maven-guru + + + + + + scm:git:https://github.com/haraldk/TwelveMonkeys + scm:git:https://github.com/haraldk/TwelveMonkeys + https://github.com/haraldk/TwelveMonkeys + HEAD + + + + UTF-8 + + + + twelvemonkeys-${project.artifactId}-${project.version} + + + org.apache.maven.plugins + maven-jar-plugin + + + + twelvemonkeys-${project.artifactId} + TwelveMonkeys + ${project.version} + ${project.scm.url} + + + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.5 + + UTF-8 + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + true + + + package + attach-tests + + test-jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + true + + + + package + attach-sources + + jar + test-jar + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + true + + 1.5 + 1.5 + false + -g:lines + + iso-8859-1 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.10 + + + + java.awt.headless + true + + + + + + + org.apache.maven.plugins + maven-idea-plugin + true + 2.2 + + 1.5 + 1.5 + true + + + + + org.apache.maven.plugins + maven-release-plugin + 2.4.2 + + + org.apache.maven.scm + maven-scm-provider-gitexe + 1.9 + + + + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.16 + + + org.codehaus.mojo + cobertura-maven-plugin + 2.6 + + + org.apache.maven.plugins + maven-pmd-plugin + 3.0.1 + + 1.5 + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.10 + + + + + + + central + http://repo.dev.escenic.com/content/repositories/thirdparty-snapshots + false + + + From 0db676f1bea77f061e43e7347fc71f989730206b Mon Sep 17 00:00:00 2001 From: Torstein Krause Johansen Date: Tue, 28 Jun 2016 14:24:25 +0200 Subject: [PATCH 17/17] 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(""); - - inIncludeTag = false; - } - else if (namespacePrefix.equals(this.namespacePrefix) - && localName.equals(openParamTagName)) { - - //System.out.println(""); - - 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(""); + + inIncludeTag = false; + } + else if (namespacePrefix.equals(this.namespacePrefix) + && localName.equals(openParamTagName)) { + + //System.out.println(""); + + 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(""); - } - else { - out.println(""); - } - } - - out.println(""); - - } - out.println("
" + table.get(row, col) + " " + table.get(row, col) + "
"); - } - } - 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(""); + } + else { + out.println(""); + } + } + + out.println(""); + + } + out.println("
" + table.get(row, col) + " " + table.get(row, col) + "
"); + } + } + 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

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
equalAvailability: 1.0

Tag for testing if an attribute is equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: - *
    - *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. - *
  2. The attribute must exist. - *
  3. The attribute must be an instance of one of the supported classes: - *
      - *
    • {@code java.lang.String} - *
    • {@code javax.servlet.http.Cookie} - *
    - *
  4. 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

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
equalAvailability: 1.0

Tag for testing if an attribute is equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: + *
    + *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. + *
  2. The attribute must exist. + *
  3. The attribute must be an instance of one of the supported classes: + *
      + *
    • {@code java.lang.String} + *
    • {@code javax.servlet.http.Cookie} + *
    + *
  4. 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

- * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
notEqualAvailability: 1.0

Tag for testing if an attribute is NOT equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: - *
    - *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. - *
  2. The attribute must exist. - *
  3. The attribute must be an instance of one of the supported classes: - *
      - *
    • {@code java.lang.String} - *
    • {@code javax.servlet.http.Cookie} - *
    - *
  4. 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

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
notEqualAvailability: 1.0

Tag for testing if an attribute is NOT equal to a given value.

Tag BodyJSP    
Restrictions

None

AttributesNameRequiredRuntime Expression EvaluationAvailability
 name Yes Yes 1.0
 

The attribute name

 value No Yes 1.0
 

The value for equality testing

VariablesNone
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: + *
    + *
  1. The attribute name property ({@code name} -> {@code mObjectName}) must not be empty. + *
  2. The attribute must exist. + *
  3. The attribute must be an instance of one of the supported classes: + *
      + *
    • {@code java.lang.String} + *
    • {@code javax.servlet.http.Cookie} + *
    + *
  4. 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)); + } + + +}