changeset 290:5d0c0da080ec gen2 scaleable_1

digilib servlet version 2 ("scaleable digilib") - first stab at using thread pools to limit resource use - using Dug Leas util.concurrent - doesn't mix with tomcat :-(
author robcast
date Thu, 21 Oct 2004 20:53:37 +0200
parents 9f7b864f955f
children 1cec876f2788
files servlet/src/digilib/image/ImageLoaderDocuImage.java servlet/src/digilib/image/ImageSize.java servlet/src/digilib/image/JAIImageLoaderDocuImage.java servlet/src/digilib/io/Directory.java servlet/src/digilib/io/DocuDirCache.java servlet/src/digilib/io/DocuDirectory.java servlet/src/digilib/io/ImageFile.java servlet/src/digilib/io/ImageFileset.java servlet/src/digilib/servlet/DigilibConfiguration.java servlet/src/digilib/servlet/DigilibImageWorker.java servlet/src/digilib/servlet/DigilibJob.java servlet/src/digilib/servlet/DigilibManager.java servlet/src/digilib/servlet/DigilibSender.java servlet/src/digilib/servlet/DigilibWorker.java servlet/src/digilib/servlet/DocumentBean.java servlet/src/digilib/servlet/Initialiser.java servlet/src/digilib/servlet/Mapper.java servlet/src/digilib/servlet/Raster.java servlet/src/digilib/servlet/Scaler.java servlet/src/digilib/servlet/ServletOps.java servlet/src/digilib/servlet/Texter.java
diffstat 21 files changed, 5517 insertions(+), 820 deletions(-) [+]
line wrap: on
line diff
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Mon Oct 18 15:40:54 2004 +0200
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Thu Oct 21 20:53:37 2004 +0200
@@ -1,164 +1,480 @@
 /* ImageLoaderDocuImage -- Image class implementation using JDK 1.4 ImageLoader
 
-  Digital Image Library servlet components
+ Digital Image Library servlet components
 
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+ Copyright (C) 2002, 2003 Robert Casties (robcast@mail.berlios.de)
 
-  This program is free software; you can redistribute  it and/or modify it
-  under  the terms of  the GNU General  Public License as published by the
-  Free Software Foundation;  either version 2 of the  License, or (at your
-  option) any later version.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
+ This program is free software; you can redistribute  it and/or modify it
+ under  the terms of  the GNU General  Public License as published by the
+ Free Software Foundation;  either version 2 of the  License, or (at your
+ option) any later version.
+ 
+ Please read license.txt for the full details. A copy of the GPL
+ may be found at http://www.gnu.org/copyleft/lgpl.html
 
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
 
 package digilib.image;
 
-import javax.servlet.*;
-import javax.servlet.http.*;
-import java.io.*;
-import java.util.*;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.ConvolveOp;
+import java.awt.image.Kernel;
+import java.awt.image.RescaleOp;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Iterator;
 
-import java.awt.*;
-import java.awt.image.*;
-import java.awt.geom.*;
-import java.awt.image.renderable.*;
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.FileImageInputStream;
+import javax.imageio.stream.ImageInputStream;
 
-import javax.imageio.*;
+import digilib.io.FileOpException;
+import digilib.io.ImageFile;
 
-import digilib.*;
-import digilib.io.*;
-
+/** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
 public class ImageLoaderDocuImage extends DocuImageImpl {
 
-  private BufferedImage img;
+	/** image object */
+	protected BufferedImage img;
+
+	/** interpolation type */
+	protected RenderingHints renderHint;
+
+	/** ImageIO image reader */
+	protected ImageReader reader;
+
+	/** File that was read */
+	protected File imgFile;
+
+	/* loadSubimage is supported. */
+	public boolean isSubimageSupported() {
+		return true;
+	}
+
+	public void setQuality(int qual) {
+		quality = qual;
+		renderHint = new RenderingHints(null);
+		//hint.put(RenderingHints.KEY_ANTIALIASING,
+		// RenderingHints.VALUE_ANTIALIAS_OFF);
+		// setup interpolation quality
+		if (qual > 0) {
+			logger.debug("quality q1");
+			renderHint.put(RenderingHints.KEY_INTERPOLATION,
+					RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+		} else {
+			logger.debug("quality q0");
+			renderHint.put(RenderingHints.KEY_INTERPOLATION,
+					RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
+		}
+	}
 
-  public ImageLoaderDocuImage() {
-  }
+	public int getHeight() {
+		int h = 0;
+		try {
+			if (img == null) {
+				h = reader.getHeight(0);
+			} else {
+				h = img.getHeight();
+			}
+		} catch (IOException e) {
+			logger.debug("error in getHeight", e);
+		}
+		return h;
+	}
 
-  public ImageLoaderDocuImage(Utils u) {
-    util = u;
-  }
+	public int getWidth() {
+		int w = 0;
+		try {
+			if (img == null) {
+				w = reader.getWidth(0);
+			} else {
+				w = img.getWidth();
+			}
+		} catch (IOException e) {
+			logger.debug("error in getHeight", e);
+		}
+		return w;
+	}
+
+	/* load image file */
+	public void loadImage(ImageFile f) throws FileOpException {
+		logger.debug("loadImage " + f.getFile());
+		//System.gc();
+		try {
+			img = ImageIO.read(f.getFile());
+			if (img == null) {
+				throw new FileOpException("Unable to load File!");
+			}
+		} catch (IOException e) {
+			throw new FileOpException("Error reading image.");
+		}
+	}
 
-  /**
-   *  load image file
-   */
-  public void loadImage(File f) throws FileOpException {
-    util.dprintln(10, "loadImage!");
-    System.gc();
-    try {
-      for (int i = 0; i < ImageIO.getReaderFormatNames().length; i++) {
-    System.out.println("ImageLoader reader:"+ImageIO.getReaderFormatNames()[i]);
-      }
-      for (int i = 0; i < ImageIO.getWriterFormatNames().length; i++) {
-    System.out.println("ImageLoader writer:"+ImageIO.getWriterFormatNames()[i]);
-      }
-      img = ImageIO.read(f);
-      if (img == null) {
-        util.dprintln(3, "ERROR(loadImage): unable to load file");
-        throw new FileOpException("Unable to load File!");
-      }
-    }
-    catch (IOException e) {
-      throw new FileOpException("Error reading image.");
-    }
-  }
+	/**
+	 * Get an ImageReader for the image file.
+	 * 
+	 * @return
+	 */
+	public ImageReader getReader(ImageFile f) throws IOException {
+		logger.debug("preloadImage " + f.getFile());
+		if (reader != null) {
+			logger.debug("Reader was not null!");
+			// clean up old reader
+			dispose();
+		}
+		//System.gc();
+		RandomAccessFile rf = new RandomAccessFile(f.getFile(), "r");
+		ImageInputStream istream = new FileImageInputStream(rf);
+		//Iterator readers = ImageIO.getImageReaders(istream);
+		String mt = f.getMimetype();
+		logger.debug("File type:" + mt);
+		Iterator readers = ImageIO.getImageReadersByMIMEType(mt);
+		if (!readers.hasNext()) {
+			throw new FileOpException("Unable to load File!");
+		}
+		reader = (ImageReader) readers.next();
+		/* are there more readers? */
+		logger.debug("ImageIO: this reader: " + reader.getClass());
+		while (readers.hasNext()) {
+			logger.debug("ImageIO: next reader: " + readers.next().getClass());
+		}
+		//*/
+		reader.setInput(istream);
+		imgFile = f.getFile();
+		return reader;
+	}
+
+	/* Load an image file into the Object. */
+	public void loadSubimage(ImageFile f, Rectangle region, int prescale)
+			throws FileOpException {
+		logger.debug("loadSubimage");
+		//System.gc();
+		try {
+			if ((reader == null) || (imgFile != f.getFile())) {
+				getReader(f);
+			}
+			// set up reader parameters
+			ImageReadParam readParam = reader.getDefaultReadParam();
+			readParam.setSourceRegion(region);
+			if (prescale > 1) {
+				readParam.setSourceSubsampling(prescale, prescale, 0, 0);
+			}
+			// read image
+			logger.debug("loading..");
+			img = reader.read(0, readParam);
+			logger.debug("loaded");
+		} catch (IOException e) {
+			throw new FileOpException("Unable to load File!");
+		}
+		if (img == null) {
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
+	/* write image of type mt to Stream */
+	public void writeImage(String mt, OutputStream ostream)
+			throws FileOpException {
+		logger.debug("writeImage");
+		try {
+			// setup output
+			String type = "png";
+			if (mt == "image/jpeg") {
+				type = "jpeg";
+			} else if (mt == "image/png") {
+				type = "png";
+			} else {
+				// unknown mime type
+				throw new FileOpException("Unknown mime type: " + mt);
+			}
+
+			/*
+			 * JPEG doesn't do transparency so we have to convert any RGBA image
+			 * to RGB :-( *Java2D BUG*
+			 */
+			if ((type == "jpeg") && (img.getColorModel().hasAlpha())) {
+				logger.debug("BARF: JPEG with transparency!!");
+				int w = img.getWidth();
+				int h = img.getHeight();
+				// BufferedImage.TYPE_INT_RGB seems to be fastest (JDK1.4.1,
+				// OSX)
+				int destType = BufferedImage.TYPE_INT_RGB;
+				BufferedImage img2 = new BufferedImage(w, h, destType);
+				img2.createGraphics().drawImage(img, null, 0, 0);
+				img = img2;
+			}
+
+			// render output
+			logger.debug("writing");
+			if (ImageIO.write(img, type, ostream)) {
+				// writing was OK
+				return;
+			} else {
+				throw new FileOpException(
+						"Error writing image: Unknown image format!");
+			}
+		} catch (IOException e) {
+			throw new FileOpException("Error writing image.");
+		}
+	}
 
-  /**
-   *  write image of type mt to Stream
-   */
-  public void writeImage(String mt, ServletResponse res)
-         throws FileOpException {
-    util.dprintln(10, "writeImage!");
-    try {
-      // setup output
-      String type = "png";
-      if (mt == "image/jpeg") {
-        type = "jpeg";
-      } else if (mt == "image/png") {
-        type = "png";
-      } else {
-        // unknown mime type
-        util.dprintln(2, "ERROR(writeImage): Unknown mime type "+mt);
-        throw new FileOpException("Unknown mime type: "+mt);
-      }
-      res.setContentType(mt);
-      // render output
-      if (ImageIO.write(img, type, res.getOutputStream())) {
-        // writing was OK
-        return;
-      } else {
-        throw new FileOpException("Error writing image: Unknown image format!");
-      }
-    } catch (IOException e) {
-      // e.printStackTrace();
-      throw new FileOpException("Error writing image.");
-    }
-  }
+	public void scale(double scale, double scaleY) throws ImageOpException {
+		logger.debug("scale");
+		/* for downscaling in high quality the image is blurred first */
+		if ((scale <= 0.5) && (quality > 1)) {
+			int bl = (int) Math.floor(1 / scale);
+			blur(bl);
+		}
+		/* then scaled */
+		AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform
+				.getScaleInstance(scale, scale), renderHint);
+		BufferedImage scaledImg = null;
+		// enforce destination image type (*Java2D BUG*)
+		int type = img.getType();
+		// FIXME: which type would be best?
+		if ((quality > 0) && (type != 0)) {
+			logger.debug("creating destination image");
+			Rectangle2D dstBounds = scaleOp.getBounds2D(img);
+			scaledImg = new BufferedImage((int) dstBounds.getWidth(),
+					(int) dstBounds.getHeight(), type);
+		}
+		logger.debug("scaling...");
+		scaledImg = scaleOp.filter(img, scaledImg);
+		logger.debug("destination image type " + scaledImg.getType());
+		if (scaledImg == null) {
+			throw new ImageOpException("Unable to scale");
+		}
+		//DEBUG
+		logger.debug("SCALE: " + scale + " ->" + scaledImg.getWidth() + "x"
+				+ scaledImg.getHeight());
+		img = scaledImg;
+		scaledImg = null;
+	}
 
-  public int getWidth() {
-    if (img != null) {
-      return img.getWidth();
-    }
-    return 0;
-  }
+	public void blur(int radius) throws ImageOpException {
+		//DEBUG
+		logger.debug("blur: " + radius);
+		// minimum radius is 2
+		int klen = Math.max(radius, 2);
+		// FIXME: use constant kernels for most common sizes
+		int ksize = klen * klen;
+		// kernel is constant 1/k
+		float f = 1f / ksize;
+		float[] kern = new float[ksize];
+		for (int i = 0; i < ksize; i++) {
+			kern[i] = f;
+		}
+		Kernel blur = new Kernel(klen, klen, kern);
+		// blur with convolve operation
+		ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP,
+				renderHint);
+		// blur needs explicit destination image type for color *Java2D BUG*
+		BufferedImage blurredImg = null;
+		if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+			blurredImg = new BufferedImage(img.getWidth(), img.getHeight(), img
+					.getType());
+		}
+		blurredImg = blurOp.filter(img, blurredImg);
+		if (blurredImg == null) {
+			throw new ImageOpException("Unable to scale");
+		}
+		img = blurredImg;
+	}
+
+	public void crop(int x_off, int y_off, int width, int height)
+			throws ImageOpException {
+		// setup Crop
+		BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
+		logger.debug("CROP:" + croppedImg.getWidth() + "x"
+				+ croppedImg.getHeight());
+		//DEBUG
+		//    util.dprintln(2, " time
+		// "+(System.currentTimeMillis()-startTime)+"ms");
+		if (croppedImg == null) {
+			throw new ImageOpException("Unable to crop");
+		}
+		img = croppedImg;
+	}
+
+	public void enhance(float mult, float add) throws ImageOpException {
+		/*
+		 * Only one constant should work regardless of the number of bands
+		 * according to the JDK spec. Doesn't work on JDK 1.4 for OSX and Linux
+		 * (at least). RescaleOp scaleOp = new RescaleOp( (float)mult,
+		 * (float)add, null); scaleOp.filter(img, img);
+		 */
+
+		/* The number of constants must match the number of bands in the image. */
+		int ncol = img.getColorModel().getNumComponents();
+		float[] dm = new float[ncol];
+		float[] da = new float[ncol];
+		for (int i = 0; i < ncol; i++) {
+			dm[i] = (float) mult;
+			da[i] = (float) add;
+		}
+		RescaleOp scaleOp = new RescaleOp(dm, da, null);
+		scaleOp.filter(img, img);
+	}
+
+	public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
+
+		/*
+		 * The number of constants must match the number of bands in the image.
+		 * We do only 3 (RGB) bands.
+		 */
+
+		int ncol = img.getColorModel().getNumColorComponents();
+		if ((ncol != 3) || (rgbm.length != 3) || (rgba.length != 3)) {
+			logger
+					.debug("ERROR(enhance): unknown number of color bands or coefficients ("
+							+ ncol + ")");
+			return;
+		}
+		RescaleOp scaleOp = new RescaleOp(rgbOrdered(rgbm), rgbOrdered(rgba),
+				null);
+		scaleOp.filter(img, img);
+	}
 
