Merge branch 'master' of ssh://forkalsrud.org/home/gitroot/album

Conflicts:
	src/org/forkalsrud/album/web/AlbumServlet.java
This commit is contained in:
Knut Forkalsrud 2009-01-24 10:59:42 -08:00
commit b3f35ba64c
4 changed files with 217 additions and 9 deletions

123
etc/ehcache-1.5.0.xsd Normal file
View 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
View 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>

View file

@ -108,6 +108,11 @@
<version>2.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.5.0</version>
</dependency>
</dependencies>
<repositories>
<repository>

View file

@ -4,6 +4,7 @@ import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
@ -22,6 +23,10 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;
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.DirectoryEntry;
import org.forkalsrud.album.exif.Entry;
@ -33,6 +38,7 @@ public class AlbumServlet
{
File base;
String basePrefix;
Cache imageCache;
@Override
public void init()
@ -44,6 +50,8 @@ public class AlbumServlet
PropertyConfigurator.configure("log4j.properties");
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
CacheManager cacheManager = new CacheManager();
imageCache = cacheManager.getCache("imageCache");
}
@Override
@ -80,16 +88,18 @@ public class AlbumServlet
return;
}
if (req.getParameter("size") != null) {
String size = req.getParameter("size");
if (size != null) {
try {
res.setContentType("image/jpeg");
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);
return;
} catch (Exception e) {
throw new RuntimeException("sadness", e);
}
return;
}
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
@ -132,15 +142,46 @@ public class AlbumServlet
return false;
}
synchronized void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
boolean notModified(HttpServletRequest req, File f) {
long reqDate = req.getDateHeader("If-Modified-Since");
long fDate = f.lastModified();
return reqDate > 0 && fDate > 0 && fDate <= reqDate;
}
void scaleImage(HttpServletRequest req, HttpServletResponse res, File file, Thumbnail thumbnail, String size) throws IOException {
if (notModified(req, file)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
System.out.println(file.getName() + " not modified (based on date)");
return;
}
String fileEtag = thumbnail.getEtag() + "-" + size;
if (etagMatches(req, fileEtag)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
System.out.println(file.getName() + " not modified (based on etag)");
return;
}
res.setDateHeader("Last-Modified", file.lastModified());
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 outd;
if (size.endsWith("h")) {
@ -210,13 +251,26 @@ public class AlbumServlet
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");
ImageWriter writer = writers.next();
// ImageWriteParam wParam = writer.getDefaultWriteParam();
// wParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
// wParam.setCompressionQuality(1.0f);
ImageOutputStream ios = ImageIO.createImageOutputStream(res.getOutputStream());
ByteArrayOutputStream bits = new ByteArrayOutputStream();
ImageOutputStream ios = ImageIO.createImageOutputStream(bits);
writer.setOutput(ios);
// writer.write(null, new IIOImage(buf2, null, null), wParam);
writer.write(buf2);
ios.flush();
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);
}
}
@ -262,6 +316,12 @@ public class AlbumServlet
}
}
}
static class CachedImage {
long lastModified;
String mimeType;
byte[] bits;
}
}
// eof