mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 48f82a159f | |||
| 7105738811 | |||
| 10aa4ba41e | |||
| 6fb06da4d7 | |||
| a963e1c355 | |||
| 966a9da45d | |||
| 319b2c4e18 | |||
| e9bf7d080c | |||
| fb3691e2ee | |||
| 25f9cc5c55 | |||
| 94777ddc96 | |||
| a4c12d0d64 | |||
| 08a69886b1 | |||
| ab85ff0ec8 | |||
| 7de8231471 | |||
| 0de9f79029 | |||
| eeb56acdde | |||
| a6862cfec8 | |||
| f8284700b4 | |||
| 38caeb22e0 | |||
| b2c5915db8 | |||
| 3911191b04 | |||
| bc328419ac | |||
| da4efe98bf | |||
| 6653f4a85d | |||
| 511a29beb9 | |||
| 5617b4323c | |||
| 16d0af357d | |||
| 74927d5396 | |||
| 7e809dd834 | |||
| 7aa95a08bc | |||
| c28963ae49 | |||
| 0327f5fc1a | |||
| 1c59057c30 | |||
| 3e1f85c4dc | |||
| 11227a68a0 | |||
| 62ba73a30e | |||
| 1f33afb5a1 | |||
| 9d3f271867 | |||
| 812e12acb0 | |||
| 060b6cf852 | |||
| e68ce7ffd1 | |||
| 778cdef69c | |||
| d46a76fca8 | |||
| 105a1ee466 | |||
| aa030f526c | |||
| 976e5d6210 | |||
| 6daca00fcd | |||
| ef05872934 | |||
| 1ddab866fd | |||
| ce997a6951 | |||
| 23bf5cb7b2 | |||
| 564778f415 | |||
| e28bf8fb44 | |||
| cf8d630d01 | |||
| 0ff7224912 | |||
| 196081a317 | |||
| ff50180d86 | |||
| 8f2c482167 | |||
| eab24890ca | |||
| cd42d81817 | |||
| ba5c667b6c | |||
| 4e9fa9442c | |||
| 4d2326c18d | |||
| 94eac2d6e5 | |||
| f63a33d541 | |||
| 00f8d87f36 | |||
| 4c2ab6da7b | |||
| b5088312e2 | |||
| f04f968f12 | |||
| 8896092e31 | |||
| 2f9768a1d4 | |||
| 06bcf22242 | |||
| 20c7f8e60e | |||
| 15a9ad0a9b | |||
| 7ae2d636dc | |||
| 12e756b23c | |||
| 4e2bf131d2 | |||
| d0c4a07556 | |||
| 21059c8d5a | |||
| fa7b530809 | |||
| 790cf3b32e | |||
| b1baaad23b | |||
| 7fa704ace5 | |||
| 8d07f4fe90 | |||
| 32bba6857b |
@@ -0,0 +1,53 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Reported bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Version information**
|
||||
1. The version of the TwelveMonkeys ImageIO library in use.
|
||||
For example: 4.0.0
|
||||
|
||||
2. The *exact* output of `java --version` (or `java -version` for older Java releases).
|
||||
For example:
|
||||
|
||||
java version "1.8.0_271"
|
||||
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
|
||||
|
||||
3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Compile the below sample code
|
||||
2. Download the sample image file
|
||||
3. Run the code with the sample file
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Example code**
|
||||
Preferably as a failing JUnit test, or a standalone program with a `main` method that showcases the problem.
|
||||
|
||||
Less is more. Don't add your entire project, only the code required to reproduce the problem. 😀
|
||||
|
||||
**Sample file(s)**
|
||||
Attach any sample files needed to reproduce the problem. Use a ZIP-file if the format is not directly supported by GitHub.
|
||||
|
||||
**Stak trace**
|
||||
Always include the stack trace you experience.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
Do not add screenshots of code or stack traces. 😀
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: New feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem or use case is.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: Trouble shooting and programming help
|
||||
about: "General programming issues will reach a wider audience at StackOverflow. Tag
|
||||
questions with javax-imageio and/or twelvemonkeys \U0001F600 "
|
||||
title: ''
|
||||
labels: Trouble-shooting
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
General programming issues and problems will reach a much wider audience at StackOverflow, we suggest you ask them there. This will offload our work with maintaining the library, and make sure you get better help sooner.
|
||||
|
||||
Tag the question with `javax-imageio` and/or `twelvemonkeys` and we'll find them there.
|
||||
@@ -0,0 +1,11 @@
|
||||
**What is fixed** Add link to the issue this PR fixes.
|
||||
|
||||
Example: Fixes #42.
|
||||
|
||||
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
|
||||
|
||||
**What is changed** Briefly describe the changes proposed in this pull request:
|
||||
|
||||
* Fixed rare exception happening in `x >= 42` case
|
||||
* Small optimization of `decompress()` method
|
||||
* Corrected API doc for `compress()` method to reflect current implementation
|
||||
@@ -31,19 +31,19 @@ As there is lots of legacy data out there, we see the need for open implementati
|
||||
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | DCX | Multi-page PCX fax document | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PNTG | Apple MacPaint Picture Format | ✔ | | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PNTG | Apple MacPaint Picture Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PBM | NetPBM Portable Bit Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PGM | NetPBM Portable Grey Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PFM | Portable Float Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | (✔) | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only
|
||||
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | BigTIFF | | ✔ | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | BigTIFF | | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| XWD | XWD | X11 Window Dump Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
|
||||
@@ -271,22 +271,32 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Optional dependency. Needed only if you deploy ImageIO plugins as part of a web app.
|
||||
Make sure you add the IIOProviderContextListener to your web.xml, see above.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||
<artifactId>servlet</artifactId>
|
||||
<version>3.8.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Or Jakarta version, for Servlet API 5.0
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||
<artifactId>servlet</artifactId>
|
||||
<version>3.7.0</version>
|
||||
<classifier>jakarta</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
@@ -295,13 +305,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
||||
|
||||
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
||||
|
||||
twelvemonkeys-common-lang-3.7.0.jar
|
||||
twelvemonkeys-common-io-3.7.0.jar
|
||||
twelvemonkeys-common-image-3.7.0.jar
|
||||
twelvemonkeys-imageio-core-3.7.0.jar
|
||||
twelvemonkeys-imageio-metadata-3.7.0.jar
|
||||
twelvemonkeys-imageio-jpeg-3.7.0.jar
|
||||
twelvemonkeys-imageio-tiff-3.7.0.jar
|
||||
twelvemonkeys-common-lang-3.8.0.jar
|
||||
twelvemonkeys-common-io-3.8.0.jar
|
||||
twelvemonkeys-common-image-3.8.0.jar
|
||||
twelvemonkeys-imageio-core-3.8.0.jar
|
||||
twelvemonkeys-imageio-metadata-3.8.0.jar
|
||||
twelvemonkeys-imageio-jpeg-3.8.0.jar
|
||||
twelvemonkeys-imageio-tiff-3.8.0.jar
|
||||
|
||||
#### Deploying the plugins in a web app
|
||||
|
||||
@@ -367,44 +377,44 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
|
||||
|
||||
### Links to prebuilt binaries
|
||||
|
||||
##### Latest version (3.7.0)
|
||||
##### Latest version (3.8.0)
|
||||
|
||||
Requires Java 7 or later.
|
||||
|
||||
Common dependencies
|
||||
* [common-lang-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.7.0/common-lang-3.7.0.jar)
|
||||
* [common-io-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.7.0/common-io-3.7.0.jar)
|
||||
* [common-image-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.7.0/common-image-3.7.0.jar)
|
||||
* [common-lang-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.8.0/common-lang-3.8.0.jar)
|
||||
* [common-io-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.8.0/common-io-3.8.0.jar)
|
||||
* [common-image-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.8.0/common-image-3.8.0.jar)
|
||||
|
||||
ImageIO dependencies
|
||||
* [imageio-core-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.7.0/imageio-core-3.7.0.jar)
|
||||
* [imageio-metadata-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.7.0/imageio-metadata-3.7.0.jar)
|
||||
* [imageio-core-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.8.0/imageio-core-3.8.0.jar)
|
||||
* [imageio-metadata-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.8.0/imageio-metadata-3.8.0.jar)
|
||||
|
||||
ImageIO plugins
|
||||
* [imageio-bmp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.7.0/imageio-bmp-3.7.0.jar)
|
||||
* [imageio-hdr-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.7.0/imageio-hdr-3.7.0.jar)
|
||||
* [imageio-icns-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.7.0/imageio-icns-3.7.0.jar)
|
||||
* [imageio-iff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.7.0/imageio-iff-3.7.0.jar)
|
||||
* [imageio-jpeg-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.7.0/imageio-jpeg-3.7.0.jar)
|
||||
* [imageio-pcx-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.7.0/imageio-pcx-3.7.0.jar)
|
||||
* [imageio-pict-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.7.0/imageio-pict-3.7.0.jar)
|
||||
* [imageio-pnm-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.7.0/imageio-pnm-3.7.0.jar)
|
||||
* [imageio-psd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.7.0/imageio-psd-3.7.0.jar)
|
||||
* [imageio-sgi-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.7.0/imageio-sgi-3.7.0.jar)
|
||||
* [imageio-tga-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.7.0/imageio-tga-3.7.0.jar)
|
||||
* [imageio-thumbsdb-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.7.0/imageio-thumbsdb-3.7.0.jar)
|
||||
* [imageio-tiff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.7.0/imageio-tiff-3.7.0.jar)
|
||||
* [imageio-webp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.7.0/imageio-webp-3.7.0.jar)
|
||||
* [imageio-xwd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.7.0/imageio-xwd-3.7.0.jar)
|
||||
* [imageio-bmp-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.8.0/imageio-bmp-3.8.0.jar)
|
||||
* [imageio-hdr-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.8.0/imageio-hdr-3.8.0.jar)
|
||||
* [imageio-icns-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.8.0/imageio-icns-3.8.0.jar)
|
||||
* [imageio-iff-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.8.0/imageio-iff-3.8.0.jar)
|
||||
* [imageio-jpeg-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.8.0/imageio-jpeg-3.8.0.jar)
|
||||
* [imageio-pcx-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.8.0/imageio-pcx-3.8.0.jar)
|
||||
* [imageio-pict-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.8.0/imageio-pict-3.8.0.jar)
|
||||
* [imageio-pnm-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.8.0/imageio-pnm-3.8.0.jar)
|
||||
* [imageio-psd-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.8.0/imageio-psd-3.8.0.jar)
|
||||
* [imageio-sgi-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.8.0/imageio-sgi-3.8.0.jar)
|
||||
* [imageio-tga-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.8.0/imageio-tga-3.8.0.jar)
|
||||
* [imageio-thumbsdb-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.8.0/imageio-thumbsdb-3.8.0.jar)
|
||||
* [imageio-tiff-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.8.0/imageio-tiff-3.8.0.jar)
|
||||
* [imageio-webp-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.8.0/imageio-webp-3.8.0.jar)
|
||||
* [imageio-xwd-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.8.0/imageio-xwd-3.8.0.jar)
|
||||
|
||||
ImageIO plugins requiring 3rd party libs
|
||||
* [imageio-batik-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.7.0/imageio-batik-3.7.0.jar)
|
||||
* [imageio-batik-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.8.0/imageio-batik-3.8.0.jar)
|
||||
|
||||
Photoshop Path support for ImageIO
|
||||
* [imageio-clippath-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.7.0/imageio-clippath-3.7.0.jar)
|
||||
* [imageio-clippath-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.8.0/imageio-clippath-3.8.0.jar)
|
||||
|
||||
Servlet support
|
||||
* [servlet-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.7.0/servlet-3.7.0.jar)
|
||||
* [servlet-3.8.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.0/servlet-3.8.0.jar)
|
||||
|
||||
##### Old version (3.0.x)
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -48,6 +48,13 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.11.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-rasterizer-ext</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
+563
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
|
||||
|
||||
/**
|
||||
* A helper class for working with ICC color profiles.
|
||||
* <p>
|
||||
* Standard ICC color profiles are read from system-specific locations
|
||||
* for known operating systems.
|
||||
* </p>
|
||||
* <p>
|
||||
* Color profiles may be configured by placing a property-file
|
||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||
* on the classpath, specifying the full path to the profiles.
|
||||
* ICC color profiles are probably already present on your system, or
|
||||
* can be downloaded from
|
||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
|
||||
* * </p>
|
||||
* <p>
|
||||
* Example property file:
|
||||
* </p>
|
||||
* <pre>
|
||||
* # icc_profiles.properties
|
||||
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
|
||||
* GENERIC_CMYK=/path/to/Generic CMYK.icc
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
public final class ColorProfiles {
|
||||
/**
|
||||
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
|
||||
*/
|
||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||
|
||||
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
|
||||
static final int ICC_PROFILE_HEADER_SIZE = 128;
|
||||
|
||||
static {
|
||||
// In case we didn't activate through SPI already
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
}
|
||||
|
||||
private ColorProfiles() {
|
||||
}
|
||||
|
||||
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||
// Get *entire profile data*... :-/
|
||||
return getProfileHeaderWithProfileId(profile.getData());
|
||||
}
|
||||
|
||||
static byte[] getProfileHeaderWithProfileId(byte[] data) {
|
||||
// ICC profile header is the first 128 bytes
|
||||
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
|
||||
|
||||
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
|
||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||
// - KCMS keeps the values in the file...
|
||||
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||
// + Clear out rendering intent, as this may be updated by application
|
||||
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||
|
||||
// Clear out any existing MD5, as it is no longer correct
|
||||
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||
|
||||
// Generate new MD5 and store in header
|
||||
byte[] md5 = computeMD5(header, data);
|
||||
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private static byte[] computeMD5(byte[] header, byte[] data) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
|
||||
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
|
||||
return digest.digest();
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @see java.awt.color.ColorSpace#CS_sRGB
|
||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||
*/
|
||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default GRAY profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @see java.awt.color.ColorSpace#CS_GRAY
|
||||
*/
|
||||
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||
// (that is 1 single bit of difference right there.. ;-)
|
||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||
|
||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||
// except the getRenderingIntent method is package protected in java.awt.color
|
||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
// Fix profile before validation
|
||||
profileCleaner.fixProfile(profile);
|
||||
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given input stream, as-is, with no validation.
|
||||
*
|
||||
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
|
||||
*
|
||||
* @param input the input stream to read from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||
* @throws IOException If an I/O error occurs while reading the stream.
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the stream does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(InputStream)
|
||||
* @see #readProfile(InputStream)
|
||||
*/
|
||||
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
return ICC_Profile.getInstance(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given input stream, with extra validation.
|
||||
*
|
||||
* If a matching profile already exists in cache, the cached instance is returned.
|
||||
*
|
||||
* @param input the input stream to read from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||
* @throws IOException If an I/O error occurs while reading the stream.
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the stream does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(InputStream)
|
||||
*/
|
||||
public static ICC_Profile readProfile(final InputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
DataInputStream dataInput = new DataInputStream(input);
|
||||
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
|
||||
try {
|
||||
dataInput.readFully(header);
|
||||
|
||||
int size = validateHeaderAndGetSize(header);
|
||||
byte[] data = Arrays.copyOf(header, size);
|
||||
dataInput.readFully(data, header.length, size - header.length);
|
||||
|
||||
return createProfile(data);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
throw new IllegalArgumentException("Truncated ICC Profile data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ICC Profile from the given byte array, as-is, with no validation.
|
||||
*
|
||||
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
|
||||
* except that extraneous bytes at the end of the array is ignored.
|
||||
*
|
||||
* @param input the byte array to create a profile from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object created from the byte array
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the byte array does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(byte[])
|
||||
* @see #createProfile(byte[])
|
||||
*/
|
||||
public static ICC_Profile createProfileRaw(final byte[] input) {
|
||||
int size = validateHeaderAndGetSize(input);
|
||||
|
||||
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
|
||||
// does not discard extra bytes at the end. We'll chop them off here for convenience
|
||||
return ICC_Profile.getInstance(limit(input, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given byte array, with extra validation.
|
||||
* Extraneous bytes at the end of the array are ignored.
|
||||
*
|
||||
* If a matching profile already exists in cache, the cached instance is returned.
|
||||
*
|
||||
* @param input the byte array to create a profile from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object created from the byte array
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the byte array does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(byte[])
|
||||
*/
|
||||
public static ICC_Profile createProfile(final byte[] input) {
|
||||
int size = validateAndGetSize(input);
|
||||
|
||||
// Look up in cache before returning, these are already validated
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(input);
|
||||
ICC_Profile internal = getInternalProfile(profileHeader);
|
||||
if (internal != null) {
|
||||
return internal;
|
||||
}
|
||||
|
||||
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
|
||||
if (cached != null) {
|
||||
return cached.getProfile();
|
||||
}
|
||||
|
||||
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
|
||||
|
||||
// We'll validate & cache by creating a color space and returning its profile...
|
||||
// TODO: Rewrite with separate cache for profiles...
|
||||
return ColorSpaces.createColorSpace(profile).getProfile();
|
||||
}
|
||||
|
||||
private static byte[] limit(byte[] input, int size) {
|
||||
return input.length == size ? input : Arrays.copyOf(input, size);
|
||||
}
|
||||
|
||||
private static int validateAndGetSize(byte[] input) {
|
||||
int size = validateHeaderAndGetSize(input);
|
||||
|
||||
if (size < 0 || size > input.length) {
|
||||
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static int validateHeaderAndGetSize(byte[] input) {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
|
||||
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
|
||||
}
|
||||
|
||||
int size = intBigEndian(input, ICC_Profile.icHdrSize);
|
||||
|
||||
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
|
||||
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
|
||||
int profileCSType = getCsType(profileHeader);
|
||||
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int intBigEndian(byte[] data, int index) {
|
||||
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
|
||||
}
|
||||
|
||||
private static int getCsType(byte[] profileHeader) {
|
||||
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
|
||||
|
||||
switch (csSig) {
|
||||
case ICC_Profile.icSigXYZData:
|
||||
return ColorSpace.TYPE_XYZ;
|
||||
case ICC_Profile.icSigLabData:
|
||||
return ColorSpace.TYPE_Lab;
|
||||
case ICC_Profile.icSigLuvData:
|
||||
return ColorSpace.TYPE_Luv;
|
||||
case ICC_Profile.icSigYCbCrData:
|
||||
return ColorSpace.TYPE_YCbCr;
|
||||
case ICC_Profile.icSigYxyData:
|
||||
return ColorSpace.TYPE_Yxy;
|
||||
case ICC_Profile.icSigRgbData:
|
||||
return ColorSpace.TYPE_RGB;
|
||||
case ICC_Profile.icSigGrayData:
|
||||
return ColorSpace.TYPE_GRAY;
|
||||
case ICC_Profile.icSigHsvData:
|
||||
return ColorSpace.TYPE_HSV;
|
||||
case ICC_Profile.icSigHlsData:
|
||||
return ColorSpace.TYPE_HLS;
|
||||
case ICC_Profile.icSigCmykData:
|
||||
return ColorSpace.TYPE_CMYK;
|
||||
// Note: There is no TYPE_* 10...
|
||||
case ICC_Profile.icSigCmyData:
|
||||
return ColorSpace.TYPE_CMY;
|
||||
case ICC_Profile.icSigSpace2CLR:
|
||||
return ColorSpace.TYPE_2CLR;
|
||||
case ICC_Profile.icSigSpace3CLR:
|
||||
return ColorSpace.TYPE_3CLR;
|
||||
case ICC_Profile.icSigSpace4CLR:
|
||||
return ColorSpace.TYPE_4CLR;
|
||||
case ICC_Profile.icSigSpace5CLR:
|
||||
return ColorSpace.TYPE_5CLR;
|
||||
case ICC_Profile.icSigSpace6CLR:
|
||||
return ColorSpace.TYPE_6CLR;
|
||||
case ICC_Profile.icSigSpace7CLR:
|
||||
return ColorSpace.TYPE_7CLR;
|
||||
case ICC_Profile.icSigSpace8CLR:
|
||||
return ColorSpace.TYPE_8CLR;
|
||||
case ICC_Profile.icSigSpace9CLR:
|
||||
return ColorSpace.TYPE_9CLR;
|
||||
case ICC_Profile.icSigSpaceACLR:
|
||||
return ColorSpace.TYPE_ACLR;
|
||||
case ICC_Profile.icSigSpaceBCLR:
|
||||
return ColorSpace.TYPE_BCLR;
|
||||
case ICC_Profile.icSigSpaceCCLR:
|
||||
return ColorSpace.TYPE_CCLR;
|
||||
case ICC_Profile.icSigSpaceDCLR:
|
||||
return ColorSpace.TYPE_DCLR;
|
||||
case ICC_Profile.icSigSpaceECLR:
|
||||
return ColorSpace.TYPE_ECLR;
|
||||
case ICC_Profile.icSigSpaceFCLR:
|
||||
return ColorSpace.TYPE_FCLR;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
|
||||
}
|
||||
}
|
||||
|
||||
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
|
||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||
|
||||
if (stream != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(stream);
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||
if (profilePath != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void fixProfile(ICC_Profile profile) {
|
||||
profileCleaner.fixProfile(profile);
|
||||
}
|
||||
|
||||
static boolean validationAltersProfileHeader() {
|
||||
return profileCleaner.validationAltersProfileHeader();
|
||||
}
|
||||
|
||||
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
|
||||
static class sRGB {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||
}
|
||||
|
||||
static class CIEXYZ {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||
}
|
||||
|
||||
static class PYCC {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||
}
|
||||
|
||||
static class GRAY {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
}
|
||||
|
||||
static class LINEAR_RGB {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||
}
|
||||
|
||||
static class Profiles {
|
||||
// TODO: Honour java.iccprofile.path property?
|
||||
private static final Properties PROFILES = loadProfiles();
|
||||
|
||||
private static Properties loadProfiles() {
|
||||
Properties systemDefaults;
|
||||
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||
System.err.printf(
|
||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||
ignore.getMessage()
|
||||
);
|
||||
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
systemDefaults = null;
|
||||
}
|
||||
|
||||
// Create map with defaults and add user overrides if any
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||
);
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
// Most likely, this file won't be there
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("User ICC profiles: " + profiles);
|
||||
System.out.println("System ICC profiles : " + systemDefaults);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static String getPath(final String profileName) {
|
||||
return PROFILES.getProperty(profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
-380
@@ -30,24 +30,17 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.util.LRUHashMap;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
|
||||
|
||||
/**
|
||||
* A helper class for working with ICC color profiles and color spaces.
|
||||
@@ -84,9 +77,6 @@ public final class ColorSpaces {
|
||||
|
||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||
|
||||
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
|
||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||
|
||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||
|
||||
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
||||
@@ -97,14 +87,13 @@ public final class ColorSpaces {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static final int CS_GENERIC_CMYK = 5001;
|
||||
|
||||
static final int ICC_PROFILE_HEADER_SIZE = 128;
|
||||
|
||||
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
|
||||
// Weak references to hold the color spaces while cached
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||
|
||||
// Cache for the latest used color spaces
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
|
||||
|
||||
static {
|
||||
// In case we didn't activate through SPI already
|
||||
@@ -129,7 +118,7 @@ public final class ColorSpaces {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// Fix profile before lookup/create
|
||||
profileCleaner.fixProfile(profile);
|
||||
fixProfile(profile);
|
||||
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
||||
|
||||
@@ -141,55 +130,20 @@ public final class ColorSpaces {
|
||||
return getCachedOrCreateCS(profile, profileHeader);
|
||||
}
|
||||
|
||||
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||
// Get *entire profile data*... :-/
|
||||
return getProfileHeaderWithProfileId(profile.getData());
|
||||
}
|
||||
|
||||
private static byte[] getProfileHeaderWithProfileId(byte[] data) {
|
||||
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
|
||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||
// - KCMS keeps the values in the file...
|
||||
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||
// + Clear out rendering intent, as this may be updated by application
|
||||
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||
|
||||
// Clear out any existing MD5, as it is no longer correct
|
||||
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||
|
||||
// Generate new MD5 and store in header
|
||||
byte[] md5 = computeMD5(data);
|
||||
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
|
||||
|
||||
// ICC profile header is the first 128 bytes
|
||||
return Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
|
||||
}
|
||||
|
||||
private static byte[] computeMD5(byte[] data) {
|
||||
try {
|
||||
return MessageDigest.getInstance("MD5").digest(data);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||
}
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
|
||||
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
}
|
||||
|
||||
@@ -210,7 +164,7 @@ public final class ColorSpaces {
|
||||
cache.put(key, cs);
|
||||
|
||||
// On LCMS, validation *alters* the profile header, need to re-generate key
|
||||
if (profileCleaner.validationAltersProfileHeader()) {
|
||||
if (ColorProfiles.validationAltersProfileHeader()) {
|
||||
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
|
||||
}
|
||||
}
|
||||
@@ -225,11 +179,11 @@ public final class ColorSpaces {
|
||||
}
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
|
||||
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
|
||||
return getCachedCS(new Key(profileHeader));
|
||||
}
|
||||
|
||||
private static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||
static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||
// Validate the color space, to avoid caching bad profiles/color spaces
|
||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
||||
@@ -240,220 +194,27 @@ public final class ColorSpaces {
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*
|
||||
* @see java.awt.color.ColorSpace#CS_sRGB
|
||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||
return ColorProfiles.isCS_sRGB(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default GRAY profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*
|
||||
* @see java.awt.color.ColorSpace#CS_GRAY
|
||||
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
|
||||
return ColorProfiles.isCS_GRAY(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||
// (that is 1 single bit of difference right there.. ;-)
|
||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||
|
||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||
// except the getRenderingIntent method is package protected in java.awt.color
|
||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
// Fix profile before validation
|
||||
profileCleaner.fixProfile(profile);
|
||||
validateColorSpace(new ICC_ColorSpace(profile));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
|
||||
return ICC_Profile.getInstance(input);
|
||||
}
|
||||
|
||||
public static ICC_Profile readProfile(final InputStream input) throws IOException {
|
||||
// TODO: Implement this smarter?
|
||||
// Could read the header 128 bytes, get size + magic, then read read rest into array and feed the byte[] method...
|
||||
ICC_Profile profile = ICC_Profile.getInstance(input);
|
||||
|
||||
if (profile == null) {
|
||||
throw new IllegalArgumentException("Invalid ICC Profile Data");
|
||||
}
|
||||
|
||||
return createProfile(profile.getData());
|
||||
}
|
||||
|
||||
public static ICC_Profile createProfileRaw(final byte[] input) {
|
||||
try {
|
||||
return readProfileRaw(new ByteArrayInputStream(input));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new IllegalArgumentException("Invalid ICC Profile Data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static ICC_Profile createProfile(final byte[] input) {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
|
||||
throw new IllegalArgumentException("Truncated ICC profile, length < 128: " + input.length);
|
||||
}
|
||||
int size = intBigEndian(input, 0);
|
||||
if (size < 0 || size > input.length) {
|
||||
throw new IllegalArgumentException("Truncated ICC profile, length < " + size + ": " + input.length);
|
||||
}
|
||||
|
||||
if (input[36] != 'a' || input[37] != 'c' || input[38] != 's' || input[39] != 'p') {
|
||||
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
|
||||
}
|
||||
|
||||
// Look up in cache before returning, these are already validated
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(input);
|
||||
int csType = getCsType(profileHeader);
|
||||
|
||||
ICC_ColorSpace internal = getInternalCS(csType, profileHeader);
|
||||
if (internal != null) {
|
||||
return internal.getProfile();
|
||||
}
|
||||
|
||||
ICC_ColorSpace cached = getCachedCS(profileHeader);
|
||||
if (cached != null) {
|
||||
return cached.getProfile();
|
||||
}
|
||||
|
||||
// WEIRDNESS: Unlike the InputStream version, the byte version
|
||||
// of ICC_Profile.getInstance() does not discard extra bytes at the end.
|
||||
// We'll chop them off here for convenience
|
||||
byte[] profileBytes = input.length == size ? input : Arrays.copyOf(input, size);
|
||||
ICC_Profile profile = ICC_Profile.getInstance(profileBytes);
|
||||
|
||||
// We'll validate & cache by creating a color space and returning its profile...
|
||||
// TODO: Rewrite with separate cache for profiles...
|
||||
return createColorSpace(profile).getProfile();
|
||||
}
|
||||
|
||||
private static int intBigEndian(byte[] data, int index) {
|
||||
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
|
||||
}
|
||||
|
||||
private static int getCsType(byte[] profileHeader) {
|
||||
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
|
||||
|
||||
// TODO: Wonder why they didn't just use the sig as type, when there is obviously a 1:1 mapping...
|
||||
|
||||
switch (csSig) {
|
||||
case ICC_Profile.icSigXYZData:
|
||||
return ColorSpace.TYPE_XYZ;
|
||||
case ICC_Profile.icSigLabData:
|
||||
return ColorSpace.TYPE_Lab;
|
||||
case ICC_Profile.icSigLuvData:
|
||||
return ColorSpace.TYPE_Luv;
|
||||
case ICC_Profile.icSigYCbCrData:
|
||||
return ColorSpace.TYPE_YCbCr;
|
||||
case ICC_Profile.icSigYxyData:
|
||||
return ColorSpace.TYPE_Yxy;
|
||||
case ICC_Profile.icSigRgbData:
|
||||
return ColorSpace.TYPE_RGB;
|
||||
case ICC_Profile.icSigGrayData:
|
||||
return ColorSpace.TYPE_GRAY;
|
||||
case ICC_Profile.icSigHsvData:
|
||||
return ColorSpace.TYPE_HSV;
|
||||
case ICC_Profile.icSigHlsData:
|
||||
return ColorSpace.TYPE_HLS;
|
||||
case ICC_Profile.icSigCmykData:
|
||||
return ColorSpace.TYPE_CMYK;
|
||||
// Note: There is no TYPE_* 10...
|
||||
case ICC_Profile.icSigCmyData:
|
||||
return ColorSpace.TYPE_CMY;
|
||||
case ICC_Profile.icSigSpace2CLR:
|
||||
return ColorSpace.TYPE_2CLR;
|
||||
case ICC_Profile.icSigSpace3CLR:
|
||||
return ColorSpace.TYPE_3CLR;
|
||||
case ICC_Profile.icSigSpace4CLR:
|
||||
return ColorSpace.TYPE_4CLR;
|
||||
case ICC_Profile.icSigSpace5CLR:
|
||||
return ColorSpace.TYPE_5CLR;
|
||||
case ICC_Profile.icSigSpace6CLR:
|
||||
return ColorSpace.TYPE_6CLR;
|
||||
case ICC_Profile.icSigSpace7CLR:
|
||||
return ColorSpace.TYPE_7CLR;
|
||||
case ICC_Profile.icSigSpace8CLR:
|
||||
return ColorSpace.TYPE_8CLR;
|
||||
case ICC_Profile.icSigSpace9CLR:
|
||||
return ColorSpace.TYPE_9CLR;
|
||||
case ICC_Profile.icSigSpaceACLR:
|
||||
return ColorSpace.TYPE_ACLR;
|
||||
case ICC_Profile.icSigSpaceBCLR:
|
||||
return ColorSpace.TYPE_BCLR;
|
||||
case ICC_Profile.icSigSpaceCCLR:
|
||||
return ColorSpace.TYPE_CCLR;
|
||||
case ICC_Profile.icSigSpaceDCLR:
|
||||
return ColorSpace.TYPE_DCLR;
|
||||
case ICC_Profile.icSigSpaceECLR:
|
||||
return ColorSpace.TYPE_ECLR;
|
||||
case ICC_Profile.icSigSpaceFCLR:
|
||||
return ColorSpace.TYPE_FCLR;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
|
||||
}
|
||||
return ColorProfiles.validateProfile(profile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -536,50 +297,6 @@ public final class ColorSpaces {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
|
||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||
|
||||
if (stream != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(stream);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||
if (profilePath != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class Key {
|
||||
private final byte[] data;
|
||||
|
||||
@@ -602,78 +319,4 @@ public final class ColorSpaces {
|
||||
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
|
||||
private static class sRGB {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||
}
|
||||
|
||||
private static class CIEXYZ {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||
}
|
||||
|
||||
private static class PYCC {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||
}
|
||||
|
||||
private static class GRAY {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
}
|
||||
|
||||
private static class LINEAR_RGB {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||
}
|
||||
|
||||
private static class Profiles {
|
||||
// TODO: Honour java.iccprofile.path property?
|
||||
private static final Properties PROFILES = loadProfiles();
|
||||
|
||||
private static Properties loadProfiles() {
|
||||
Properties systemDefaults;
|
||||
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
|
||||
);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
System.err.printf(
|
||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||
ignore.getMessage()
|
||||
);
|
||||
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
systemDefaults = null;
|
||||
}
|
||||
|
||||
// Create map with defaults and add user overrides if any
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||
);
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
// Most likely, this file won't be there
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("User ICC profiles: " + profiles);
|
||||
System.out.println("System ICC profiles : " + systemDefaults);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static String getPath(final String profileName) {
|
||||
return PROFILES.getProperty(profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
@@ -1,3 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
|
||||
@@ -45,6 +45,7 @@ import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@@ -175,15 +176,22 @@ public final class IIOUtil {
|
||||
* @param providerClassName name of the provider class.
|
||||
* @param category provider category
|
||||
*
|
||||
* @return the provider instance, or {@code null}.
|
||||
* @return the provider instance, or {@code null} if not found
|
||||
*/
|
||||
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
|
||||
try {
|
||||
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
return null;
|
||||
// NOTE: While more verbose, this is more OSGi-friendly than using
|
||||
// registry.getServiceProviderByClass(Class.forName(providerClassName))
|
||||
Iterator<T> providers = registry.getServiceProviders(category, true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
T provider = providers.next();
|
||||
|
||||
if (provider.getClass().getName().equals(providerClassName)) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
@@ -1,2 +1,4 @@
|
||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
||||
# Use SPI loading as a hook for early profile activation
|
||||
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
||||
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ColorProfilesTest {
|
||||
@Test
|
||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile = createBrokenProfile(internal);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||
byte[] data = internal.getData();
|
||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
||||
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
||||
return ICC_Profile.getInstance(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsOffendingColorProfile() {
|
||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_sRGBTrue() {
|
||||
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_sRGBFalse() {
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_sRGBNull() {
|
||||
ColorProfiles.isCS_sRGB(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_GRAYTrue() {
|
||||
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_GRAYFalse() {
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_GRAYNull() {
|
||||
ColorProfiles.isCS_GRAY(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileNull() {
|
||||
ColorProfiles.createProfile(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileNull() throws IOException {
|
||||
ColorProfiles.readProfile(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawNull() {
|
||||
ColorProfiles.createProfileRaw(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawNull() throws IOException {
|
||||
ColorProfiles.readProfileRaw(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileRaw() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
|
||||
assertArrayEquals(data, profileRaw.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileRaw() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
assertArrayEquals(data, profileRaw.getData());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawBadData() {
|
||||
ColorProfiles.createProfileRaw(new byte[5]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawBadData() throws IOException {
|
||||
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
|
||||
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileBadData() {
|
||||
ColorProfiles.createProfile(new byte[5]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileBadData() throws IOException {
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSame() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSame() throws IOException {
|
||||
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileDifferent() throws IOException {
|
||||
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
|
||||
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||
|
||||
assertNotSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSameAsCached() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsCached() throws IOException {
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSameAsInternal() {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsInternal() throws IOException {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
}
|
||||
+6
-94
@@ -35,7 +35,6 @@ import org.junit.Test;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -91,34 +90,6 @@ public class ColorSpacesTest {
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile = createBrokenProfile(internal);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||
byte[] data = internal.getData();
|
||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
||||
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
||||
return ICC_Profile.getInstance(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsOffendingColorProfile() {
|
||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||
@@ -167,11 +138,13 @@ public class ColorSpacesTest {
|
||||
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBTrue() {
|
||||
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBFalse() {
|
||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
@@ -180,16 +153,19 @@ public class ColorSpacesTest {
|
||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_sRGBNull() {
|
||||
ColorSpaces.isCS_sRGB(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_GRAYTrue() {
|
||||
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_GRAYFalse() {
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
@@ -198,6 +174,7 @@ public class ColorSpacesTest {
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_GRAYNull() {
|
||||
ColorSpaces.isCS_GRAY(null);
|
||||
@@ -216,69 +193,4 @@ public class ColorSpacesTest {
|
||||
|
||||
assertNotSame(cs1, cs2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileBytesSame() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile1 = ColorSpaces.createProfile(profile.getData());
|
||||
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSame() throws IOException {
|
||||
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileDifferent() throws IOException {
|
||||
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
|
||||
ICC_Profile profile1 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||
|
||||
assertNotSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileBytesSameAsCached() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
|
||||
ICC_Profile profile2 = ColorSpaces.createProfile(profile.getData());
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsCached() throws IOException {
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
|
||||
ICC_Profile profile2 = ColorSpaces.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileBytesSameAsInternal() {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorSpaces.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsInternal() throws IOException {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorSpaces.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class ProfileDeferralActivatorTest {
|
||||
|
||||
@Test
|
||||
public void testActivateProfiles() {
|
||||
// Should just run with no exceptions...
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpiRegistration() {
|
||||
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
|
||||
ServiceRegistry registry = mock(ServiceRegistry.class);
|
||||
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
|
||||
|
||||
spi.onRegistration(registry, category);
|
||||
|
||||
verify(registry, only()).deregisterServiceProvider(spi, category);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -78,7 +78,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
||||
protected abstract ImageWriterSpi createProvider();
|
||||
|
||||
protected final T createWriter() throws IOException {
|
||||
return writerClass.cast(provider.createWriterInstance(null));
|
||||
return writerClass.cast(provider.createWriterInstance());
|
||||
}
|
||||
|
||||
protected abstract List<? extends RenderedImage> getTestData();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
|
||||
+4
-18
@@ -31,6 +31,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
@@ -335,7 +336,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
|
||||
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
|
||||
else if (bogusAdobeDCT
|
||||
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|
||||
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|
||||
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|
||||
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
|
||||
if (DEBUG) {
|
||||
@@ -630,7 +631,7 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
|
||||
|
||||
return ColorSpaces.createProfile(profileData);
|
||||
return ColorProfiles.createProfile(profileData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -950,10 +951,6 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
return null;
|
||||
}
|
||||
|
||||
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
|
||||
int iccChunkDataSize = segment.data.length - segmentDataStart;
|
||||
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
|
||||
|
||||
return readICCProfileSafe(stream, allowBadIndexes);
|
||||
}
|
||||
else if (!segments.isEmpty()) {
|
||||
@@ -989,9 +986,6 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
InputStream[] streams = new InputStream[count];
|
||||
streams[badICC ? 0 : chunkNumber - 1] = stream;
|
||||
|
||||
int iccChunkDataSize = 0;
|
||||
int iccSize = 0;
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
Application segment = segments.get(i);
|
||||
stream = new DataInputStream(segment.data());
|
||||
@@ -1004,12 +998,6 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
int index = badICC ? i : chunkNumber - 1;
|
||||
streams[index] = stream;
|
||||
|
||||
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
|
||||
iccChunkDataSize += segment.data.length - segmentDataStart;
|
||||
if (index == 0) {
|
||||
iccSize = intFromBigEndian(segment.data, segmentDataStart);
|
||||
}
|
||||
}
|
||||
|
||||
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
|
||||
@@ -1020,10 +1008,8 @@ public final class JPEGImageReader extends ImageReaderBase {
|
||||
|
||||
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
|
||||
try {
|
||||
ICC_Profile profile = ColorSpaces.readProfileRaw(stream);
|
||||
|
||||
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
|
||||
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
|
||||
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
|
||||
}
|
||||
catch (IOException | RuntimeException e) {
|
||||
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.psd.PSD;
|
||||
@@ -353,7 +353,7 @@ public final class JPEGSegmentUtil {
|
||||
Directory psd = new PSDReader().read(stream);
|
||||
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
|
||||
if (iccEntry != null) {
|
||||
ICC_Profile profile = ColorSpaces.createProfileRaw((byte[]) iccEntry.getValue());
|
||||
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
|
||||
System.err.println("ICC Profile: " + profile);
|
||||
}
|
||||
System.err.println("PSD: " + psd);
|
||||
|
||||
+127
-42
@@ -30,27 +30,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getType;
|
||||
import static com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry.getValueLength;
|
||||
|
||||
/**
|
||||
* TIFFWriter
|
||||
*
|
||||
@@ -62,7 +57,24 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
|
||||
private static final int WORD_LENGTH = 2;
|
||||
private static final int LONGWORD_LENGTH = 4;
|
||||
private static final long ENTRY_LENGTH = 12;
|
||||
|
||||
// TODO: We probably want to gloss over client code writing IFDs in BigTIFF (or vice versa) somehow... Silently convert IFD -> IFD8
|
||||
private final boolean longOffsets;
|
||||
private final int offsetSize;
|
||||
private final long entryLength;
|
||||
private final int directoryCountLength;
|
||||
|
||||
public TIFFWriter() {
|
||||
this(LONGWORD_LENGTH);
|
||||
}
|
||||
|
||||
public TIFFWriter(int offsetSize) {
|
||||
this.offsetSize = Validate.isTrue(offsetSize == 4 || offsetSize == 8, offsetSize, "offsetSize must be 4 for TIFF or 8 for BigTIFF");
|
||||
|
||||
longOffsets = offsetSize == 8;
|
||||
directoryCountLength = longOffsets ? 8 : WORD_LENGTH;
|
||||
entryLength = 2 * WORD_LENGTH + 2 * offsetSize;
|
||||
}
|
||||
|
||||
public boolean write(final Collection<? extends Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
return write(new IFD(entries), stream);
|
||||
@@ -91,7 +103,7 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
}
|
||||
|
||||
// Offset to next IFD (EOF)
|
||||
stream.writeInt(0);
|
||||
writeOffset(stream, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -100,7 +112,12 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
// Header
|
||||
ByteOrder byteOrder = stream.getByteOrder();
|
||||
stream.writeShort(byteOrder == ByteOrder.BIG_ENDIAN ? TIFF.BYTE_ORDER_MARK_BIG_ENDIAN : TIFF.BYTE_ORDER_MARK_LITTLE_ENDIAN);
|
||||
stream.writeShort(42);
|
||||
stream.writeShort(longOffsets ? TIFF.BIGTIFF_MAGIC : TIFF.TIFF_MAGIC);
|
||||
|
||||
if (longOffsets) {
|
||||
stream.writeShort(offsetSize); // Always 8 in this case
|
||||
stream.writeShort(0);
|
||||
}
|
||||
}
|
||||
|
||||
public long writeIFD(final Collection<Entry> entries, final ImageOutputStream stream) throws IOException {
|
||||
@@ -122,37 +139,42 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
long dataSize = computeDataSize(ordered);
|
||||
|
||||
// Offset to this IFD
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + LONGWORD_LENGTH;
|
||||
final long ifdOffset = stream.getStreamPosition() + dataSize + offsetSize;
|
||||
|
||||
if (!isSubIFD) {
|
||||
stream.writeInt(assertIntegerOffset(ifdOffset));
|
||||
dataOffset += LONGWORD_LENGTH;
|
||||
writeOffset(stream, ifdOffset);
|
||||
dataOffset += offsetSize;
|
||||
|
||||
// Seek to offset
|
||||
stream.seek(ifdOffset);
|
||||
}
|
||||
else {
|
||||
dataOffset += WORD_LENGTH + ordered.size() * ENTRY_LENGTH;
|
||||
dataOffset += directoryCountLength + ordered.size() * entryLength;
|
||||
}
|
||||
|
||||
// Write directory
|
||||
stream.writeShort(ordered.size());
|
||||
writeDirectoryCount(stream, ordered.size());
|
||||
|
||||
for (Entry entry : ordered) {
|
||||
// Write tag id
|
||||
// Write tag id, type & value count
|
||||
stream.writeShort((Integer) entry.getIdentifier());
|
||||
// Write tag type
|
||||
stream.writeShort(getType(entry));
|
||||
// Write value count
|
||||
stream.writeInt(getCount(entry));
|
||||
writeValueCount(stream, getCount(entry));
|
||||
|
||||
// Write value
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
// TODO: This could possibly be a compound directory, in which case the count should be > 1
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
long streamPosition = stream.getStreamPosition();
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Directory) {
|
||||
if (value instanceof CompoundDirectory) {
|
||||
// Can't have both nested and linked IFDs
|
||||
throw new AssertionError("SubIFD cannot contain linked IFDs");
|
||||
}
|
||||
|
||||
// We can't write offset here, we need to write value, as both LONG/IFD and LONG8/IFD8 is allowed
|
||||
// TODO: Or possibly gloss over, by always writing IFD8 for BigTIFF?
|
||||
long streamPosition = stream.getStreamPosition() + offsetSize;
|
||||
writeValueInline(dataOffset, getType(entry), stream);
|
||||
stream.seek(dataOffset);
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
Directory subIFD = (Directory) value;
|
||||
writeIFD(subIFD, stream, true);
|
||||
dataOffset += computeDataSize(subIFD);
|
||||
stream.seek(streamPosition);
|
||||
@@ -165,8 +187,26 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
return ifdOffset;
|
||||
}
|
||||
|
||||
public long computeIFDSize(final Collection<Entry> directory) {
|
||||
return WORD_LENGTH + computeDataSize(new IFD(directory)) + directory.size() * ENTRY_LENGTH;
|
||||
private void writeDirectoryCount(ImageOutputStream stream, int count) throws IOException {
|
||||
if (longOffsets) {
|
||||
stream.writeLong(count);
|
||||
}
|
||||
else {
|
||||
stream.writeShort(count);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeValueCount(ImageOutputStream stream, int count) throws IOException {
|
||||
if (longOffsets) {
|
||||
stream.writeLong(count);
|
||||
}
|
||||
else {
|
||||
stream.writeInt(count);
|
||||
}
|
||||
}
|
||||
|
||||
public long computeIFDSize(final Collection<? extends Entry> directory) {
|
||||
return directoryCountLength + computeDataSize(new IFD(directory)) + directory.size() * entryLength;
|
||||
}
|
||||
|
||||
private long computeDataSize(final Directory directory) {
|
||||
@@ -179,13 +219,13 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
throw new IllegalArgumentException(String.format("Unknown size for entry %s", entry));
|
||||
}
|
||||
|
||||
if (length > LONGWORD_LENGTH) {
|
||||
if (length > offsetSize) {
|
||||
dataSize += length;
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
Directory subIFD = (Directory) entry.getValue();
|
||||
long subIFDSize = WORD_LENGTH + subIFD.size() * ENTRY_LENGTH + computeDataSize(subIFD);
|
||||
long subIFDSize = directoryCountLength + computeDataSize(subIFD) + subIFD.size() * entryLength;
|
||||
dataSize += subIFDSize;
|
||||
}
|
||||
}
|
||||
@@ -233,11 +273,11 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
short type = getType(entry);
|
||||
long valueLength = getValueLength(type, getCount(entry));
|
||||
|
||||
if (valueLength <= LONGWORD_LENGTH) {
|
||||
if (valueLength <= offsetSize) {
|
||||
writeValueInline(entry.getValue(), type, stream);
|
||||
|
||||
// Pad
|
||||
for (long i = valueLength; i < LONGWORD_LENGTH; i++) {
|
||||
for (long i = valueLength; i < offsetSize; i++) {
|
||||
stream.write(0);
|
||||
}
|
||||
|
||||
@@ -348,12 +388,28 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
doubles = (double[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF FLOAT: " + value.getClass());
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF DOUBLE: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeDoubles(doubles, 0, doubles.length);
|
||||
|
||||
break;
|
||||
case TIFF.TYPE_LONG8:
|
||||
case TIFF.TYPE_SLONG8:
|
||||
if (longOffsets) {
|
||||
long[] longs;
|
||||
|
||||
if (value instanceof long[]) {
|
||||
longs = (long[]) value;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported type for TIFF LONG8: " + value.getClass());
|
||||
}
|
||||
|
||||
stream.writeLongs(longs, 0, longs.length);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -377,6 +433,7 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
break;
|
||||
case TIFF.TYPE_LONG:
|
||||
case TIFF.TYPE_SLONG:
|
||||
case TIFF.TYPE_IFD:
|
||||
stream.writeInt(((Number) value).intValue());
|
||||
break;
|
||||
case TIFF.TYPE_RATIONAL:
|
||||
@@ -391,6 +448,13 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
case TIFF.TYPE_DOUBLE:
|
||||
stream.writeDouble(((Number) value).doubleValue());
|
||||
break;
|
||||
case TIFF.TYPE_LONG8:
|
||||
case TIFF.TYPE_SLONG8:
|
||||
case TIFF.TYPE_IFD8:
|
||||
if (longOffsets) {
|
||||
stream.writeLong(((Number) value).longValue());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
|
||||
@@ -399,18 +463,39 @@ public final class TIFFWriter extends MetadataWriter {
|
||||
}
|
||||
|
||||
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
|
||||
stream.writeInt(assertIntegerOffset(dataOffset));
|
||||
writeOffset(stream, dataOffset);
|
||||
long position = stream.getStreamPosition();
|
||||
stream.seek(dataOffset);
|
||||
writeValueInline(value, type, stream);
|
||||
stream.seek(position);
|
||||
}
|
||||
|
||||
private int assertIntegerOffset(long offset) throws IIOException {
|
||||
if (offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||
public void writeOffset(final ImageOutputStream output, long offset) throws IOException {
|
||||
if (longOffsets) {
|
||||
output.writeLong(assertLongOffset(offset));
|
||||
}
|
||||
else {
|
||||
output.writeInt(assertIntegerOffset(offset)); // Treated as unsigned
|
||||
}
|
||||
}
|
||||
|
||||
public int offsetSize() {
|
||||
return offsetSize;
|
||||
}
|
||||
|
||||
private int assertIntegerOffset(final long offset) throws IIOException {
|
||||
if (offset < 0 || offset > Integer.MAX_VALUE - (long) Integer.MIN_VALUE) {
|
||||
throw new IIOException("Integer overflow for TIFF stream");
|
||||
}
|
||||
|
||||
return (int) offset;
|
||||
}
|
||||
|
||||
private long assertLongOffset(final long offset) throws IIOException {
|
||||
if (offset < 0) {
|
||||
throw new IIOException("Long overflow for BigTIFF stream");
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
+345
@@ -0,0 +1,345 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
/**
|
||||
* TIFFWriterTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
*/
|
||||
public class BigTIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
// TODO: Replace with BigTIFF resource
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
protected TIFFReader createReader() {
|
||||
return new TIFFReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TIFFWriter createWriter() {
|
||||
return new TIFFWriter(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteReadSimple() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(164, data.length);
|
||||
assertEquals('M', data[0]);
|
||||
assertEquals('M', data[1]);
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(43, data[3]);
|
||||
|
||||
Directory read = createReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(5, read.size());
|
||||
|
||||
// TODO: Assert that the tags are written in ascending order (don't test the read directory, but the file structure)!
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals(1600, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_HEIGHT));
|
||||
assertEquals(900, read.getEntryById(TIFF.TAG_IMAGE_HEIGHT).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_ORIENTATION));
|
||||
assertEquals(1, read.getEntryById(TIFF.TAG_ORIENTATION).getValue());
|
||||
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_ARTIST));
|
||||
assertEquals("Harald K.", read.getEntryById(TIFF.TAG_ARTIST).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteMotorola() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(94, data.length);
|
||||
assertEquals('M', data[0]);
|
||||
assertEquals('M', data[1]);
|
||||
assertEquals(0, data[2]);
|
||||
assertEquals(43, data[3]);
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteIntel() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_LONG, Integer.MAX_VALUE));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
byte[] data = output.toByteArray();
|
||||
|
||||
assertEquals(94, data.length);
|
||||
assertEquals('I', data[0]);
|
||||
assertEquals('I', data[1]);
|
||||
assertEquals(43, data[2]);
|
||||
assertEquals(0, data[3]);
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(2, read.size());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
|
||||
assertEquals("TwelveMonkeys ImageIO", read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
|
||||
assertNotNull(read.getEntryById(TIFF.TAG_IMAGE_WIDTH));
|
||||
assertEquals((long) Integer.MAX_VALUE, read.getEntryById(TIFF.TAG_IMAGE_WIDTH).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestingIFD8Long8() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(1, read.size());
|
||||
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestingIFDLong() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
|
||||
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertNotNull(read);
|
||||
assertEquals(1, read.size());
|
||||
assertEquals(subIFD, read.getEntryById(TIFF.TAG_SUB_IFD)); // Recursively tests content!
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWriteRead() throws IOException {
|
||||
Directory original = createReader().read(getDataAsIIS());
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
|
||||
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
|
||||
|
||||
try {
|
||||
createWriter().write(original, imageOutput);
|
||||
}
|
||||
finally {
|
||||
imageOutput.close();
|
||||
}
|
||||
|
||||
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
|
||||
assertEquals(original, read);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSize() throws IOException {
|
||||
ArrayList<Entry> entries = new ArrayList<>();
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, 1));
|
||||
entries.add(new TIFFEntry(TIFF.TAG_IMAGE_WIDTH, TIFF.TYPE_SHORT, 1600));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
entries.add(new TIFFEntry(TIFF.TAG_ARTIST, TIFF.TYPE_ASCII, "Harald K."));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
|
||||
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.writeIFD(entries, stream);
|
||||
|
||||
assertEquals(140, writer.computeIFDSize(entries));
|
||||
assertEquals(148, stream.getStreamPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSizeNestedIFD8Long8() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG8, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD8, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.writeIFD(entries, stream);
|
||||
|
||||
assertEquals(162, writer.computeIFDSize(entries));
|
||||
assertEquals(170, stream.getStreamPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testComputeIFDSizeNestedIFDLong() throws IOException {
|
||||
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
|
||||
|
||||
TIFFEntry subSubSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(artist)));
|
||||
TIFFEntry subSubSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubSubIFD)));
|
||||
TIFFEntry subSubIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_LONG, new IFD(Collections.singletonList(subSubSubIFD)));
|
||||
TIFFEntry subIFD = new TIFFEntry(TIFF.TAG_SUB_IFD, TIFF.TYPE_IFD, new IFD(Collections.singletonList(subSubIFD)));
|
||||
|
||||
List<Entry> entries = Collections.<Entry>singletonList(subIFD);
|
||||
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.writeIFD(entries, stream);
|
||||
|
||||
assertEquals(162, writer.computeIFDSize(entries)); // 162 = 5 * (8 + 20) + 22
|
||||
assertEquals(170, stream.getStreamPosition()); // 170 = 8 + 5 * (8 + 20) + 22
|
||||
}
|
||||
|
||||
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
streamPos++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
streamPos += len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-8
@@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -84,7 +85,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -132,7 +133,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -167,7 +168,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -204,7 +205,7 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
|
||||
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
new TIFFWriter().write(directory, imageStream);
|
||||
createWriter().write(directory, imageStream);
|
||||
imageStream.flush();
|
||||
|
||||
assertEquals(output.size(), imageStream.getStreamPosition());
|
||||
@@ -247,9 +248,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
writer.writeIFD(entries, stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
assertEquals(94, writer.computeIFDSize(entries));
|
||||
assertEquals(98, stream.getStreamPosition());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -266,9 +268,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
|
||||
TIFFWriter writer = createWriter();
|
||||
|
||||
ImageOutputStream stream = new NullImageOutputStream();
|
||||
writer.write(new IFD(entries), stream);
|
||||
writer.writeIFD(entries, stream);
|
||||
|
||||
assertEquals(stream.getStreamPosition(), writer.computeIFDSize(entries) + 12);
|
||||
assertEquals(92, writer.computeIFDSize(entries)); // 92 = 5 * (2 + 12) + 22
|
||||
assertEquals(96, stream.getStreamPosition()); // 96 = 4 + 5 * (2 + 12) + 22
|
||||
}
|
||||
|
||||
private static class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pcx</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pdf</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pict</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-psd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -56,7 +56,7 @@ final class ICCProfile extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
try (InputStream stream = IIOUtil.createStreamAdapter(pInput, size)) {
|
||||
profile = ColorSpaces.readProfile(stream);
|
||||
profile = ColorProfiles.readProfileRaw(stream);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-22
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, Harald Kuhr
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,37 +28,42 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.image.aoi;
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:erlend@escenic.com">Erlend Hamnaberg</a>
|
||||
* @version $Revision: $
|
||||
* PSDImageWriteParam
|
||||
*/
|
||||
@Deprecated
|
||||
public class AreaOfInterestFactory {
|
||||
private final static AtomicReference<AreaOfInterestFactory> DEFAULT =
|
||||
new AtomicReference<AreaOfInterestFactory>(new AreaOfInterestFactory());
|
||||
public final class PSDImageWriteParam extends ImageWriteParam {
|
||||
|
||||
public static void setDefault(AreaOfInterestFactory factory) {
|
||||
DEFAULT.set(factory);
|
||||
PSDImageWriteParam() {
|
||||
this(Locale.getDefault());
|
||||
}
|
||||
|
||||
public static AreaOfInterestFactory getDefault() {
|
||||
return DEFAULT.get();
|
||||
PSDImageWriteParam(final Locale locale) {
|
||||
super(locale);
|
||||
|
||||
compressionTypes = new String[] {
|
||||
"None",
|
||||
"PackBits",
|
||||
// Two ZIP compression types are defined in spec, never seen in the wild...
|
||||
// "ZIP",
|
||||
// "ZIP+Predictor",
|
||||
};
|
||||
compressionType = compressionTypes[1];
|
||||
canWriteCompressed = true;
|
||||
}
|
||||
|
||||
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");
|
||||
static int getCompressionType(final ImageWriteParam param) {
|
||||
if (param == null || param.getCompressionMode() != MODE_EXPLICIT || param.getCompressionType() == null || param.getCompressionType().equals("None")) {
|
||||
return PSD.COMPRESSION_NONE;
|
||||
}
|
||||
if (aoiPercent) {
|
||||
return new PercentAreaOfInterest(pDefaultWidth, pDefaultHeight);
|
||||
else if (param.getCompressionType().equals("PackBits")) {
|
||||
return PSD.COMPRESSION_RLE;
|
||||
}
|
||||
else if (aoiUniform) {
|
||||
return new UniformAreaOfInterest(pDefaultWidth, pDefaultHeight);
|
||||
}
|
||||
return new DefaultAreaOfInterest(pDefaultWidth, pDefaultHeight);
|
||||
|
||||
throw new IllegalArgumentException(String.format("Unsupported compression type: %s", param.getCompressionType()));
|
||||
}
|
||||
}
|
||||
+400
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.RasterUtils;
|
||||
import com.twelvemonkeys.io.enc.EncoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsEncoder;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Minimal ImageWriter for Adobe Photoshop Document (PSD) format.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDImageWriter.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
||||
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
|
||||
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
|
||||
*/
|
||||
public final class PSDImageWriter extends ImageWriterBase {
|
||||
|
||||
PSDImageWriter(ImageWriterSpi provider) {
|
||||
super(provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||
// TODO: Implement
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
|
||||
// TODO: Implement
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageWriteParam getDefaultWriteParam() {
|
||||
return new PSDImageWriteParam(getLocale());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(IIOMetadata streamMetadata, IIOImage iioImage, ImageWriteParam param) throws IOException {
|
||||
assertOutput();
|
||||
imageOutput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
RenderedImage image = iioImage.getRenderedImage();
|
||||
SampleModel sampleModel = image.getSampleModel();
|
||||
|
||||
int colorComponents = image.getColorModel().getColorSpace().getNumComponents();
|
||||
int channels = sampleModel.getNumBands();
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
int bits = getBitsPerSample(sampleModel);
|
||||
int mode = getColorMode(image.getColorModel());
|
||||
// TODO: Allow stream metadata or param to force PSD/PSB (version 1/2)?
|
||||
boolean largeFormat = width > PSDHeader.PSD_MAX_SIZE || height > PSDHeader.PSD_MAX_SIZE;
|
||||
|
||||
new PSDHeader(channels, width, height, bits, mode, largeFormat).write(imageOutput);
|
||||
writeColorModeData(image, mode);
|
||||
writeImageResources(image, mode);
|
||||
|
||||
// Length of the layer and mask information section. (**PSB** length is 8 bytes.)
|
||||
// TODO: Write an empty dummy layer here, if there's alpha? See below... Or see if Photoshop handles alpha if no layers at all...
|
||||
if (largeFormat) {
|
||||
imageOutput.writeLong(0);
|
||||
}
|
||||
else {
|
||||
imageOutput.writeInt(0);
|
||||
}
|
||||
|
||||
processImageStarted(0);
|
||||
|
||||
// Image Data Section (composite layer only).
|
||||
// The last section of a Photoshop file contains the image pixel data.
|
||||
// Image data is stored in planar order: first all the red data, then all the green data, etc.
|
||||
// Each plane is stored in scan-line order, with no pad bytes,
|
||||
final int compression = PSDImageWriteParam.getCompressionType(param);
|
||||
imageOutput.writeShort(compression);
|
||||
|
||||
long byteCountPos = imageOutput.getStreamPosition();
|
||||
// PSB (large format) byte counts are actually 32 bit offsets, not 16 bit as described in spec
|
||||
int[] byteCounts = new int[compression == PSD.COMPRESSION_RLE ? height * channels : 0];
|
||||
imageOutput.skipBytes(byteCounts.length * (largeFormat ? 4 : 2));
|
||||
|
||||
// TODO: Loop over tiles?
|
||||
Raster tile = sampleModel.getTransferType() == DataBuffer.TYPE_INT && sampleModel instanceof SinglePixelPackedSampleModel
|
||||
? RasterUtils.asByteRaster(image.getTile(0, 0))
|
||||
: image.getTile(0, 0);
|
||||
|
||||
for (int channel = 0; channel < channels; channel++) {
|
||||
// TODO: Alpha issues:
|
||||
// 1. Alpha channel is written (but not read, because there are no layers, and alpha is considered present only if layer count is negative)
|
||||
// - Can we write a small hidden layer, just to have -1 layers?
|
||||
// 2. Alpha needs to be premultiplied against white background (to avoid inverse halo)
|
||||
Raster channelRaster = tile.createChild(0, 0, width, height, 0, 0, new int[] {channel});
|
||||
|
||||
switch (bits) {
|
||||
case 1:
|
||||
// TODO: Figure out why we can't write multi-pixel packed 1 bit samples as bytes...
|
||||
case 8:
|
||||
write8BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
|
||||
break;
|
||||
case 16:
|
||||
write16BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
|
||||
break;
|
||||
case 32:
|
||||
write32BitChannel(channel, colorComponents, mode, compression, channelRaster, byteCounts);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError(); // Should be guarded against already
|
||||
}
|
||||
|
||||
processImageProgress(channel * 100f / channels);
|
||||
}
|
||||
|
||||
updateByteCounts(byteCountPos, byteCounts, largeFormat);
|
||||
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
private void updateByteCounts(long byteCountPos, int[] byteCounts, boolean largeFormat) throws IOException {
|
||||
if (byteCounts.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update byte counts for RLE
|
||||
long pos = imageOutput.getStreamPosition();
|
||||
|
||||
imageOutput.seek(byteCountPos);
|
||||
if (largeFormat) {
|
||||
imageOutput.writeInts(byteCounts, 0, byteCounts.length);
|
||||
}
|
||||
else {
|
||||
for (int byteCount : byteCounts) {
|
||||
imageOutput.writeShort(byteCount);
|
||||
}
|
||||
}
|
||||
|
||||
imageOutput.seek(pos);
|
||||
}
|
||||
|
||||
private void writeColorModeData(RenderedImage image, int mode) throws IOException {
|
||||
if (mode == PSD.COLOR_MODE_INDEXED) {
|
||||
IndexColorModel icm = (IndexColorModel) image.getColorModel();
|
||||
|
||||
// Indexed color images: length is 768; color data contains the color table for the image, in non-interleaved order.
|
||||
imageOutput.writeInt(768);
|
||||
byte[] colors = new byte[256];
|
||||
|
||||
icm.getReds(colors);
|
||||
imageOutput.write(colors);
|
||||
icm.getGreens(colors);
|
||||
imageOutput.write(colors);
|
||||
icm.getBlues(colors);
|
||||
imageOutput.write(colors);
|
||||
}
|
||||
else {
|
||||
imageOutput.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeImageResources(RenderedImage image, int mode) throws IOException {
|
||||
// Length of image resource section. The length may be zero
|
||||
imageOutput.writeInt(0);
|
||||
long startImageResources = imageOutput.getStreamPosition();
|
||||
|
||||
// Write ICC color profile if not "native" sRGB or gray (or bitmap/indexed)
|
||||
if (mode != PSD.COLOR_MODE_BITMAP && mode != PSD.COLOR_MODE_INDEXED) {
|
||||
ColorSpace colorSpace = image.getColorModel().getColorSpace();
|
||||
if (!colorSpace.isCS_sRGB() && colorSpace instanceof ICC_ColorSpace) {
|
||||
ICC_Profile profile = ((ICC_ColorSpace) colorSpace).getProfile();
|
||||
ICCProfile.writeData(imageOutput, profile);
|
||||
}
|
||||
}
|
||||
|
||||
// Write creator software (Exif)
|
||||
Entry software = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO PSD writer " + originatingProvider.getVersion());
|
||||
PSDEXIF1Data.writeData(imageOutput, Collections.singleton(software));
|
||||
|
||||
long endImageResources = imageOutput.getStreamPosition();
|
||||
|
||||
// Update image resources length
|
||||
imageOutput.seek(startImageResources - 4);
|
||||
imageOutput.writeInt((int) (endImageResources - startImageResources));
|
||||
imageOutput.seek(endImageResources);
|
||||
}
|
||||
|
||||
private void write8BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
byte[] rowBytes = null;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
rowBytes = (byte[]) raster.getDataElements(0, y, width, 1, rowBytes);
|
||||
|
||||
// Photoshop likes to store CMYK values inverted (but not the alpha value)
|
||||
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
|
||||
for (int i = 0; i < rowBytes.length; i++) {
|
||||
rowBytes[i] = (byte) (0xff - rowBytes[i] & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
if (compression == PSD.COMPRESSION_NONE) {
|
||||
imageOutput.write(rowBytes);
|
||||
}
|
||||
else if (compression == PSD.COMPRESSION_RLE) {
|
||||
long startPos = imageOutput.getStreamPosition();
|
||||
|
||||
// The RLE compressed data follows, with each scan line compressed separately
|
||||
try (OutputStream stream = new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder())) {
|
||||
stream.write(rowBytes);
|
||||
}
|
||||
|
||||
long endPos = imageOutput.getStreamPosition();
|
||||
byteCounts[y + channel * height] = (int) (endPos - startPos);
|
||||
}
|
||||
else {
|
||||
throw new IIOException("PSD with ZIP compression not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write16BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
short[] row = null;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
row = (short[]) raster.getDataElements(0, y, width, 1, row);
|
||||
|
||||
// Photoshop likes to store CMYK values inverted (but not the alpha value)
|
||||
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
row[i] = (short) (0xffff - row[i] & 0xffff);
|
||||
}
|
||||
}
|
||||
|
||||
if (compression == PSD.COMPRESSION_NONE) {
|
||||
imageOutput.writeShorts(row, 0, row.length);
|
||||
}
|
||||
else if (compression == PSD.COMPRESSION_RLE) {
|
||||
long startPos = imageOutput.getStreamPosition();
|
||||
|
||||
// The RLE compressed data follows, with each scan line compressed separately
|
||||
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
|
||||
for (short sample : row) {
|
||||
stream.writeShort(sample);
|
||||
}
|
||||
}
|
||||
|
||||
long endPos = imageOutput.getStreamPosition();
|
||||
byteCounts[y + channel * height] = (int) (endPos - startPos);
|
||||
}
|
||||
else {
|
||||
throw new IIOException("PSD with ZIP compression not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void write32BitChannel(int channel, int colorComponents, int colorMode, int compression, Raster raster, int[] byteCounts) throws IOException {
|
||||
int width = raster.getWidth();
|
||||
int height = raster.getHeight();
|
||||
|
||||
int[] row = null;
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
row = (int[]) raster.getDataElements(0, y, width, 1, row);
|
||||
|
||||
// Photoshop likes to store CMYK values inverted (but not the alpha value)
|
||||
if (colorMode == PSD.COLOR_MODE_CMYK && channel < colorComponents) {
|
||||
for (int i = 0; i < row.length; i++) {
|
||||
row[i] = 0xffffffff - row[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (compression == PSD.COMPRESSION_NONE) {
|
||||
imageOutput.writeInts(row, 0, row.length);
|
||||
}
|
||||
else if (compression == PSD.COMPRESSION_RLE) {
|
||||
long startPos = imageOutput.getStreamPosition();
|
||||
|
||||
// The RLE compressed data follows, with each scan line compressed separately
|
||||
try (DataOutputStream stream = new DataOutputStream(new EncoderStream(IIOUtil.createStreamAdapter(imageOutput), new PackBitsEncoder()))) {
|
||||
for (int sample : row) {
|
||||
stream.writeInt(sample);
|
||||
}
|
||||
}
|
||||
|
||||
long endPos = imageOutput.getStreamPosition();
|
||||
byteCounts[y + channel * height] = (int) (endPos - startPos);
|
||||
}
|
||||
else {
|
||||
throw new IIOException("PSD with ZIP compression not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int getColorMode(ColorModel colorModel) {
|
||||
if (colorModel instanceof IndexColorModel) {
|
||||
if (colorModel.getPixelSize() == 1) {
|
||||
return PSD.COLOR_MODE_BITMAP;
|
||||
}
|
||||
else {
|
||||
return PSD.COLOR_MODE_INDEXED;
|
||||
}
|
||||
}
|
||||
|
||||
int csType = colorModel.getColorSpace().getType();
|
||||
switch (csType) {
|
||||
case ColorSpace.TYPE_GRAY:
|
||||
if (colorModel.getPixelSize() == 1) {
|
||||
return PSD.COLOR_MODE_BITMAP;
|
||||
}
|
||||
else {
|
||||
return PSD.COLOR_MODE_GRAYSCALE;
|
||||
}
|
||||
case ColorSpace.TYPE_RGB:
|
||||
return PSD.COLOR_MODE_RGB;
|
||||
case ColorSpace.TYPE_CMYK:
|
||||
return PSD.COLOR_MODE_CMYK;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported color space type for PSD: " + csType);
|
||||
}
|
||||
}
|
||||
|
||||
static int getBitsPerSample(SampleModel sampleModel) {
|
||||
int bits = sampleModel.getSampleSize(0);
|
||||
|
||||
for (int i = 1; i < sampleModel.getNumBands(); i++) {
|
||||
if (bits != sampleModel.getSampleSize(i)) {
|
||||
throw new IllegalArgumentException("All samples must be of equal size for PSD: " + bits);
|
||||
}
|
||||
}
|
||||
|
||||
switch (bits) {
|
||||
case 1:
|
||||
case 8:
|
||||
case 16:
|
||||
case 32:
|
||||
return (short) bits;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported sample size for PSD (expected 1, 8, 16 or 32): " + bits);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
BufferedImage image = ImageIO.read(new File(args[0]));
|
||||
ImageIO.write(image, "PSD", new File("test.psd"));
|
||||
}
|
||||
}
|
||||
Executable → Regular
+32
-33
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -28,51 +28,50 @@
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter.getBitsPerSample;
|
||||
import static com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter.getColorMode;
|
||||
|
||||
/**
|
||||
* ServletCacheResponse
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ServletCacheResponse.java#2 $
|
||||
* PSDImageWriterSpi
|
||||
*/
|
||||
@Deprecated
|
||||
public final class ServletCacheResponse extends AbstractCacheResponse {
|
||||
private HttpServletResponse response;
|
||||
public final class PSDImageWriterSpi extends ImageWriterSpiBase {
|
||||
|
||||
public ServletCacheResponse(HttpServletResponse pResponse) {
|
||||
response = pResponse;
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() throws IOException {
|
||||
return response.getOutputStream();
|
||||
public PSDImageWriterSpi() {
|
||||
super(new PSDProviderInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int pStatusCode) {
|
||||
response.setStatus(pStatusCode);
|
||||
super.setStatus(pStatusCode);
|
||||
public boolean canEncodeImage(ImageTypeSpecifier type) {
|
||||
// PSD supports:
|
||||
// - 1, 8, 16 or 32 bit/sample
|
||||
// - Number of samples <= 56
|
||||
// - RGB, CMYK, Gray, Indexed color
|
||||
try {
|
||||
getBitsPerSample(type.getSampleModel());
|
||||
getColorMode(type.getColorModel());
|
||||
}
|
||||
catch (IllegalArgumentException ignore) {
|
||||
// We can't write this type
|
||||
return false;
|
||||
}
|
||||
|
||||
return type.getNumBands() <= 56; // Can't be negative
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String pHeaderName, String pHeaderValue) {
|
||||
response.addHeader(pHeaderName, pHeaderValue);
|
||||
super.addHeader(pHeaderName, pHeaderValue);
|
||||
public ImageWriter createWriterInstance(Object extension) {
|
||||
return new PSDImageWriter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String pHeaderName, String pHeaderValue) {
|
||||
response.setHeader(pHeaderName, pHeaderValue);
|
||||
super.setHeader(pHeaderName, pHeaderValue);
|
||||
}
|
||||
|
||||
HttpServletResponse getResponse() {
|
||||
return response;
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Adobe Photoshop Document (PSD) image writer";
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -54,8 +54,8 @@ final class PSDProviderInfo extends ReaderWriterProviderInfo {
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.psd.PSDImageReader",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriter",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
|
||||
false, null, null, null, null,
|
||||
true, PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, null, null
|
||||
);
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
com.twelvemonkeys.imageio.plugins.psd.PSDImageWriterSpi
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PSDImageWriterTest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDImageWriterTest.java,v 1.0 05/05/2021 haraldk Exp$
|
||||
*/
|
||||
public class PSDImageWriterTest extends ImageWriterAbstractTest<PSDImageWriter> {
|
||||
@Override
|
||||
protected ImageWriterSpi createProvider() {
|
||||
return new PSDImageWriterSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends RenderedImage> getTestData() {
|
||||
return Arrays.asList(
|
||||
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
|
||||
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
|
||||
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
|
||||
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
|
||||
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY),
|
||||
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_BINARY),
|
||||
new BufferedImage(30, 20, BufferedImage.TYPE_BYTE_INDEXED)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-reference</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tga</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-thumbsdb</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
||||
|
||||
Executable → Regular
+67
-58
@@ -1,58 +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 of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.image;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* This exception is a subclass of ServletException, and acts just as a marker
|
||||
* for exceptions thrown by the ImageServlet API.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: ImageServletException.java#2 $
|
||||
*/
|
||||
@Deprecated
|
||||
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) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BigTIFFImageWriterSpi
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BigTIFFImageWriterSpi.java,v 1.0 18.09.13 12:46 haraldk Exp$
|
||||
*/
|
||||
public final class BigTIFFImageWriterSpi extends ImageWriterSpiBase {
|
||||
// TODO: Implement canEncodeImage better
|
||||
|
||||
public BigTIFFImageWriterSpi() {
|
||||
super(new BigTIFFProviderInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier type) {
|
||||
// TODO: Test bit depths compatibility
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TIFFImageWriter createWriterInstance(final Object extension) {
|
||||
return new TIFFImageWriter(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "BigTIFF image writer";
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -50,8 +50,8 @@ final class BigTIFFProviderInfo extends ReaderWriterProviderInfo {
|
||||
},
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageReaderSpi"},
|
||||
null,
|
||||
null,
|
||||
"com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriter",
|
||||
new String[] {"com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi"},
|
||||
false, TIFFStreamMetadata.SUN_NATIVE_STREAM_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFStreamMetadataFormat", null, null,
|
||||
true, TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME, "com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat", null, null
|
||||
);
|
||||
|
||||
+65
-4
@@ -34,10 +34,8 @@ import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.ComponentColorModel;
|
||||
import java.awt.image.ComponentSampleModel;
|
||||
import java.awt.image.SampleModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.awt.image.*;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.awt.image.DataBuffer.getDataTypeSize;
|
||||
|
||||
@@ -99,4 +97,67 @@ final class ExtraSamplesColorModel extends ComponentColorModel {
|
||||
private int getAlphaComponent() {
|
||||
return super.getNumComponents() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getDataElements(final int rgb, final Object pixel) {
|
||||
return super.getDataElements(rgb, pixel == null ? createDataArray() : pixel);
|
||||
}
|
||||
|
||||
private Object createDataArray() {
|
||||
switch (transferType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
return new byte[numComponents];
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
return new short[numComponents];
|
||||
case DataBuffer.TYPE_INT:
|
||||
return new int[numComponents];
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
return new float[numComponents];
|
||||
case DataBuffer.TYPE_DOUBLE:
|
||||
return new double[numComponents];
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("This method has not been implemented for transferType " + transferType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (other == null || getClass() != other.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExtraSamplesColorModel that = (ExtraSamplesColorModel) other;
|
||||
|
||||
if (hasAlpha() != that.hasAlpha() ||
|
||||
isAlphaPremultiplied() != that.isAlphaPremultiplied() ||
|
||||
getPixelSize() != that.getPixelSize() ||
|
||||
getTransparency() != that.getTransparency() ||
|
||||
numComponents != that.numComponents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int[] nBits = getComponentSize();
|
||||
int[] nb = that.getComponentSize();
|
||||
|
||||
if ((nBits == null) || (nb == null)) {
|
||||
return ((nBits == null) && (nb == null));
|
||||
}
|
||||
|
||||
for (int i = 0; i < nBits.length; i++) {
|
||||
if (nBits[i] != nb[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(super.hashCode(), numComponents, componentSize);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter;
|
||||
import com.twelvemonkeys.imageio.color.CIELabColorConverter.Illuminant;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.YCbCrConverter;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
@@ -2493,12 +2494,12 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
return value;
|
||||
}
|
||||
|
||||
private ICC_Profile getICCProfile() throws IOException {
|
||||
private ICC_Profile getICCProfile() {
|
||||
Entry entry = currentIFD.getEntryById(TIFF.TAG_ICC_PROFILE);
|
||||
|
||||
if (entry != null) {
|
||||
try {
|
||||
return ColorSpaces.createProfile((byte[]) entry.getValue());
|
||||
return ColorProfiles.createProfile((byte[]) entry.getValue());
|
||||
}
|
||||
catch (CMMException | IllegalArgumentException e) {
|
||||
processWarningOccurred("Ignoring broken/incompatible ICC profile: " + e.getMessage());
|
||||
|
||||
+18
-22
@@ -32,7 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
@@ -204,26 +204,26 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
long streamPosition = imageOutput.getStreamPosition();
|
||||
|
||||
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
||||
long stripOffset = streamPosition + 4 + ifdSize + 4;
|
||||
long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize();
|
||||
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
|
||||
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||
|
||||
// If we have a previous IFD, update pointer
|
||||
if (streamPosition > lastIFDPointerOffset) {
|
||||
imageOutput.seek(lastIFDPointerOffset);
|
||||
imageOutput.writeInt((int) ifdPointer);
|
||||
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||
imageOutput.seek(nextIFDPointerOffset);
|
||||
}
|
||||
|
||||
imageOutput.writeInt(0); // Update next IFD pointer later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Update next IFD pointer later
|
||||
}
|
||||
else {
|
||||
imageOutput.writeInt(0); // Update current IFD pointer later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Update current IFD pointer later
|
||||
}
|
||||
|
||||
long stripOffset = imageOutput.getStreamPosition();
|
||||
@@ -262,7 +262,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes case of ordering tags
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||
|
||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||
|
||||
@@ -270,10 +270,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// However, need to update here, because to the writeIFD method writes the pointer, but at the incorrect offset
|
||||
// TODO: Refactor writeIFD to take an offset
|
||||
imageOutput.seek(lastIFDPointerOffset);
|
||||
imageOutput.writeInt((int) ifdPointer);
|
||||
tiffWriter.writeOffset(imageOutput, ifdPointer);
|
||||
imageOutput.seek(nextIFDPointerOffset);
|
||||
|
||||
imageOutput.writeInt(0); // Next IFD pointer updated later
|
||||
tiffWriter.writeOffset(imageOutput, 0); // Next IFD pointer updated later
|
||||
}
|
||||
|
||||
return nextIFDPointerOffset;
|
||||
@@ -855,7 +855,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// * is not sRGB (assuming sRGB to be the default RGB interpretation), and
|
||||
// * is not gray scale (assuming photometric either BlackIsZero or WhiteIsZero)
|
||||
ColorSpace colorSpace = colorModel.getColorSpace();
|
||||
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorSpaces.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
|
||||
if (colorSpace instanceof ICC_ColorSpace && !colorSpace.isCS_sRGB() && !ColorProfiles.isCS_GRAY(((ICC_ColorSpace) colorSpace).getProfile())) {
|
||||
entries.put(TIFF.TAG_ICC_PROFILE, new TIFFEntry(TIFF.TAG_ICC_PROFILE, ((ICC_ColorSpace) colorSpace).getProfile().getData()));
|
||||
}
|
||||
}
|
||||
@@ -959,11 +959,15 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
configureStreamByteOrder(streamMetadata, imageOutput);
|
||||
|
||||
writingSequence = true;
|
||||
sequenceTIFFWriter = new TIFFWriter();
|
||||
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
|
||||
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
|
||||
sequenceLastIFDPos = imageOutput.getStreamPosition();
|
||||
}
|
||||
|
||||
private boolean isBigTIFF() throws IOException {
|
||||
return "bigtiff".equalsIgnoreCase(getFormatName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
|
||||
if (!writingSequence) {
|
||||
@@ -1019,8 +1023,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
BufferedImage original;
|
||||
// BufferedImage original = ImageIO.read(file);
|
||||
ImageInputStream inputStream = ImageIO.createImageInputStream(file);
|
||||
try {
|
||||
try (ImageInputStream inputStream = ImageIO.createImageInputStream(file)) {
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);
|
||||
|
||||
if (!readers.hasNext()) {
|
||||
@@ -1050,9 +1053,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
original = reader.read(0, param);
|
||||
}
|
||||
finally {
|
||||
inputStream.close();
|
||||
}
|
||||
|
||||
System.err.println("original: " + original);
|
||||
|
||||
@@ -1088,12 +1088,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// output.deleteOnExit();
|
||||
|
||||
System.err.println("output: " + output);
|
||||
TIFFImageWriter writer = new TIFFImageWriter(null);
|
||||
TIFFImageWriter writer = new TIFFImageWriter(new TIFFImageWriterSpi());
|
||||
// ImageWriter writer = ImageIO.getImageWritersByFormatName("PNG").next();
|
||||
// ImageWriter writer = ImageIO.getImageWritersByFormatName("BMP").next();
|
||||
ImageOutputStream stream = ImageIO.createImageOutputStream(output);
|
||||
|
||||
try {
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(output)) {
|
||||
writer.setOutput(stream);
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
@@ -1111,9 +1110,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
System.err.println("Write time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
}
|
||||
finally {
|
||||
stream.close();
|
||||
}
|
||||
|
||||
System.err.println("output.length: " + output.length());
|
||||
|
||||
|
||||
+1
@@ -1 +1,2 @@
|
||||
com.twelvemonkeys.imageio.plugins.tiff.TIFFImageWriterSpi
|
||||
com.twelvemonkeys.imageio.plugins.tiff.BigTIFFImageWriterSpi
|
||||
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.imageio.util.ImageReaderAbstractTest.assertRGBEquals;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
/**
|
||||
* BigTIFFImageWriterTest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BigTIFFImageWriterTest.java,v 1.0 19.09.13 13:22 haraldk Exp$
|
||||
*/
|
||||
public class BigTIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter> {
|
||||
@Override
|
||||
protected ImageWriterSpi createProvider() {
|
||||
return new BigTIFFImageWriterSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<? extends RenderedImage> getTestData() {
|
||||
return Arrays.asList(
|
||||
new BufferedImage(300, 200, BufferedImage.TYPE_INT_RGB),
|
||||
new BufferedImage(301, 199, BufferedImage.TYPE_INT_ARGB),
|
||||
new BufferedImage(299, 201, BufferedImage.TYPE_3BYTE_BGR),
|
||||
new BufferedImage(160, 90, BufferedImage.TYPE_4BYTE_ABGR),
|
||||
new BufferedImage(90, 160, BufferedImage.TYPE_BYTE_GRAY),
|
||||
new BufferedImage(30, 20, BufferedImage.TYPE_USHORT_GRAY)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void roundrtip() throws IOException {
|
||||
TIFFImageWriter writer = createWriter();
|
||||
ImageReader reader = ImageIO.getImageReader(writer);
|
||||
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/bigtiff/BigTIFF.tif"))) {
|
||||
reader.setInput(input);
|
||||
BufferedImage image = reader.read(0);
|
||||
|
||||
ByteArrayOutputStream temp = new ByteArrayOutputStream();
|
||||
try (ImageOutputStream output = ImageIO.createImageOutputStream(temp)) {
|
||||
writer.setOutput(output);
|
||||
writer.write(image);
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
|
||||
// Validate we actually write BigTIFF
|
||||
byte[] data = temp.toByteArray();
|
||||
assertArrayEquals(new byte[] { 'M', 'M', 0, TIFF.BIGTIFF_MAGIC}, Arrays.copyOf(data, 4));
|
||||
|
||||
// Read image back and see that it is the same
|
||||
try (ImageInputStream stream = new ByteArrayImageInputStream(data)) {
|
||||
reader.setInput(stream);
|
||||
BufferedImage after = reader.read(0);
|
||||
|
||||
for (int y = 0; y < image.getHeight(); y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
assertRGBEquals("Pixel values differ: ", image.getRGB(x, y), after.getRGB(x, y), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
-16
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.image.ResampleOp;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
@@ -39,20 +40,23 @@ import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ExtraSamplesColorModelTest {
|
||||
|
||||
private BufferedImage createExtraSamplesImage(int w, int h, ColorSpace cs, boolean hasAlpha, int extraComponents) {
|
||||
int samplesPerPixel = cs.getNumComponents() + (hasAlpha ? 1 : 0) + extraComponents;
|
||||
|
||||
ExtraSamplesColorModel colorModel = new ExtraSamplesColorModel(cs, hasAlpha, true, DataBuffer.TYPE_BYTE, extraComponents);
|
||||
ExtraSamplesColorModel colorModel = createExtraSamplesColorModel(cs, hasAlpha, extraComponents);
|
||||
SampleModel sampleModel = new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, samplesPerPixel, samplesPerPixel * w, createOffsets(samplesPerPixel));
|
||||
|
||||
WritableRaster raster = Raster.createWritableRaster(sampleModel, new Point(0, 0));
|
||||
|
||||
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable());
|
||||
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable<>());
|
||||
}
|
||||
|
||||
private ExtraSamplesColorModel createExtraSamplesColorModel(ColorSpace cs, boolean hasAlpha, int extraComponents) {
|
||||
return new ExtraSamplesColorModel(cs, hasAlpha, true, DataBuffer.TYPE_BYTE, extraComponents);
|
||||
}
|
||||
|
||||
private static int[] createOffsets(int samplesPerPixel) {
|
||||
@@ -66,8 +70,8 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledGray() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
@@ -78,8 +82,8 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledGrayAlpha() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
@@ -90,8 +94,8 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledRGB() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
@@ -102,8 +106,8 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledRGBAlpha() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
@@ -114,8 +118,8 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledCMYK() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
@@ -126,12 +130,43 @@ public class ExtraSamplesColorModelTest {
|
||||
@Test
|
||||
public void testImageWithExtraSamplesCanBeResampledCMYKAlpha() {
|
||||
for (int i = 1; i < 8; i++) {
|
||||
BufferedImage bufferedImage = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(bufferedImage, null);
|
||||
BufferedImage image = createExtraSamplesImage(10, 10, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, i);
|
||||
BufferedImage resampled = new ResampleOp(5, 5, ResampleOp.FILTER_LANCZOS).filter(image, null);
|
||||
|
||||
assertNotNull(resampled);
|
||||
assertEquals(5, resampled.getWidth());
|
||||
assertEquals(5, resampled.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetRGB() {
|
||||
BufferedImage image = createExtraSamplesImage(1, 1, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
|
||||
|
||||
image.setRGB(0, 0, Color.BLACK.getRGB());
|
||||
assertEquals(Color.BLACK.getRGB(), image.getRGB(0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetRGBs() {
|
||||
BufferedImage image = createExtraSamplesImage(2, 2, ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
|
||||
image.setRGB(0, 0, 2, 1, new int[]{Color.BLACK.getRGB(), Color.WHITE.getRGB()}, 0, 2);
|
||||
assertEquals(Color.BLACK.getRGB(), image.getRGB(0, 0));
|
||||
assertEquals(Color.WHITE.getRGB(), image.getRGB(1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEquals() {
|
||||
ExtraSamplesColorModel original = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 1);
|
||||
ExtraSamplesColorModel equal = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 1);
|
||||
|
||||
assertEquals(original, equal);
|
||||
assertEquals(equal, original);
|
||||
|
||||
ExtraSamplesColorModel different = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), true, 2);
|
||||
ExtraSamplesColorModel differentToo = createExtraSamplesColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_sRGB), false, 1);
|
||||
|
||||
assertNotEquals(original, different);
|
||||
assertNotEquals(original, differentToo);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
|
||||
|
||||
+4
-3
@@ -32,6 +32,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.webp;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
@@ -287,7 +288,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
long chunkStart = imageInput.getStreamPosition();
|
||||
|
||||
if (nextChunk == WebP.CHUNK_ICCP) {
|
||||
iccProfile = ColorSpaces.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
|
||||
iccProfile = ColorProfiles.readProfile(IIOUtil.createStreamAdapter(imageInput, chunkLength));
|
||||
}
|
||||
else {
|
||||
processWarningOccurred(String.format("Expected 'ICCP' chunk, '%s' chunk encountered", fourCC(nextChunk)));
|
||||
@@ -365,7 +366,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||
readHeader(imageIndex);
|
||||
|
||||
if (iccProfile != null && !ColorSpaces.isCS_sRGB(iccProfile)) {
|
||||
if (iccProfile != null && !ColorProfiles.isCS_sRGB(iccProfile)) {
|
||||
ICC_ColorSpace colorSpace = ColorSpaces.createColorSpace(iccProfile);
|
||||
int[] bandOffsets = header.containsALPH ? new int[] {0, 1, 2, 3} : new int[] {0, 1, 2};
|
||||
return ImageTypeSpecifiers.createInterleaved(colorSpace, bandOffsets, DataBuffer.TYPE_BYTE, header.containsALPH, false);
|
||||
@@ -525,7 +526,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
if (!iccProfile.equals(destinationProfile)) {
|
||||
if (DEBUG) {
|
||||
System.err.println("Converting from " + iccProfile + " to " + (ColorSpaces.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
|
||||
System.err.println("Converting from " + iccProfile + " to " + (ColorProfiles.isCS_sRGB(destinationProfile) ? "sRGB" : destinationProfile));
|
||||
}
|
||||
|
||||
WritableRaster raster = colorModel.hasAlpha()
|
||||
|
||||
+27
-1
@@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Locale;
|
||||
@@ -76,7 +77,8 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
|
||||
switch (chunk) {
|
||||
// TODO. Support lossless
|
||||
// case WebP.CHUNK_VP8L:
|
||||
// case WebP.CHUNK_VP8X:
|
||||
case WebP.CHUNK_VP8X:
|
||||
return containsSupportedChunk(stream, chunk);
|
||||
case WebP.CHUNK_VP8_:
|
||||
return true;
|
||||
default:
|
||||
@@ -89,6 +91,30 @@ public final class WebPImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean containsSupportedChunk(ImageInputStream stream, int chunk) throws IOException {
|
||||
// Temporary: Seek for VP8_, either first or second (after ICCP), or inside ANMF...
|
||||
try {
|
||||
while (chunk != WebP.CHUNK_VP8L && chunk != WebP.CHUNK_ALPH) {
|
||||
long length = stream.readUnsignedInt();
|
||||
stream.seek(stream.getStreamPosition() + length);
|
||||
chunk = stream.readInt();
|
||||
|
||||
// Look inside ANMF chunks...
|
||||
if (chunk == WebP.CHUNK_ANMF) {
|
||||
stream.seek(stream.getStreamPosition() + 4 + 16);
|
||||
chunk = stream.readInt();
|
||||
}
|
||||
|
||||
if (chunk == WebP.CHUNK_VP8_) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EOFException ignore) {}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReader createReaderInstance(final Object extension) {
|
||||
return new WebPImageReader(this);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<artifactId>imageio-xwd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>Twelvemonkeys</name>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
|
||||
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
|
||||
<url>https://github.com/haraldk/TwelveMonkeys</url>
|
||||
<tag>twelvemonkeys-3.7.0</tag>
|
||||
<tag>twelvemonkeys-3.8.1</tag>
|
||||
</scm>
|
||||
|
||||
<properties>
|
||||
|
||||
+33
-48
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.7.2-SNAPSHOT</version>
|
||||
<version>3.8.1</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
@@ -12,37 +12,6 @@
|
||||
<name>TwelveMonkeys :: Servlet</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-image</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common-io</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
@@ -50,31 +19,17 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>jsp-api</artifactId>
|
||||
<version>2.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.4</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.1</version>
|
||||
<version>4.13.2</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.12.4</version>
|
||||
<version>4.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -107,6 +62,36 @@
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jakarta</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<finalName>twelvemonkeys-${project.artifactId}-${project.version}-jakarta</finalName>
|
||||
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>${project.groupId}:${project.artifactId}</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>javax.servlet</pattern>
|
||||
<shadedPattern>jakarta.servlet</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
@@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* AbstractServletMapAdapter
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: AbstractServletMapAdapter.java#1 $
|
||||
*/
|
||||
abstract class AbstractServletMapAdapter<T> extends AbstractMap<String, T> {
|
||||
// TODO: This map is now a little too lazy.. Should cache entries!
|
||||
private transient Set<Entry<String, T>> entries;
|
||||
|
||||
protected abstract Iterator<String> keysImpl();
|
||||
|
||||
protected abstract T valueImpl(String pName);
|
||||
|
||||
@Override
|
||||
public T get(final Object pKey) {
|
||||
if (pKey instanceof String) {
|
||||
return valueImpl((String) pKey);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
// Avoid creating expensive entry set for computing size
|
||||
int size = 0;
|
||||
|
||||
for (Iterator<String> names = keysImpl(); names.hasNext(); names.next()) {
|
||||
size++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public Set<Entry<String, T>> entrySet() {
|
||||
if (entries == null) {
|
||||
entries = new AbstractSet<Entry<String, T>>() {
|
||||
public Iterator<Entry<String, T>> iterator() {
|
||||
return new Iterator<Entry<String, T>>() {
|
||||
Iterator<String> keys = keysImpl();
|
||||
|
||||
public boolean hasNext() {
|
||||
return keys.hasNext();
|
||||
}
|
||||
|
||||
public Entry<String, T> next() {
|
||||
// TODO: Replace with cached lookup
|
||||
return new HeaderEntry(keys.next());
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
keys.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return AbstractServletMapAdapter.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private class HeaderEntry implements Entry<String, T> {
|
||||
final String key;
|
||||
|
||||
public HeaderEntry(final String pKey) {
|
||||
key = pKey;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public T getValue() {
|
||||
return get(key);
|
||||
}
|
||||
|
||||
public T setValue(final T pValue) {
|
||||
// Write-through if supported
|
||||
return put(key, pValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
T value = getValue();
|
||||
return (key == null ? 0 : key.hashCode()) ^
|
||||
(value == null ? 0 : value.hashCode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
if (pOther == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pOther instanceof Entry) {
|
||||
Entry other = (Entry) pOther;
|
||||
return ((other.getKey() == null && getKey() == null) ||
|
||||
(getKey() != null && getKey().equals(other.getKey()))) &&
|
||||
((other.getValue() == null && getValue() == null) ||
|
||||
(getValue() != null && getValue().equals(other.getValue())));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
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.HttpServletRequestWrapper;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
/**
|
||||
* BrowserHelperFilter
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: BrowserHelperFilter.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public class BrowserHelperFilter extends GenericFilter {
|
||||
private static final String HTTP_HEADER_ACCEPT = "Accept";
|
||||
protected static final String HTTP_HEADER_USER_AGENT = "User-Agent";
|
||||
|
||||
// TODO: Consider using unmodifiable LinkedHashMap<Pattern, String> instead
|
||||
private Pattern[] knownAgentPatterns;
|
||||
private String[] knownAgentAccepts;
|
||||
|
||||
/**
|
||||
* Sets the accept-mappings for this filter
|
||||
* @param pPropertiesFile name of accept-mappings properties files
|
||||
* @throws ServletConfigException if the accept-mappings properties
|
||||
* file cannot be read.
|
||||
*/
|
||||
@InitParam(name = "accept-mappings-file")
|
||||
public void setAcceptMappingsFile(String pPropertiesFile) throws ServletConfigException {
|
||||
// NOTE: Format is:
|
||||
// <agent-name>=<reg-exp>
|
||||
// <agent-name>.accept=<http-accept-header>
|
||||
|
||||
Properties mappings = new Properties();
|
||||
|
||||
try {
|
||||
log("Reading Accept-mappings properties file: " + pPropertiesFile);
|
||||
mappings.load(getServletContext().getResourceAsStream(pPropertiesFile));
|
||||
|
||||
//System.out.println("--> Loaded file: " + pPropertiesFile);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new ServletConfigException("Could not read Accept-mappings properties file: " + pPropertiesFile, e);
|
||||
}
|
||||
|
||||
parseMappings(mappings);
|
||||
}
|
||||
|
||||
private void parseMappings(Properties mappings) {
|
||||
List<Pattern> patterns = new ArrayList<Pattern>();
|
||||
List<String> accepts = new ArrayList<String>();
|
||||
|
||||
for (Object key : mappings.keySet()) {
|
||||
String agent = (String) key;
|
||||
|
||||
if (agent.endsWith(".accept")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//System.out.println("--> Adding accept-mapping for User-Agent: " + agent);
|
||||
|
||||
try {
|
||||
String accept = (String) mappings.get(agent + ".accept");
|
||||
|
||||
if (!StringUtil.isEmpty(accept)) {
|
||||
patterns.add(Pattern.compile((String) mappings.get(agent)));
|
||||
accepts.add(accept);
|
||||
//System.out.println("--> " + agent + " accepts: " + accept);
|
||||
}
|
||||
else {
|
||||
log("Missing Accept mapping for User-Agent: " + agent);
|
||||
}
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
log("Could not parse User-Agent identification for " + agent, e);
|
||||
}
|
||||
|
||||
knownAgentPatterns = patterns.toArray(new Pattern[patterns.size()]);
|
||||
knownAgentAccepts = accepts.toArray(new String[accepts.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
public void init() throws ServletException {
|
||||
if (knownAgentAccepts == null || knownAgentAccepts.length == 0) {
|
||||
throw new ServletConfigException("No User-Agent/Accept mappings for filter: " + getFilterName());
|
||||
}
|
||||
}
|
||||
|
||||
protected void doFilterImpl(ServletRequest pRequest, ServletResponse pResponse, FilterChain pChain) throws IOException, ServletException {
|
||||
if (pRequest instanceof HttpServletRequest) {
|
||||
//System.out.println("--> Trying to find User-Agent/Accept headers...");
|
||||
HttpServletRequest request = (HttpServletRequest) pRequest;
|
||||
|
||||
// Check if User-Agent is in list of known agents
|
||||
if (knownAgentPatterns != null && knownAgentPatterns.length > 0) {
|
||||
String agent = request.getHeader(HTTP_HEADER_USER_AGENT);
|
||||
//System.out.println("--> User-Agent: " + agent);
|
||||
|
||||
for (int i = 0; i < knownAgentPatterns.length; i++) {
|
||||
Pattern pattern = knownAgentPatterns[i];
|
||||
//System.out.println("--> Pattern: " + pattern);
|
||||
|
||||
if (pattern.matcher(agent).matches()) {
|
||||
// TODO: Consider merge known with real accept, in case plugins add extra capabilities?
|
||||
final String fakeAccept = knownAgentAccepts[i];
|
||||
//System.out.println("--> User-Agent: " + agent + " accepts: " + fakeAccept);
|
||||
|
||||
pRequest = new HttpServletRequestWrapper(request) {
|
||||
public String getHeader(String pName) {
|
||||
if (HTTP_HEADER_ACCEPT.equals(pName)) {
|
||||
return fakeAccept;
|
||||
}
|
||||
|
||||
return super.getHeader(pName);
|
||||
}
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pChain.doFilter(pRequest, pResponse);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* DebugServlet class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: DebugServlet.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,403 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Enumeration;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
import com.twelvemonkeys.lang.BeanUtil;
|
||||
|
||||
/**
|
||||
* Defines a generic, protocol-independent filter.
|
||||
* <p>
|
||||
* {@code GenericFilter} is inspired by {@link GenericServlet}, and
|
||||
* implements the {@code Filter} and {@code FilterConfig} interfaces.
|
||||
* </p>
|
||||
* <p>
|
||||
* {@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.
|
||||
* </p>
|
||||
* <p>
|
||||
* {@code GenericFilter} 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* To write a generic filter, you need only override the abstract
|
||||
* {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: GenericFilter.java#1 $
|
||||
*
|
||||
* @see Filter
|
||||
* @see FilterConfig
|
||||
*/
|
||||
@Deprecated
|
||||
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}).
|
||||
* <p>
|
||||
* Set this variable to true, to make sure the filter runs once per request.
|
||||
* </p>
|
||||
* <p>
|
||||
* <em>NOTE: As of Servlet 2.4, this field
|
||||
* should always be left to it's default value ({@code false}).
|
||||
* <br>
|
||||
* To run the filter once per request, the {@code filter-mapping} element
|
||||
* of the web-descriptor should include a {@code dispatcher} element:
|
||||
* </em>
|
||||
* </p>
|
||||
* <pre><dispatcher>REQUEST</dispatcher></pre>
|
||||
*/
|
||||
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.
|
||||
* <p>
|
||||
* 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, <em>if</em> you are
|
||||
* overriding this form of the method,
|
||||
* always call {@code super.init(config)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This implementation will also set all configured key/value pairs, that
|
||||
* have a matching setter method annotated with {@link InitParam}.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* Subclasses <em>should not override this method</em>, but rather the
|
||||
* abstract {@link #doFilterImpl(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)} doFilterImpl} method.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
* <p>
|
||||
* Note that the method will mark the request as filtered on first
|
||||
* invocation.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@Deprecated
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import com.twelvemonkeys.lang.BeanUtil;
|
||||
|
||||
/**
|
||||
* Defines a generic, protocol-independent servlet.
|
||||
* <p>
|
||||
* {@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.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: GenericServlet.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This implementation will also set all configured key/value pairs, that
|
||||
* have a matching setter method annotated with {@link InitParam}.
|
||||
* </p>
|
||||
*
|
||||
* @param pConfig the servlet config
|
||||
* @throws ServletException if the servlet could not be initialized.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import com.twelvemonkeys.lang.BeanUtil;
|
||||
|
||||
/**
|
||||
* Defines a generic, HTTP specific servlet.
|
||||
* <p>
|
||||
* {@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.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
*
|
||||
* @version $Id: HttpServlet.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
* <p>
|
||||
* 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)}.
|
||||
* </p>
|
||||
* <p>
|
||||
* This implementation will also set all configured key/value pairs, that
|
||||
* have a matching setter method annotated with {@link InitParam}.
|
||||
* </p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation to be used by servlets/filters, to have their {@code init}-method
|
||||
* automatically convert and set values from their respective configuration.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: InitParam.java#1 $
|
||||
* @see com.twelvemonkeys.servlet.ServletConfigurator
|
||||
* @see com.twelvemonkeys.servlet.GenericFilter#init(javax.servlet.FilterConfig)
|
||||
* @see com.twelvemonkeys.servlet.GenericServlet#init(javax.servlet.ServletConfig)
|
||||
* @see com.twelvemonkeys.servlet.HttpServlet#init(javax.servlet.ServletConfig)
|
||||
*/
|
||||
// TODO: Actually implement for version 3.0!
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.METHOD/*, TODO: ElementType.FIELD*/})
|
||||
@Deprecated
|
||||
public @interface InitParam {
|
||||
static final String UNDEFINED = "";
|
||||
String name() default UNDEFINED;
|
||||
String defaultValue() default UNDEFINED; // TODO: Consider separate annotation?
|
||||
boolean required() default false; // TODO: Consider separate annotation?
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* 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:<pre>
|
||||
* ByteArrayOutputStream buffer = new ByteArraOutputStream();
|
||||
* ServletOutputStream adapter = new OutputStreamAdapter(buffer);
|
||||
* </pre>
|
||||
* <p>
|
||||
* 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:
|
||||
* </p>
|
||||
* <pre>
|
||||
* 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);
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author $Author: haku $
|
||||
* @version $Id: OutputStreamAdapter.java#1 $
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2013, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletRequest;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* ServletAttributesMap
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ServletAttributesMap.java,v 1.0 01.03.13 10:34 haraldk Exp$
|
||||
*/
|
||||
class ServletAttributesMapAdapter extends AbstractServletMapAdapter<Object> {
|
||||
private final ServletContext context;
|
||||
private final ServletRequest request;
|
||||
|
||||
ServletAttributesMapAdapter(final ServletContext context) {
|
||||
this(notNull(context), null);
|
||||
}
|
||||
|
||||
ServletAttributesMapAdapter(final ServletRequest request) {
|
||||
this(null, notNull(request));
|
||||
}
|
||||
|
||||
private ServletAttributesMapAdapter(final ServletContext context, final ServletRequest request) {
|
||||
this.context = context;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Enumeration<String> getAttributeNames() {
|
||||
return context != null ? context.getAttributeNames() : request.getAttributeNames();
|
||||
}
|
||||
|
||||
private Object getAttribute(final String name) {
|
||||
return context != null ? context.getAttribute(name) : request.getAttribute(name);
|
||||
}
|
||||
|
||||
private Object setAttribute(String name, Object value) {
|
||||
Object oldValue = getAttribute(name);
|
||||
|
||||
if (context != null) {
|
||||
context.setAttribute(name, value);
|
||||
}
|
||||
else {
|
||||
request.setAttribute(name, value);
|
||||
}
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
private Object removeAttribute(String name) {
|
||||
Object oldValue = getAttribute(name);
|
||||
|
||||
if (context != null) {
|
||||
context.removeAttribute(name);
|
||||
}
|
||||
else {
|
||||
request.removeAttribute(name);
|
||||
}
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterator<String> keysImpl() {
|
||||
final Enumeration<String> keys = getAttributeNames();
|
||||
return new Iterator<String>() {
|
||||
private String key;
|
||||
|
||||
public boolean hasNext() {
|
||||
return keys.hasMoreElements();
|
||||
}
|
||||
|
||||
public String next() {
|
||||
key = keys.nextElement();
|
||||
return key;
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
// Support removal of attribute through key iterator
|
||||
removeAttribute(key);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object valueImpl(String pName) {
|
||||
return getAttribute(pName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object put(String key, Object value) {
|
||||
return setAttribute(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object remove(Object key) {
|
||||
return key instanceof String ? removeAttribute((String) key) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
/**
|
||||
* ServletConfigException.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: ServletConfigException.java#2 $
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* This {@code Map} is not synchronized.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: ServletConfigMapAdapter.java#2 $
|
||||
*/
|
||||
class ServletConfigMapAdapter extends AbstractMap<String, String> implements Map<String, String>, 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<Entry<String, String>> 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<Entry<String, String>> entrySet() {
|
||||
if (entrySet == null) {
|
||||
entrySet = createEntrySet();
|
||||
}
|
||||
return entrySet;
|
||||
}
|
||||
|
||||
private Set<Entry<String, String>> createEntrySet() {
|
||||
return new AbstractSet<Entry<String, String>>() {
|
||||
// Cache size, if requested, -1 means not calculated
|
||||
private int size = -1;
|
||||
|
||||
public Iterator<Entry<String, String>> iterator() {
|
||||
return new Iterator<Entry<String, String>>() {
|
||||
// Iterator is backed by initParameterNames enumeration
|
||||
final Enumeration names = getInitParameterNames();
|
||||
|
||||
public boolean hasNext() {
|
||||
return names.hasMoreElements();
|
||||
}
|
||||
|
||||
public Entry<String, String> next() {
|
||||
final String key = (String) names.nextElement();
|
||||
return new Entry<String, String>() {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2011, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import com.twelvemonkeys.util.convert.ConversionException;
|
||||
import com.twelvemonkeys.util.convert.Converter;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.ServletConfig;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* ServletConfigurator
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ServletConfigurator.java,v 1.0 Apr 30, 2010 2:51:38 PM haraldk Exp$
|
||||
* @see com.twelvemonkeys.servlet.InitParam
|
||||
*/
|
||||
final class ServletConfigurator {
|
||||
// TODO: Rethink @InitParam? Allow annotation of method parameters instead? Allows setLocation(@InitParam int x, @InitParam int y)
|
||||
// TODO: At least allow field injection
|
||||
// TODO: defaultValue, required
|
||||
|
||||
private ServletConfigurator() {
|
||||
}
|
||||
|
||||
public static void configure(final Servlet pServlet, final ServletConfig pConfig) throws ServletConfigException {
|
||||
new Configurator(pServlet, pConfig.getServletName()).configure(ServletUtil.asMap(pConfig));
|
||||
}
|
||||
|
||||
public static void configure(final Filter pFilter, final FilterConfig pConfig) throws ServletConfigException {
|
||||
new Configurator(pFilter, pConfig.getFilterName()).configure(ServletUtil.asMap(pConfig));
|
||||
}
|
||||
|
||||
private static class Configurator {
|
||||
private final Object servletOrFilter;
|
||||
private final String name;
|
||||
|
||||
private Configurator(final Object servletOrFilter, final String name) {
|
||||
this.servletOrFilter = servletOrFilter;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
private void configure(final Map<String, String> pMapping) throws ServletConfigException {
|
||||
// Loop over methods with InitParam annotations
|
||||
for (Method method : annotatedMethods(servletOrFilter.getClass(), InitParam.class)) {
|
||||
assertAcceptableMethod(method);
|
||||
|
||||
// Get value or default, throw exception if missing required value
|
||||
Object value = getConfiguredValue(method, pMapping);
|
||||
|
||||
if (value != null) {
|
||||
// Inject value to this method
|
||||
try {
|
||||
method.invoke(servletOrFilter, value);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
// We know the method is accessible, so this should never happen
|
||||
throw new Error(e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw new ServletConfigException(String.format("Could not configure %s: %s", name, e.getCause().getMessage()), e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Loop over fields with InitParam annotations
|
||||
|
||||
// TODO: Log warning for mappings not present among InitParam annotated methods?
|
||||
}
|
||||
|
||||
private Object getConfiguredValue(final Method method, final Map<String, String> mapping) throws ServletConfigException {
|
||||
InitParam initParam = method.getAnnotation(InitParam.class);
|
||||
String paramName = getParameterName(method, initParam);
|
||||
|
||||
// Get parameter value
|
||||
String stringValue = mapping.get(paramName);
|
||||
|
||||
if (stringValue == null && initParam.name().equals(InitParam.UNDEFINED)) {
|
||||
stringValue = mapping.get(StringUtil.camelToLisp(paramName));
|
||||
}
|
||||
|
||||
if (stringValue == null) {
|
||||
// InitParam support required = true and throw exception if not present in map
|
||||
if (initParam.required()) {
|
||||
throw new ServletConfigException(
|
||||
String.format(
|
||||
"Could not configure %s: Required init-parameter \"%s\" of type %s is missing",
|
||||
name, paramName, method.getParameterTypes()[0]
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (!initParam.defaultValue().equals(InitParam.UNDEFINED)) {
|
||||
// Support default values
|
||||
stringValue = initParam.defaultValue();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert value based on method arguments...
|
||||
return stringValue == null ? null : convertValue(method, stringValue);
|
||||
}
|
||||
|
||||
private Object convertValue(final Method method, final String stringValue) throws ServletConfigException {
|
||||
// We know it's a single parameter method
|
||||
Class<?> type = method.getParameterTypes()[0];
|
||||
|
||||
try {
|
||||
return String.class.equals(type) ? stringValue : Converter.getInstance().toObject(stringValue, type);
|
||||
}
|
||||
catch (ConversionException e) {
|
||||
throw new ServletConfigException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getParameterName(final Method method, final InitParam initParam) throws ServletConfigException {
|
||||
String paramName = initParam.name();
|
||||
|
||||
if (paramName.equals(InitParam.UNDEFINED)) {
|
||||
String methodName = method.getName();
|
||||
if (methodName.startsWith("set") && methodName.length() > 3) {
|
||||
paramName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
|
||||
}
|
||||
else {
|
||||
throw new ServletConfigException(
|
||||
String.format(
|
||||
"Could not configure %s: InitParam annotated method must either specify name or follow Bean standard for properties (ie. setFoo => 'foo'): %s",
|
||||
name, method
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return paramName;
|
||||
}
|
||||
|
||||
private void assertAcceptableMethod(final Method method) throws ServletConfigException {
|
||||
// Try to use setAccessible, if not public
|
||||
boolean isAccessible = Modifier.isPublic(method.getModifiers());
|
||||
|
||||
if (!isAccessible) {
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
isAccessible = true;
|
||||
}
|
||||
catch (SecurityException ignore) {
|
||||
// Won't be accessible, we'll fail below
|
||||
}
|
||||
}
|
||||
|
||||
if (!isAccessible || method.getReturnType() != Void.TYPE || method.getParameterTypes().length != 1) {
|
||||
throw new ServletConfigException(
|
||||
String.format(
|
||||
"Could not configure %s: InitParam annotated method must be public void and have a single parameter argument list: %s",
|
||||
name, method
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all methods annotated with the given annotations.
|
||||
*
|
||||
* @param pClass the class to get annotated methods from
|
||||
* @param pAnnotations the annotations to test for
|
||||
* @return an iterable that allows iterating over all methods with the given annotations.
|
||||
*/
|
||||
private Iterable<Method> annotatedMethods(final Class<?> pClass, final Class<? extends Annotation>... pAnnotations) {
|
||||
return new Iterable<Method>() {
|
||||
public Iterator<Method> iterator() {
|
||||
Set<Method> methods = new LinkedHashSet<Method>();
|
||||
|
||||
Class<?> cl = pClass;
|
||||
while (cl.getSuperclass() != null) { // There's no annotations of interest on java.lang.Object
|
||||
methods.addAll(Arrays.asList(cl.getDeclaredMethods()));
|
||||
|
||||
// TODO: What about interface methods? Do we really want them?
|
||||
Class<?>[] interfaces = cl.getInterfaces();
|
||||
for (Class<?> i : interfaces) {
|
||||
methods.addAll(Arrays.asList(i.getDeclaredMethods()));
|
||||
}
|
||||
|
||||
cl = cl.getSuperclass();
|
||||
}
|
||||
|
||||
return new FilterIterator<Method>(methods.iterator(), new FilterIterator.Filter<Method>() {
|
||||
public boolean accept(final Method pMethod) {
|
||||
for (Class<? extends Annotation> annotation : pAnnotations) {
|
||||
if (!pMethod.isAnnotationPresent(annotation) || isOverriddenWithAnnotation(pMethod, annotation)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pMethod the method to test for override
|
||||
* @param pAnnotation the annotation that must be present
|
||||
* @return {@code true} iff the method is overridden in a subclass, and has annotation
|
||||
* @see <a href="http://java.sun.com/docs/books/jls/third_edition/html/classes.html#8.4.8">The Java Language Specification: Classes: Inheritance, Overriding, and Hiding</a>
|
||||
*/
|
||||
private boolean isOverriddenWithAnnotation(final Method pMethod, final Class<? extends Annotation> pAnnotation) {
|
||||
if (Modifier.isPrivate(pMethod.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class cl = pClass;
|
||||
|
||||
// Loop down up from subclass to superclass declaring the method
|
||||
while (cl != null && !pMethod.getDeclaringClass().equals(cl)) {
|
||||
try {
|
||||
Method override = cl.getDeclaredMethod(pMethod.getName(), pMethod.getParameterTypes());
|
||||
|
||||
// Overridden, test if it has the annotation present
|
||||
if (override.isAnnotationPresent(pAnnotation)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
catch (NoSuchMethodException ignore) {
|
||||
}
|
||||
|
||||
cl = cl.getSuperclass();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import com.twelvemonkeys.util.CollectionUtil;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* ServletHeadersMapAdapter
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ServletHeadersMapAdapter.java#1 $
|
||||
*/
|
||||
class ServletHeadersMapAdapter extends AbstractServletMapAdapter<List<String>> {
|
||||
|
||||
protected final HttpServletRequest request;
|
||||
|
||||
public ServletHeadersMapAdapter(final HttpServletRequest pRequest) {
|
||||
request = notNull(pRequest, "request");
|
||||
}
|
||||
|
||||
protected List<String> valueImpl(final String pName) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<String> headers = request.getHeaders(pName);
|
||||
return headers == null ? null : toList(CollectionUtil.iterator(headers));
|
||||
}
|
||||
|
||||
private static List<String> toList(final Iterator<String> pValues) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
CollectionUtil.addAll(list, pValues);
|
||||
return Collections.unmodifiableList(list);
|
||||
}
|
||||
|
||||
protected Iterator<String> keysImpl() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
return headerNames == null ? null : CollectionUtil.iterator(headerNames);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import com.twelvemonkeys.util.CollectionUtil;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* ServletParametersMapAdapter
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ServletParametersMapAdapter.java#1 $
|
||||
*/
|
||||
class ServletParametersMapAdapter extends AbstractServletMapAdapter<List<String>> {
|
||||
// TODO: Be able to piggyback on HttpServletRequest.getParameterMap when available?
|
||||
|
||||
protected final ServletRequest request;
|
||||
|
||||
public ServletParametersMapAdapter(final ServletRequest pRequest) {
|
||||
request = notNull(pRequest, "request");
|
||||
}
|
||||
|
||||
protected List<String> valueImpl(String pName) {
|
||||
String[] values = request.getParameterValues(pName);
|
||||
return values == null ? null : Arrays.asList(values);
|
||||
}
|
||||
|
||||
protected Iterator<String> keysImpl() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<String> names = request.getParameterNames();
|
||||
return names == null ? null : CollectionUtil.iterator(names);
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletResponse;
|
||||
|
||||
/**
|
||||
* A delegate for handling stream support in wrapped servlet responses.
|
||||
* <p>
|
||||
* Client code should delegate {@code getOutputStream}, {@code getWriter},
|
||||
* {@code flushBuffer} and {@code resetBuffer} methods from the servlet response.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ServletResponseStreamDelegate.java#2 $
|
||||
*/
|
||||
@Deprecated
|
||||
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).
|
||||
* <p>
|
||||
* This implementation simply returns the output stream from the wrapped
|
||||
* response.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -1,785 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
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;
|
||||
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletRequestWrapper;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.convert.ConversionException;
|
||||
import com.twelvemonkeys.util.convert.Converter;
|
||||
|
||||
|
||||
/**
|
||||
* Various servlet related helper methods.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author Eirik Torske
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ServletUtil.java#3 $
|
||||
*/
|
||||
@Deprecated
|
||||
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}.
|
||||
* @see Converter#toObject
|
||||
*/
|
||||
// TODO: Well, it's done. Need some thinking... We probably don't want default if conversion fails...
|
||||
static <T> T getParameter(final ServletRequest pReq, final String pName, final Class<T> 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.
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
* @see javax.servlet.http.HttpServletRequest#getServletPath()
|
||||
*/
|
||||
// TODO: Read the spec, seems to be a mismatch with the Servlet API...
|
||||
static String getScriptName(final HttpServletRequest pRequest) {
|
||||
String requestURI = pRequest.getRequestURI();
|
||||
return StringUtil.getLastElement(requestURI, "/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request URI relative to the current context path.
|
||||
* <p>
|
||||
* As an example:
|
||||
* </p>
|
||||
* <pre>
|
||||
* requestURI = "/webapp/index.jsp"
|
||||
* contextPath = "/webapp"
|
||||
* </pre>
|
||||
* 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.
|
||||
* <small>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.</small>
|
||||
*
|
||||
* @param pConfig the servlet configuration
|
||||
* @return a {@code Map} view of the config
|
||||
* @throws IllegalArgumentException if {@code pConfig} is {@code null}
|
||||
*/
|
||||
public static Map<String, String> asMap(final ServletConfig pConfig) {
|
||||
return new ServletConfigMapAdapter(pConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unmodifiable {@code Map} view of the given
|
||||
* {@code FilterConfig}s init-parameters.
|
||||
* <small>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.</small>
|
||||
*
|
||||
* @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<String, String> asMap(final FilterConfig pConfig) {
|
||||
return new ServletConfigMapAdapter(pConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unmodifiable {@code Map} view of the given
|
||||
* {@code ServletContext}s init-parameters.
|
||||
* <small>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.</small>
|
||||
*
|
||||
* @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<String, String> initParamsAsMap(final ServletContext pContext) {
|
||||
return new ServletConfigMapAdapter(pContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an <em>modifiable</em> {@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<String, Object> attributesAsMap(final ServletContext pContext) {
|
||||
return new ServletAttributesMapAdapter(pContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an <em>modifiable</em> {@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<String, Object> 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<String, List<String>> 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<String, List<String>> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,311 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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 com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
/**
|
||||
* ThrottleFilter, a filter for easing server during heavy load.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: ThrottleFilter.java#1 $
|
||||
* @see #setMaxConcurrentThreadCount
|
||||
* @see #setResponseMessages
|
||||
*/
|
||||
@Deprecated
|
||||
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<String, String> responseMessageNames = new HashMap<String, String>(10);
|
||||
|
||||
/**
|
||||
* The reposne message sent to user agents, if the request is rejected
|
||||
*/
|
||||
private String[] responseMessageTypes = null;
|
||||
|
||||
/**
|
||||
* Cache for response messages
|
||||
*/
|
||||
private Map<String, CacheEntry> responseCache = new HashMap<String, CacheEntry>(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.
|
||||
* <br>
|
||||
* The format is {@code <mime-type>=<filename>,
|
||||
* <mime-type>=<filename>}.
|
||||
* <br>
|
||||
* 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<String> types = new ArrayList<String>();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* TimingFilter class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: TimingFilter.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.ServletResponseWrapper;
|
||||
|
||||
/**
|
||||
* Removes extra unneccessary white space from a servlet response.
|
||||
* White space is defined as per {@link Character#isWhitespace(char)}.
|
||||
* <p>
|
||||
* 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).
|
||||
* </p>
|
||||
* <p>
|
||||
* <em>At the moment this filter has no concept of encoding</em>.
|
||||
* This means, that if some multi-byte escape sequence contains one or more
|
||||
* bytes that <em>individually</em> is treated as a white space, these bytes
|
||||
* may be skipped.
|
||||
* As <a href="http://en.wikipedia.org/wiki/UTF-8">UTF-8</a>
|
||||
* guarantees that no bytes are repeated in this way, this filter can safely
|
||||
* filter UTF-8.
|
||||
* Simple 8 bit character encodings, like the
|
||||
* <a href="http://en.wikipedia.org/wiki/ISO/IEC_8859">ISO/IEC 8859</a> standard, or
|
||||
* <a href="http://en.wikipedia.org/wiki/Windows-1252">Windows-1252"</a>
|
||||
* are always safe.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Configuration</b>
|
||||
* <br>
|
||||
* 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.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Servlet 2.4 version, filter section:</b>
|
||||
* </p>
|
||||
* <pre>
|
||||
* <!-- 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>
|
||||
* </pre>
|
||||
* <b>Filter-mapping section:</b><br>
|
||||
* <pre>
|
||||
* <!-- 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>
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: TrimWhiteSpaceFilter.java#2 $
|
||||
*/
|
||||
@Deprecated
|
||||
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?
|
||||
}
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* AbstractCacheRequest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: AbstractCacheRequest.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractCacheRequest implements CacheRequest {
|
||||
private final URI requestURI;
|
||||
private final String method;
|
||||
|
||||
protected AbstractCacheRequest(final URI pRequestURI, final String pMethod) {
|
||||
requestURI = Validate.notNull(pRequestURI, "requestURI");
|
||||
method = Validate.notNull(pMethod, "method");
|
||||
}
|
||||
|
||||
public URI getRequestURI() {
|
||||
return requestURI;
|
||||
}
|
||||
|
||||
public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
// TODO: Consider overriding equals/hashcode
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder(getClass().getSimpleName())
|
||||
.append("[URI=").append(requestURI)
|
||||
.append(", parameters=").append(getParameters())
|
||||
.append(", headers=").append(getHeaders())
|
||||
.append("]").toString();
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AbstractCacheResponse
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: AbstractCacheResponse.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class AbstractCacheResponse implements CacheResponse {
|
||||
private int status;
|
||||
private final Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>(); // Insertion order
|
||||
private final Map<String, List<String>> readableHeaders = Collections.unmodifiableMap(headers);
|
||||
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(int pStatusCode) {
|
||||
status = pStatusCode;
|
||||
}
|
||||
|
||||
public void addHeader(String pHeaderName, String pHeaderValue) {
|
||||
setHeader(pHeaderName, pHeaderValue, true);
|
||||
}
|
||||
|
||||
public void setHeader(String pHeaderName, String pHeaderValue) {
|
||||
setHeader(pHeaderName, pHeaderValue, false);
|
||||
}
|
||||
|
||||
private void setHeader(String pHeaderName, String pHeaderValue, boolean pAdd) {
|
||||
List<String> values = pAdd ? headers.get(pHeaderName) : null;
|
||||
|
||||
if (values == null) {
|
||||
values = new ArrayList<String>();
|
||||
headers.put(pHeaderName, values);
|
||||
}
|
||||
|
||||
values.add(pHeaderValue);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getHeaders() {
|
||||
return readableHeaders;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
/**
|
||||
* CacheException
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: CacheException.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public class CacheException extends Exception {
|
||||
public CacheException(Throwable pCause) {
|
||||
super(pCause);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.servlet.GenericFilter;
|
||||
import com.twelvemonkeys.servlet.ServletConfigException;
|
||||
import com.twelvemonkeys.servlet.ServletUtil;
|
||||
|
||||
/**
|
||||
* A Filter that provides response caching, for HTTP {@code GET} requests.
|
||||
* <p>
|
||||
* Originally based on ideas and code found in the ONJava article
|
||||
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
|
||||
* Servlet Filters Every Web Application Should Have</a>
|
||||
* by Jayson Falkner.
|
||||
* </p>
|
||||
*
|
||||
* @author Jayson Falkner
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: CacheFilter.java#4 $
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CacheRequest
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: CacheRequest.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public interface CacheRequest {
|
||||
URI getRequestURI();
|
||||
|
||||
String getMethod();
|
||||
|
||||
Map<String, List<String>> getHeaders();
|
||||
|
||||
Map<String, List<String>> getParameters();
|
||||
|
||||
String getServerName();
|
||||
|
||||
int getServerPort();
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* CacheResponse
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: CacheResponse.java#1 $
|
||||
*/
|
||||
@Deprecated
|
||||
public interface CacheResponse {
|
||||
OutputStream getOutputStream() throws IOException;
|
||||
|
||||
void setStatus(int pStatusCode);
|
||||
|
||||
int getStatus();
|
||||
|
||||
void addHeader(String pHeaderName, String pHeaderValue);
|
||||
|
||||
void setHeader(String pHeaderName, String pHeaderValue);
|
||||
|
||||
Map<String, List<String>> getHeaders();
|
||||
}
|
||||
-266
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.net.HTTPUtil;
|
||||
import com.twelvemonkeys.servlet.ServletResponseStreamDelegate;
|
||||
|
||||
/**
|
||||
* CacheResponseWrapper class description.
|
||||
* <p>
|
||||
* Based on ideas and code found in the ONJava article
|
||||
* <a href="http://www.onjava.com/pub/a/onjava/2003/11/19/filters.html">Two
|
||||
* Servlet Filters Every Web Application Should Have</a>
|
||||
* by Jayson Falkner.
|
||||
* </p>
|
||||
*
|
||||
* @author Jayson Falkner
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: CacheResponseWrapper.java#3 $
|
||||
*/
|
||||
@Deprecated
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* CachedEntity
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CachedEntity.java#3 $
|
||||
*/
|
||||
@Deprecated
|
||||
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();
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* CachedEntity
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CachedEntityImpl.java#3 $
|
||||
*/
|
||||
@Deprecated
|
||||
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<String> 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 <server>:<port> 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 <server>:<port> 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 + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* CachedResponse
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CachedResponse.java#3 $
|
||||
*/
|
||||
@Deprecated
|
||||
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<String, List<String>> 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();
|
||||
}
|
||||
-220
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.servlet.cache;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* CachedResponseImpl
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: CachedResponseImpl.java#4 $
|
||||
*/
|
||||
@Deprecated
|
||||
class CachedResponseImpl implements CachedResponse {
|
||||
final protected Map<String, List<String>> headers;
|
||||
protected int headersSize;
|
||||
protected ByteArrayOutputStream content = null;
|
||||
int status;
|
||||
|
||||
protected CachedResponseImpl() {
|
||||
headers = new LinkedHashMap<String, List<String>>(); // Keep headers in insertion order
|
||||
}
|
||||
|
||||
// For use by HTTPCache, when recreating CachedResponses from disk cache
|
||||
CachedResponseImpl(final int pStatus, final LinkedHashMap<String, List<String>> 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<String> 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<String> 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<String> 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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user