-  public int getHeight() {
-    if (img != null) {
-      return img.getHeight();
-    }
-    return 0;
-  }
-
-  /**
-   *  crop and scale image
-   *    take rectangle width,height at position x_off,y_off
-   *    and scale by scale
-   */
-   public void cropAndScale(int x_off, int y_off, int width, int height,
-         float scale, int qual) throws ImageOpException {
-    util.dprintln(10, "cropAndScale!");
-
-    int scaleInt = 0;
-    // setup interpolation quality
-    if (qual > 0) {
-      util.dprintln(4, "quality q1");
-      scaleInt = AffineTransformOp.TYPE_BILINEAR;
-    } else {
-      util.dprintln(4, "quality q0");
-      scaleInt = AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
-    }
+	/**
+	 * Ensures that the array f is in the right order to map the images RGB
+	 * components. (not shure what happens
+	 */
+	public float[] rgbOrdered(float[] fa) {
+		/*
+		 * TODO: this is UGLY, UGLY!!
+		 */
+		float[] fb;
+		int t = img.getType();
+		if (img.getColorModel().hasAlpha()) {
+			fb = new float[4];
+			if ((t == BufferedImage.TYPE_INT_ARGB)
+					|| (t == BufferedImage.TYPE_INT_ARGB_PRE)) {
+				// RGB Type
+				fb[0] = fa[0];
+				fb[1] = fa[1];
+				fb[2] = fa[2];
+				fb[3] = 1f;
+			} else {
+				// this isn't tested :-(
+				fb[0] = 1f;
+				fb[1] = fa[0];
+				fb[2] = fa[1];
+				fb[3] = fa[2];
+			}
+		} else {
+			fb = new float[3];
+			if (t == BufferedImage.TYPE_3BYTE_BGR) {
+				// BGR Type (actually it looks like RBG...)
+				fb[0] = fa[0];
+				fb[1] = fa[2];
+				fb[2] = fa[1];
+			} else {
+				fb[0] = fa[0];
+				fb[1] = fa[1];
+				fb[2] = fa[2];
+			}
+		}
+		return fb;
+	}
 
-    // setup Crop
-    BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
-
-    img = null; // free img
-    util.dprintln(3, "CROP:"+croppedImg.getWidth()+"x"+croppedImg.getHeight()); //DEBUG
-//    util.dprintln(2, "  time "+(System.currentTimeMillis()-startTime)+"ms");
-
-    if (croppedImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in crop");
-      throw new ImageOpException("Unable to crop");
-    }
+	public void rotate(double angle) throws ImageOpException {
+		// setup rotation
+		double rangle = Math.toRadians(angle);
+		// create offset to make shure the rotated image has no negative
+		// coordinates
+		double w = img.getWidth();
+		double h = img.getHeight();
+		AffineTransform trafo = new AffineTransform();
+		// center of rotation
+		double x = (w / 2);
+		double y = (h / 2);
+		trafo.rotate(rangle, x, y);
+		// try rotation to see how far we're out of bounds
+		AffineTransformOp rotOp = new AffineTransformOp(trafo, renderHint);
+		Rectangle2D rotbounds = rotOp.getBounds2D(img);
+		double xoff = rotbounds.getX();
+		double yoff = rotbounds.getY();
+		// move image back in line
+		trafo
+				.preConcatenate(AffineTransform.getTranslateInstance(-xoff,
+						-yoff));
+		// transform image
+		rotOp = new AffineTransformOp(trafo, renderHint);
+		BufferedImage rotImg = rotOp.filter(img, null);
+		// calculate new bounding box
+		//Rectangle2D bounds = rotOp.getBounds2D(img);
+		if (rotImg == null) {
+			throw new ImageOpException("Unable to rotate");
+		}
+		img = rotImg;
+		// crop new image (with self-made rounding)
+		/*
+		 * img = rotImg.getSubimage( (int) (bounds.getX()+0.5), (int)
+		 * (bounds.getY()+0.5), (int) (bounds.getWidth()+0.5), (int)
+		 * (bounds.getHeight()+0.5));
+		 */
+	}
 
-    // setup scale
-    AffineTransformOp scaleOp = new AffineTransformOp(
-                      AffineTransform.getScaleInstance(scale, scale),
-                      scaleInt);
-    BufferedImage scaledImg = scaleOp.filter(croppedImg, null);
-    croppedImg = null; // free opCrop
+	public void mirror(double angle) throws ImageOpException {
+		// setup mirror
+		double mx = 1;
+		double my = 1;
+		double tx = 0;
+		double ty = 0;
+		if (Math.abs(angle - 0) < epsilon) { // 0 degree
+			mx = -1;
+			tx = getWidth();
+		} else if (Math.abs(angle - 90) < epsilon) { // 90 degree
+			my = -1;
+			ty = getHeight();
+		} else if (Math.abs(angle - 180) < epsilon) { // 180 degree
+			mx = -1;
+			tx = getWidth();
+		} else if (Math.abs(angle - 270) < epsilon) { // 270 degree
+			my = -1;
+			ty = getHeight();
+		} else if (Math.abs(angle - 360) < epsilon) { // 360 degree
+			mx = -1;
+			tx = getWidth();
+		}
+		AffineTransformOp mirOp = new AffineTransformOp(new AffineTransform(mx,
+				0, 0, my, tx, ty), renderHint);
+		BufferedImage mirImg = mirOp.filter(img, null);
+		if (mirImg == null) {
+			throw new ImageOpException("Unable to mirror");
+		}
+		img = mirImg;
+	}
 
-    if (scaledImg == null) {
-      util.dprintln(2, "ERROR(cropAndScale): error in scale");
-      throw new ImageOpException("Unable to scale");
-    }
-    img = scaledImg;
-  }
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see java.lang.Object#finalize()
+	 */
+	protected void finalize() throws Throwable {
+		dispose();
+		super.finalize();
+	}
+
+	public void dispose() {
+		// we must dispose the ImageReader because it keeps the filehandle
+		// open!
+		if (reader != null) {
+			reader.dispose();
+			reader = null;
+		}
+		img = null;
+	}
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageSize.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,220 @@
+/*
+ * ImageFile.java -- digilib image file class. 
+ * Digital Image Library servlet components 
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de) 
+ * This program is free software; you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version. Please read license.txt for the full details. A copy of the GPL may
+ * be found at http://www.gnu.org/copyleft/lgpl.html You should have received a
+ * copy of the GNU General Public License along with this program; if not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307 USA Created on 26.08.2003
+ */
+
+package digilib.image;
+
+/** Class for image size (width, height).
+ * 
+ * A width or height of 0 is treated as a 'wildcard' that matches any size.
+ * 
+ * @author casties
+ *          
+ */
+public class ImageSize {
+	public int width;
+	public int height;
+
+	public ImageSize() {
+		super();
+	}
+
+	public ImageSize(int width, int height) {
+		this.width = width;
+		this.height = height;
+	}
+
+	public ImageSize(ImageSize i) {
+		this.width = i.width;
+		this.height = i.height;
+	}
+
+	public void setSize(int width, int height) {
+		this.width = width;
+		this.height = height;
+	}
+
+	/**
+	 * Returns if the size of this image is smaller in every dimension than the
+	 * other image.
+	 * 
+	 * 
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean isTotallySmallerThan(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height <= is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width <= is.width);
+		}
+		return ((this.width <= is.width)&&(this.height <= is.height));
+	}
+
+	/**
+	 * Returns if the size of this image is smaller in at least one dimension
+	 * than the other image.
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean isSmallerThan(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height <= is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width <= is.width);
+		}
+		return ((this.width <= is.width) || (this.height <= is.height));
+	}
+
+	/**
+	 * Returns if the size of this image is bigger in every dimension than the
+	 * other image.
+	 * 
+	 * 
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean isTotallyBiggerThan(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height >= is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width >= is.width);
+		}
+		return ((this.width >= is.width) && (this.height >= is.height));
+	}
+
+	/**
+	 * Returns if the size of this image is bigger in at least one dimension
+	 * than the other image.
+	 * 
+	 * 
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean isBiggerThan(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height >= is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width >= is.width);
+		}
+		return ((this.width >= is.width) || (this.height >= is.height));
+	}
+
+	/**
+	 * Returns if this image has the same size or height as the other image.
+	 * 
+	 * 
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean fitsIn(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height == is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width == is.width);
+		}
+		return (
+			(this.width == is.width)
+				&& (this.height <= is.height)
+				|| (this.width <= is.width)
+				&& (this.height == is.height));
+	}
+
+	/**
+	 * Returns if the size of this image is the same as the other image.
+	 * 
+	 * 
+	 * 
+	 * @param is
+	 * @return
+	 */
+	public boolean equals(ImageSize is) {
+		if ((this.width == 0)||(is.width == 0)) {
+			// width wildcard
+			return (this.height == is.height);
+		}
+		if ((this.height == 0)||(is.height == 0)) {
+			// height wildcard
+			return (this.width == is.width);
+		}
+		return ((this.width == is.width) && (this.height == is.height));
+	}
+
+	/**
+	 * @return
+	 */
+	public int getHeight() {
+		return height;
+	}
+
+	/**
+	 * @param height
+	 */
+	public void setHeight(int height) {
+		this.height = height;
+	}
+
+	/**
+	 * @return
+	 */
+	public int getWidth() {
+		return width;
+	}
+
+	/**
+	 * @param width
+	 */
+	public void setWidth(int width) {
+		this.width = width;
+	}
+
+	/**
+	 * Returns the aspect ratio.
+	 * 
+	 * Aspect ratio is (width/height). So it's <1 for portrait and  >1 for
+	 * landscape.
+	 * 
+	 * @return
+	 */
+	public float getAspect() {
+		return (height > 0) ? ((float) width / (float) height) : 0;
+	}
+	
+	/* (non-Javadoc)
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		String s = "[" + width + "x" + height + "]";
+		return s;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,184 @@
+/* JAIImageLoaderDocuImage -- Image class implementation using JAI's ImageLoader Plugin
+
+  Digital Image Library servlet components
+
+  Copyright (C) 2002, 2003 Robert Casties (robcast@mail.berlios.de)
+
+  This program is free software; you can redistribute  it and/or modify it
+  under  the terms of  the GNU General  Public License as published by the
+  Free Software Foundation;  either version 2 of the  License, or (at your
+  option) any later version.
+   
+  Please read license.txt for the full details. A copy of the GPL
+  may be found at http://www.gnu.org/copyleft/lgpl.html
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+*/
+
+package digilib.image;
+
+import java.awt.Rectangle;
+import java.awt.image.renderable.ParameterBlock;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Iterator;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReadParam;
+import javax.imageio.ImageReader;
+import javax.imageio.stream.FileImageInputStream;
+import javax.imageio.stream.ImageInputStream;
+import javax.media.jai.JAI;
+
+import digilib.io.FileOpException;
+import digilib.io.ImageFile;
+
+/** DocuImage implementation using the Java Advanced Imaging API and the ImageLoader
+ * API of Java 1.4.
+ */
+public class JAIImageLoaderDocuImage extends JAIDocuImage {
+
+	/** ImageIO image reader */
+	protected ImageReader reader;
+	/** current image file */
+	protected File imgFile;
+
+	/* loadSubimage is supported. */
+	public boolean isSubimageSupported() {
+		return true;
+	}
+
+	public int getHeight() {
+		int h = 0;
+		try {
+			if (img == null) {
+				h = reader.getHeight(0);
+			} else {
+				h = img.getHeight();
+			}
+		} catch (IOException e) {
+			logger.debug("error in getHeight", e);
+		}
+		return h;
+	}
+
+	public int getWidth() {
+		int w = 0;
+		try {
+			if (img == null) {
+				w = reader.getWidth(0);
+			} else {
+				w = img.getWidth();
+			}
+		} catch (IOException e) {
+			logger.debug("error in getHeight", e);
+		}
+		return w;
+	}
+
+	/* Load an image file into the Object. */
+	public void loadImage(ImageFile f) throws FileOpException {
+		logger.debug("loadImage: "+f.getFile());
+		//System.gc();
+		img = JAI.create("ImageRead", f.getFile().getAbsolutePath());
+		if (img == null) {
+			throw new FileOpException("Unable to load File!");
+		}
+	}
+
+	/* Get an ImageReader for the image file. */
+	public ImageReader getReader(ImageFile f) throws IOException {
+		logger.debug("preloadImage: "+f.getFile());
+		//System.gc();
+		RandomAccessFile rf = new RandomAccessFile(f.getFile(), "r");
+		ImageInputStream istream = new FileImageInputStream(rf);
+		//Iterator readers = ImageIO.getImageReaders(istream);
+		Iterator readers = ImageIO.getImageReadersByMIMEType(f.getMimetype());
+		if (! readers.hasNext()) {
+			throw new FileOpException("Unable to load File!");
+		}
+		reader = (ImageReader) readers.next();
+		logger.debug("JAIImageIO: this reader: " + reader.getClass());
+		while (readers.hasNext()) {
+			logger.debug("  next reader: " + readers.next().getClass());
+		}
+		reader.setInput(istream);
+		return reader;
+	}
+
+	/* Load an image file into the Object. */
+	public void loadSubimage(ImageFile f, Rectangle region, int prescale)
+		throws FileOpException {
+		logger.debug("loadSubimage: "+f.getFile());
+		//System.gc();
+		try {
+			if ((reader == null) || (imgFile != f.getFile())) {
+				getReader(f);
+			}
+			ImageReadParam readParam = reader.getDefaultReadParam();
+			readParam.setSourceRegion(region);
+			readParam.setSourceSubsampling(prescale, prescale, 0, 0);
+			img = reader.read(0, readParam);
+			/* JAI imageread seems to ignore the readParam :-(
+			ImageInputStream istream = (ImageInputStream) reader.getInput();
+			ParameterBlockJAI pb = new ParameterBlockJAI("imageread");
+			pb.setParameter("Input", istream);
+			pb.setParameter("ReadParam", readParam);
+			pb.setParameter("Reader", reader);
+			img = JAI.create("imageread", pb);
+			*/
+		} catch (IOException e) {
+			throw new FileOpException("Unable to load File!");
+		}
+		if (img == null) {
+			throw new FileOpException("Unable to load File!");
+		}
+		imgFile = f.getFile();
+	}
+
+
+	/* Write the current image to an OutputStream. */
+	public void writeImage(String mt, OutputStream ostream)
+		throws FileOpException {
+		logger.debug("writeImage");
+		try {
+			// setup output
+			ParameterBlock pb3 = new ParameterBlock();
+			pb3.addSource(img);
+			pb3.add(ostream);
+			if (mt == "image/jpeg") {
+				pb3.add("JPEG");
+			} else if (mt == "image/png") {
+				pb3.add("PNG");
+			} else {
+				// unknown mime type
+				throw new FileOpException("Unknown mime type: " + mt);
+			}
+			// render output
+			JAI.create("ImageWrite", pb3);
+		} catch (IOException e) {
+			throw new FileOpException("Error writing image.");
+		}
+	}
+
+	/* (non-Javadoc)
+	 * @see java.lang.Object#finalize()
+	 */
+	protected void finalize() throws Throwable {
+		dispose();
+		super.finalize();
+	}
+
+	public void dispose() {
+		// we must dispose the ImageReader because it keeps the filehandle open!
+		reader.dispose();
+		reader = null;
+		img = null;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/Directory.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,139 @@
+/* Directory -- Filesystem directory object
+
+  Digital Image Library servlet components
+
+  Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+
+  This program is free software; you can redistribute  it and/or modify it
+  under  the terms of  the GNU General  Public License as published by the
+  Free Software Foundation;  either version 2 of the  License, or (at your
+  option) any later version.
+   
+  Please read license.txt for the full details. A copy of the GPL
+  may be found at http://www.gnu.org/copyleft/lgpl.html
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ * Created on 26.08.2003
+ *
+ */
+package digilib.io;
+
+import java.io.File;
+import java.util.Arrays;
+
+import org.apache.log4j.Logger;
+
+/** Class for filesystem directories
+ * @author casties
+ *
+ */
+public class Directory {
+
+	protected Logger logger = Logger.getLogger(this.getClass());
+
+	/** File object pointing to the directory */
+	protected File dir = null;
+	/** parent directory */
+	protected Directory parent = null;
+	/** list of filenames in the directory */
+	protected String[] list = null;
+
+	/** Default constructor.
+	 * 
+	 */
+	public Directory() {
+		super();
+	}
+	
+	/** Constructor taking a File object.
+	 * 
+	 * @param d
+	 */
+	public Directory(File d) {
+		dir = d;
+	}
+
+	/** Constructor taking a File object and a parent.
+	 * 
+	 * @param dir
+	 * @param parent
+	 */
+	public Directory(File dir, Directory parent) {
+		this.dir = dir;
+		this.parent = parent;
+	}
+
+	/** Constructor taking a directory name.
+	 * 
+	 * @param d
+	 */
+	public Directory(String dn) {
+		dir = new File(dn);
+	}
+	
+	
+	/** Reads the names of the files in the directory.
+	 * Fills the filenames array. Returns if the operation was successful.
+	 * 
+	 * @return
+	 */
+	public boolean readDir() {
+		if (dir != null) {
+			//logger.debug("reading dir: "+dir.getPath());
+			list = dir.list();
+			Arrays.sort(list);
+			//logger.debug("  done");
+		}
+		return (list != null);
+	}
+	
+	/**
+	 * @return
+	 */
+	public File getDir() {
+		return dir;
+	}
+
+	/**
+	 * @param dir
+	 */
+	public void setDir(File dir) {
+		this.dir = dir;
+	}
+	
+	/**
+	 * @return
+	 */
+	Directory getParent() {
+		return parent;
+	}
+
+	/**
+	 * @param parent
+	 */
+	void setParent(Directory parent) {
+		this.parent = parent;
+	}
+
+
+	/**
+	 * @return Returns the filenames.
+	 */
+	public String[] getFilenames() {
+		return list;
+	}
+	
+	/**
+	 * @param filenames The filenames to set.
+	 */
+	public void setFilenames(String[] filenames) {
+		this.list = filenames;
+	}
+	
+	public void clearFilenames() {
+		this.list = null;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/DocuDirCache.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,355 @@
+/*
+ * DocuDirCache.java
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ * 
+ * Created on 03.03.2003
+ */
+
+package digilib.io;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import digilib.servlet.DigilibConfiguration;
+
+/**
+ * @author casties
+ */
+public class DocuDirCache {
+
+	/** general logger for this class */
+	Logger logger = Logger.getLogger(this.getClass());
+
+	/** HashMap of directories */
+	Map map = null;
+
+	/** names of base directories */
+	String[] baseDirNames = null;
+
+	/** array of allowed file classes (image/text) */
+	private int[] fileClasses = null;
+
+	/** number of files in the whole cache (approximate) */
+	long numFiles = 0;
+
+	/** number of cache hits */
+	long hits = 0;
+
+	/** number of cache misses */
+	long misses = 0;
+
+	/** use safe (but slow) indexing */
+	boolean safeDirIndex = false;
+
+	/** the root directory element */
+	public static Directory ROOT = null;
+
+	/**
+	 * Constructor with array of base directory names and file classes.
+	 * 
+	 * @param bd
+	 *            base directory names
+	 */
+	public DocuDirCache(String[] bd, int[] fileClasses,
+			DigilibConfiguration dlConfig) {
+		baseDirNames = bd;
+		map = new HashMap();
+		this.fileClasses = fileClasses;
+		safeDirIndex = dlConfig.getAsBoolean("safe-dir-index");
+	}
+
+	/**
+	 * Constructor with array of base directory names.
+	 * 
+	 * @param bd
+	 *            base directory names
+	 */
+	public DocuDirCache(String[] bd) {
+		baseDirNames = bd;
+		map = new HashMap();
+		// default file class is CLASS_IMAGE
+		fileClasses = new int[1];
+		fileClasses[0] = FileOps.CLASS_IMAGE;
+	}
+
+	/**
+	 * The number of directories in the cache.
+	 * 
+	 * @return
+	 */
+	public int size() {
+		return (map != null) ? map.size() : 0;
+	}
+
+	/**
+	 * Add a DocuDirectory to the cache.
+	 * 
+	 * @param newdir
+	 */
+	public void put(DocuDirectory newdir) {
+		String s = newdir.getDirName();
+		if (map.containsKey(s)) {
+			logger.warn("Duplicate key in DocuDirCache.put -- ignoring!");
+		} else {
+			map.put(s, newdir);
+			numFiles += newdir.size();
+		}
+	}
+
+	/**
+	 * Add a directory to the cache and check its parents.
+	 * 
+	 * @param newDir
+	 */
+	public synchronized void putDir(DocuDirectory newDir) {
+		put(newDir);
+		String parent = FileOps.parent(newDir.getDirName());
+		if (parent != "") {
+			// check the parent in the cache
+			DocuDirectory pd = (DocuDirectory) map.get(parent);
+			if (pd == null) {
+				// the parent is unknown
+				pd = new DocuDirectory(parent, this);
+				putDir(pd);
+			}
+			newDir.setParent(pd);
+		}
+	}
+
+	/**
+	 * Get a list with all children of a directory.
+	 * 
+	 * Returns a List of DocuDirectory's. Returns an empty List if the directory
+	 * has no children. If recurse is false then only direct children are
+	 * returned.
+	 * 
+	 * @param dirname
+	 * @param recurse
+	 *            find all children and their children.
+	 * @return
+	 */
+	public List getChildren(String dirname, boolean recurse) {
+		List l = new LinkedList();
+		for (Iterator i = map.keySet().iterator(); i.hasNext();) {
+			String n = (String) i.next();
+			DocuDirectory dd = (DocuDirectory) map.get(n);
+			if (recurse) {
+				if (dd.getDirName().startsWith(dirname)) {
+					l.add(dd);
+				}
+			} else {
+				if (FileOps.parent(dd.getDirName()).equals(dirname)) {
+					l.add(dd);
+				}
+			}
+		}
+		return l;
+	}
+
+	/**
+	 * Returns the DocuDirent with the pathname <code>fn</code> and the index
+	 * <code>in</code> and the class <code>fc</code>.
+	 * 
+	 * If <code>fn</code> is a file then the corresponding DocuDirent is
+	 * returned and the index is ignored.
+	 * 
+	 * @param fn
+	 *            digilib pathname
+	 * @param in
+	 *            file index
+	 * @param fc
+	 *            file class
+	 * @return
+	 */
+	public DocuDirent getFile(String fn, int in, int fc) {
+		DocuDirectory dd;
+		// file number is 1-based, vector index is 0-based
+		int n = in - 1;
+		// first, assume fn is a directory and look in the cache
+		dd = (DocuDirectory) map.get(fn);
+		if (dd == null) {
+			// cache miss
+			misses++;
+			/*
+			 * see if fn is a directory
+			 */
+			File f = new File(baseDirNames[0], fn);
+			if (f.isDirectory()) {
+				dd = new DocuDirectory(fn, this);
+				if (dd.isValid()) {
+					// add to the cache
+					putDir(dd);
+				}
+			} else {
+				/*
+				 * maybe it's a file
+				 */
+				// get the parent directory string (like we store it in the
+				// cache)
+				String d = FileOps.parent(fn);
+				// try it in the cache
+				dd = (DocuDirectory) map.get(d);
+				if (dd == null) {
+					// try to read from disk
+					dd = new DocuDirectory(d, this);
+					if (dd.isValid()) {
+						// add to the cache
+						putDir(dd);
+					} else {
+						// invalid path
+						return null;
+					}
+				} else {
+					// it was not a real cache miss
+					misses--;
+				}
+				// get the file's index
+				n = dd.indexOf(f.getName(), fc);
+			}
+		} else {
+			// cache hit
+			hits++;
+		}
+		dd.refresh();
+		if (dd.isValid()) {
+			try {
+				return dd.get(n, fc);
+			} catch (IndexOutOfBoundsException e) {
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the DocuDirectory indicated by the pathname <code>fn</code>.
+	 * 
+	 * If <code>fn</code> is a file then its parent directory is returned.
+	 * 
+	 * @param fn
+	 *            digilib pathname
+	 * @return
+	 */
+	public DocuDirectory getDirectory(String fn) {
+		DocuDirectory dd;
+		// first, assume fn is a directory and look in the cache
+		dd = (DocuDirectory) map.get(fn);
+		if (dd == null) {
+			// cache miss
+			misses++;
+			// see if it's a directory
+			File f = new File(baseDirNames[0], fn);
+			if (f.isDirectory()) {
+				dd = new DocuDirectory(fn, this);
+				if (dd.isValid()) {
+					// add to the cache
+					putDir(dd);
+				}
+			} else {
+				// maybe it's a file
+				if (f.canRead()) {
+					// try the parent directory in the cache
+					dd = (DocuDirectory) map.get(f.getParent());
+					if (dd == null) {
+						// try to read from disk
+						dd = new DocuDirectory(f.getParent(), this);
+						if (dd.isValid()) {
+							// add to the cache
+							putDir(dd);
+						} else {
+							// invalid path
+							return null;
+						}
+					} else {
+						// not a real cache miss then
+						misses--;
+					}
+				} else {
+					// it's not even a file :-(
+					return null;
+				}
+			}
+		} else {
+			// cache hit
+			hits++;
+		}
+		dd.refresh();
+		if (dd.isValid()) {
+			return dd;
+		}
+		return null;
+	}
+
+	/**
+	 * @return String[]
+	 */
+	public String[] getBaseDirNames() {
+		return baseDirNames;
+	}
+
+	/**
+	 * @return long
+	 */
+	public long getNumFiles() {
+		return numFiles;
+	}
+
+	/**
+	 * Sets the baseDirNames.
+	 * 
+	 * @param baseDirNames
+	 *            The baseDirNames to set
+	 */
+	public void setBaseDirNames(String[] baseDirNames) {
+		this.baseDirNames = baseDirNames;
+	}
+
+	/**
+	 * @return long
+	 */
+	public long getHits() {
+		return hits;
+	}
+
+	/**
+	 * @return long
+	 */
+	public long getMisses() {
+		return misses;
+	}
+
+	/**
+	 * @return
+	 */
+	public int[] getFileClasses() {
+		return fileClasses;
+	}
+
+	/**
+	 * @param fileClasses
+	 */
+	public void setFileClasses(int[] fileClasses) {
+		this.fileClasses = fileClasses;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/DocuDirectory.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,563 @@
+/* DocuDirectory -- Directory of DocuFilesets.
+
+ Digital Image Library servlet components
+
+ Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+
+ This program is free software; you can redistribute  it and/or modify it
+ under  the terms of  the GNU General  Public License as published by the
+ Free Software Foundation;  either version 2 of the  License, or (at your
+ option) any later version.
+ 
+ Please read license.txt for the full details. A copy of the GPL
+ may be found at http://www.gnu.org/copyleft/lgpl.html
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ * Created on 25.02.2003
+ */
+
+package digilib.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.xml.sax.SAXException;
+
+/**
+ * @author casties
+ */
+public class DocuDirectory extends Directory {
+
+	/** list of files (DocuDirent) */
+	private ArrayList[] list = null;
+
+	/** directory object is valid (exists on disk) */
+	private boolean isValid = false;
+
+	/** reference of the parent DocuDirCache */
+	private DocuDirCache cache = null;
+
+	/** directory name (digilib canonical form) */
+	private String dirName = null;
+
+	/** directory metadata */
+	private Map dirMeta = null;
+
+	/** state of metadata is valid */
+	private boolean metaChecked = false;
+
+	/** unresolved file metadata */
+	private Map unresolvedFileMeta = null;
+
+	/** time of last access of this object (not the filesystem) */
+	private long objectATime = 0;
+
+	/** time directory was last modified on the file system */
+	private long dirMTime = 0;
+
+	/**
+	 * Constructor with digilib directory path and a parent DocuDirCache.
+	 * 
+	 * Directory names at the given path are appended to the base directories
+	 * from the cache. The directory is checked on disk and isValid is set.
+	 * 
+	 * @see readDir
+	 * 
+	 * @param path
+	 *            digilib directory path name
+	 * @param cache
+	 *            parent DocuDirCache
+	 */
+	public DocuDirectory(String path, DocuDirCache cache) {
+		this.dirName = path;
+		this.cache = cache;
+		initDir();
+		checkDir();
+	}
+
+	/**
+	 * Sets and checks the dir object.
+	 *  
+	 */
+	protected void initDir() {
+		String baseDirName = cache.getBaseDirNames()[0];
+		// clear directory first
+		list = new ArrayList[FileOps.NUM_CLASSES];
+		isValid = false;
+		dirMTime = 0;
+		// the first directory has to exist
+		dir = new File(baseDirName, dirName);
+	}
+
+	/**
+	 * number of DocuFiles in this directory.
+	 *  
+	 */
+	public int size() {
+		return ((list != null) && (list[0] != null)) ? list[0].size() : 0;
+	}
+
+	/**
+	 * number of files of this class in this directory.
+	 * 
+	 * @param fc
+	 *            fileClass
+	 */
+	public int size(int fc) {
+		return ((list != null) && (list[fc] != null)) ? list[fc].size() : 0;
+	}
+
+	/**
+	 * Returns the ImageFile at the index.
+	 * 
+	 * @param index
+	 * @return
+	 */
+	public ImageFileset get(int index) {
+		if ((list == null) || (list[0] == null) || (index >= list[0].size())) {
+			return null;
+		}
+		return (ImageFileset) list[0].get(index);
+	}
+
+	/**
+	 * Returns the file of the class at the index.
+	 * 
+	 * @param index
+	 * @param fc
+	 *            fileClass
+	 * @return
+	 */
+	public DocuDirent get(int index, int fc) {
+		if ((list == null) || (list[fc] == null) || (index >= list[fc].size())) {
+			return null;
+		}
+		return (DocuDirent) list[fc].get(index);
+	}
+
+	/**
+	 * Checks if the directory exists on the filesystem.
+	 * 
+	 * Sets isValid.
+	 * 
+	 * @return
+	 */
+	public boolean checkDir() {
+		if (dir == null) {
+			initDir();
+		}
+		isValid = dir.isDirectory();
+		return isValid;
+	}
+
+	/**
+	 * Read the filesystem directory and fill this object.
+	 * 
+	 * Clears the List and (re)reads all files.
+	 * 
+	 * @return boolean the directory exists
+	 */
+	public synchronized boolean readDir() {
+		// check directory first
+		checkDir();
+		if (!isValid) {
+			return false;
+		}
+		// first file extension to try for scaled directories
+		String scalext = null;
+		// read all filenames
+		//logger.debug("reading directory " + dir.getPath());
+		/*
+		 * using ReadableFileFilter is safer (we won't get directories with file
+		 * extensions) but slower.
+		 */
+		File[] allFiles = null;
+		if (cache.safeDirIndex) {
+			allFiles = dir.listFiles(new FileOps.ReadableFileFilter());
+		} else {
+			allFiles = dir.listFiles();
+		}
+		//logger.debug("  done");
+		if (allFiles == null) {
+			// not a directory
+			return false;
+		}
+		// list of base dirs from the parent cache
+		String[] baseDirNames = cache.getBaseDirNames();
+		// number of base dirs
+		int nb = baseDirNames.length;
+		// array of base dirs
+		Directory[] dirs = new Directory[nb];
+		// first entry is this directory
+		dirs[0] = this;
+		// fill array with the remaining directories
+		for (int j = 1; j < nb; j++) {
+			File d = new File(baseDirNames[j], dirName);
+			if (d.isDirectory()) {
+				dirs[j] = new Directory(d);
+				//logger.debug("  reading scaled directory " + d.getPath());
+				dirs[j].readDir();
+				//logger.debug("    done");
+			}
+		}
+
+		// go through all file classes
+		for (int classIdx = 0; classIdx < FileOps.NUM_CLASSES; classIdx++) {
+			int fileClass = cache.getFileClasses()[classIdx];
+			//logger.debug("filtering directory "+dir.getPath()+" for class
+			// "+fc);
+			File[] fileList = FileOps.listFiles(allFiles, FileOps
+					.filterForClass(fileClass));
+			//logger.debug(" done");
+			// number of files in the directory
+			int numFiles = fileList.length;
+			if (numFiles > 0) {
+				// create new list
+				list[fileClass] = new ArrayList(numFiles);
+				// sort the file names alphabetically and iterate the list
+				Arrays.sort(fileList);
+				Map hints = FileOps.newHints(FileOps.HINT_BASEDIRS, dirs);
+				hints.put(FileOps.HINT_FILEEXT, scalext);
+				for (int i = 0; i < numFiles; i++) {
+					DocuDirent f = FileOps.fileForClass(fileClass, fileList[i],
+							hints);
+					// add the file to our list
+					list[fileClass].add(f);
+					f.setParent(this);
+				}
+			}
+		}
+		// clear the scaled directories
+		for (int j = 1; j < nb; j++) {
+			if (dirs[j] != null) {
+				dirs[j].clearFilenames();
+			}
+		}
+		// update number of cached files if this was the first time
+		if (dirMTime == 0) {
+			cache.numFiles += size();
+		}
+		dirMTime = dir.lastModified();
+		// read metadata as well
+		readMeta();
+		return isValid;
+	}
+
+	/**
+	 * Check to see if the directory has been modified and reread if necessary.
+	 * 
+	 * @return boolean the directory is valid
+	 */
+	public boolean refresh() {
+		if (isValid) {
+			if (dir.lastModified() > dirMTime) {
+				// on-disk modification time is more recent
+				readDir();
+			}
+			touch();
+		}
+		return isValid;
+	}
+
+	/**
+	 * Read directory metadata.
+	 *  
+	 */
+	public void readMeta() {
+		// check for directory metadata...
+		File mf = new File(dir, "index.meta");
+		if (mf.canRead()) {
+			XMLMetaLoader ml = new XMLMetaLoader();
+			try {
+				// read directory meta file
+				Map fileMeta = ml.loadURL(mf.getAbsolutePath());
+				if (fileMeta == null) {
+					return;
+				}
+				// meta for the directory itself is in the "" bin
+				dirMeta = (Map) fileMeta.remove("");
+				// read meta for files in this directory
+				readFileMeta(fileMeta, null);
+				// is there meta for other files left?
+				if (fileMeta.size() > 0) {
+					unresolvedFileMeta = fileMeta;
+				}
+			} catch (SAXException e) {
+				logger.warn("error parsing index.meta", e);
+			} catch (IOException e) {
+				logger.warn("error reading index.meta", e);
+			}
+		}
+		readParentMeta();
+		metaChecked = true;
+	}
+
+	/**
+	 * Read metadata from all known parent directories.
+	 *  
+	 */
+	public void readParentMeta() {
+		// check the parent directories for additional file meta
+		Directory dd = parent;
+		String path = dir.getName();
+		while (dd != null) {
+			if (((DocuDirectory) dd).hasUnresolvedFileMeta()) {
+				readFileMeta(((DocuDirectory) dd).unresolvedFileMeta, path);
+			}
+			// prepend parent dir path
+			path = dd.dir.getName() + "/" + path;
+			// become next parent
+			dd = dd.parent;
+		}
+	}
+
+	/**
+	 * Read metadata for the files in this directory.
+	 * 
+	 * Takes a Map with meta-information, adding the relative path before the
+	 * lookup.
+	 * 
+	 * @param fileMeta
+	 * @param relPath
+	 * @param fc
+	 *            fileClass
+	 */
+	protected void readFileMeta(Map fileMeta, String relPath) {
+		if (list == null) {
+			// there are no files
+			return;
+		}
+		String path = (relPath != null) ? (relPath + "/") : "";
+		// go through all file classes
+		for (int nc = 0; nc < list.length; nc++) {
+			int fc = cache.getFileClasses()[nc];
+			if (list[fc] == null) {
+				continue;
+			}
+			// iterate through the list of files in this directory
+			for (Iterator i = list[fc].iterator(); i.hasNext();) {
+				DocuDirent f = (DocuDirent) i.next();
+				// prepend path to the filename
+				String fn = path + f.getName();
+				// look up meta for this file and remove from dir
+				Map meta = (Map) fileMeta.remove(fn);
+				if (meta != null) {
+					// store meta in file
+					f.setFileMeta(meta);
+				}
+			}
+		}
+	}
+
+	protected void notifyChildMeta(Map childmeta) {
+		List children = cache.getChildren(this.dirName, true);
+		if (children.size() > 0) {
+			for (Iterator i = children.iterator(); i.hasNext();) {
+				// TODO: finish this!
+				//((DocuDirectory) i.next()).readFileMeta()
+			}
+		}
+	}
+
+	/**
+	 * Update access time.
+	 * 
+	 * @return long time of last access.
+	 */
+	public long touch() {
+		long t = objectATime;
+		objectATime = System.currentTimeMillis();
+		return t;
+	}
+
+	/**
+	 * Searches for the file with the name <code>fn</code>.
+	 * 
+	 * Searches the directory for the file with the name <code>fn</code> and
+	 * returns its index. Returns -1 if the file cannot be found.
+	 * 
+	 * @param fn
+	 *            filename
+	 * @param fc
+	 *            file class
+	 * @return int index of file <code>fn</code>
+	 */
+	public int indexOf(String fn) {
+		int fc = FileOps.classForFilename(fn);
+		return indexOf(fn, fc);
+	}
+
+	/**
+	 * Searches for the file with the name <code>fn</code> and class fc.
+	 * 
+	 * Searches the directory for the file with the name <code>fn</code> and
+	 * returns its index. Returns -1 if the file cannot be found.
+	 * 
+	 * @param fn
+	 *            filename
+	 * @return int index of file <code>fn</code>
+	 */
+	public int indexOf(String fn, int fc) {
+		if (!isRead()) {
+			// read directory now
+			if (!readDir()) {
+				return -1;
+			}
+		}
+		List fileList = list[fc];
+		// empty directory?
+		if (fileList == null) {
+			return -1;
+		}
+		// search for exact match
+		int idx = Collections.binarySearch(fileList, fn);
+		if (idx >= 0) {
+			return idx;
+		} else {
+			// try closest matches without extension
+			idx = -idx - 1;
+			String fb = FileOps.basename(fn);
+			DocuDirent fs;
+			if ((idx < fileList.size())
+					&& (FileOps.basename(((DocuDirent) fileList.get(idx))
+							.getName()).equals(fb))) {
+				// idx matches
+				return idx;
+			} else if ((idx > 0)
+					&& (FileOps.basename(((DocuDirent) fileList.get(idx - 1))
+							.getName()).equals(fb))) {
+				// idx-1 matches
+				return idx - 1;
+			} else if ((idx + 1 < fileList.size())
+					&& (FileOps.basename(((DocuDirent) fileList.get(idx - 1))
+							.getName()).equals(fb))) {
+				// idx+1 matches
+				return idx + 1;
+			}
+
+		}
+		return -1;
+	}
+
+	/**
+	 * Finds the DocuDirent with the name <code>fn</code>.
+	 * 
+	 * Searches the directory for the DocuDirent with the name <code>fn</code>
+	 * and returns it. Returns null if the file cannot be found.
+	 * 
+	 * @param fn
+	 *            filename
+	 * @return DocuDirent
+	 */
+	public DocuDirent find(String fn) {
+		int fc = FileOps.classForFilename(fn);
+		int i = indexOf(fn, fc);
+		if (i >= 0) {
+			return (DocuDirent) list[0].get(i);
+		}
+		return null;
+	}
+
+	/**
+	 * Finds the DocuDirent with the name <code>fn</code> and class
+	 * <code>fc</code>.
+	 * 
+	 * Searches the directory for the DocuDirent with the name <code>fn</code>
+	 * and returns it. Returns null if the file cannot be found.
+	 * 
+	 * @param fn
+	 *            filename
+	 * @return DocuDirent
+	 */
+	public DocuDirent find(String fn, int fc) {
+		int i = indexOf(fn, fc);
+		if (i >= 0) {
+			return (DocuDirent) list[fc].get(i);
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the digilib canonical name.
+	 * 
+	 * @return
+	 */
+	public String getDirName() {
+		return dirName;
+	}
+
+	/**
+	 * The directory is valid (exists on disk).
+	 * 
+	 * @return boolean
+	 */
+	public boolean isValid() {
+		return isValid;
+	}
+
+	/**
+	 * The directory has been read from disk.
+	 * 
+	 * @return
+	 */
+	public boolean isRead() {
+		return (dirMTime != 0);
+	}
+
+	/**
+	 * @return long
+	 */
+	public long getAccessTime() {
+		return objectATime;
+	}
+
+	/**
+	 * @return Hashtable
+	 */
+	public Map getDirMeta() {
+		return dirMeta;
+	}
+
+	/**
+	 * Checks metadata
+	 *  
+	 */
+	public void checkMeta() {
+		if (metaChecked) {
+			return;
+		} else {
+			readMeta();
+		}
+	}
+
+	/**
+	 * @return long
+	 */
+	public long getDirMTime() {
+		return dirMTime;
+	}
+
+	/**
+	 * Sets the dirMeta.
+	 * 
+	 * @param dirMeta
+	 *            The dirMeta to set
+	 */
+	public void setDirMeta(Map dirMeta) {
+		this.dirMeta = dirMeta;
+	}
+
+	public boolean hasUnresolvedFileMeta() {
+		return (this.unresolvedFileMeta != null);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/ImageFile.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,136 @@
+/* ImageFile.java -- digilib image file class.
+
+  Digital Image Library servlet components
+
+  Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+
+  This program is free software; you can redistribute  it and/or modify it
+  under  the terms of  the GNU General  Public License as published by the
+  Free Software Foundation;  either version 2 of the  License, or (at your
+  option) any later version.
+   
+  Please read license.txt for the full details. A copy of the GPL
+  may be found at http://www.gnu.org/copyleft/lgpl.html
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ * Created on 25.02.2003
+ */
+ 
+package digilib.io;
+
+import java.io.File;
+
+import digilib.image.ImageSize;
+
+/**
+ * @author casties
+ */
+public class ImageFile {
+	
+	// file name
+	private String filename = null;
+	// parent ImageFileset
+	private ImageFileset parent = null;
+	// parent directory
+	private Directory dir = null;
+	// mime file type
+	private String mimetype = null;
+	// image size in pixels
+	private ImageSize pixelSize = null;
+
+	public ImageFile(String fn, ImageFileset parent, Directory dir) {
+		this.filename = fn;
+		this.parent = parent;
+		this.dir = dir;
+	}
+	
+	public ImageFile(String fn) {
+		File f = new File(fn);
+		this.dir = new Directory(f.getParentFile());
+		this.filename = f.getName();
+	}
+	
+	/** Returns the file name (without path).
+	 * 
+	 * @return
+	 */
+	public String getName() {
+		return filename;
+	}
+
+
+	/**
+	 * @return File
+	 */
+	public File getFile() {
+		if (dir == null) {
+			return null;
+		}
+		File f = new File(dir.getDir(), filename);
+		return f;
+	}
+
+	/**
+	 * @return ImageSize
+	 */
+	public ImageSize getSize() {
+		return pixelSize;
+	}
+
+	/**
+	 * @return String
+	 */
+	public String getMimetype() {
+		return mimetype;
+	}
+
+	/**
+	 * Sets the imageSize.
+	 * @param imageSize The imageSize to set
+	 */
+	public void setSize(ImageSize imageSize) {
+		this.pixelSize = imageSize;
+	}
+
+	/**
+	 * Sets the mimetype.
+	 * @param mimetype The mimetype to set
+	 */
+	public void setMimetype(String filetype) {
+		this.mimetype = filetype;
+	}
+
+	/**
+	 * @return ImageFileset
+	 */
+	public ImageFileset getParent() {
+		return parent;
+	}
+
+	/**
+	 * Sets the parent.
+	 * @param parent The parent to set
+	 */
+	public void setParent(ImageFileset parent) {
+		this.parent = parent;
+	}
+
+	/**
+	 * @return boolean
+	 */
+	public boolean isChecked() {
+		return (pixelSize != null);
+	}
+	
+	/** Returns the aspect ratio of the image (width/height).
+	 * 
+	 * @return
+	 */
+	public float getAspect() {
+		return (pixelSize != null) ? pixelSize.getAspect() : 0;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/ImageFileset.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,418 @@
+/* ImageFileset -- digilib image file info class.  
+ * Digital Image Library servlet components  
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)  
+ * 
+ * This program is free software; you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.  
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be 
+ * found at http://www.gnu.org/copyleft/lgpl.html  
+ * 
+ * You should have received a copy of the GNU General Public License along 
+ * with this program; if not, write to the Free Software Foundation, Inc., 
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  
+ */
+
+package digilib.io;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import digilib.image.ImageOps;
+import digilib.image.ImageSize;
+
+/**
+ * @author casties
+ */
+public class ImageFileset extends DocuDirent {
+
+	/** this is an image file */
+	protected static int fileClass = FileOps.CLASS_IMAGE;
+	
+	/** logger for this class */
+	private static Logger logger = Logger.getLogger(ImageFileset.class);
+
+	/** list of files (ImageFile) */
+	private ArrayList list = null;
+
+	/** aspect ratio (width/height) */
+	private float aspect = 0;
+
+	/** resolution of the biggest image (DPI) */
+	private float resX = 0;
+
+	/** resolution of the biggest image (DPI) */
+	private float resY = 0;
+
+	/**
+	 * Creator for empty fileset.
+	 * 
+	 * 
+	 * @param initialCapacity
+	 */
+	public ImageFileset() {
+		list = new ArrayList();
+	}
+
+	/**
+	 * Constructor with a file and hints.
+	 * 
+	 * The hints are expected to contain 'basedirs' and 'scaledfilext' keys.
+	 * 
+	 * @param file
+	 * @param hints
+	 */
+	public ImageFileset(File file, Map hints) {
+		Directory[] dirs = (Directory[]) hints.get(FileOps.HINT_BASEDIRS);
+		int nb = dirs.length;
+		list = new ArrayList(nb);
+		parent = dirs[0];
+		fill(dirs, file, hints);
+	}
+
+	/**
+	 * Adds an ImageFile to this Fileset.
+	 * 
+	 * The files should be added in the order of higher to lower resolutions.
+	 * The first file is considered the hires "original".
+	 * 
+	 * 
+	 * @param f
+	 *            file to add
+	 * @return true (always)
+	 */
+	public boolean add(ImageFile f) {
+		f.setParent(this);
+		return list.add(f);
+	}
+
+	/**
+	 * The number of image files in this Fileset.
+	 * 
+	 * 
+	 * @return number of image files
+	 */
+	public int size() {
+		return (list != null) ? list.size() : 0;
+	}
+
+	/**
+	 * Gets the default File.
+	 *  
+	 */
+	public File getFile() {
+		return (list != null) ? ((ImageFile) list.get(0)).getFile() : null;
+	}
+
+	/**
+	 * Get the ImageFile at the index.
+	 * 
+	 * 
+	 * @param index
+	 * @return
+	 */
+	public ImageFile get(int index) {
+		return (ImageFile) list.get(index);
+	}
+
+	/**
+	 * Get the next smaller ImageFile than the given size.
+	 * 
+	 * Returns the ImageFile from the set that has a width and height smaller or
+	 * equal the given size. Returns null if there isn't any smaller image.
+	 * Needs DocuInfo instance to checkFile().
+	 * 
+	 * 
+	 * @param size
+	 * @param info
+	 * @return
+	 */
+	public ImageFile getNextSmaller(ImageSize size) {
+		for (Iterator i = getHiresIterator(); i.hasNext();) {
+			ImageFile f = (ImageFile) i.next();
+			try {
+				if (!f.isChecked()) {
+					ImageOps.checkFile(f);
+				}
+				if (f.getSize().isTotallySmallerThan(size)) {
+					return f;
+				}
+			} catch (IOException e) {
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Get the next bigger ImageFile than the given size.
+	 * 
+	 * Returns the ImageFile from the set that has a width or height bigger or
+	 * equal the given size. Returns null if there isn't any bigger image. Needs
+	 * DocuInfo instance to checkFile().
+	 * 
+	 * 
+	 * @param size
+	 * @param info
+	 * @return
+	 */
+	public ImageFile getNextBigger(ImageSize size) {
+		for (ListIterator i = getLoresIterator(); i.hasPrevious();) {
+			ImageFile f = (ImageFile) i.previous();
+			try {
+				if (!f.isChecked()) {
+					ImageOps.checkFile(f);
+				}
+				if (f.getSize().isBiggerThan(size)) {
+					return f;
+				}
+			} catch (IOException e) {
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the biggest ImageFile in the set.
+	 * 
+	 * 
+	 * @return
+	 */
+	public ImageFile getBiggest() {
+		return this.get(0);
+	}
+
+	/**
+	 * Returns the biggest ImageFile in the set.
+	 * 
+	 * 
+	 * @return
+	 */
+	public ImageFile getSmallest() {
+		return this.get(this.size() - 1);
+	}
+
+	/**
+	 * Get an Iterator for this Fileset starting at the highest resolution
+	 * images.
+	 * 
+	 * 
+	 * @return
+	 */
+	public ListIterator getHiresIterator() {
+		return list.listIterator();
+	}
+
+	/**
+	 * Get an Iterator for this Fileset starting at the lowest resolution
+	 * images.
+	 * 
+	 * The Iterator starts at the last element, so you have to use it backwards
+	 * with hasPrevious() and previous().
+	 * 
+	 * 
+	 * @return
+	 */
+	public ListIterator getLoresIterator() {
+		return list.listIterator(list.size());
+	}
+
+	/**
+	 * Fill the ImageFileset with files from different base directories.
+	 * 
+	 * 
+	 * @param dirs
+	 *            list of base directories
+	 * @param fl
+	 *            file (from first base dir)
+	 * @param hints
+	 *  
+	 */
+	void fill(Directory[] dirs, File fl, Map hints) {
+		String scalext = (String) hints.get(FileOps.HINT_FILEEXT);
+		int nb = dirs.length;
+		String fn = fl.getName();
+		String baseFn = FileOps.basename(fn);
+		// add the first ImageFile to the ImageFileset
+		add(new ImageFile(fn, this, parent));
+		// iterate the remaining base directories
+		for (int dirIdx = 1; dirIdx < nb; dirIdx++) {
+			if (dirs[dirIdx] == null) {
+				continue;
+			}
+			// read the directory
+			if (dirs[dirIdx].getFilenames() == null) {
+				dirs[dirIdx].readDir();
+			}
+			String[] dirFiles = dirs[dirIdx].getFilenames();
+			// try the same filename as the original
+			int fileIdx = Arrays.binarySearch(dirFiles, fn);
+			if (fileIdx < 0) {
+				// try closest matches without extension
+				fileIdx = -fileIdx - 1;
+				// try idx
+				if ((fileIdx < dirFiles.length)
+						&& (FileOps.basename(dirFiles[fileIdx]).equals(baseFn))) {
+					// idx ok
+				} else if ((fileIdx > 0)
+						&& (FileOps.basename(dirFiles[fileIdx - 1])
+								.equals(baseFn))) {
+					// idx-1 ok
+					fileIdx = fileIdx - 1;
+				} else if ((fileIdx+1 < dirFiles.length)
+						&& (FileOps.basename(dirFiles[fileIdx + 1])
+								.equals(baseFn))) {
+					// idx+1 ok
+					fileIdx = fileIdx + 1;
+				} else {
+					// basename doesn't match
+					continue;
+				}
+			}
+			if (FileOps.classForFilename(dirFiles[fileIdx]) == FileOps.CLASS_IMAGE) {
+				/* logger.debug("adding file " + dirFiles[fileIdx]
+						+ " to Fileset " + this.getName()); */
+				add(new ImageFile(dirFiles[fileIdx], this, dirs[dirIdx]));
+			}
+		}
+	}
+
+	/**
+	 * Checks metadata and sets resolution in resX and resY.
+	 *  
+	 */
+	public void checkMeta() {
+		if (metaChecked) {
+			return;
+		}
+		if (fileMeta == null) {
+			// try to read metadata file
+			readMeta();
+			if (fileMeta == null) {
+				// try directory metadata
+				((DocuDirectory) parent).checkMeta();
+				if (((DocuDirectory) parent).getDirMeta() != null) {
+					fileMeta = ((DocuDirectory) parent).getDirMeta();
+				} else {
+					// try parent directory metadata
+					DocuDirectory gp = (DocuDirectory) parent.getParent();
+					if (gp != null) {
+						gp.checkMeta();
+						if (gp.getDirMeta() != null) {
+							fileMeta = gp.getDirMeta();
+						} else {
+							// no metadata available
+							metaChecked = true;
+							return;
+						}
+					}
+				}
+			}
+		}
+		metaChecked = true;
+		String s;
+		float dpi = 0;
+		float dpix = 0;
+		float dpiy = 0;
+		float sizex = 0;
+		float sizey = 0;
+		float pixx = 0;
+		float pixy = 0;
+		// DPI is valid for X and Y
+		if (fileMeta.containsKey("original-dpi")) {
+			try {
+				dpi = Float.parseFloat((String) fileMeta.get("original-dpi"));
+			} catch (NumberFormatException e) {
+			}
+			if (dpi != 0) {
+				resX = dpi;
+				resY = dpi;
+				return;
+			}
+		}
+		// DPI-X and DPI-Y
+		if (fileMeta.containsKey("original-dpi-x")
+				&& fileMeta.containsKey("original-dpi-y")) {
+			try {
+				dpix = Float.parseFloat((String) fileMeta
+						.get("original-dpi-x"));
+				dpiy = Float.parseFloat((String) fileMeta
+						.get("original-dpi-y"));
+			} catch (NumberFormatException e) {
+			}
+			if ((dpix != 0) && (dpiy != 0)) {
+				resX = dpix;
+				resY = dpiy;
+				return;
+			}
+		}
+		// SIZE-X and SIZE-Y and PIXEL-X and PIXEL-Y
+		if (fileMeta.containsKey("original-size-x")
+				&& fileMeta.containsKey("original-size-y")
+				&& fileMeta.containsKey("original-pixel-x")
+				&& fileMeta.containsKey("original-pixel-y")) {
+			try {
+				sizex = Float.parseFloat((String) fileMeta
+						.get("original-size-x"));
+				sizey = Float.parseFloat((String) fileMeta
+						.get("original-size-y"));
+				pixx = Float.parseFloat((String) fileMeta
+						.get("original-pixel-x"));
+				pixy = Float.parseFloat((String) fileMeta
+						.get("original-pixel-y"));
+			} catch (NumberFormatException e) {
+			}
+			if ((sizex != 0) && (sizey != 0) && (pixx != 0) && (pixy != 0)) {
+				resX = pixx / (sizex * 100 / 2.54f);
+				resY = pixy / (sizey * 100 / 2.54f);
+				return;
+			}
+		}
+	}
+
+	/**
+	 * @return
+	 */
+	public float getResX() {
+		return resX;
+	}
+
+	/**
+	 * @return
+	 */
+	public float getResY() {
+		return resY;
+	}
+
+	/**
+	 * Sets the aspect ratio from an ImageSize.
+	 * 
+	 * 
+	 * @param f
+	 */
+	public void setAspect(ImageSize s) {
+		aspect = s.getAspect();
+	}
+
+	/**
+	 * Returns the aspect ratio.
+	 * 
+	 * Aspect ratio is (width/height). So it's <1 for portrait and >1 for
+	 * landscape.
+	 * 
+	 * 
+	 * @return
+	 */
+	public float getAspect() {
+		return aspect;
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibConfiguration.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,267 @@
+/*
+ * DigilibConfiguration -- Holding all parameters for digilib servlet.
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ */
+
+package digilib.servlet;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.Logger;
+
+import digilib.image.DocuImage;
+import digilib.image.DocuImageImpl;
+import digilib.io.FileOps;
+import digilib.io.XMLListLoader;
+
+/**
+ * Class to hold the digilib servlet configuration parameters. The parameters
+ * can be read from the digilib-config file and be passed to other servlets or
+ * beans. <br>errorImgFileName: image file to send in case of error. <br>
+ * denyImgFileName: image file to send if access is denied. <br>baseDirs:
+ * array of base directories in order of preference (prescaled versions first).
+ * <br>useAuth: use authentication information. <br>authConfPath:
+ * authentication configuration file. <br>... <br>
+ * 
+ * @author casties
+ *  
+ */
+public class DigilibConfiguration extends ParameterMap {
+
+	private static final long serialVersionUID = -6630487070791637120L;
+
+	/** DocuImage class instance */
+	private Class docuImageClass = null;
+
+	/** Log4J logger */
+	private Logger logger = Logger.getLogger("digilib.config");
+
+	/**
+	 * Default constructor defines all parameters and their default values.
+	 *  
+	 */
+	public DigilibConfiguration() {
+		// create HashMap(20)
+		super(20);
+		// we start with a default logger config
+		BasicConfigurator.configure();
+
+		/*
+		 * Definition of parameters and default values. System parameters that
+		 * are not read from config file have a type 's'.
+		 */
+
+		// digilib servlet version
+		newParameter(
+			"servlet.version",
+			digilib.servlet.Scaler.dlVersion,
+			null,
+			's');
+		// configuration file location
+		newParameter("servlet.config.file", null, null, 's');
+		// DocuDirCache instance
+		newParameter("servlet.dir.cache", null, null, 's');
+		// DocuImage class instance
+		newParameter(
+			"servlet.docuimage.class",
+			digilib.image.JAIDocuImage.class,
+			null,
+			's');
+		// AuthOps instance for authentication
+		newParameter("servlet.auth.op", null, null, 's');
+		// worker queues
+		newParameter("servlet.fast.queue", null, null, 's');
+		newParameter("servlet.slow.queue", null, null, 's');
+
+		/*
+		 * parameters that can be read from config file have a type 'f'
+		 */
+
+		// image file to send in case of error
+		newParameter(
+			"error-image",
+			new File("/docuserver/images/icons/scalerror.gif"),
+			null,
+			'f');
+		// image file to send if access is denied
+		newParameter(
+			"denied-image",
+			new File("/docuserver/images/icons/denied.gif"),
+			null,
+			'f');
+		// base directories in order of preference (prescaled versions last)
+		String[] bd = { "/docuserver/images", "/docuserver/scaled/small" };
+		newParameter("basedir-list", bd, null, 'f');
+		// use authentication information
+		newParameter("use-authorization", Boolean.FALSE, null, 'f');
+		// authentication configuration file
+		newParameter("auth-file", new File("digilib-auth.xml"), null, 'f');
+		// sending image files as-is allowed
+		newParameter("sendfile-allowed", Boolean.TRUE, null, 'f');
+		// Debug level
+		newParameter("debug-level", new Integer(5), null, 'f');
+		// Type of DocuImage instance
+		newParameter(
+			"docuimage-class",
+			"digilib.image.JAIDocuImage",
+			null,
+			'f');
+		// part of URL used to indicate authorized access
+		newParameter("auth-url-path", "authenticated/", null, 'f');
+		// degree of subsampling on image load
+		newParameter("subsample-minimum", new Float(2f), null, 'f');
+		// default scaling quality
+		newParameter("default-quality", new Integer(1), null, 'f');
+		// use mapping file to translate paths
+		newParameter("use-mapping", Boolean.FALSE, null, 'f');
+		// mapping file location
+		newParameter("mapping-file", new File("digilib-map.xml"), null, 'f');
+		// log4j config file location
+		newParameter("log-config-file", new File("log4j-config.xml"), null, 'f');
+		// maximum destination image size (0 means no limit)
+		newParameter("max-image-size", new Integer(0), null, 'f');
+		// use safe (but slower) directory indexing
+		newParameter("safe-dir-index", Boolean.FALSE, null, 'f');
+		// number of fast worker threads
+		newParameter("worker-fast-lanes", new Integer(2), null, 'f');
+		// length of fast worker queue
+		newParameter("worker-fast-queue", new Integer(100), null, 'f');
+		// number of slow worker threads
+		newParameter("worker-slow-lanes", new Integer(2), null, 'f');
+		// length of slow worker queue
+		newParameter("worker-slow-queue", new Integer(100), null, 'f');
+
+	}
+
+	/**
+	 * Constructor taking a ServletConfig. Reads the config file location from
+	 * an init parameter and loads the config file. Calls <code>readConfig()</code>.
+	 * 
+	 * @see readConfig()
+	 */
+	public DigilibConfiguration(ServletConfig c) throws Exception {
+		this();
+		readConfig(c);
+	}
+
+	/**
+	 * read parameter list from the XML file in init parameter "config-file"
+	 */
+	public void readConfig(ServletConfig c) throws Exception {
+
+		/*
+		 * Get config file name. The file name is first looked for as an init
+		 * parameter, then in a fixed location in the webapp.
+		 */
+		if (c == null) {
+			// no config no file...
+			return;
+		}
+		String fn = c.getInitParameter("config-file");
+		if (fn == null) {
+			fn = ServletOps.getConfigFile("digilib-config.xml", c);
+			if (fn == null) {
+				logger.fatal("readConfig: no param config-file");
+				throw new ServletException("ERROR: no digilib config file!");
+			}
+		}
+		File f = new File(fn);
+		// setup config file list reader
+		XMLListLoader lilo =
+			new XMLListLoader("digilib-config", "parameter", "name", "value");
+		// read config file into HashMap
+		Map confTable = lilo.loadURL(f.toURL().toString());
+
+		// set config file path parameter
+		setValue("servlet.config.file", f.getCanonicalPath());
+
+		/*
+		 * read parameters
+		 */
+
+		for (Iterator i = confTable.keySet().iterator(); i.hasNext();) {
+			String key = (String) i.next();
+			String val = (String) confTable.get(key);
+			Parameter p = get(key);
+			if (p != null) {
+				if (p.getType() == 's') {
+					// type 's' Parameters are not overwritten.
+					continue;
+				}
+				if (!p.setValueFromString(val)) {
+					/*
+					 * automatic conversion failed -- try special cases
+					 */
+
+					// basedir-list
+					if (key.equals("basedir-list")) {
+						// split list into directories
+						String[] sa = FileOps.pathToArray(val);
+						if (sa != null) {
+							p.setValue(sa);
+						}
+					}
+
+				}
+			} else {
+				// parameter unknown -- just add
+				newParameter(key, null, val, 'f');
+			}
+		}
+
+	}
+
+	/**
+	 * Creates a new DocuImage instance.
+	 * 
+	 * The type of DocuImage is specified by docuImageType.
+	 * 
+	 * @return DocuImage
+	 */
+	public DocuImage getDocuImageInstance() {
+		DocuImageImpl di = null;
+		try {
+			if (docuImageClass == null) {
+				docuImageClass = Class.forName(getAsString("docuimage-class"));
+			}
+			di = (DocuImageImpl) docuImageClass.newInstance();
+		} catch (Exception e) {
+		}
+		return di;
+	}
+
+	/**
+	 * @return Returns the docuImageClass.
+	 */
+	public Class getDocuImageClass() {
+		return docuImageClass;
+	}
+	/**
+	 * @param docuImageClass The docuImageClass to set.
+	 */
+	public void setDocuImageClass(Class docuImageClass) {
+		this.docuImageClass = docuImageClass;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibImageWorker.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,263 @@
+/* DigilibImageWorker.java -- worker for image operations
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 19.10.2004
+ */
+
+package digilib.servlet;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import digilib.image.DocuImage;
+import digilib.image.ImageOpException;
+import digilib.io.FileOpException;
+import digilib.io.ImageFile;
+
+/**
+ * worker for image operations.
+ * 
+ * @author casties
+ *  
+ */
+public class DigilibImageWorker extends DigilibWorker {
+	
+	private DigilibConfiguration dlConfig;
+
+	HttpServletResponse response;
+
+	long startTime;
+
+	String mimeType;
+
+	int scaleQual;
+
+	DigilibRequest dlRequest;
+
+	float paramROT;
+
+	float paramCONT;
+
+	float paramBRGT;
+
+	float[] paramRGBM;
+
+	float[] paramRGBA;
+
+	ImageFile fileToLoad;
+
+	float areaXoff;
+
+	float areaYoff;
+
+	float areaWidth;
+
+	float areaHeight;
+
+	float scaleXY;
+
+	Rectangle2D outerUserImgArea;
+
+	Rectangle2D innerUserImgArea;
+
+	float minSubsample;
+
+	boolean wholeRotArea;
+
+	/**
+	 * @param dlConfig
+	 * @param response
+	 * @param mimeType
+	 * @param scaleQual
+	 * @param dlRequest
+	 * @param paramROT
+	 * @param paramCONT
+	 * @param paramBRGT
+	 * @param paramRGBM
+	 * @param paramRGBA
+	 * @param fileToLoad
+	 * @param areaXoff
+	 * @param outerUserImgArea
+	 * @param innerUserImgArea
+	 * @param minSubsample
+	 * @param wholeRotArea
+	 */
+	public DigilibImageWorker(DigilibConfiguration dlConfig,
+			HttpServletResponse response, String mimeType, int scaleQual,
+			DigilibRequest dlRequest, float paramROT, float paramCONT,
+			float paramBRGT, float[] paramRGBM, float[] paramRGBA,
+			ImageFile fileToLoad, float scaleXY, Rectangle2D outerUserImgArea,
+			Rectangle2D innerUserImgArea, float minSubsample,
+			boolean wholeRotArea) {
+		super();
+		this.dlConfig = dlConfig;
+		this.response = response;
+		this.mimeType = mimeType;
+		this.scaleQual = scaleQual;
+		this.dlRequest = dlRequest;
+		this.paramROT = paramROT;
+		this.paramCONT = paramCONT;
+		this.paramBRGT = paramBRGT;
+		this.paramRGBM = paramRGBM;
+		this.paramRGBA = paramRGBA;
+		this.fileToLoad = fileToLoad;
+		this.scaleXY = scaleXY;
+		this.outerUserImgArea = outerUserImgArea;
+		this.innerUserImgArea = innerUserImgArea;
+		this.minSubsample = minSubsample;
+		this.wholeRotArea = wholeRotArea;
+	}
+
+	/*
+	 * do the work
+	 */
+	public void work() throws FileOpException, IOException, ImageOpException {
+		;
+		logger.debug("image worker " + this.getName() + " working");
+		startTime = System.currentTimeMillis();
+
+		/* crop and scale image */
+
+		// new DocuImage instance
+		DocuImage docuImage = dlConfig.getDocuImageInstance();
+		if (docuImage == null) {
+			throw new ImageOpException("Unable to load DocuImage class!");
+		}
+
+		// set interpolation quality
+		docuImage.setQuality(scaleQual);
+
+		// use subimage loading if possible
+		if (docuImage.isSubimageSupported()) {
+			logger.debug("Subimage: scale " + scaleXY + " = " + (1 / scaleXY));
+			float subf = 1f;
+			float subsamp = 1f;
+			if (scaleXY < 1) {
+				subf = 1 / scaleXY;
+				// for higher quality reduce subsample factor by
+				// minSubsample
+				if (scaleQual > 0) {
+					subsamp = (float) Math.max(Math.floor(subf / minSubsample),
+							1d);
+				} else {
+					subsamp = (float) Math.floor(subf);
+				}
+				scaleXY = subsamp / subf;
+				logger.debug("Using subsampling: " + subsamp + " rest "
+						+ scaleXY);
+			}
+
+			docuImage.loadSubimage(fileToLoad, outerUserImgArea.getBounds(),
+					(int) subsamp);
+
+			logger.debug("SUBSAMP: " + subsamp + " -> " + docuImage.getWidth()
+					+ "x" + docuImage.getHeight());
+
+			docuImage.scale(scaleXY, scaleXY);
+
+		} else {
+			// else load and crop the whole file
+			docuImage.loadImage(fileToLoad);
+			docuImage.crop((int) areaXoff, (int) areaYoff, (int) areaWidth,
+					(int) areaHeight);
+
+			docuImage.scale(scaleXY, scaleXY);
+		}
+
+		// mirror image
+		// operation mode: "hmir": mirror horizontally, "vmir": mirror
+		// vertically
+		if (dlRequest.hasOption("mo", "hmir")) {
+			docuImage.mirror(0);
+		}
+		if (dlRequest.hasOption("mo", "vmir")) {
+			docuImage.mirror(90);
+		}
+
+		// rotate image
+		if (paramROT != 0d) {
+			docuImage.rotate(paramROT);
+			if (wholeRotArea) {
+				// crop to the inner bounding box
+				float xcrop = (float) (docuImage.getWidth() - innerUserImgArea
+						.getWidth()
+						* scaleXY);
+				float ycrop = (float) (docuImage.getHeight() - innerUserImgArea
+						.getHeight()
+						* scaleXY);
+				if ((xcrop > 0) || (ycrop > 0)) {
+					// only crop smaller
+					xcrop = (xcrop > 0) ? xcrop : 0;
+					ycrop = (ycrop > 0) ? ycrop : 0;
+					// crop image
+					docuImage.crop((int) (xcrop / 2), (int) (ycrop / 2),
+							(int) (docuImage.getWidth() - xcrop),
+							(int) (docuImage.getHeight() - ycrop));
+				}
+			}
+
+		}
+
+		// color modification
+		if ((paramRGBM != null) || (paramRGBA != null)) {
+			// make shure we actually have two arrays
+			if (paramRGBM == null) {
+				paramRGBM = new float[3];
+			}
+			if (paramRGBA == null) {
+				paramRGBA = new float[3];
+			}
+			// calculate "contrast" values (c=2^x)
+			float[] mult = new float[3];
+			for (int i = 0; i < 3; i++) {
+				mult[i] = (float) Math.pow(2, (float) paramRGBM[i]);
+			}
+			docuImage.enhanceRGB(mult, paramRGBA);
+		}
+
+		// contrast and brightness enhancement
+		if ((paramCONT != 0f) || (paramBRGT != 0f)) {
+			float mult = (float) Math.pow(2, paramCONT);
+			docuImage.enhance(mult, paramBRGT);
+		}
+
+		logger.debug("time " + (System.currentTimeMillis() - startTime) + "ms");
+
+		/* write the resulting image */
+
+		// setup output -- if source is JPG then dest will be JPG else it's
+		// PNG
+		if (mimeType.equals("image/jpeg") || mimeType.equals("image/jp2")) {
+			mimeType = "image/jpeg";
+		} else {
+			mimeType = "image/png";
+		}
+		response.setContentType(mimeType);
+
+		// write the image
+		docuImage.writeImage(mimeType, response.getOutputStream());
+		response.flushBuffer();
+		
+		logger.info("image worker " + this.getName() + " done in "
+				+ (System.currentTimeMillis() - startTime));
+
+		docuImage.dispose();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibJob.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,75 @@
+/* DigilibJob.java -- digilib job for worker
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 18.10.2004
+ */
+package digilib.servlet;
+
+import digilib.image.DocuImage;
+
+/** digilib job for worker.
+ * 
+ * @author casties
+ *
+ */
+public class DigilibJob {
+
+	private DigilibConfiguration config;
+	
+	private DigilibRequest request;
+	
+	private DocuImage image;
+	
+	
+	/**
+	 * @return Returns the config.
+	 */
+	public DigilibConfiguration getConfig() {
+		return config;
+	}
+	/**
+	 * @param config The config to set.
+	 */
+	public void setConfig(DigilibConfiguration config) {
+		this.config = config;
+	}
+	/**
+	 * @return Returns the image.
+	 */
+	public DocuImage getImage() {
+		return image;
+	}
+	/**
+	 * @param image The image to set.
+	 */
+	public void setImage(DocuImage image) {
+		this.image = image;
+	}
+	/**
+	 * @return Returns the request.
+	 */
+	public DigilibRequest getRequest() {
+		return request;
+	}
+	/**
+	 * @param request The request to set.
+	 */
+	public void setRequest(DigilibRequest request) {
+		this.request = request;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibManager.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,62 @@
+/* DigilibManager.java -- work queue manager
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 18.10.2004
+ */
+
+package digilib.servlet;
+
+import EDU.oswego.cs.dl.util.concurrent.BoundedBuffer;
+import EDU.oswego.cs.dl.util.concurrent.Executor;
+import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
+
+/** work queue manager.
+ * 
+ * @author casties
+ *
+ */
+public class DigilibManager implements Executor {
+	
+	private PooledExecutor workerQueue = null;
+	
+	private BoundedBuffer jobQueue = null;
+	
+	/**
+	 * @param numFastLanes
+	 * @param numSlowLanes
+	 */
+	public DigilibManager(int numLanes, int queueMax) {
+		super();
+		
+		// create job queue
+		jobQueue = new BoundedBuffer(queueMax);
+		// create work queue
+		workerQueue = new PooledExecutor(jobQueue, numLanes);
+		workerQueue.abortWhenBlocked();
+
+	}
+	
+	
+	public void execute(Runnable worker) throws InterruptedException {
+		workerQueue.execute(worker);
+	}
+	
+	public int getQueueSize() {
+		return jobQueue.size();
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibSender.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,73 @@
+/* DigilibSender.java -- image file send worker
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 18.10.2004
+ */
+package digilib.servlet;
+
+import java.io.File;
+
+import javax.servlet.http.HttpServletResponse;
+
+import digilib.io.FileOpException;
+
+/**
+ * image file send worker.
+ * 
+ * @author casties
+ *  
+ */
+public class DigilibSender extends DigilibWorker {
+
+	private File file;
+
+	private String mimetype;
+
+	private HttpServletResponse response;
+
+	/**
+	 * @param file
+	 * @param mimetype
+	 * @param response
+	 */
+	public DigilibSender(File file, String mimetype,
+			HttpServletResponse response) {
+		super();
+		this.file = file;
+		this.mimetype = mimetype;
+		this.response = response;
+	}
+
+	/**
+	 * Actually send the file.
+	 *  
+	 */
+	public void work() {
+		logger.debug("worker " + this.getName() + " sending file:"
+				+ file.getName());
+		try {
+			//sleep(2000);
+			ServletOps.sendFileImmediately(file, mimetype, response);
+		} catch (FileOpException e) {
+			logger.error("Unable to send file " + file.getPath() + " because "
+					+ e);
+		}
+		logger.debug("worker "+this.getName()+" done");
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/DigilibWorker.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,107 @@
+/* DigilibWorker.java -- image operation worker
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 18.10.2004
+ */
+package digilib.servlet;
+
+import org.apache.log4j.Logger;
+
+import EDU.oswego.cs.dl.util.concurrent.FIFOSemaphore;
+import EDU.oswego.cs.dl.util.concurrent.Semaphore;
+
+
+
+/** image operation worker.
+ * 
+ * @author casties
+ *
+ */
+public abstract class DigilibWorker extends Thread {
+	
+	protected static Logger logger = Logger.getLogger(DigilibWorker.class);
+
+	private static int runningThreads = 0;
+	
+	public static Semaphore lock = new FIFOSemaphore(4);
+
+	protected boolean busy = false;
+	
+	protected Exception error; 
+	
+	/**
+	 * @param job
+	 */
+	public DigilibWorker() {
+		super();
+		busy = true;
+		error = null;
+	}
+	
+	
+	public abstract void work() throws Exception;
+	
+	/** Actually do the work.
+	 * 
+	 * @see java.lang.Runnable#run()
+	 */
+	public void run() {
+		try {
+			lock.acquire();
+		logger.debug((++runningThreads)+" running threads");
+		try {
+			work();
+		} catch (Exception e) {
+			error = e;
+			logger.error(e);
+		}
+		synchronized (this) {
+			busy = false;
+			this.notify();
+		}
+		runningThreads--;
+		lock.release();
+		} catch (InterruptedException e1) {
+			// TODO Auto-generated catch block
+			e1.printStackTrace();
+		}
+	}
+	
+	/**
+	 * @return Returns the busy.
+	 */
+	public boolean isBusy() {
+		return busy;
+	}
+
+	/** returns if an error occurred.
+	 * 
+	 * @return
+	 */
+	public boolean hasError() {
+		return (error != null);
+	}
+	
+	/**
+	 * @return Returns the error.
+	 */
+	public Exception getError() {
+		return error;
+	}
+	
+}
--- a/servlet/src/digilib/servlet/DocumentBean.java	Mon Oct 18 15:40:54 2004 +0200
+++ b/servlet/src/digilib/servlet/DocumentBean.java	Thu Oct 21 20:53:37 2004 +0200
@@ -1,198 +1,295 @@
-/* DocumentBean -- Access control bean for JSP
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
-  This program is free software; you can redistribute  it and/or modify it
-  under  the terms of  the GNU General  Public License as published by the
-  Free Software Foundation;  either version 2 of the  License, or (at your
-  option) any later version.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
+/*
+ * DocumentBean -- Access control bean for JSP
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ */
 
 package digilib.servlet;
 
+import java.util.List;
 
-import java.util.*;
-import javax.servlet.*;
-import javax.servlet.http.*;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
 
-import digilib.*;
-import digilib.io.*;
-import digilib.auth.*;
+import digilib.auth.AuthOpException;
+import digilib.auth.AuthOps;
+import digilib.io.DocuDirCache;
+import digilib.io.DocuDirectory;
+import digilib.io.FileOps;
+import digilib.io.ImageFileset;
 
-public class DocumentBean implements AuthOps {
+public class DocumentBean {
+
+	// general logger
+	private static Logger logger = Logger.getLogger("digilib.docubean");
+
+	// AuthOps object to check authorization
+	private AuthOps authOp;
 
-  // Utils object for logging
-  private Utils util = new Utils(5);
-  // AuthOps object to check authorization
-  private AuthOps authOp;
-  // FileOps object
-  private FileOps fileOp = new FileOps(util);
+	// use authorization database
+	private boolean useAuthentication = true;
+
+	// path to add for authenticated access
+	private String authURLPath = "";
+
+	// DocuDirCache
+	private DocuDirCache dirCache = null;
+
+	// DigilibConfiguration object
+	private DigilibConfiguration dlConfig;
+
+	// DigilibRequest object
+	private DigilibRequest dlRequest = null;
 
-  // base directories in order of preference (prescaled versions first)
-  private String[] baseDirs = {"/docuserver/scaled/small", "/docuserver/images", "/docuserver/scans/quellen"};
-  // part of URL path to prepend for authenticated access
-  private String authURLpath = "authenticated/";
+	/**
+	 * Constructor for DocumentBean.
+	 */
+	public DocumentBean() {
+		super();
+	}
 
-
-  public DocumentBean() {
-  }
+	public DocumentBean(ServletConfig conf) {
+		try {
+			setConfig(conf);
+		} catch (Exception e) {
+			logger.fatal("ERROR: Unable to read config: ", e);
+		}
+	}
 
-  public void setConfig(ServletConfig conf) throws ServletException {
-    util.dprintln(10, "setConfig");
-    // servletOps takes a ServletConfig to get the config file name
-    ServletOps servletOp = new ServletOps(util, conf);
-    /**
-     *  basedir-list : List of document directories
-     */
-    String bl = servletOp.tryToGetInitParam("basedir-list", null);
-    if ((bl != null)&&(bl.length() > 0)) {
-      // split list into directories
-      StringTokenizer dirs = new StringTokenizer(bl, ":");
-      int n = dirs.countTokens();
-      if (n > 0) {
-        // add directories into array
-        baseDirs = new String[n];
-        for (int i = 0; i < n; i++) {
-          baseDirs[i] = dirs.nextToken();
-        }
-      }
-      util.dprintln(3, "basedir-list: "+bl);
-    }
-    /**
-     *  auth-url-path : part of URL to indicate authenticated access
-     */
-    String au = servletOp.tryToGetInitParam("auth-url-path", null);
-    if ((au != null)&&(au.length() > 0)) {
-      authURLpath = au;
-      util.dprintln(3, "auth-url-path: "+au);
-    }
-    /**
-     *  authentication
-     */
-    try {
-      // DB version
-      //private AuthOps authOp = new DBAuthOpsImpl(util);
-      // XML version
-      String cp = servletOp.tryToGetInitParam("auth-file", "/docuserver/www/digitallibrary/WEB-INF/digilib-auth.xml");
-      util.dprintln(3, "auth-file: "+cp);
-      authOp = new XMLAuthOps(util, cp);
-    } catch (AuthOpException e) {
-      throw new ServletException(e);
-    }
-  }
+	public void setConfig(ServletConfig conf) throws ServletException {
+		logger.debug("setConfig");
+		// get our ServletContext
+		ServletContext context = conf.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig = (DigilibConfiguration) context
+				.getAttribute("digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// create new Configuration
+			throw new ServletException("ERROR: No configuration!");
+		}
+
+		// get cache
+		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+
+		/*
+		 * authentication
+		 */
+		useAuthentication = dlConfig.getAsBoolean("use-authorization");
+		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+		authURLPath = dlConfig.getAsString("auth-url-path");
+		if (useAuthentication && (authOp == null)) {
+			throw new ServletException(
+					"ERROR: use-authorization configured but no AuthOp!");
+		}
+	}
+
+	/**
+	 * check if the request must be authorized to access filepath
+	 */
+	public boolean isAuthRequired(DigilibRequest request)
+			throws AuthOpException {
+		logger.debug("isAuthRequired");
+		return useAuthentication ? authOp.isAuthRequired(request) : false;
+	}
+
+	/**
+	 * check if the request is allowed to access filepath
+	 */
+	public boolean isAuthorized(DigilibRequest request) throws AuthOpException {
+		logger.debug("isAuthorized");
+		return useAuthentication ? authOp.isAuthorized(request) : true;
+	}
+
+	/**
+	 * return a list of authorization roles needed for request to access the
+	 * specified path
+	 */
+	public List rolesForPath(DigilibRequest request) throws AuthOpException {
+		logger.debug("rolesForPath");
+		return useAuthentication ? authOp.rolesForPath(request) : null;
+	}
+
+	/**
+	 * check request authorization against a list of roles
+	 */
+	public boolean isRoleAuthorized(List roles, DigilibRequest request) {
+		logger.debug("isRoleAuthorized");
+		return useAuthentication ? authOp.isRoleAuthorized(roles, request)
+				: true;
+	}
+
+	/**
+	 * check for authenticated access and redirect if necessary
+	 */
+	public boolean doAuthentication(HttpServletResponse response)
+			throws Exception {
+		return doAuthentication(dlRequest, response);
+	}
 
-  public String getDocuPath(HttpServletRequest request) {
-    util.dprintln(10, "getDocuPath");
-    // fetch query string
-    String qs = request.getQueryString();
-    String fn = "";
-    if (qs != null && qs.length() > 0) {
-       // the file name is in the request before the first "+"
-       int endfn = qs.indexOf("+");
-       if (endfn > 0) {
-         fn = qs.substring(0, endfn);
-       } else {
-	 fn = qs;
-       }
-    }
-    util.dprintln(4, "docuPath: "+fn);
-    return fn;
-  }
-
-  /**
-   *  check if the request must be authorized to access filepath
-   */
-  public boolean isAuthRequired(HttpServletRequest request) throws AuthOpException {
-    util.dprintln(10, "isAuthRequired");
-    return authOp.isAuthRequired(getDocuPath(request), request);
-  }
+	/**
+	 * check for authenticated access and redirect if necessary
+	 */
+	public boolean doAuthentication(DigilibRequest request,
+			HttpServletResponse response) throws Exception {
+		logger.debug("doAuthentication");
+		if (!useAuthentication) {
+			// shortcut if no authentication
+			return true;
+		}
+		// check if we are already authenticated
+		if (((HttpServletRequest) request.getServletRequest()).getRemoteUser() == null) {
+			logger.debug("unauthenticated so far");
+			// if not maybe we must?
+			if (isAuthRequired(request)) {
+				logger.debug("auth required, redirect");
+				// we are not yet authenticated -> redirect
+				response.sendRedirect(authURLPath
+						+ ((HttpServletRequest) request.getServletRequest())
+								.getServletPath()
+						+ "?"
+						+ ((HttpServletRequest) request.getServletRequest())
+								.getQueryString());
+			}
+		}
+		return true;
+	}
 
-  public boolean isAuthRequired(String filepath, HttpServletRequest request) throws AuthOpException {
-    util.dprintln(10, "isAuthRequired");
-    return authOp.isAuthRequired(filepath, request);
-  }
-
-  /**
-   *  check if the request is allowed to access filepath
-   */
-  public boolean isAuthorized(HttpServletRequest request) throws AuthOpException {
-    util.dprintln(10, "isAuthorized");
-    return authOp.isAuthorized(getDocuPath(request), request);
-  }
+	/**
+	 * Sets the current DigilibRequest. Also completes information in the request.
+	 * 
+	 * @param dlRequest
+	 *            The dlRequest to set.
+	 */
+	public void setRequest(DigilibRequest dlRequest) throws Exception {
+		this.dlRequest = dlRequest;
+		if (dirCache == null) {
+			return;
+		}
+		String fn = dlRequest.getFilePath();
+		// get information about the file
+		ImageFileset fileset = (ImageFileset) dirCache.getFile(fn, dlRequest
+				.getAsInt("pn"), FileOps.CLASS_IMAGE);
+		if (fileset == null) {
+			return;
+		}
+		// add file name
+		dlRequest.setValue("img.fn", fileset.getName());
+		// add dpi
+		dlRequest.setValue("img.dpix", new Double(fileset.getResX()));
+		dlRequest.setValue("img.dpiy", new Double(fileset.getResY()));
+		// get number of pages in directory
+		DocuDirectory dd = dirCache.getDirectory(fn);
+		if (dd != null) {
+			dlRequest.setValue("pt", dd.size());
+		}
+	}
 
-  public boolean isAuthorized(String filepath, HttpServletRequest request) throws AuthOpException {
-    util.dprintln(10, "isAuthorized");
-    return authOp.isAuthorized(filepath, request);
-  }
+	/**
+	 * get the first page number in the directory (not yet functional)
+	 */
+	public int getFirstPage(DigilibRequest request) {
+		logger.debug("getFirstPage");
+		return 1;
+	}
 
-  /**
-   *  return a list of authorization roles needed for request
-   *  to access the specified path
-   */
-  public List rolesForPath(String filepath, HttpServletRequest request) throws AuthOpException {
-    util.dprintln(10, "rolesForPath");
-    return authOp.rolesForPath(filepath, request);
-  }
+	/**
+	 * get the number of pages/files in the directory
+	 */
+	public int getNumPages() throws Exception {
+		return getNumPages(dlRequest);
+	}
 
-  /**
-   * check request authorization against a list of roles
-   */
-  public boolean isRoleAuthorized(List roles, HttpServletRequest request) {
-    util.dprintln(10, "isRoleAuthorized");
-    return authOp.isRoleAuthorized(roles, request);
-  }
+	/**
+	 * get the number of pages/files in the directory
+	 */
+	public int getNumPages(DigilibRequest request) throws Exception {
+		logger.debug("getNumPages");
+		DocuDirectory dd = (dirCache != null) ? dirCache.getDirectory(request
+				.getFilePath()) : null;
+		if (dd != null) {
+			return dd.size();
+		}
+		return 0;
+	}
 
-  /**
-   * check for authenticated access and redirect if necessary
-   */
-  public boolean doAuthentication(HttpServletRequest request, HttpServletResponse response) throws Exception {
-    util.dprintln(10, "doAuthentication");
-    // check if we are already authenticated
-    if (request.getRemoteUser() == null) {
-      util.dprintln(3, "unauthenticated so far");
-      // if not maybe we must?
-      if (isAuthRequired(request)) {
-        util.dprintln(3, "auth required, redirect");
-	// we are not yet authenticated -> redirect
-	response.sendRedirect(authURLpath+request.getServletPath()+"?"+request.getQueryString());
-      }
-    }
-    return true;
-  }
+	/**
+	 * Returns the dlConfig.
+	 * 
+	 * @return DigilibConfiguration
+	 */
+	public DigilibConfiguration getDlConfig() {
+		return dlConfig;
+	}
+
+	/**
+	 * returns if the zoom area in the request can be moved
+	 * 
+	 * @return
+	 */
+	public boolean canMoveRight() {
+		float ww = dlRequest.getAsFloat("ww");
+		float wx = dlRequest.getAsFloat("wx");
+		return (ww + wx < 1.0);
+	}
 
-  /**
-   *  get the first page number in the directory
-   *  (not yet functional)
-   */
-  public int getFirstPage(HttpServletRequest request) {
-    return getFirstPage(getDocuPath(request), request);
-  }
-
-  public int getFirstPage(String filepath, HttpServletRequest request) {
-    util.dprintln(10, "getFirstPage");
-    return 1;
-  }
+	/**
+	 * returns if the zoom area in the request can be moved
+	 * 
+	 * @return
+	 */
+	public boolean canMoveLeft() {
+		float ww = dlRequest.getAsFloat("ww");
+		float wx = dlRequest.getAsFloat("wx");
+		return ((ww < 1.0) && (wx > 0));
+	}
 
-  /**
-   *  get the number of pages/files in the directory
-   */
-  public int getNumPages(HttpServletRequest request) throws Exception {
-    return getNumPages(getDocuPath(request), request);
-  }
+	/**
+	 * returns if the zoom area in the request can be moved
+	 * 
+	 * @return
+	 */
+	public boolean canMoveUp() {
+		float wh = dlRequest.getAsFloat("wh");
+		float wy = dlRequest.getAsFloat("wy");
+		return ((wh < 1.0) && (wy > 0));
+	}
 
-  public int getNumPages(String filepath, HttpServletRequest request) throws Exception {
-    util.dprintln(10, "getNumPages");
-    return fileOp.getNumFilesVariant(baseDirs, "/"+filepath, true);
-  }
+	/**
+	 * returns if the zoom area in the request can be moved
+	 * 
+	 * @return
+	 */
+	public boolean canMoveDown() {
+		float wh = dlRequest.getAsFloat("wh");
+		float wy = dlRequest.getAsFloat("wy");
+		return (wh + wy < 1.0);
+	}
 
-}
+	/**
+	 * @return Returns the dlRequest.
+	 */
+	public DigilibRequest getRequest() {
+		return dlRequest;
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/Initialiser.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,173 @@
+/* Initialiser.java -- initalisation servlet for setup tasks
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2004 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ * Created on 18.10.2004
+ */
+package digilib.servlet;
+
+import java.io.File;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.xml.DOMConfigurator;
+
+import digilib.auth.AuthOps;
+import digilib.auth.XMLAuthOps;
+import digilib.io.AliasingDocuDirCache;
+import digilib.io.DocuDirCache;
+import digilib.io.FileOps;
+
+/**
+ * Initalisation servlet for setup tasks.
+ * 
+ * @author casties
+ *  
+ */
+public class Initialiser extends HttpServlet {
+
+	private static final long serialVersionUID = -5126621114382549343L;
+
+	/** servlet version */
+	public static final String iniVersion = "0.1a1";
+
+	/** gengeral logger for this class */
+	private static Logger logger = Logger.getLogger("digilib.init");
+
+	/** AuthOps instance */
+	AuthOps authOp;
+
+	/** DocuDirCache instance */
+	DocuDirCache dirCache;
+
+	/** DigilibConfiguration instance */
+	DigilibConfiguration dlConfig;
+
+	/** use authorization database */
+	boolean useAuthentication = false;
+
+	private DigilibManager fastQueue;
+
+	private DigilibManager slowQueue;
+
+	/**
+	 * Initialisation on first run.
+	 * 
+	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+	 */
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+
+		System.out
+				.println("***** Digital Image Library Initialisation Servlet (version "
+						+ iniVersion + ") *****");
+
+		// get our ServletContext
+		ServletContext context = config.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig = (DigilibConfiguration) context
+				.getAttribute("digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// create new Configuration
+			try {
+				dlConfig = new DigilibConfiguration(config);
+
+				/*
+				 * further initialization
+				 */
+
+				// set up the logger
+				File logConf = ServletOps.getConfigFile((File) dlConfig
+						.getValue("log-config-file"), config);
+				DOMConfigurator.configure(logConf.getAbsolutePath());
+				dlConfig.setValue("log-config-file", logConf);
+				// say hello in the log file
+				logger
+						.info("***** Digital Image Library Initialisation Servlet (version "
+								+ iniVersion + ") *****");
+				// directory cache
+				String[] bd = (String[]) dlConfig.getValue("basedir-list");
+				int[] fcs = { FileOps.CLASS_IMAGE, FileOps.CLASS_TEXT,
+						FileOps.CLASS_SVG };
+				if (dlConfig.getAsBoolean("use-mapping")) {
+					// with mapping file
+					File mapConf = ServletOps.getConfigFile((File) dlConfig
+							.getValue("mapping-file"), config);
+					dirCache = new AliasingDocuDirCache(bd, fcs, mapConf,
+							dlConfig);
+					dlConfig.setValue("mapping-file", mapConf);
+				} else {
+					// without mapping
+					dirCache = new DocuDirCache(bd, fcs, dlConfig);
+				}
+				dlConfig.setValue("servlet.dir.cache", dirCache);
+				// useAuthentication
+				if (dlConfig.getAsBoolean("use-authorization")) {
+					// DB version
+					//authOp = new DBAuthOpsImpl(util);
+					// XML version
+					File authConf = ServletOps.getConfigFile((File) dlConfig
+							.getValue("auth-file"), config);
+					authOp = new XMLAuthOps(authConf);
+					dlConfig.setValue("servlet.auth.op", authOp);
+					dlConfig.setValue("auth-file", authConf);
+				}
+				// DocuImage class
+				Class cl = Class.forName(dlConfig
+						.getAsString("docuimage-class"));
+				dlConfig.setDocuImageClass(cl);
+				dlConfig.setValue("servlet.docuimage.class", cl.getName());
+				// DigilibManager work queue
+				int fl = dlConfig.getAsInt("worker-fast-lanes");
+				int fq = dlConfig.getAsInt("worker-fast-queue");
+				fastQueue = new DigilibManager(fl, fq);
+				dlConfig.setValue("servlet.fast.queue", fastQueue);
+				int sl = dlConfig.getAsInt("worker-slow-lanes");
+				int sq = dlConfig.getAsInt("worker-slow-queue");
+				slowQueue = new DigilibManager(sl, sq);
+				dlConfig.setValue("servlet.slow.queue", slowQueue);
+				// set as the servlets main config
+				context.setAttribute("digilib.servlet.configuration", dlConfig);
+
+			} catch (Exception e) {
+				throw new ServletException(e);
+			}
+		} else {
+			// say hello in the log file
+			logger
+					.info("***** Digital Image Library Initialisation Servlet (version "
+							+ iniVersion + ") *****");
+			logger.warn("Already initialised?");
+			// set our AuthOps
+			useAuthentication = dlConfig.getAsBoolean("use-authorization");
+			// AuthOps instance
+			authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+			// DocuDirCache instance
+			dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+			// work queues
+			fastQueue = (DigilibManager) dlConfig
+					.getValue("servlet.fast.queue");
+			slowQueue = (DigilibManager) dlConfig
+					.getValue("servlet.slow.queue");
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/Mapper.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,399 @@
+/*
+ * Mapper -- Servlet for creating image-maps from SVG graphics
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html You should have received a copy of
+ * the GNU General Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ * 
+ * Created on 25.11.2003 by casties
+ */
+
+package digilib.servlet;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
+import org.apache.batik.transcoder.SVGAbstractTranscoder;
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.apache.batik.util.XMLResourceDescriptor;
+import org.apache.log4j.Logger;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.XMLFilterImpl;
+
+import digilib.auth.AuthOps;
+import digilib.io.DocuDirCache;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
+import digilib.io.SVGFile;
+
+/**
+ * Servlet for creating image-maps from SVG graphics
+ * 
+ * @author casties
+ *  
+ */
+public class Mapper extends HttpServlet {
+
+	private static final long serialVersionUID = 6134014474526638223L;
+
+	/** Servlet version */
+	public static String servletVersion = "0.1a0";
+	/** DigilibConfiguration instance */
+	DigilibConfiguration dlConfig = null;
+	/** general logger */
+	Logger logger = Logger.getLogger("digilib.mapper");
+	/** AuthOps instance */
+	AuthOps authOp;
+	/** DocuDirCache instance */
+	DocuDirCache dirCache;
+	/** SVG document factory */
+	SAXSVGDocumentFactory docFactory;
+
+	/** use authentication */
+	boolean useAuthentication = false;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+	 */
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+
+		System.out.println(
+			"***** Digital Image Library Image Map Servlet (version "
+				+ servletVersion
+				+ ") *****");
+
+		// get our ServletContext
+		ServletContext context = config.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig =
+			(DigilibConfiguration) context.getAttribute(
+				"digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// no Configuration
+			throw new ServletException("ERROR: No Configuration!");
+		}
+		// say hello in the log file
+		logger.info(
+			"***** Digital Image Library Image Map Servlet (version "
+				+ servletVersion
+				+ ") *****");
+
+		// set our AuthOps
+		useAuthentication = dlConfig.getAsBoolean("use-authorization");
+		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+		// DocuDirCache instance
+		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+		// parser name as a String (I hate you for not using JAXP, Batik!)
+		String parserName = null;
+		try {
+			// try the proper JAXP way
+			parserName =
+				SAXParserFactory
+					.newInstance()
+					.newSAXParser()
+					.getXMLReader()
+					.getClass()
+					.getName();
+		} catch (Exception e) {
+			// fall back to Batik's hack
+			parserName = XMLResourceDescriptor.getXMLParserClassName();
+		}
+		logger.debug("parser name: " + parserName);
+		docFactory = new SAXSVGDocumentFactory(parserName);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+	 *      javax.servlet.http.HttpServletResponse)
+	 */
+	protected void doGet(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	/*
+	 */
+	protected void doPost(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	protected void processRequest(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+
+		logger.debug("request: " + request.getQueryString());
+		// time for benchmarking
+		long startTime = System.currentTimeMillis();
+
+		/*
+		 * request parameters
+		 */
+		DigilibRequest dlRequest =
+			(DigilibRequest) request.getAttribute("digilib.servlet.request");
+
+		// destination image width
+		int paramDW = dlRequest.getAsInt("dw");
+		// destination image height
+		int paramDH = dlRequest.getAsInt("dh");
+		// relative area x_offset (0..1)
+		float paramWX = dlRequest.getAsFloat("wx");
+		// relative area y_offset
+		float paramWY = dlRequest.getAsFloat("wy");
+		// relative area width (0..1)
+		double paramWW = dlRequest.getAsFloat("ww");
+		// relative area height
+		double paramWH = dlRequest.getAsFloat("wh");
+
+		try {
+
+			/*
+			 * find the file to load/send
+			 */
+
+			// get PathInfo
+			String loadPathName = dlRequest.getFilePath();
+			// find the file(set)
+			SVGFile fileToLoad =
+				(SVGFile) dirCache.getFile(
+					loadPathName,
+					dlRequest.getAsInt("pn"),
+					FileOps.CLASS_SVG);
+			if (fileToLoad == null) {
+				throw new FileOpException(
+					"File "
+						+ loadPathName
+						+ "("
+						+ dlRequest.getAsString("pn")
+						+ ") not found.");
+			}
+
+			/*
+			 * read the SVG document
+			 */
+
+			// read the document
+			SVGDocument doc =
+				docFactory.createSVGDocument(
+					fileToLoad.getFile().toURI().toString());
+			// extract the SVG root
+			SVGSVGElement svgroot = doc.getRootElement();
+			// get document width and height
+			float imgWidth = svgroot.getWidth().getBaseVal().getValue();
+			float imgHeight = svgroot.getHeight().getBaseVal().getValue();
+
+			/*
+			 * set up the transcoder
+			 */
+
+			// create a PNG transcoder
+			MapTranscoder transcoder = new MapTranscoder();
+			// create the transcoder input
+			TranscoderInput input = new TranscoderInput(doc);
+			logger.info("Loading: " + fileToLoad.getFile());
+			// create the transcoder output
+			TranscoderOutput output =
+//			new TranscoderOutput(new FileWriter("/tmp/maptest.out"));
+			new TranscoderOutput(new MapFilter());
+			// output is image/png
+			//response.setContentType("image/png");
+
+			// area of interest
+			Rectangle2D aoi =
+				new Rectangle2D.Double(
+					paramWX * imgWidth,
+					paramWY * imgHeight,
+					paramWW * imgWidth,
+					paramWH * imgHeight);
+			transcoder.addTranscodingHint(PNGTranscoder.KEY_AOI, aoi);
+
+			// destination image dimensions
+			if (paramDW > 0) {
+				transcoder.addTranscodingHint(
+					SVGAbstractTranscoder.KEY_WIDTH,
+					new Float(paramDW));
+			}
+			if (paramDH > 0) {
+				transcoder.addTranscodingHint(
+					SVGAbstractTranscoder.KEY_HEIGHT,
+					new Float(paramDH));
+			}
+
+			/*
+			 * transcode
+			 */
+
+			transcoder.transcode(input, output);
+
+			logger.info(
+				"Done in " + (System.currentTimeMillis() - startTime) + "ms");
+
+			/*
+			 * error handling
+			 */
+
+		} catch (FileOpException e) {
+			logger.error("ERROR: File IO Error: ", e);
+			try {
+				ServletOps.htmlMessage("ERROR: File IO Error: " + e, response);
+			} catch (Exception ex) {
+			} // so we don't get a loop
+		} catch (TranscoderException e) {
+			logger.error("ERROR: SVG encoder error: ", e);
+			try {
+				ServletOps.htmlMessage(
+					"ERROR: SVG encoder error: " + e,
+					response);
+			} catch (Exception ex) {
+			} // so we don't get a loop
+		}
+
+	}
+
+	protected class MapFilter extends XMLFilterImpl {
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see org.xml.sax.ContentHandler#characters(char[], int, int)
+		 */
+		public void characters(char[] ch, int start, int length)
+			throws SAXException {
+			// TODO Auto-generated method stub
+			logger.debug(
+				"characters('"
+					+ ch.toString()
+					+ "',"
+					+ start
+					+ ","
+					+ length
+					+ ")");
+			super.characters(ch, start, length);
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see org.xml.sax.ContentHandler#endDocument()
+		 */
+		public void endDocument() throws SAXException {
+			// TODO Auto-generated method stub
+			logger.debug("endDocument");
+			super.endDocument();
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
+		 *      java.lang.String, java.lang.String)
+		 */
+		public void endElement(String uri, String localName, String qName)
+			throws SAXException {
+			// TODO Auto-generated method stub
+			logger.debug(
+				"endElement(" + uri + "," + localName + "," + qName + ")");
+			super.endElement(uri, localName, qName);
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see org.xml.sax.ContentHandler#startDocument()
+		 */
+		public void startDocument() throws SAXException {
+			// TODO Auto-generated method stub
+			logger.debug("startDocument");
+			super.startDocument();
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
+		 *      java.lang.String, java.lang.String, org.xml.sax.Attributes)
+		 */
+		public void startElement(
+			String uri,
+			String localName,
+			String qName,
+			Attributes atts)
+			throws SAXException {
+			// TODO Auto-generated method stub
+			logger.debug(
+				"startElement("
+					+ uri
+					+ ","
+					+ localName
+					+ ","
+					+ qName
+					+ ","
+					+ atts.toString()
+					+ ")");
+			super.startElement(uri, localName, qName, atts);
+		}
+
+	}
+	
+	class MapTranscoder extends SVGAbstractTranscoder {
+		
+		/**
+		 * 
+		 */
+		public MapTranscoder() {
+			super();
+		}
+
+}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/Raster.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,293 @@
+/*
+ * Raster -- Servlet for displaying rasterized SVG graphics
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html You should have received a copy of
+ * the GNU General Public License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ * 02111-1307 USA
+ * 
+ * Created on 25.11.2003 by casties
+ */
+
+package digilib.servlet;
+
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.batik.dom.svg.SAXSVGDocumentFactory;
+import org.apache.batik.transcoder.TranscoderException;
+import org.apache.batik.transcoder.TranscoderInput;
+import org.apache.batik.transcoder.TranscoderOutput;
+import org.apache.batik.transcoder.image.PNGTranscoder;
+import org.apache.batik.util.XMLResourceDescriptor;
+import org.apache.log4j.Logger;
+import org.w3c.dom.svg.SVGDocument;
+import org.w3c.dom.svg.SVGSVGElement;
+
+import digilib.auth.AuthOps;
+import digilib.io.DocuDirCache;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
+import digilib.io.SVGFile;
+
+/**
+ * Servlet for displaying SVG graphics
+ * 
+ * @author casties
+ *  
+ */
+public class Raster extends HttpServlet {
+
+	private static final long serialVersionUID = -7756999389932675241L;
+
+	/** Servlet version */
+	public static String servletVersion = "0.1b1";
+	/** DigilibConfiguration instance */
+	DigilibConfiguration dlConfig = null;
+	/** general logger */
+	Logger logger = Logger.getLogger("digilib.raster");
+	/** AuthOps instance */
+	AuthOps authOp;
+	/** DocuDirCache instance */
+	DocuDirCache dirCache;
+	/** SVG document factory */
+	SAXSVGDocumentFactory docFactory;
+
+	/** use authentication */
+	boolean useAuthentication = false;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+	 */
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+
+		System.out.println(
+			"***** Digital Image Library SVG Render Servlet (version "
+				+ servletVersion
+				+ ") *****");
+
+		// get our ServletContext
+		ServletContext context = config.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig =
+			(DigilibConfiguration) context.getAttribute(
+				"digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// no config
+			throw new ServletException("ERROR: No Configuration!");
+		}
+		// say hello in the log file
+		logger.info(
+			"***** Digital Image Library SVG Render Servlet (version "
+				+ servletVersion
+				+ ") *****");
+
+		// set our AuthOps
+		useAuthentication = dlConfig.getAsBoolean("use-authorization");
+		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+		// DocuDirCache instance
+		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+		// parser name as a String (I hate you for not using JAXP, Batik!)
+		String parserName = null;
+		try {
+			// try the proper JAXP way
+			parserName = SAXParserFactory.newInstance().newSAXParser().getXMLReader().getClass().getName();
+		} catch (Exception e) {
+			// fall back to Batik's hack
+			parserName = XMLResourceDescriptor.getXMLParserClassName();
+		}
+		logger.debug("parser name: "+parserName);
+		docFactory = new SAXSVGDocumentFactory(parserName);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+	 *      javax.servlet.http.HttpServletResponse)
+	 */
+	protected void doGet(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	/*
+	 */
+	protected void doPost(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	protected void processRequest(
+		HttpServletRequest request,
+		HttpServletResponse response)
+		throws ServletException, IOException {
+
+		logger.debug("request: "+request.getQueryString());
+		// time for benchmarking
+		long startTime = System.currentTimeMillis();
+
+		/*
+		 * request parameters
+		 */
+		DigilibRequest dlRequest =
+			(DigilibRequest) request.getAttribute("digilib.servlet.request");
+
+		// destination image width
+		int paramDW = dlRequest.getAsInt("dw");
+		// destination image height
+		int paramDH = dlRequest.getAsInt("dh");
+		// relative area x_offset (0..1)
+		float paramWX = dlRequest.getAsFloat("wx");
+		// relative area y_offset
+		float paramWY = dlRequest.getAsFloat("wy");
+		// relative area width (0..1)
+		double paramWW = dlRequest.getAsFloat("ww");
+		// relative area height
+		double paramWH = dlRequest.getAsFloat("wh");
+
+		try {
+
+			/*
+			 * find the file to load/send
+			 */
+
+			// get PathInfo
+			String loadPathName = dlRequest.getFilePath();
+			// find the file(set)
+			SVGFile fileToLoad =
+				(SVGFile) dirCache.getFile(
+					loadPathName,
+					dlRequest.getAsInt("pn"),
+					FileOps.CLASS_SVG);
+			if (fileToLoad == null) {
+				throw new FileOpException(
+					"File "
+						+ loadPathName
+						+ "("
+						+ dlRequest.getAsString("pn")
+						+ ") not found.");
+			}
+
+			/*
+			 * read the SVG document
+			 */
+
+			// read the document
+			SVGDocument doc =
+				docFactory.createSVGDocument(
+					fileToLoad.getFile().toURI().toString());
+			// extract the SVG root
+			SVGSVGElement svgroot = doc.getRootElement();
+			// get document width and height
+			float imgWidth = svgroot.getWidth().getBaseVal().getValue();
+			float imgHeight = svgroot.getHeight().getBaseVal().getValue();
+
+			/*
+			 * set up the transcoder
+			 */
+
+			// create a PNG transcoder
+			PNGTranscoder transcoder = new PNGTranscoder();
+			// create the transcoder input
+			//InputStream is = new FileInputStream(fileToLoad.getFile());
+			TranscoderInput input = new TranscoderInput(doc);
+			logger.info("Loading: " + fileToLoad.getFile());
+			// create the transcoder output
+			TranscoderOutput output =
+				new TranscoderOutput(response.getOutputStream());
+			// output is image/png
+			response.setContentType("image/png");
+
+			// area of interest
+			Rectangle2D aoi =
+			new Rectangle2D.Double(
+					paramWX * imgWidth,
+					paramWY * imgHeight,
+					paramWW * imgWidth,
+					paramWH * imgHeight);
+			transcoder.addTranscodingHint(PNGTranscoder.KEY_AOI, aoi);
+
+			// destination image dimensions
+			if (paramDW > 0) {
+				transcoder.addTranscodingHint(
+					PNGTranscoder.KEY_WIDTH,
+					new Float(paramDW));
+			}
+			if (paramDH > 0) {
+				transcoder.addTranscodingHint(
+					PNGTranscoder.KEY_HEIGHT,
+					new Float(paramDH));
+			}
+
+			/*
+			 * transcode
+			 */
+
+			transcoder.transcode(input, output);
+
+			logger.info(
+				"Done in " + (System.currentTimeMillis() - startTime) + "ms");
+
+			/*
+			 * error handling
+			 */
+			
+		} catch (FileOpException e) {
+			logger.error("ERROR: File IO Error: ", e);
+			try {
+				ServletOps.htmlMessage("ERROR: File IO Error: " + e, response);
+			} catch (Exception ex) {
+			} // so we don't get a loop
+		} catch (TranscoderException e) {
+			logger.error("ERROR: SVG encoder error: ", e);
+			try {
+				ServletOps.htmlMessage(
+					"ERROR: SVG encoder error: " + e,
+					response);
+			} catch (Exception ex) {
+			} // so we don't get a loop
+		}
+
+	}
+
+}
--- a/servlet/src/digilib/servlet/Scaler.java	Mon Oct 18 15:40:54 2004 +0200
+++ b/servlet/src/digilib/servlet/Scaler.java	Thu Oct 21 20:53:37 2004 +0200
@@ -1,377 +1,684 @@
-/* Scaler -- Scaler servlet main class
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
-  This program is free software; you can redistribute  it and/or modify it
-  under  the terms of  the GNU General  Public License as published by the
-  Free Software Foundation;  either version 2 of the  License, or (at your
-  option) any later version.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
+/*
+ * Scaler -- Scaler servlet main class
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2001, 2002, 2003 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ */
 
 package digilib.servlet;
 
-import javax.servlet.*;
-import javax.servlet.http.*;
-import java.io.*;
-import java.util.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.NoninvertibleTransformException;
+import java.awt.geom.Rectangle2D;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
 
-import digilib.*;
-import digilib.io.*;
-import digilib.image.*;
-import digilib.auth.*;
+import digilib.auth.AuthOpException;
+import digilib.auth.AuthOps;
+import digilib.image.ImageOpException;
+import digilib.image.ImageOps;
+import digilib.image.ImageSize;
+import digilib.io.DocuDirCache;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
+import digilib.io.ImageFile;
+import digilib.io.ImageFileset;
 
+//import tilecachetool.*;
 
+/**
+ * @author casties
+ */
 //public class Scaler extends HttpServlet implements SingleThreadModel {
 public class Scaler extends HttpServlet {
 
-  // Utils instance with debuglevel
-  Utils util;
-  // ServletOpss instance
-  ServletOps servletOp;
-  // FileOps instance
-  FileOps fileOp;
-  // AuthOps instance
-  AuthOps authOp;
-  // global DocuImage instance (don't reuse inside a request!)
-  DocuImage globalImage;
+	private static final long serialVersionUID = -325080527268912852L;
+
+	/** digilib servlet version (for all components) */
+	public static final String dlVersion = "2.0a6";
+
+	/** logger for accounting requests */
+	private static Logger accountlog = Logger.getLogger("account.request");
+
+	/** gengeral logger for this class */
+	private static Logger logger = Logger.getLogger("digilib.servlet");
+
+	/** logger for authentication related */
+	private static Logger authlog = Logger.getLogger("digilib.auth");
+
+	/** general error code */
+	public static final int ERROR_UNKNOWN = 0;
+
+	/** error code for authentication error */
+	public static final int ERROR_AUTH = 1;
+
+	/** error code for file operation error */
+	public static final int ERROR_FILE = 2;
+
+	/** error code for image operation error */
+	public static final int ERROR_IMAGE = 3;
+
+	/** DocuDirCache instance */
+	DocuDirCache dirCache;
+
+	/** authentication error image file */
+	File denyImgFile;
+
+	/** image error image file */
+	File errorImgFile;
+
+	/** subsampling before scaling */
+	float minSubsample = 2f;
+
+	/** send files as is? */
+	boolean sendFileAllowed = true;
+
+	/** default scaling quality */
+	int defaultQuality = 1;
+
+	/** DigilibConfiguration instance */
+	DigilibConfiguration dlConfig;
+
+	/** fast worker queue */
+	DigilibManager fastQueue;
+
+	/** slow image worker queue */
+	DigilibManager slowQueue;
+
+	/** use authorization database */
+	boolean useAuthorization = true;
+
+	/** AuthOps instance */
+	AuthOps authOp;
+
+	// EXPRIMENTAL
+	/** try to enlarge cropping area for "oblique" angles */
+	boolean wholeRotArea = false;
+
+	/**
+	 * Initialisation on first run.
+	 * 
+	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+	 */
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+
+		System.out
+				.println("***** Digital Image Library Image Scaler Servlet (version "
+						+ dlVersion + ") *****");
+		// say hello in the log file
+		logger
+				.info("***** Digital Image Library Image Scaler Servlet (version "
+						+ dlVersion + ") *****");
+
+		// get our ServletContext
+		ServletContext context = config.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig = (DigilibConfiguration) context
+				.getAttribute("digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// no Configuration
+			throw new ServletException("No Configuration!");
+		}
+		// set our AuthOps
+		useAuthorization = dlConfig.getAsBoolean("use-authorization");
+		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+		// work queues
+		fastQueue = (DigilibManager) dlConfig.getValue("servlet.fast.queue");
+		slowQueue = (DigilibManager) dlConfig.getValue("servlet.slow.queue");
+
+		// DocuDirCache instance
+		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+		denyImgFile = (File) dlConfig.getValue("denied-image");
+		errorImgFile = (File) dlConfig.getValue("error-image");
+		sendFileAllowed = dlConfig.getAsBoolean("sendfile-allowed");
+		minSubsample = dlConfig.getAsFloat("subsample-minimum");
+		defaultQuality = dlConfig.getAsInt("default-quality");
+	}
+
+	/** Process the HTTP Get request */
+	public void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		accountlog.info("GET from " + request.getRemoteAddr());
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	/** Process the HTTP Post request */
+	public void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		accountlog.info("POST from " + request.getRemoteAddr());
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	/** main request handler. */
+void processRequest(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException {
+
+		if (dlConfig == null) {
+			throw new ServletException("ERROR: No Configuration!");
+		}
+
+		accountlog.debug("request: " + request.getQueryString());
+		logger.debug("request: " + request.getQueryString());
+
+		// time for benchmarking
+		long startTime = System.currentTimeMillis();
+		// output mime/type
+		String mimeType = "image/png";
+
+		/* parameters for a session */
+
+		// scale the image file to fit window size i.e. respect dw,dh
+		boolean scaleToFit = true;
+		// scale the image by a fixed factor only
+		boolean absoluteScale = false;
+		// only crop the image to fit
+		boolean cropToFit = false;
+		// send the file as is
+		boolean sendFile = false;
+		// use low resolution images only
+		boolean loresOnly = false;
+		// use hires images only
+		boolean hiresOnly = false;
+		// interpolation to use for scaling
+		int scaleQual = defaultQuality;
+		// send html error message (or image file)
+		boolean errorMsgHtml = false;
+		// mirror the image
+		boolean doMirror = false;
+		// angle of mirror axis
+		float mirrorAngle = 0;
+		// original (hires) image resolution
+		float origResX = 0;
+		float origResY = 0;
+
+		/* request parameters */
+
+		DigilibRequest dlRequest = (DigilibRequest) request
+				.getAttribute("digilib.servlet.request");
+
+		// destination image width
+		int paramDW = dlRequest.getAsInt("dw");
+		// destination image height
+		int paramDH = dlRequest.getAsInt("dh");
+		// relative area x_offset (0..1)
+		float paramWX = dlRequest.getAsFloat("wx");
+		// relative area y_offset
+		float paramWY = dlRequest.getAsFloat("wy");
+		// relative area width (0..1)
+		float paramWW = dlRequest.getAsFloat("ww");
+		// relative area height
+		float paramWH = dlRequest.getAsFloat("wh");
+		// scale factor (additional to dw/width, dh/height)
+		float paramWS = dlRequest.getAsFloat("ws");
+		// rotation angle
+		float paramROT = dlRequest.getAsFloat("rot");
+		// contrast enhancement
+		float paramCONT = dlRequest.getAsFloat("cont");
+		// brightness enhancement
+		float paramBRGT = dlRequest.getAsFloat("brgt");
+		// color modification
+		float[] paramRGBM = null;
+		Parameter p = dlRequest.get("rgbm");
+		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+			paramRGBM = p.parseAsFloatArray("/");
+		}
+		float[] paramRGBA = null;
+		p = dlRequest.get("rgba");
+		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+			paramRGBA = p.parseAsFloatArray("/");
+		}
+		// destination resolution (DPI)
+		float paramDDPIX = dlRequest.getAsFloat("ddpix");
+		float paramDDPIY = dlRequest.getAsFloat("ddpiy");
+		if ((paramDDPIX == 0) || (paramDDPIY == 0)) {
+			// if X or Y resolution isn't set, use DDPI
+			paramDDPIX = dlRequest.getAsFloat("ddpi");
+			paramDDPIY = paramDDPIX;
+		}
+
+		/*
+		 * operation mode: "fit": always fit to page, "clip": send original
+		 * resolution cropped, "file": send whole file (if allowed)
+		 */
+		if (dlRequest.hasOption("mo", "clip")) {
+			scaleToFit = false;
+			absoluteScale = false;
+			cropToFit = true;
+			sendFile = false;
+			hiresOnly = true;
+		} else if (dlRequest.hasOption("mo", "fit")) {
+			scaleToFit = true;
+			absoluteScale = false;
+			cropToFit = false;
+			sendFile = false;
+			hiresOnly = false;
+		} else if (dlRequest.hasOption("mo", "osize")) {
+			scaleToFit = false;
+			absoluteScale = true;
+			cropToFit = false;
+			sendFile = false;
+			hiresOnly = true;
+		}
+		// operation mode: "lores": try to use scaled image, "hires": use
+		// unscaled image
+		//   "autores": try best fitting resolution
+		if (dlRequest.hasOption("mo", "lores")) {
+			loresOnly = true;
+			hiresOnly = false;
+		} else if (dlRequest.hasOption("mo", "hires")) {
+			loresOnly = false;
+			hiresOnly = true;
+		} else if (dlRequest.hasOption("mo", "autores")) {
+			loresOnly = false;
+			hiresOnly = false;
+		}
+		// operation mode: "errtxt": error message in html, "errimg": error
+		// image
+		if (dlRequest.hasOption("mo", "errtxt")) {
+			errorMsgHtml = true;
+		} else if (dlRequest.hasOption("mo", "errimg")) {
+			errorMsgHtml = false;
+		}
+		// operation mode: "q0" - "q2": interpolation quality
+		if (dlRequest.hasOption("mo", "q0")) {
+			scaleQual = 0;
+		} else if (dlRequest.hasOption("mo", "q1")) {
+			scaleQual = 1;
+		} else if (dlRequest.hasOption("mo", "q2")) {
+			scaleQual = 2;
+		}
+
+		// check with the maximum allowed size (if set)
+		int maxImgSize = dlConfig.getAsInt("max-image-size");
+		if (maxImgSize > 0) {
+			paramDW = (paramDW * paramWS > maxImgSize) ? (int) (maxImgSize / paramWS)
+					: paramDW;
+			paramDH = (paramDH * paramWS > maxImgSize) ? (int) (maxImgSize / paramWS)
+					: paramDH;
+		}
 
-  // use authorization database
-  boolean useAuthentication = true;
-  // image file to send in case of error
-  File errorImgFile = new File("/docuserver/images/icons/scalerror.gif");
-  // image file to send if access is denied
-  File denyImgFile = new File("/docuserver/images/icons/denied.gif");
-  // base directories in order of preference (prescaled versions first)
-  String[] baseDirs = {"/docuserver/scaled/small", "/docuserver/images", "/docuserver/scans/quellen"};
+		//"big" try for all file/image actions
+		try {
+
+			// ImageFileset of the image to load
+			ImageFileset fileset = null;
+
+			/* find the file to load/send */
+
+			// get PathInfo
+			String loadPathName = dlRequest.getFilePath();
+
+			/* check permissions */
+			if (useAuthorization) {
+				// get a list of required roles (empty if no restrictions)
+				List rolesRequired = authOp.rolesForPath(loadPathName, request);
+				if (rolesRequired != null) {
+					authlog.debug("Role required: " + rolesRequired);
+					authlog.debug("User: " + request.getRemoteUser());
+					// is the current request/user authorized?
+					if (!authOp.isRoleAuthorized(rolesRequired, request)) {
+						// send deny answer and abort
+						throw new AuthOpException();
+					}
+				}
+			}
+
+			// find the file(set)
+			ImageFile fileToLoad;
+			fileset = (ImageFileset) dirCache.getFile(loadPathName, dlRequest
+					.getAsInt("pn"), FileOps.CLASS_IMAGE);
+			if (fileset == null) {
+				throw new FileOpException("File " + loadPathName + "("
+						+ dlRequest.getAsInt("pn") + ") not found.");
+			}
+
+			/* calculate expected source image size */
+			ImageSize expectedSourceSize = new ImageSize();
+			if (scaleToFit) {
+				float scale = (1 / Math.min(paramWW, paramWH)) * paramWS;
+				expectedSourceSize.setSize((int) (paramDW * scale),
+						(int) (paramDH * scale));
+			} else {
+				expectedSourceSize.setSize((int) (paramDW * paramWS),
+						(int) (paramDH * paramWS));
+			}
+
+			/* select a resolution */
+			if (hiresOnly) {
+				// get first element (= highest resolution)
+				fileToLoad = fileset.getBiggest();
+			} else if (loresOnly) {
+				// enforced lores uses next smaller resolution
+				fileToLoad = fileset.getNextSmaller(expectedSourceSize);
+				if (fileToLoad == null) {
+					// this is the smallest we have
+					fileToLoad = fileset.getSmallest();
+				}
+			} else {
+				// autores: use next higher resolution
+				fileToLoad = fileset.getNextBigger(expectedSourceSize);
+				if (fileToLoad == null) {
+					// this is the highest we have
+					fileToLoad = fileset.getBiggest();
+				}
+			}
+			logger.info("Loading: " + fileToLoad.getFile());
+
+			/*
+			 * send the image if its mo=(raw)file
+			 */
+			if (dlRequest.hasOption("mo", "file")
+					|| dlRequest.hasOption("mo", "rawfile")) {
+				if (sendFileAllowed) {
+					String mt = null;
+					if (dlRequest.hasOption("mo", "rawfile")) {
+						mt = "application/octet-stream";
+					}
+					logger.debug("Sending RAW File as is.");
+					ServletOps.sendFile(fileToLoad.getFile(), mt, response,
+							fastQueue);
+					logger.info("Done in "
+							+ (System.currentTimeMillis() - startTime) + "ms");
+					return;
+				}
+			}
+
+			/*
+			 * prepare resolution for original size
+			 */
+			if (absoluteScale) {
+				// get original resolution from metadata
+				fileset.checkMeta();
+				origResX = fileset.getResX();
+				origResY = fileset.getResY();
+				if ((origResX == 0) || (origResY == 0)) {
+					throw new ImageOpException("Missing image DPI information!");
+				}
+
+				if ((paramDDPIX == 0) || (paramDDPIY == 0)) {
+					throw new ImageOpException(
+							"Missing display DPI information!");
+				}
+			}
+
+			// check the source image
+			if (!fileToLoad.isChecked()) {
+				ImageOps.checkFile(fileToLoad);
+			}
+			// get the source image type
+			mimeType = fileToLoad.getMimetype();
+			// get the source image size
+			ImageSize imgSize = fileToLoad.getSize();
+
+			// decide if the image can be sent as is
+			boolean mimetypeSendable = mimeType.equals("image/jpeg")
+					|| mimeType.equals("image/png")
+					|| mimeType.equals("image/gif");
+			boolean imagoOptions = dlRequest.hasOption("mo", "hmir")
+					|| dlRequest.hasOption("mo", "vmir") || (paramROT != 0)
+					|| (paramRGBM != null) || (paramRGBA != null)
+					|| (paramCONT != 0) || (paramBRGT != 0);
+			boolean imageSendable = mimetypeSendable && !imagoOptions;
+
+			/*
+			 * if not autoRes and image smaller than requested size then send as
+			 * is. if autoRes and image has requested size then send as is. if
+			 * not autoScale and not scaleToFit nor cropToFit then send as is
+			 * (mo=file)
+			 */
+			if (imageSendable
+					&& ((loresOnly && fileToLoad.getSize().isSmallerThan(
+							expectedSourceSize)) || (!(loresOnly || hiresOnly) && fileToLoad
+							.getSize().fitsIn(expectedSourceSize)))) {
+
+				logger.debug("Sending File as is.");
+
+				ServletOps.sendFile(fileToLoad.getFile(), null, response,
+						fastQueue);
+
+				logger.info("Done in "
+						+ (System.currentTimeMillis() - startTime) + "ms");
+				return;
+			}
+
+			// set missing dw or dh from aspect ratio
+			float imgAspect = fileToLoad.getAspect();
+			if (paramDW == 0) {
+				paramDW = (int) Math.round(paramDH * imgAspect);
+			} else if (paramDH == 0) {
+				paramDH = (int) Math.round(paramDW / imgAspect);
+			}
+
+			/* crop and scale the image */
+
+			logger.debug("IMG: " + imgSize.getWidth() + "x"
+					+ imgSize.getHeight());
+			logger.debug("time " + (System.currentTimeMillis() - startTime)
+					+ "ms");
+
+			// coordinates and scaling
+			float areaXoff;
+			float areaYoff;
+			float areaWidth;
+			float areaHeight;
+			float scaleX;
+			float scaleY;
+			float scaleXY;
+
+			// coordinates using Java2D
+			// image size in pixels
+			Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize
+					.getWidth(), imgSize.getHeight());
+			// user window area in [0,1] coordinates
+			Rectangle2D relUserArea = new Rectangle2D.Float(paramWX, paramWY,
+					paramWW, paramWH);
+			// transform from relative [0,1] to image coordinates.
+			AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize
+					.getWidth(), imgSize.getHeight());
+			// transform user coordinate area to image coordinate area
+			Rectangle2D userImgArea = imgTrafo.createTransformedShape(
+					relUserArea).getBounds2D();
+
+			// calculate scaling factors based on inner user area
+			if (scaleToFit) {
+				areaWidth = (float) userImgArea.getWidth();
+				areaHeight = (float) userImgArea.getHeight();
+				scaleX = paramDW / areaWidth * paramWS;
+				scaleY = paramDH / areaHeight * paramWS;
+				scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
+			} else if (absoluteScale) {
+				// absolute scale
+				scaleX = paramDDPIX / origResX;
+				scaleY = paramDDPIY / origResY;
+				// currently only same scale :-(
+				scaleXY = scaleX;
+				areaWidth = paramDW / scaleXY * paramWS;
+				areaHeight = paramDH / scaleXY * paramWS;
+				// reset user area size
+				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
+						areaWidth, areaHeight);
+			} else {
+				// crop to fit
+				areaWidth = paramDW * paramWS;
+				areaHeight = paramDH * paramWS;
+				// reset user area size
+				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
+						areaWidth, areaHeight);
+				scaleX = 1f;
+				scaleY = 1f;
+				scaleXY = 1f;
+			}
+
+			// enlarge image area for rotations to cover additional pixels
+			Rectangle2D outerUserImgArea = userImgArea;
+			Rectangle2D innerUserImgArea = userImgArea;
+			if (wholeRotArea) {
+				if (paramROT != 0) {
+					try {
+						// rotate user area coordinates around center of user
+						// area
+						AffineTransform rotTrafo = AffineTransform
+								.getRotateInstance(Math.toRadians(paramROT),
+										userImgArea.getCenterX(), userImgArea
+												.getCenterY());
+						// get bounds from rotated end position
+						innerUserImgArea = rotTrafo.createTransformedShape(
+								userImgArea).getBounds2D();
+						// get bounds from back-rotated bounds
+						outerUserImgArea = rotTrafo.createInverse()
+								.createTransformedShape(innerUserImgArea)
+								.getBounds2D();
+					} catch (NoninvertibleTransformException e1) {
+						// this shouldn't happen anyway
+						logger.error(e1);
+					}
+				}
+			}
+
+			logger.debug("Scale " + scaleXY + "(" + scaleX + "," + scaleY
+					+ ") on " + outerUserImgArea);
+
+			// clip area at the image border
+			outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
+
+			// check image parameters sanity
+			if ((outerUserImgArea.getWidth() < 1) || (outerUserImgArea.getHeight() < 1)
+					|| (scaleXY * outerUserImgArea.getWidth() < 2) || (scaleXY * outerUserImgArea.getHeight() < 2)) {
+				logger.error("ERROR: invalid scale parameter set!");
+				throw new ImageOpException("Invalid scale parameter set!");
+			}
+
+			/*
+			 * submit the image worker job
+			 */
+
+			DigilibWorker job = new DigilibImageWorker(dlConfig, response,
+					mimeType, scaleQual, dlRequest, paramROT, paramCONT,
+					paramBRGT, paramRGBM, paramRGBA, fileToLoad, scaleXY, outerUserImgArea,
+					innerUserImgArea, minSubsample, wholeRotArea);
+			
+			try {
+				// we're cheating
+				job.run();
+			} catch (Exception e1) {
+				throw new ImageOpException(e1.toString());
+			}
+			
+			/*
+			try {
+				slowQueue.execute(job);
+				logger.debug("servlet job submitted by "
+						+ Thread.currentThread().getName() + " ("
+						+ slowQueue.getQueueSize() + " in queue)");
+				
+				synchronized (job) {
+					while (job.isBusy()) {
+						Thread.yield();
+						job.wait();
+					}
+				}
+			} catch (InterruptedException e1) {
+				throw new ImageOpException("INTERRUPTED: " + e1.getMessage());
+			}
+			*/
 
 
-  /*********************************************************
-   *             Initialize global variables
-   *********************************************************/
-  public void init(ServletConfig config) throws ServletException {
-    super.init(config);
-
-    // first we need an Utils to setup ServletOps UGLY!!
-    util = new Utils(5);
-    // servletOps takes a ServletConfig to get the config file name
-    servletOp = new ServletOps(util, config);
-    // then we can start reading parameters UGLY!!
-
-    // Utils instance with debuglevel
-    int debugLevel = servletOp.tryToGetInitParam("debug-level", 10);
-    util = new Utils(debugLevel);
-    // reset Util for ServletOps instance
-    servletOp.setUtils(util);
-    // image file to send in case of error
-    String errorImgFileName = servletOp.tryToGetInitParam("error-image", "/docuserver/images/icons/scalerror.gif");
-    errorImgFile = new File(errorImgFileName);
-    // image file to send if access is denied
-    String denyImgFileName = servletOp.tryToGetInitParam("denied-image", "/docuserver/images/icons/denied.gif");
-    denyImgFile = new File(denyImgFileName);
-    // base directories in order of preference (prescaled versions first)
-    String baseDirList = servletOp.tryToGetInitParam("basedir-list", "/docuserver/scaled/small:/docuserver/images:/docuserver/scans/quellen");
-    // split list into directories
-    StringTokenizer dirs = new StringTokenizer(baseDirList, ":");
-    int n = dirs.countTokens();
-    // add directories into array
-    baseDirs = new String[n];
-    for (int i = 0; i < n; i++) {
-      baseDirs[i] = dirs.nextToken();
-    }
-    // use authentication information
-    String useAuth = servletOp.tryToGetInitParam("use-authorization", "true");
-    if ((useAuth.indexOf("false") > 0)||(useAuth.indexOf("FALSE") > 0)) {
-      useAuthentication = false;
-    } else {
-      useAuthentication = true;
-      try {
-        // DB version
-        //authOp = new DBAuthOpsImpl(util);
-        // XML version
-        String cnfPath = servletOp.tryToGetInitParam("auth-file", "/docuserver/www/digitallibrary/WEB-INF/digilib-auth.xml");
-        authOp = new XMLAuthOps(util, cnfPath);
-      } catch (AuthOpException e) {
-        throw new ServletException(e);
-      }
-    }
-    // FileOps instance
-    fileOp = new FileOps(util);
-    // global DocuImage instance (don't reuse inside a request!)
-    globalImage = new JAIDocuImage(util);
-//    globalImage = new JIMIDocuImage(util);
-    //globalImage = new ImageLoaderDocuImage(util);
-
-  }
-
-  /**Process the HTTP Get request*/
-  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-    util.dprintln(1, "The servlet has received a GET!");
-    processRequest(request, response);
-  }
-
-  /**Process the HTTP Post request*/
-  public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-    util.dprintln(1, "The servlet has received a POST!");
-    processRequest(request, response);
-  }
-
-  /**Clean up resources*/
-  public void destroy() {
-  }
-
-/**********************************************************************
- *                       main request handler
- **********************************************************************/
+			logger.debug("servlet done in "
+					+ (System.currentTimeMillis() - startTime));
 
-  void processRequest(HttpServletRequest request, HttpServletResponse response)
-    throws ServletException, IOException {
-
-    // time for benchmarking
-    long startTime = System.currentTimeMillis();
-    // output mime/type
-    String mimeType = "image/png";
-
-    /**
-     * parameters for a session
-     */
-
-    // scale the image file to fit window size
-    boolean scaleToFit = true;
-    // use heuristics (GIF?) to scale or not
-    boolean forcedScale = false;
-    // try prescaled images first
-    boolean preScaledFirst = true;
-    // interpolation to use for scaling
-    int scaleQual = 0;
-    // send html error message (or image file)
-    boolean errorMsgHtml = false;
-
-    /**
-     *  request parameter
-     */
-
-    // file/dir to load
-    String param_fn = servletOp.tryToGetParam("fn", "", request);
-    // page number
-    int param_pn = servletOp.tryToGetParam("pn", 1, request);
-    // destination image width
-    int param_dw = servletOp.tryToGetParam("dw", 300, request);
-    // destination image height
-    int param_dh = servletOp.tryToGetParam("dh", 400, request);
-    // relative area x_offset (0..1)
-    float param_wx = servletOp.tryToGetParam("wx", 0f, request);
-    // relative area y_offset
-    float param_wy = servletOp.tryToGetParam("wy", 0f, request);
-    // relative area width (0..1)
-    float param_ww = servletOp.tryToGetParam("ww", 1f, request);
-    // relative area height
-    float param_wh = servletOp.tryToGetParam("wh", 1f, request);
-    // scale factor (additional to dw/width, dh/height)
-    float param_ws = servletOp.tryToGetParam("ws", 1f, request);
-    // operation mode: flags separated by "+"
-    String param_mo = servletOp.tryToGetParam("mo", "", request);
-    // operation mode: "fit": always fit to page, "file": send as-is
-    if (param_mo.indexOf("fit") >= 0) {
-      scaleToFit = true;
-      forcedScale = true;
-    } else if (param_mo.indexOf("file") >= 0) {
-      scaleToFit = false;
-      forcedScale = true;
-    }
-    // operation mode: "errtxt": error message in html, "errimg": error image
-    if (param_mo.indexOf("errtxt") >= 0) {
-      errorMsgHtml = true;
-    } else if (param_mo.indexOf("errimg") >= 0) {
-      errorMsgHtml = false;
-    }
-    // operation mode: "q0" - "q2": interpolation quality
-    if (param_mo.indexOf("q0") >= 0) {
-      scaleQual = 0;
-    } else if (param_mo.indexOf("q1") >= 0) {
-      scaleQual = 1;
-    } else if (param_mo.indexOf("q2") >= 0) {
-      scaleQual = 2;
-    }
-    // operation mode: "lores": try to use scaled image, "hires": unscaled image
-    if (param_mo.indexOf("lores") >= 0) {
-      preScaledFirst = true;
-    } else if (param_mo.indexOf("hires") >= 0) {
-      preScaledFirst = false;
-    }
-
-    Utils.dprintln(1, "Parameter values: fn:"+param_fn+" pn:"+param_pn+" dw:"+param_dw+" dh:"+param_dh+" wx:"+param_wx+" wy:"+param_wy+" ww:"+param_ww+" wh:"+param_wh+" ws:"+param_ws+" mo:"+param_mo);
-
-    //"big" try for all file/image actions
-    try {
+			/* error handling */
 
-    // DocuImage instance
-    DocuImage docuImage = new JAIDocuImage(util);
-//    DocuImage docuImage = new JIMIDocuImage(util);
-    //DocuImage docuImage = new ImageLoaderDocuImage(util);
-
-
-    /**
-     *  find the file to load/send
-     */
-
-    String loadPathName = "";
-    // if there's PathInfo, append
-    if (request.getPathInfo() != null) {
-      loadPathName += request.getPathInfo();
-    }
-    // append fn parameter
-    loadPathName += param_fn;
-    // if it's zoomed, try hires version (to be optimized...)
-    if ((param_ww < 1f) || (param_wh < 1f)) {
-      preScaledFirst = false;
-    }
-
-    if (useAuthentication) {
-      // check permissions
-      List rolesRequired = authOp.rolesForPath(loadPathName, request);
-      if (rolesRequired != null) {
-        Utils.dprintln(1, "Role required: "+rolesRequired);
-        Utils.dprintln(2, "User: "+request.getRemoteUser());
-        if (! authOp.isRoleAuthorized(rolesRequired, request)) {
-          Utils.dprintln(1, "ERROR: access denied!");
-          if (errorMsgHtml) {
-            servletOp.htmlMessage("ERROR: Unauthorized access!", response);
-          } else {
-            docuImage.sendFile(denyImgFile, response);
-          }
-          return;
-        }
-      }
-    }
-
-    // find the file
-    File fileToLoad = fileOp.getFileVariant(baseDirs, loadPathName, param_pn, preScaledFirst);
-
-    Utils.dprintln(1, "Loading: "+fileToLoad);
-
-    // get the source image type (if it's known)
-    mimeType = fileOp.mimeForFile(fileToLoad);
-
-    // if not forced and source is GIF/PNG then send-as-is if not zoomed
-    if((!forcedScale && (mimeType == "image/gif" || mimeType == "image/png")
-        && (param_ww == 1f) && (param_wh == 1f)) || (forcedScale && !scaleToFit)) {
-
-      Utils.dprintln(1, "Sending File as is.");
-
-      docuImage.sendFile(fileToLoad, response);
-
-      Utils.dprintln(1, "Done in "+(System.currentTimeMillis()-startTime)+"ms");
-      return;
-    }
-
-    // load file
-    docuImage.loadImage(fileToLoad);
-
-    /**
-     *  crop and scale the image
-     */
-
-    // get size
-    int imgWidth = docuImage.getWidth();
-    int imgHeight = docuImage.getHeight();
-
-    util.dprintln(2, "IMG: "+imgWidth+"x"+imgHeight);
-    util.dprintln(2, "time "+(System.currentTimeMillis()-startTime)+"ms");
+		} // end of "big" try
+		catch (IOException e) {
+			logger.error("ERROR: File IO Error: " + e);
+			digilibError(errorMsgHtml, ERROR_FILE,
+					"ERROR: File IO Error: " + e, response);
+		} catch (AuthOpException e) {
+			logger.error("ERROR: Authorization error: " + e);
+			digilibError(errorMsgHtml, ERROR_AUTH,
+					"ERROR: Authorization error: " + e, response);
+		} catch (ImageOpException e) {
+			logger.error("ERROR: Image Error: " + e);
+			digilibError(errorMsgHtml, ERROR_IMAGE,
+					"ERROR: Image Operation Error: " + e, response);
+		} catch (RuntimeException e) {
+			// JAI likes to throw RuntimeExceptions ;-(
+			logger.error("ERROR: Other Image Error: " + e);
+			digilibError(errorMsgHtml, ERROR_IMAGE,
+					"ERROR: Other Image Operation Error: " + e, response);
+		}
+	}
+	/**
+	 * Sends an error to the client as text or image.
+	 * 
+	 * @param asHTML
+	 * @param type
+	 * @param msg
+	 * @param response
+	 */
+	public void digilibError(boolean asHTML, int type, String msg,
+			HttpServletResponse response) {
+		try {
+			File img = null;
+			if (type == ERROR_AUTH) {
+				if (msg == null) {
+					msg = "ERROR: Unauthorized access!";
+				}
+				img = denyImgFile;
+			} else {
+				if (msg == null) {
+					msg = "ERROR: Other image error!";
+				}
+				img = this.errorImgFile;
+			}
+			if (asHTML && (img != null)) {
+				ServletOps.htmlMessage(msg, response);
+			} else {
+				ServletOps.sendFile(img, null, response, fastQueue);
+			}
+		} catch (IOException e) {
+			logger.error("Error sending error!", e);
+		}
 
-    // calculate absolute from relative coordinates
-    float areaXoff = param_wx * imgWidth;
-    float areaYoff = param_wy * imgHeight;
-    float areaWidth = param_ww * imgWidth;
-    float areaHeight = param_wh * imgHeight;
-    // calculate scaling factors
-    float scaleX = param_dw / areaWidth * param_ws;
-    float scaleY = param_dh / areaHeight * param_ws;
-    float scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
-
-    util.dprintln(1, "Scale "+scaleXY+"("+scaleX+","+scaleY+") on "+areaXoff+","+areaYoff+" "+areaWidth+"x"+areaHeight);
-
-    // fit area to image
-    areaWidth = (areaXoff + areaWidth > imgWidth) ? imgWidth - areaXoff : areaWidth;
-    areaHeight = (areaYoff + areaHeight > imgHeight) ? imgHeight - areaYoff : areaHeight;
-
-    util.dprintln(2, "cropped: "+areaXoff+","+areaYoff+" "+areaWidth+"x"+areaHeight);
-
-    // check image parameters
-    if ((areaWidth < 1)||(areaHeight < 1)
-       ||(scaleXY * areaWidth < 2)||(scaleXY * areaHeight < 2)) {
-      Utils.dprintln(1, "ERROR: invalid scale parameter set!");
-      throw new ImageOpException("Invalid scale parameter set!");
-    }
-
-    // crop and scale image
-    docuImage.cropAndScale((int)areaXoff, (int)areaYoff, (int)areaWidth, (int)areaHeight,
-                            scaleXY, scaleQual);
-
-    util.dprintln(2, "time "+(System.currentTimeMillis()-startTime)+"ms");
-
-    /**
-     *  write the resulting image
-     */
-
-    // setup output -- if source is JPG then dest will be JPG else it's PNG
-    if (mimeType != "image/jpeg") {
-      mimeType="image/png";
-    }
-
-    // write the image
-    docuImage.writeImage(mimeType, response);
+	}
 
-    util.dprintln(1, "Done in "+(System.currentTimeMillis()-startTime)+"ms");
-
-    /**
-     *  error handling
-     */
-
-    }//"big" try
-    catch (FileOpException e) {
-      util.dprintln(1, "ERROR: File IO Error: "+e);
-      try {
-        if (errorMsgHtml) {
-          servletOp.htmlMessage("ERROR: File IO Error: "+e, response);
-        } else {
-          globalImage.sendFile(errorImgFile, response);
-        }
-      } catch (FileOpException ex) {} // so we don't get a loop
-      return;
-    }
-    catch (AuthOpException e) {
-      Utils.dprintln(1, "ERROR: Authorization error: "+e);
-      try {
-        if (errorMsgHtml) {
-          servletOp.htmlMessage("ERROR: Authorization error: "+e, response);
-        } else {
-          globalImage.sendFile(errorImgFile, response);
-        }
-      } catch (FileOpException ex) {} // so we don't get a loop
-      return;
-    }
-    catch (ImageOpException e) {
-      Utils.dprintln(1, "ERROR: Image Error: "+e);
-      try {
-        if (errorMsgHtml) {
-          servletOp.htmlMessage("ERROR: Image Operation Error: "+e, response);
-        } else {
-          globalImage.sendFile(errorImgFile, response);
-        }
-      } catch (FileOpException ex) {} // so we don't get a loop
-      return;
-    }
-
-  }
-
-}//Scaler class
+} //Scaler class
--- a/servlet/src/digilib/servlet/ServletOps.java	Mon Oct 18 15:40:54 2004 +0200
+++ b/servlet/src/digilib/servlet/ServletOps.java	Thu Oct 21 20:53:37 2004 +0200
@@ -1,156 +1,217 @@
-/* ServletOps -- Servlet utility class
-
-  Digital Image Library servlet components
-
-  Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
-
-  This program is free software; you can redistribute  it and/or modify it
-  under  the terms of  the GNU General  Public License as published by the
-  Free Software Foundation;  either version 2 of the  License, or (at your
-  option) any later version.
-   
-  Please read license.txt for the full details. A copy of the GPL
-  may be found at http://www.gnu.org/copyleft/lgpl.html
-
-  You should have received a copy of the GNU General Public License
-  along with this program; if not, write to the Free Software
-  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-*/
+/*
+ * ServletOps -- Servlet utility class
+ * 
+ * Digital Image Library servlet components
+ * 
+ * Copyright (C) 2001, 2002 Robert Casties (robcast@mail.berlios.de)
+ * 
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation; either version 2 of the License, or (at your option) any later
+ * version.
+ * 
+ * Please read license.txt for the full details. A copy of the GPL may be found
+ * at http://www.gnu.org/copyleft/lgpl.html
+ * 
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA
+ *  
+ */
 
 package digilib.servlet;
 
-import javax.servlet.*;
-import javax.servlet.http.*;
-import java.io.*;
-import java.util.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.StringTokenizer;
 
-import digilib.*;
-import digilib.io.*;
+import javax.servlet.ServletConfig;
+import javax.servlet.http.HttpServletResponse;
 
+import org.apache.log4j.Logger;
+
+import EDU.oswego.cs.dl.util.concurrent.Executor;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
 
 public class ServletOps {
 
-  private Utils util = null;
-  private Hashtable confTable = null;
-
-  public ServletOps() {
-    util = new Utils();
-  }
+	private static Logger logger = Logger.getLogger("servlet.op");
 
-  public ServletOps(Utils u) {
-    util = u;
-  }
+	/**
+	 * convert a string with a list of pathnames into an array of strings using
+	 * the system's path seperator string
+	 */
+	public static String[] getPathArray(String paths) {
+		// split list into directories
+		StringTokenizer dirs = new StringTokenizer(paths,
+				java.io.File.pathSeparator);
+		int n = dirs.countTokens();
+		if (n < 1) {
+			return null;
+		}
+		// add directories into array
+		String[] pathArray = new String[n];
+		for (int i = 0; i < n; i++) {
+			pathArray[i] = dirs.nextToken();
+		}
+		return pathArray;
+	}
 
-  public ServletOps(Utils u, ServletConfig sc) throws ServletException {
-    util = u;
-    setConfig(sc);
-  }
-
-  public void setUtils(Utils u) {
-    util = u;
-  }
+	/**
+	 * get a real File for a config File.
+	 * 
+	 * If the File is not absolute the path is appended to the WEB-INF directory
+	 * of the web-app.
+	 * 
+	 * @param file
+	 * @param sc
+	 * @return
+	 */
+	public static File getConfigFile(File f, ServletConfig sc) {
+		// is the filename absolute?
+		if (!f.isAbsolute()) {
+			// relative path -> use getRealPath to resolve in WEB-INF
+			String fn = sc.getServletContext().getRealPath(
+					"WEB-INF/" + f.getPath());
+			f = new File(fn);
+		}
+		return f;
+	}
 
-  /**
-   * read parameter list from the XML file in init parameter "config-file"
-   */
-  public void setConfig(ServletConfig c) throws ServletException {
-    // reset parameter table
-    confTable = null;
-    if (c == null) {
-      return;
-    }
-    // get config file name
-    String fn = c.getInitParameter("config-file");
-    if (fn == null) {
-      util.dprintln(4, "setConfig: no param config-file");
-      return;
-    }
-    File f = new File(fn);
-    // setup config file list reader
-    XMLListLoader lilo = new XMLListLoader("digilib-config", "parameter", "name", "value");
-    try {
-      confTable = lilo.loadURL(f.toURL().toString());
-    } catch (Exception e) {
-      util.dprintln(4, "setConfig: unable to read file "+fn);
-      throw new ServletException(e);
-    }
-  }
+	/**
+	 * get a real file name for a config file pathname.
+	 * 
+	 * If filename starts with "/" its treated as absolute else the path is
+	 * appended to the WEB-INF directory of the web-app.
+	 * 
+	 * @param filename
+	 * @param sc
+	 * @return
+	 */
+	public static String getConfigFile(String filename, ServletConfig sc) {
+		File f = new File(filename);
+		// is the filename absolute?
+		if (!f.isAbsolute()) {
+			// relative path -> use getRealPath to resolve in WEB-INF
+			filename = sc.getServletContext()
+					.getRealPath("WEB-INF/" + filename);
+		}
+		return filename;
+	}
 
-  /**
-   *  print a servlet response and exit
-   */
-  public static void htmlMessage(String s, HttpServletResponse response) throws IOException {
-    response.setContentType("text/html; charset=iso-8859-1");
-    PrintWriter out = response.getWriter();
-    out.println("<html>");
-    out.println("<head><title>Scaler</title></head>");
-    out.println("<body>");
-    out.println("<p>"+s+"</p>");
-    out.println("</body></html>");
-  }
+	/**
+	 * print a servlet response and exit
+	 */
+	public static void htmlMessage(String msg, HttpServletResponse response)
+			throws IOException {
+		htmlMessage("Scaler", msg, response);
+	}
+
+	/**
+	 * print a servlet response and exit
+	 */
+	public static void htmlMessage(String title, String msg,
+			HttpServletResponse response) throws IOException {
+		response.setContentType("text/html; charset=iso-8859-1");
+		PrintWriter out = response.getWriter();
+		out.println("<html>");
+		out.println("<head><title>" + title + "</title></head>");
+		out.println("<body>");
+		out.println("<p>" + msg + "</p>");
+		out.println("</body></html>");
+	}
 
-  /**
-   *  get a parameter from request and return it if set, otherwise return default
-   */
-  public int tryToGetParam(String s, int i, HttpServletRequest r) {
-    try {
-      i = Integer.parseInt(r.getParameter(s));
-    } catch(Exception e) {
-      util.dprintln(4, "trytoGetParam(int) failed on param "+s);
-      //e.printStackTrace();
-    }
-    return i;
-  }
-  public float tryToGetParam(String s, float f, HttpServletRequest r) {
-    try {
-      f = Float.parseFloat(r.getParameter(s));
-    } catch(Exception e) {
-      util.dprintln(4, "trytoGetParam(float) failed on param "+s);
-      //e.printStackTrace();
-    }
-    return f;
-  }
-  public String tryToGetParam(String s, String x, HttpServletRequest r) {
-    if (r.getParameter(s) != null) {
-      x = r.getParameter(s);
-    } else {
-      util.dprintln(4, "trytoGetParam(string) failed on param "+s);
-    }
-    return x;
-  }
-
+	/**
+	 * Transfers an image file as-is with the mime type mt.
+	 * 
+	 * The local file is copied to the <code>OutputStream</code> of the
+	 * <code>ServletResponse</code>. If mt is null then the mime-type is
+	 * auto-detected with mimeForFile.
+	 * 
+	 * @param mt
+	 *            mime-type of the file.
+	 * @param f
+	 *            Image file to be sent.
+	 * @param res
+	 *            ServletResponse where the image file will be sent.
+	 * @throws FileOpException
+	 *             Exception is thrown for a IOException.
+	 */
+	public static void sendFileImmediately(File f, String mt,
+			HttpServletResponse response) throws FileOpException {
+		logger.debug("sendRawFile(" + mt + ", " + f + ")");
+		if (mt == null) {
+			// auto-detect mime-type
+			mt = FileOps.mimeForFile(f);
+			if (mt == null) {
+				throw new FileOpException("Unknown file type.");
+			}
+		}
+		response.setContentType(mt);
+		// open file
+		try {
+			if (mt.equals("application/octet-stream")) {
+				response.addHeader("Content-Disposition",
+						"attachment; filename=\"" + f.getName() + "\"");
+			}
+			FileInputStream inFile = new FileInputStream(f);
+			OutputStream outStream = response.getOutputStream();
+			byte dataBuffer[] = new byte[4096];
+			int len;
+			while ((len = inFile.read(dataBuffer)) != -1) {
+				// copy out file
+				outStream.write(dataBuffer, 0, len);
+			}
+			inFile.close();
+			response.flushBuffer();
+		} catch (IOException e) {
+			throw new FileOpException("Unable to send file.");
+		}
+	}
 
-  /**
-   *  get an init parameter from config and return it if set, otherwise return default
-   */
-  public int tryToGetInitParam(String s, int i) {
-    //System.out.println("trytogetInitParam("+s+", "+i+")");
-    try {
-      //System.out.println("trytogetInitParam: "+(String)confTable.get(s));
-      i = Integer.parseInt((String)confTable.get(s));
-    } catch(Exception e) {
-      util.dprintln(4, "trytogetInitParam(int) failed on param "+s);
-      //e.printStackTrace();
-    }
-    return i;
-  }
-  public float tryToGetInitParam(String s, float f) {
-    try {
-      f = Float.parseFloat((String)confTable.get(s));
-    } catch(Exception e) {
-      util.dprintln(4, "trytoGetInitParam(float) failed on param "+s);
-      //e.printStackTrace();
-    }
-    return f;
-  }
-  public String tryToGetInitParam(String s, String x) {
-    if ((confTable != null)&&((String)confTable.get(s) != null)) {
-      x = (String)confTable.get(s);
-    } else {
-      util.dprintln(4, "trytoGetInitParam(string) failed on param "+s);
-    }
-    return x;
-  }
+	/**
+	 * Transfers an image file as-is with the mime type mt using a work queue.
+	 * 
+	 * The local file is copied to the <code>OutputStream</code> of the
+	 * <code>ServletResponse</code>. If mt is null then the mime-type is
+	 * auto-detected with mimeForFile.
+	 * 
+	 * @param mt
+	 *            mime-type of the file.
+	 * @param f
+	 *            Image file to be sent.
+	 * @param res
+	 *            ServletResponse where the image file will be sent.
+	 * @throws FileOpException
+	 *             Exception is thrown for a IOException.
+	 */
+	public static void sendFile(File f, String mimetype,
+			HttpServletResponse response, Executor workQueue)
+			throws FileOpException {
+		// we're cheating
+		sendFileImmediately(f, mimetype, response);
+		/*
+		// create worker
+		DigilibSender job = new DigilibSender(f, null, response);
+		try {
+			logger.debug("queue size: "
+					+ ((DigilibManager) workQueue).getQueueSize());
+			workQueue.execute(job);
+			logger.debug("job sent!");
+			synchronized (job) {
+				while (job.isBusy()) {
+					job.wait();
+				}
+			}
+		} catch (InterruptedException e) {
+			throw new FileOpException("INTERRUPTED: Unable to send file. " + e);
+		}
+		*/
 
-}
+	}
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/Texter.java	Thu Oct 21 20:53:37 2004 +0200
@@ -0,0 +1,189 @@
+/* Texter.java -- Servlet for displaying text  
+ * Digital Image Library servlet components  
+ * Copyright (C) 2003 Robert Casties (robcast@mail.berlios.de)
+ *  
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.  Please read license.txt for the full details. A copy of
+ * the GPL may be found at http://www.gnu.org/copyleft/lgpl.html  
+ * You should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA  
+ * 
+ * Created on 15.09.2003 by casties  
+ */
+
+package digilib.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import digilib.auth.AuthOps;
+import digilib.io.DocuDirCache;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
+import digilib.io.TextFile;
+
+/**
+ * Servlet for displaying text
+ * 
+ * 
+ * @author casties
+ *  
+ */
+public class Texter extends HttpServlet {
+
+	private static final long serialVersionUID = -8539178734033662322L;
+
+	/** Servlet version */
+	public static String tlVersion = "0.1b1";
+
+	/** DigilibConfiguration instance */
+	DigilibConfiguration dlConfig = null;
+
+	/** general logger */
+	Logger logger = Logger.getLogger("digilib.texter");
+
+	/** FileOps instance */
+	FileOps fileOp;
+
+	/** AuthOps instance */
+	AuthOps authOp;
+
+	/** ServletOps instance */
+	ServletOps servletOp;
+
+	/** DocuDirCache instance */
+	DocuDirCache dirCache;
+
+	/** use authentication */
+	boolean useAuthentication = false;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+	 */
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+
+		System.out.println("***** Digital Image Library Text Servlet (version "
+				+ tlVersion + ") *****");
+
+		// get our ServletContext
+		ServletContext context = config.getServletContext();
+		// see if there is a Configuration instance
+		dlConfig = (DigilibConfiguration) context
+				.getAttribute("digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// no Configuration
+			throw new ServletException("No Configuration!");
+		}
+		// say hello in the log file
+		logger.info("***** Digital Image Library Text Servlet (version "
+				+ tlVersion + ") *****");
+
+		// set our AuthOps
+		useAuthentication = dlConfig.getAsBoolean("use-authorization");
+		authOp = (AuthOps) dlConfig.getValue("servlet.auth.op");
+		// DocuDirCache instance
+		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
+	 *      javax.servlet.http.HttpServletResponse)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
+	 *      javax.servlet.http.HttpServletResponse)
+	 */
+	protected void doPost(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException {
+		// create new request with defaults
+		DigilibRequest dlReq = new DigilibRequest();
+		// set with request parameters
+		dlReq.setWithRequest(request);
+		// add DigilibRequest to ServletRequest
+		request.setAttribute("digilib.servlet.request", dlReq);
+		// do the processing
+		processRequest(request, response);
+	}
+
+protected void processRequest(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException {
+
+		/*
+		 * request parameters
+		 */
+		DigilibRequest dlRequest = (DigilibRequest) request.getAttribute("digilib.servlet.request");
+		try {
+
+			/*
+			 * find the file to load/send
+			 */
+			TextFile f = getTextFile(dlRequest, "/txt");
+			if (f != null) {
+				ServletOps.sendFileImmediately(f.getFile(), null, response);
+			} else {
+				f = getTextFile(dlRequest, "");
+				if (f != null) {
+					ServletOps.sendFileImmediately(f.getFile(),	null, response);
+				} else {	
+					ServletOps.htmlMessage("No Text-File!", response);
+				}
+			}
+
+		} catch (FileOpException e) {
+			logger.error("ERROR: File IO Error: ", e);
+			try {
+				ServletOps.htmlMessage("ERROR: File IO Error: " + e, response);
+			} catch (FileOpException ex) {
+			} // so we don't get a loop
+		}
+	}	
+
+
+	/**
+	 * Looks for a file in the given subDirectory.
+	 * 
+	 * @param dlRequest
+	 *            The received request which has the file path.
+	 * @param subDirectory
+	 *            The subDirectory of the file path where the file should
+	 *            be found.
+	 * @return The wanted Textfile or null if there wasn't a file.
+	 */
+
+	private TextFile getTextFile(DigilibRequest dlRequest, String subDirectory) {
+		String loadPathName = dlRequest.getFilePath() + subDirectory;
+		// find the file(set)
+		return (TextFile) dirCache.getFile(loadPathName, dlRequest
+				.getAsInt("pn"), FileOps.CLASS_TEXT);
+	}
+}
\ No newline at end of file