added basic server side image caching
This commit is contained in:
parent
647bae4020
commit
5c9af5b42d
4 changed files with 202 additions and 9 deletions
123
etc/ehcache-1.5.0.xsd
Normal file
123
etc/ehcache-1.5.0.xsd
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
|
||||||
|
<xs:element name="ehcache" >
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" ref="diskStore"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1"
|
||||||
|
ref="cacheManagerEventListenerFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1"
|
||||||
|
ref="cacheManagerPeerProviderFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1"
|
||||||
|
ref="cacheManagerPeerListenerFactory"/>
|
||||||
|
<xs:element ref="defaultCache"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cache"/>
|
||||||
|
</xs:sequence>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="diskStore">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="path" use="optional" />
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheManagerEventListenerFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheManagerPeerProviderFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheManagerPeerListenerFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<!-- add clone support for addition of cacheExceptionHandler. Important! -->
|
||||||
|
<xs:element name="defaultCache">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="diskExpiryThreadIntervalSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="diskSpoolBufferSizeMB" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="diskPersistent" use="optional" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="eternal" use="required" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="maxElementsInMemory" use="required" type="xs:integer"/>
|
||||||
|
<xs:attribute name="memoryStoreEvictionPolicy" use="optional" type="xs:string"/>
|
||||||
|
<xs:attribute name="overflowToDisk" use="required" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="timeToIdleSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="timeToLiveSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="maxElementsOnDisk" use="optional" type="xs:integer"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cache">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:sequence>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
|
||||||
|
<xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
|
||||||
|
</xs:sequence>
|
||||||
|
<xs:attribute name="diskExpiryThreadIntervalSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="diskSpoolBufferSizeMB" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="diskPersistent" use="optional" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="eternal" use="required" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="maxElementsInMemory" use="required" type="xs:integer"/>
|
||||||
|
<xs:attribute name="memoryStoreEvictionPolicy" use="optional" type="xs:string"/>
|
||||||
|
<xs:attribute name="name" use="required" type="xs:string"/>
|
||||||
|
<xs:attribute name="overflowToDisk" use="required" type="xs:boolean"/>
|
||||||
|
<xs:attribute name="timeToIdleSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="timeToLiveSeconds" use="optional" type="xs:integer"/>
|
||||||
|
<xs:attribute name="maxElementsOnDisk" use="optional" type="xs:integer"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheEventListenerFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="bootstrapCacheLoaderFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheExtensionFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheExceptionHandlerFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
<xs:element name="cacheLoaderFactory">
|
||||||
|
<xs:complexType>
|
||||||
|
<xs:attribute name="class" use="required"/>
|
||||||
|
<xs:attribute name="properties" use="optional"/>
|
||||||
|
<xs:attribute name="propertySeparator" use="optional"/>
|
||||||
|
</xs:complexType>
|
||||||
|
</xs:element>
|
||||||
|
</xs:schema>
|
||||||
20
etc/ehcache.xml
Normal file
20
etc/ehcache.xml
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="ehcache-1.5.0.xsd">
|
||||||
|
|
||||||
|
<diskStore path="java.io.tmpdir"/>
|
||||||
|
|
||||||
|
<defaultCache eternal="false" maxElementsInMemory="200" overflowToDisk="false" />
|
||||||
|
|
||||||
|
<cache
|
||||||
|
name="imageCache"
|
||||||
|
maxElementsInMemory="120"
|
||||||
|
maxElementsOnDisk="5000"
|
||||||
|
eternal="true"
|
||||||
|
overflowToDisk="true"
|
||||||
|
diskPersistent="true"
|
||||||
|
diskSpoolBufferSizeMB="20"
|
||||||
|
diskExpiryThreadIntervalSeconds="1"
|
||||||
|
memoryStoreEvictionPolicy="LFU"/>
|
||||||
|
|
||||||
|
</ehcache>
|
||||||
5
pom.xml
5
pom.xml
|
|
@ -108,6 +108,11 @@
|
||||||
<version>2.4</version>
|
<version>2.4</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sf.ehcache</groupId>
|
||||||
|
<artifactId>ehcache</artifactId>
|
||||||
|
<version>1.5.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<repositories>
|
<repositories>
|
||||||
<repository>
|
<repository>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
@ -20,6 +21,10 @@ import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import net.sf.ehcache.Cache;
|
||||||
|
import net.sf.ehcache.CacheManager;
|
||||||
|
import net.sf.ehcache.Element;
|
||||||
|
|
||||||
import org.forkalsrud.album.exif.Dimension;
|
import org.forkalsrud.album.exif.Dimension;
|
||||||
import org.forkalsrud.album.exif.DirectoryEntry;
|
import org.forkalsrud.album.exif.DirectoryEntry;
|
||||||
import org.forkalsrud.album.exif.Entry;
|
import org.forkalsrud.album.exif.Entry;
|
||||||
|
|
@ -31,6 +36,7 @@ public class AlbumServlet
|
||||||
{
|
{
|
||||||
File base;
|
File base;
|
||||||
String basePrefix;
|
String basePrefix;
|
||||||
|
Cache imageCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init()
|
public void init()
|
||||||
|
|
@ -39,6 +45,9 @@ public class AlbumServlet
|
||||||
System.out.println("in init of Album");
|
System.out.println("in init of Album");
|
||||||
base = new File(getServletConfig().getInitParameter("base")).getAbsoluteFile();
|
base = new File(getServletConfig().getInitParameter("base")).getAbsoluteFile();
|
||||||
basePrefix = "/" + base.getName();
|
basePrefix = "/" + base.getName();
|
||||||
|
|
||||||
|
CacheManager cacheManager = new CacheManager();
|
||||||
|
imageCache = cacheManager.getCache("imageCache");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -75,12 +84,13 @@ public class AlbumServlet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.getParameter("size") != null) {
|
String size = req.getParameter("size");
|
||||||
|
if (size != null) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
res.setContentType("image/jpeg");
|
res.setContentType("image/jpeg");
|
||||||
FileEntry e = (FileEntry)DirectoryEntry.getEntry(file);
|
FileEntry e = (FileEntry)DirectoryEntry.getEntry(file);
|
||||||
scaleImage(req, res, file, e.getThumbnail(), req.getParameter("size"));
|
scaleImage(req, res, file, e.getThumbnail(), size);
|
||||||
res.setStatus(HttpServletResponse.SC_OK);
|
res.setStatus(HttpServletResponse.SC_OK);
|
||||||
return;
|
return;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -134,7 +144,7 @@ public class AlbumServlet
|
||||||
return reqDate > 0 && fDate > 0 && fDate <= reqDate;
|
return reqDate > 0 && fDate > 0 && fDate <= reqDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
|
void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
|
||||||
|
|
||||||
if (notModified(req, file)) {
|
if (notModified(req, file)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
|
|
@ -150,6 +160,24 @@ public class AlbumServlet
|
||||||
res.setDateHeader("Last-Modified", file.lastModified());
|
res.setDateHeader("Last-Modified", file.lastModified());
|
||||||
res.setHeader("ETag", fileEtag);
|
res.setHeader("ETag", fileEtag);
|
||||||
|
|
||||||
|
|
||||||
|
String key = file.getPath() + ":" + size;
|
||||||
|
Element element = imageCache.get(key);
|
||||||
|
if (element != null) {
|
||||||
|
CachedImage cimg = (CachedImage) element.getObjectValue();
|
||||||
|
if (cimg.lastModified == file.lastModified()) {
|
||||||
|
System.out.println("cache hit on " + key);
|
||||||
|
//res.setContentType(cimg.mimeType);
|
||||||
|
res.setContentLength(cimg.bits.length);
|
||||||
|
res.getOutputStream().write(cimg.bits);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
System.out.println(" " + key + " has changed so cache entry wil be refreshed");
|
||||||
|
imageCache.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this) {
|
||||||
Dimension orig = thumbnail.getSize();
|
Dimension orig = thumbnail.getSize();
|
||||||
Dimension outd;
|
Dimension outd;
|
||||||
if (size.endsWith("h")) {
|
if (size.endsWith("h")) {
|
||||||
|
|
@ -219,15 +247,26 @@ public class AlbumServlet
|
||||||
|
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
|
||||||
ImageWriter writer = writers.next();
|
ImageWriter writer = writers.next();
|
||||||
// ImageWriteParam wParam = writer.getDefaultWriteParam();
|
|
||||||
// wParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
ByteArrayOutputStream bits = new ByteArrayOutputStream();
|
||||||
// wParam.setCompressionQuality(1.0f);
|
|
||||||
ImageOutputStream ios = ImageIO.createImageOutputStream(res.getOutputStream());
|
ImageOutputStream ios = ImageIO.createImageOutputStream(bits);
|
||||||
writer.setOutput(ios);
|
writer.setOutput(ios);
|
||||||
// writer.write(null, new IIOImage(buf2, null, null), wParam);
|
|
||||||
writer.write(buf2);
|
writer.write(buf2);
|
||||||
ios.flush();
|
ios.flush();
|
||||||
res.getOutputStream().close();
|
|
||||||
|
CachedImage cimg = new CachedImage();
|
||||||
|
cimg.lastModified = file.lastModified();
|
||||||
|
cimg.mimeType = "image/jpeg";
|
||||||
|
cimg.bits = bits.toByteArray();
|
||||||
|
imageCache.put(new Element(key, cimg));
|
||||||
|
|
||||||
|
System.out.println(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + imageCache.getSize() + " entries");
|
||||||
|
|
||||||
|
//res.setContentType(cimg.mimeType);
|
||||||
|
res.setContentLength(cimg.bits.length);
|
||||||
|
res.getOutputStream().write(cimg.bits);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -273,6 +312,12 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class CachedImage {
|
||||||
|
long lastModified;
|
||||||
|
String mimeType;
|
||||||
|
byte[] bits;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eof
|
// eof
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue