changeset 569:1f666c2b4578

Merge with PDF branch 8beefd1142b28ca9b2433205e7eea82a517bb215
author robcast
date Tue, 21 Dec 2010 09:52:16 +0100
parents 9936604d466e (current diff) 8beefd1142b2 (diff)
children fd2ef7e46119
files client/digitallibrary/WEB-INF/classes/Relato.java client/digitallibrary/WEB-INF/lib/DigilibServlet.jar client/digitallibrary/WEB-INF/lib/imageinfo.jar client/digitallibrary/greyskin/digimage-neu.jsp client/digitallibrary/greyskin/digimage.jsp client/digitallibrary/greyskin/digimage_img_inc.jsp client/digitallibrary/greyskin/digimage_tbl_inc.jsp servlet/src/digilib/image/ImageOps.java servlet/src/digilib/servlet/DigilibImageWorker.java servlet/src/digilib/servlet/DigilibWorker.java servlet/src/digilib/servlet/Parameter.java servlet/src/digilib/servlet/ParameterMap.java servlet/src/digilib/servlet/Raster.java
diffstat 81 files changed, 3985 insertions(+), 4024 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.classpath	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.web.container"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/digilib-servlet"/>
+	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0">
+		<attributes>
+			<attribute name="owner.project.facets" value="jst.web"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/java-6-openjdk">
+		<attributes>
+			<attribute name="owner.project.facets" value="java"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="build/classes"/>
+</classpath>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.project	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>digilib-client</name>
+	<comment></comment>
+	<projects>
+		<project>digilib-servlet</project>
+		<project>digilib-client</project>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+	</natures>
+</projectDescription>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/.jsdtscope	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="digitallibrary"/>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.WebProject">
+		<attributes>
+			<attribute name="hide" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.baseBrowserLibrary"/>
+	<classpathentry kind="output" path=""/>
+</classpath>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.jdt.core.prefs	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,8 @@
+#Fri Oct 08 14:51:02 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.5
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.wst.common.component	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-modules id="moduleCoreId" project-version="1.5.0">
+    <wb-module deploy-name="digilib-client">
+        <wb-resource deploy-path="/" source-path="/digitallibrary"/>
+        <dependent-module archiveName="digilib-servlet.jar" deploy-path="/WEB-INF/lib" handle="module:/resource/digilib-servlet/digilib-servlet">
+            <dependency-type>uses</dependency-type>
+        </dependent-module>
+        <dependent-module deploy-path="/" handle="org.eclipse.jst.j2ee.componentcore.util.ClasspathDependencies">
+            <dependency-type>consumes</dependency-type>
+        </dependent-module>
+        <property name="context-root" value="digitallibrary"/>
+        <property name="java-output-path" value="/digilib-client/build/classes"/>
+    </wb-module>
+</project-modules>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.wst.common.project.facet.core.xml	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<faceted-project>
+  <runtime name="Apache Tomcat v7.0"/>
+  <fixed facet="wst.jsdt.web"/>
+  <fixed facet="java"/>
+  <fixed facet="jst.web"/>
+  <installed facet="java" version="1.5"/>
+  <installed facet="jst.web" version="2.5"/>
+  <installed facet="wst.jsdt.web" version="1.0"/>
+</faceted-project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.wst.jsdt.ui.superType.container	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,1 @@
+org.eclipse.wst.jsdt.launching.baseBrowserLibrary
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.wst.jsdt.ui.superType.name	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,1 @@
+Window
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/.settings/org.eclipse.wst.ws.service.policy.prefs	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,3 @@
+#Fri Oct 08 14:56:13 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.wst.ws.service.policy.projectEnabled=false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/digitallibrary/META-INF/MANIFEST.MF	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+
--- a/client/digitallibrary/WEB-INF/classes/Relato.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-import java.io.*;
-import java.net.*;
-import java.util.*;
-import javax.servlet.*;
-import javax.servlet.http.*;
-import javax.xml.transform.*;
-import javax.xml.transform.dom.*;
-import javax.xml.transform.stream.*;
-import org.apache.xerces.parsers.DOMParser;
-import org.apache.xpath.*;
-import org.w3c.dom.*;
-
-public class Relato extends HttpServlet {
-  
-	public final static String FS = System.getProperty("file.separator"); 
-
-	// Respond to HTTP GET requests from browsers.
-	public void doGet (HttpServletRequest request, HttpServletResponse response)
-                       throws ServletException, IOException {
-
-		Hashtable params = new Hashtable();
-		Enumeration enum = request.getParameterNames();
-		while (enum.hasMoreElements()) {
-			String pName = (String) enum.nextElement();
-			params.put(pName, request.getParameter(pName));
-
-		}
-		// Set content type for HTML.
-		response.setContentType("text/html; charset=UTF-8");    
-		// Output goes to the response PrintWriter.
-		PrintWriter out = response.getWriter();
-		DOMParser parser = new DOMParser();
-
-		try {	
-			TransformerFactory tFactory = TransformerFactory.newInstance();
-			//get the real path for xml and xsl files.
-			String ctx = getServletContext().getRealPath("") + FS;        
-
-			parser.parse(ctx + (String) params.get("xml"));
-			Document document = parser.getDocument();
-			
-			Element topFrameset = (Element) XPathAPI.selectSingleNode(document, "/relato/frames/frameset");
-			topFrameset.setAttribute("onload", "init();");
-			
-			NodeList nodelist = XPathAPI.selectNodeList(document, "//frameset/frame");
-			for (int i = 0; i < nodelist.getLength(); i++) {
-				Element elem = (Element)nodelist.item(i);
-				String name = elem.getAttribute("name");
-				if (name != "") {
-					if (params.containsKey(name)) {
-						String src  = (String) params.get(name);
-						elem.setAttribute("src", src);
-					}
-				}
-			}
-
-			Source xmlSource = new DOMSource (document);
-			Source xslSource = new StreamSource (new URL("file", "", ctx+"relato/relato.xsl").openStream());
-			// Generate the transformer.
-			Transformer transformer = tFactory.newTransformer(xslSource);
-			// Perform the transformation, sending the output to the response.
-			transformer.transform(xmlSource, new StreamResult(out));
-    	}
-
-		// If an Exception occurs, return the error to the client.
-		catch (Exception e) {
-			out.write(e.getMessage());
-			e.printStackTrace(out);    
-		}
-
-		// Close the PrintWriter.
-		out.close();
-	}
-}
--- a/client/digitallibrary/WEB-INF/digilib-config.xml	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/WEB-INF/digilib-config.xml	Tue Dec 21 09:52:16 2010 +0100
@@ -58,5 +58,15 @@
   
   <!-- location of logger config file -->
   <parameter name="log-config-file" value="log4j-config.xml"/>
+
+  <!-- location for PDF files while still in progress -->
+  <parameter name="pdf-temp-dir" value="/tmp/pdf_tmp/" />
+  
+  <!-- location for PDF files upon completion -->
+  <parameter name="pdf-cache-dir" value="/tmp/pdf_cache/"/>
+
+  <!-- logo for PDFs -->
+  <parameter name="pdf-logo" value="http://www.mpiwg-berlin.mpg.de/de/images/logo.png" />
+
   
 </digilib-config>
Binary file client/digitallibrary/WEB-INF/lib/DigilibServlet.jar has changed
Binary file client/digitallibrary/WEB-INF/lib/imageinfo.jar has changed
--- a/client/digitallibrary/WEB-INF/web.xml	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/WEB-INF/web.xml	Tue Dec 21 09:52:16 2010 +0100
@@ -54,40 +54,16 @@
             digilib.servlet.Texter
         </servlet-class>
   </servlet>
-  <!-- The Raster servlet -->
-  <servlet>
-        <servlet-name>
-            Raster
-        </servlet-name>
-        <description>
-            The servlet for rastered SVG.
-        </description>
-        <servlet-class>
-            digilib.servlet.Raster
-        </servlet-class>
-  </servlet>
-  <!-- The Mapper servlet -->
+  <!-- The PDFCache servlet -->
   <servlet>
         <servlet-name>
-            Mapper
+            PDFCache
         </servlet-name>
         <description>
-            The servlet to create image maps.
+            The servlet for PDF.
         </description>
         <servlet-class>
-            digilib.servlet.Mapper
-        </servlet-class>
-  </servlet>
-  <!-- The Relato servlet -->
-  <servlet>
-        <servlet-name>
-            Relato
-        </servlet-name>
-        <description>
-            The relato servlet
-        </description>
-        <servlet-class>
-            Relato
+            digilib.servlet.PDFCache
         </servlet-class>
   </servlet>
   <!-- We want to mess around with the default JSP servlet... -->
@@ -145,47 +121,21 @@
             /authenticated/servlet/Texter/*
         </url-pattern>
   </servlet-mapping>
-  <!-- The mapping for the Raster servlet -->
+  <!-- The mapping for the Texter servlet -->
   <servlet-mapping>
         <servlet-name>
-            Raster
+            PDFCache
         </servlet-name>
         <url-pattern>
-            /servlet/Raster/*
+            /servlet/PDFCache/*
         </url-pattern>
   </servlet-mapping>
   <servlet-mapping>
         <servlet-name>
-            Raster
-        </servlet-name>
-        <url-pattern>
-            /authenticated/servlet/Raster/*
-        </url-pattern>
-  </servlet-mapping>
-  <!-- The mapping for the Mapper servlet -->
-  <servlet-mapping>
-        <servlet-name>
-            Mapper
+            PDFCache
         </servlet-name>
         <url-pattern>
-            /servlet/Mapper/*
-        </url-pattern>
-  </servlet-mapping>
-  <servlet-mapping>
-        <servlet-name>
-            Mapper
-        </servlet-name>
-        <url-pattern>
-            /authenticated/servlet/Mapper/*
-        </url-pattern>
-  </servlet-mapping>
-  <!-- The mapping for the Relato servlet -->
-  <servlet-mapping>
-        <servlet-name>
-            Relato
-        </servlet-name>
-        <url-pattern>
-            /Relato
+            /authenticated/servlet/PDFCache/*
         </url-pattern>
   </servlet-mapping>
    <!-- The mapping for the JSP servlet -->
--- a/client/digitallibrary/digilib-new.html	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/digilib-new.html	Tue Dec 21 09:52:16 2010 +0100
@@ -37,7 +37,7 @@
 
 <body>
 
-<div id="buttons" 
+<div id="buttons">
 	<div class="button">
 		<a
 			class="icon"
--- a/client/digitallibrary/digimage.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/digimage.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -26,7 +26,7 @@
 // add number of pages
 dlRequest.setValue("pt", docBean.getNumPages());
 // store objects for jsp:include
-pageContext.setAttribute("docBean", docBean, pageContext.REQUEST_SCOPE);
+pageContext.setAttribute("docBean", docBean, PageContext.REQUEST_SCOPE);
 %><html>
 <head>
     <title>Digital Document Library (L1)</title>
--- a/client/digitallibrary/dlInfo-js.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/dlInfo-js.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -19,7 +19,7 @@
 %>
 // Automatically generated JavaScript snippet with parameters
 <%
-    Object[] keys = dlRequest.keySet().toArray();
+    Object[] keys = dlRequest.getParams().keySet().toArray();
     java.util.Arrays.sort(keys);
     int l = keys.length;
     for (int i = 0; i < l; i++) {
--- a/client/digitallibrary/dlInfo-xml.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/dlInfo-xml.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -23,7 +23,7 @@
 %><!-- Automatically generated XML snippet with document parameters -->
 <document-parameters>
 <%
-    Object[] keys = dlRequest.keySet().toArray();
+    Object[] keys = dlRequest.getParams().keySet().toArray();
     java.util.Arrays.sort(keys);
     int l = keys.length;
     for (int i = 0; i < l; i++) {
--- a/client/digitallibrary/greyskin/digimage-neu.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,443 +0,0 @@
-<%@ page language="java" %><%!
-// -- JSP init -------------
-
-// create DocumentBean instance for all JSP requests
-digilib.servlet.DocumentBean docBean = new digilib.servlet.DocumentBean();
-
-// initialize DocumentBean instance in JSP init
-public void jspInit() {
-    try {
-        // set servlet init-parameter
-        docBean.setConfig(getServletConfig());
-    } catch (javax.servlet.ServletException e) {
-        System.out.println(e);
-    }
-}
-// -- end of JSP init -------------
-%><%
-// -- JSP request -------------
-
-// parsing the query
-// -----------------
-digilib.servlet.DigilibRequest dlRequest = new digilib.servlet.DigilibRequest(request);
-docBean.setRequest(dlRequest);
-// check if authentication is needed and redirect if necessary
-docBean.doAuthentication(response);
-// add number of pages
-dlRequest.setValue("pt", docBean.getNumPages());
-// store objects for jsp:include
-pageContext.setAttribute("docBean", docBean, pageContext.REQUEST_SCOPE);
-%><html>
-<head>
-    <title>Digital Document Library NG</title>
-    
-    	<style type="text/css">
-		body		{ background-color: #E0E0E0; color: black; font-size: 8pt }
-		code		{ font-family: monospace; color: blue; }
-		pre		{ color: #006060; }
-		img.png 	{ border: none; }
-		a.icon		{ margin: 0px; padding: 0px; }
-		div.button	{ margin: -4px; padding: 0px; }
-		div#scaler-table { padding-right: 40px; }
-		div#buttons	{ position: absolute; right: 10px; top: 10px; }
-		
-	</style>
-	
-
-    <script type="text/javascript" src="../baselib.js"></script>
-
-    <script type="text/javascript" src="../dllib.js"></script>
-
-<script language="JavaScript">
-
-	function highlightPNG(id, on) {
-		var elem = document.getElementById(id);
-		//var div  = elem.parentNode.parentNode;
-		elem.style.backgroundImage = on 
-			? "url('corona.png')"
-			: null;
-	}
-
-</script>
-
-<script type="text/javascript">
-  base_init();
-  var dlTarget = window.name;
-  var baseUrl = '<%= dlRequest.getAsString("base.url") %>';
-  var toolbarEnabledURL = window.location.href;
-  newParameter('fn', '', 1);
-  newParameter('pn', '1', 1);
-  newParameter('ws', '1.0', 1);
-  newParameter('mo', '', 1);
-  newParameter('mk', '', 3);
-  newParameter('wx', '0.0', 2);
-  newParameter('wy', '0.0', 2);
-  newParameter('ww', '1.0', 2);
-  newParameter('wh', '1.0', 2);
-  newParameter('pt', '<%= dlRequest.getAsString("pt") %>', 1);
-  newParameter('brgt', '0.0', 1);
-  newParameter('cont', '0.0', 1);
-  newParameter('rot', '0.0', 1);
-  newParameter('rgba', '', 1);
-  newParameter('rgbm', '', 1);
-  newParameter('ddpix', '', 1);
-  newParameter('ddpiy', '', 1);
-  document.id='digilib';
-  dl_param_init();
-</script>
-</head>
-
-<body bgcolor="#666666" onload="dl_init();">
-<div id="scaler-table">
-	<% if (dlRequest.hasOption("clop", "noarrows")) {
-	%><jsp:include page="digimage_img_inc.jsp" /><%
-	} else {
-	%><jsp:include page="digimage_tbl_inc.jsp" /><%
-	}
-	%>
-</div>
-
- <div id="dot0" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark1.gif" border="0"></div>
- <div id="dot1" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark2.gif" border="0"></div>
- <div id="dot2" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark3.gif" border="0"></div>
- <div id="dot3" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark4.gif" border="0"></div>
- <div id="dot4" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark5.gif" border="0"></div>
- <div id="dot5" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark6.gif" border="0"></div>
- <div id="dot6" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark7.gif" border="0"></div>
- <div id="dot7" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/mark8.gif" border="0"></div>
- <div id="eck1" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/olinks.gif" border="0"></div>
- <div id="eck2" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/orechts.gif" border="0"></div>
- <div id="eck3" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/ulinks.gif" border="0"></div>
- <div id="eck4" style="position:absolute; left:-20; top:100; visibility:hidden"><img src="../img/urechts.gif" border="0"></div>
- 
- 
- <div id="buttons" 
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:showOptions(0);setMark()"
-			>
-
-			<img
-				class="png"
-				id="mark"
-				onmouseover="highlightPNG('mark', 1)"
-				onmouseout="highlightPNG('mark', 0)"
-				title="set a mark"
-				src="mark.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:removeMark()"
-			>
-
-			<img
-				class="png"
-				id="delmark"
-				onmouseover="highlightPNG('delmark', 1)"
-				onmouseout="highlightPNG('delmark', 0)"
-				title="delete the last mark"
-				src="delmark.png"
-				>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:getRefWin()"
-			>
-
-			<img
-				class="png"
-				id="reference"
-				onmouseover="highlightPNG('reference', 1)"
-				onmouseout="highlightPNG('reference', 0)"
-				title="get a reference URL"
-				src="reference.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomBy(1.4)"
-			>
-
-			<img
-				class="png"
-				id="zoom-in"
-				onmouseover="highlightPNG('zoom-in', 1)"
-				onmouseout="highlightPNG('zoom-in', 0)"
-				title="zoom in"
-				src="zoom-in.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomBy(0.7)"
-			>
-
-			<img
-				class="png"
-				id="zoom-out"
-				onmouseover="highlightPNG('zoom-out', 1)"
-				onmouseout="highlightPNG('zoom-out', 0)"
-				title="zoom out"
-				src="zoom-out.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:showOptions(0);zoomArea()"
-			>
-
-			<img
-				class="png"
-				id="zoom-area"
-				onmouseover="highlightPNG('zoom-area', 1)"
-				onmouseout="highlightPNG('zoom-area', 0)"
-				title="zoom area"
-				src="zoom-area.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomFullpage()"
-			>
-
-			<img
-				class="png"
-				id="zoom-full"
-				onmouseover="highlightPNG('zoom-full', 1)"
-				onmouseout="highlightPNG('zoom-full', 0)"
-				title="view the whole image"
-				src="zoom-full.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:mirror('h')"
-			>
-
-			<img
-				class="png"
-				id="mirror-h"
-				onmouseover="highlightPNG('mirror-h', 1)"
-				onmouseout="highlightPNG('mirror-h', 0)"
-				title="mirror horizontally"
-				src="mirror-horizontal.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:mirror('v')"
-			>
-
-			<img
-				class="png"
-				id="mirror-v"
-				onmouseover="highlightPNG('mirror-v', 1)"
-				onmouseout="highlightPNG('mirror-v', 0)"
-				title="mirror vertically"
-				src="mirror-vertical.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('rot', 'Rotate (0..360) clockwise')"
-			>
-
-			<img
-				class="png"
-				id="rotate"
-				onmouseover="highlightPNG('rotate', 1)"
-				onmouseout="highlightPNG('rotate', 0)"
-				title="rotate image"
-				src="rotate.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('brgt', 'Brightness (-255..255)')"
-			>
-
-			<img
-				class="png"
-				id="brightness"
-				onmouseover="highlightPNG('brightness', 1)"
-				onmouseout="highlightPNG('brightness', 0)"
-				title="set brightness"
-				src="brightness.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('cont', 'Contrast (0..8)')"
-			>
-
-			<img
-				class="png"
-				id="contrast"
-				onmouseover="highlightPNG('contrast', 1)"
-				onmouseout="highlightPNG('contrast', 0)"
-				title="set contrast"
-				src="contrast.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('rgb', '...')"
-			>
-
-			<img
-				class="png"
-				id="rgb"
-				onmouseover="highlightPNG('rgb', 1)"
-				onmouseout="highlightPNG('rgb', 0)"
-				title="set rgb values"
-				src="rgb.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('size', '...')"
-			>
-
-			<img
-				class="png"
-				id="size"
-				onmouseover="highlightPNG('size', 1)"
-				onmouseout="highlightPNG('size', 0)"
-				title="resize page"
-				src="size.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setQualityWin('Quality (0..2)')"
-			>
-
-			<img
-				class="png"
-				id="quality"
-				onmouseover="highlightPNG('quality', 1)"
-				onmouseout="highlightPNG('quality', 0)"
-				title="set image quality"
-				src="quality.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPage('-1')"
-			>
-
-			<img
-				class="png"
-				id="back"
-				onmouseover="highlightPNG('back', 1)"
-				onmouseout="highlightPNG('back', 0)"
-				title="goto previous image"
-				src="back.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPage('+1')"
-			>
-
-			<img
-				class="png"
-				id="fwd"
-				onmouseover="highlightPNG('fwd', 1)"
-				onmouseout="highlightPNG('fwd', 0)"
-				title="goto next image"
-				src="fwd.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPageWin()"
-			>
-
-			<img
-				class="png"
-				id="page"
-				onmouseover="highlightPNG('page', 1)"
-				onmouseout="highlightPNG('page', 0)"
-				title="specify image"
-				src="page.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:help()"
-			>
-
-			<img
-				class="png"
-				id="help"
-				onmouseover="highlightPNG('help', 1)"
-				onmouseout="highlightPNG('help', 0)"
-				title="help"
-				src="help.png"
-			>
-		</a>
-	</div>
-	
-</div>
-
-
-</body>
-
-</html>
--- a/client/digitallibrary/greyskin/digimage.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,459 +0,0 @@
-<%@ page language="java" %><%!
-	// -- JSP init -------------
-	
-	// create DocumentBean instance for all JSP requests
-	digilib.servlet.DocumentBean docBean = new digilib.servlet.DocumentBean();
-	
-	// initialize DocumentBean instance in JSP init
-	public void jspInit() {
-	    try {
-		// set servlet init-parameter
-		docBean.setConfig(getServletConfig());
-	    } catch (javax.servlet.ServletException e) {
-		System.out.println(e);
-	    }
-	}
-	// -- end of JSP init -------------
-%>
-
-<%
-	// -- JSP request -------------
-	
-	// parsing the query
-	// -----------------
-	digilib.servlet.DigilibRequest dlRequest = new digilib.servlet.DigilibRequest(request);
-	docBean.setRequest(dlRequest);
-	// check if authentication is needed and redirect if necessary
-	docBean.doAuthentication(response);
-	// add number of pages
-	dlRequest.setValue("pt", docBean.getNumPages());
-	// store objects for jsp:include
-	pageContext.setAttribute("docBean", docBean, pageContext.REQUEST_SCOPE);
-%>
-
-<html>
-
-<head>
-	<title>Digital Document Library NG</title>
-	
-	<style type="text/css">
-
-		body		{ background-color: #E0E0E0; color: black; font-size: 8pt }
-		code		{ font-family: monospace; color: blue; }
-		pre		{ color: #006060; }
-
-		a.icon		{ margin: 0px; padding: 0px; }
-
-		img.png 	{ border: none; }
-		img.mark 	{ border: none; }
-
-		div.button	{ margin: -4px; padding: 0px; }
-		div.dot		{ position: absolute; left: -20; top: 100; visibility: hidden }		div#scaler-table { padding-right: 40px; }
-
-		div#buttons	{ position: absolute; right: 10px; top: 10px; }
-		
-	</style>
-	
-	<script type="text/javascript" src="../baselib.js"></script>
-	
-	<script type="text/javascript" src="../dllib.js"></script>
-
-	<script language="JavaScript">
-		function highlightPNG(id, on) {
-			var elem = document.getElementById(id);
-			//var div  = elem.parentNode.parentNode;
-			elem.style.backgroundImage = on 
-				? "url('corona.png')"
-				: null;
-		}
-
-		base_init();
-		var dlTarget = window.name;
-		var baseUrl = '<%= dlRequest.getAsString("base.url") %>';
-		var toolbarEnabledURL = window.location.href;
-		newParameter('fn', '', 1);
-		newParameter('pn', '1', 1);
-		newParameter('ws', '1.0', 1);
-		newParameter('mo', '', 1);
-		newParameter('mk', '', 3);
-		newParameter('wx', '0.0', 2);
-		newParameter('wy', '0.0', 2);
-		newParameter('ww', '1.0', 2);
-		newParameter('wh', '1.0', 2);
-		newParameter('pt', '<%= dlRequest.getAsString("pt") %>', 1);
-		newParameter('brgt', '0.0', 1);
-		newParameter('cont', '0.0', 1);
-		newParameter('rot', '0.0', 1);
-		newParameter('rgba', '', 1);
-		newParameter('rgbm', '', 1);
-		newParameter('ddpix', '', 1);
-		newParameter('ddpiy', '', 1);
-		document.id='digilib';
-		dl_param_init();
-		
-		function init() {
-			dl_init();
-			var scaler = getElement('scaler');
-			var pic = getElement('pic');
-			var ps = bestPicSize(scaler, 50);
-			var src = "../servlet/Scaler?fn=&dw=" + ps.width + "&dh=" + ps.height;
-			pic.src = src;
-			}
-	</script>
-</head>
-
-<body onload="init();">
-
- <div id="scaler-table">
- 	<div id="scaler" style="visibility:visible">
-		<img id="pic"></img>
-	</div>
- </div>
-
- <!-- marks as dynamically created divs with numbers or text? -->
- <div id="dot0" class="dot"><img class="mark" src="../img/mark1.gif" ></div>
- <div id="dot1" class="dot"><img class="mark" src="../img/mark2.gif" ></div>
- <div id="dot2" class="dot"><img class="mark" src="../img/mark3.gif" ></div>
- <div id="dot3" class="dot"><img class="mark" src="../img/mark4.gif" ></div>
- <div id="dot4" class="dot"><img class="mark" src="../img/mark5.gif" ></div>
- <div id="dot5" class="dot"><img class="mark" src="../img/mark6.gif" ></div>
- <div id="dot6" class="dot"><img class="mark" src="../img/mark7.gif" ></div>
- <div id="dot7" class="dot"><img class="mark" src="../img/mark8.gif" ></div>
-
- <!-- zoom area with a transparent div ? -->
- <div id="eck1" class="dot"><img class="mark" src="../img/olinks.gif" ></div>
- <div id="eck2" class="dot"><img class="mark" src="../img/orechts.gif" ></div>
- <div id="eck3" class="dot"><img class="mark" src="../img/ulinks.gif" ></div>
- <div id="eck4" class="dot"><img class="mark" src="../img/urechts.gif" ></div>
- 
- 
- <div id="buttons" 
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:showOptions(0);setMark()"
-			>
-
-			<img
-				class="png"
-				id="mark"
-				onmouseover="highlightPNG('mark', 1)"
-				onmouseout="highlightPNG('mark', 0)"
-				title="set a mark"
-				src="mark.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:removeMark()"
-			>
-
-			<img
-				class="png"
-				id="delmark"
-				onmouseover="highlightPNG('delmark', 1)"
-				onmouseout="highlightPNG('delmark', 0)"
-				title="delete the last mark"
-				src="delmark.png"
-				>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:getRefWin()"
-			>
-
-			<img
-				class="png"
-				id="reference"
-				onmouseover="highlightPNG('reference', 1)"
-				onmouseout="highlightPNG('reference', 0)"
-				title="get a reference URL"
-				src="reference.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomBy(1.4)"
-			>
-
-			<img
-				class="png"
-				id="zoom-in"
-				onmouseover="highlightPNG('zoom-in', 1)"
-				onmouseout="highlightPNG('zoom-in', 0)"
-				title="zoom in"
-				src="zoom-in.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomBy(0.7)"
-			>
-
-			<img
-				class="png"
-				id="zoom-out"
-				onmouseover="highlightPNG('zoom-out', 1)"
-				onmouseout="highlightPNG('zoom-out', 0)"
-				title="zoom out"
-				src="zoom-out.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:showOptions(0);zoomArea()"
-			>
-
-			<img
-				class="png"
-				id="zoom-area"
-				onmouseover="highlightPNG('zoom-area', 1)"
-				onmouseout="highlightPNG('zoom-area', 0)"
-				title="zoom area"
-				src="zoom-area.png"
-			>
-		</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:zoomFullpage()"
-			>
-
-			<img
-				class="png"
-				id="zoom-full"
-				onmouseover="highlightPNG('zoom-full', 1)"
-				onmouseout="highlightPNG('zoom-full', 0)"
-				title="view the whole image"
-				src="zoom-full.png"
-			>
-	</a> 
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:mirror('h')"
-			>
-
-			<img
-				class="png"
-				id="mirror-h"
-				onmouseover="highlightPNG('mirror-h', 1)"
-				onmouseout="highlightPNG('mirror-h', 0)"
-				title="mirror horizontally"
-				src="mirror-horizontal.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:mirror('v')"
-			>
-
-			<img
-				class="png"
-				id="mirror-v"
-				onmouseover="highlightPNG('mirror-v', 1)"
-				onmouseout="highlightPNG('mirror-v', 0)"
-				title="mirror vertically"
-				src="mirror-vertical.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('rot', 'Rotate (0..360) clockwise')"
-			>
-
-			<img
-				class="png"
-				id="rotate"
-				onmouseover="highlightPNG('rotate', 1)"
-				onmouseout="highlightPNG('rotate', 0)"
-				title="rotate image"
-				src="rotate.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('brgt', 'Brightness (-255..255)')"
-			>
-
-			<img
-				class="png"
-				id="brightness"
-				onmouseover="highlightPNG('brightness', 1)"
-				onmouseout="highlightPNG('brightness', 0)"
-				title="set brightness"
-				src="brightness.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('cont', 'Contrast (0..8)')"
-			>
-
-			<img
-				class="png"
-				id="contrast"
-				onmouseover="highlightPNG('contrast', 1)"
-				onmouseout="highlightPNG('contrast', 0)"
-				title="set contrast"
-				src="contrast.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('rgb', '...')"
-			>
-
-			<img
-				class="png"
-				id="rgb"
-				onmouseover="highlightPNG('rgb', 1)"
-				onmouseout="highlightPNG('rgb', 0)"
-				title="set rgb values"
-				src="rgb.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setParamWin('size', '...')"
-			>
-
-			<img
-				class="png"
-				id="size"
-				onmouseover="highlightPNG('size', 1)"
-				onmouseout="highlightPNG('size', 0)"
-				title="resize page"
-				src="size.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:setQualityWin('Quality (0..2)')"
-			>
-
-			<img
-				class="png"
-				id="quality"
-				onmouseover="highlightPNG('quality', 1)"
-				onmouseout="highlightPNG('quality', 0)"
-				title="set image quality"
-				src="quality.png"
-			>
-		</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPage('-1')"
-			>
-
-			<img
-				class="png"
-				id="back"
-				onmouseover="highlightPNG('back', 1)"
-				onmouseout="highlightPNG('back', 0)"
-				title="goto previous image"
-				src="back.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPage('+1')"
-			>
-
-			<img
-				class="png"
-				id="fwd"
-				onmouseover="highlightPNG('fwd', 1)"
-				onmouseout="highlightPNG('fwd', 0)"
-				title="goto next image"
-				src="fwd.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:gotoPageWin()"
-			>
-
-			<img
-				class="png"
-				id="page"
-				onmouseover="highlightPNG('page', 1)"
-				onmouseout="highlightPNG('page', 0)"
-				title="specify image"
-				src="page.png"
-			>
-	</a>
-	</div>
-	
-	<div class="button">
-		<a
-			class="icon"
-			href="javascript:help()"
-			>
-
-			<img
-				class="png"
-				id="help"
-				onmouseover="highlightPNG('help', 1)"
-				onmouseout="highlightPNG('help', 0)"
-				title="help"
-				src="help.png"
-			>
-		</a>
-	</div>
-	
-</div>
-
-
-</body>
-
-</html>
--- a/client/digitallibrary/greyskin/digimage_img_inc.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-<%@ page language="java" %><%
-// retrieve objects from context
-digilib.servlet.DocumentBean docBean = (digilib.servlet.DocumentBean) pageContext.getAttribute("docBean", pageContext.REQUEST_SCOPE);
-digilib.servlet.DigilibRequest dlRequest = docBean.getRequest();
-String ua = request.getHeader("User-Agent");
-boolean isN4 = ((ua.indexOf("Mozilla/4.") > -1)&&(ua.indexOf("MSIE") == -1));
-%>
-<%
-    if (isN4) {
-%><ilayer name="scaler"><%
-	    } else {
-%><div id="scaler"><%
-	    }
-%>
-<script type="text/javascript">
-var ps = bestPicSize(getElement('scaler'), 50);
-document.write('<img id="pic" src="<%= dlRequest.getAsString("base.url") + "/servlet/Scaler?" + dlRequest.getAsString('s') %>&dw='+ps.width+'&dh='+ps.height+'" />');
-</script>
-<%   
-    if (isN4) {
-%></ilayer><%
-	    } else {
-%></div><%
-	    }
-%>
--- a/client/digitallibrary/greyskin/digimage_tbl_inc.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-<%@ page language="java" %><%
-// retrieve objects from context
-digilib.servlet.DocumentBean docBean = (digilib.servlet.DocumentBean) pageContext.getAttribute("docBean", pageContext.REQUEST_SCOPE);
-digilib.servlet.DigilibRequest dlRequest = docBean.getRequest();
-String ua = request.getHeader("User-Agent");
-boolean isN4 = ((ua.indexOf("Mozilla/4.") > -1)&&(ua.indexOf("MSIE") == -1));
-%>
-<table border="0"  cellpadding="0" cellspacing="0">
-  <tr>
-    <td></td>
-    <td align="center"><%
-      if (docBean.canMoveUp()) {
-        %><a href="javascript:moveBy(0, -0.5)"><img src="../img/up.gif" border="0" /></a><%
-      }
-      %></td>
-      <td></td>
-  </tr>
-  <tr>
-    <td valign="center"><%
-      if (docBean.canMoveLeft()) {
-        %><a href="javascript:moveBy(-0.5, 0)"><img src="../img/left.gif" border="0" /></a><%
-      }
-      %></td>
-	  <td>
-<%
-	  if(isN4) {
-	      %><ilayer name="scaler"><%
-		  } else {
-	      %><div id="scaler" style="visibility:visible"><%
-		  }
-%>
-<script type="text/javascript">
-var ps = bestPicSize(getElement('scaler'), 50);
-document.write('<img id="pic" src="<%=
-  dlRequest.getAsString("base.url") + "/../servlet/Scaler?" + dlRequest.getAsString('s')
-%>&dw='+ps.width+'&dh='+ps.height+'" />');
-</script>
-<% 
-    if(isN4) {
-	%></ilayer><%
-	    } else {
-	%></div><%
-	    }
-%>
-    </td>
-    <td valign="center"><%
-      if (docBean.canMoveRight()) {
-        %><a href="javascript:moveBy(0.5, 0)"><img src="../img/right.gif" border="0" /></a><%
-      }
-      %></td>
-  </tr>
-  <tr>
-    <td></td>
-    <td align="center"><%
-      if (docBean.canMoveDown()) {
-        %><a href="javascript:moveBy(0, 0.5)"><img src="../img/down.gif" border="0" /></a><%
-      }
-      %></td>
-    <td></td>
-  </tr>
-</table>
--- a/client/digitallibrary/greyskin/diginew.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/greyskin/diginew.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -28,7 +28,7 @@
 	// add number of pages
 	dlRequest.setValue("pt", docBean.getNumPages());
 	// store objects for jsp:include
-	pageContext.setAttribute("docBean", docBean, pageContext.REQUEST_SCOPE);
+	pageContext.setAttribute("docBean", docBean, PageContext.REQUEST_SCOPE);
 
 %><html xmlns="http://www.w3.org/1999/xhtml">
 <head>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/digitallibrary/pdf/error.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,13 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+    pageEncoding="ISO-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<title>Error</title>
+</head>
+<body>
+<h1>Error</h1>
+There was an error while processing your request. Please check the parameters and try again.
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/digitallibrary/pdf/wip.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,14 @@
+<%@ page language="java" contentType="text/html; charset=UTF-8"
+    pageEncoding="ISO-8859-1"%>
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta http-equiv="refresh" content="10">
+<title>Work in progress ...</title>
+</head>
+<body>
+<h1>Please wait ...</h1>
+your request is being processed. The download starts automatically when the document is finished. 
+</body>
+</html>
--- a/client/digitallibrary/server/dlConfig.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/server/dlConfig.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -1,3 +1,4 @@
+<%@page import="digilib.util.DigilibJobCenter"%>
 <%@ page language="java" %>
 
 <%!
@@ -26,6 +27,9 @@
 dlRequest.setValue("pt", docBean.getNumPages(dlRequest));
 // dir cache
 digilib.io.DocuDirCache dirCache = (digilib.io.DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+// image JobCenter
+DigilibJobCenter imageProcessor =  (DigilibJobCenter)dlConfig.getValue("servlet.worker.imageexecutor");        
+
 %>
 
 <html>
@@ -38,7 +42,7 @@
 
 <table>
 <%
-    Object[] keys = dlConfig.keySet().toArray();
+    Object[] keys = dlConfig.getParams().keySet().toArray();
     java.util.Arrays.sort(keys);
     int l = keys.length;
     for (int i = 0; i < l; i++) {
@@ -70,11 +74,11 @@
 
 <table>
   <tr>
-    <td>currently waiting</td><td><b><%= digilib.servlet.DigilibWorker.getNumWaiting() %></b></td>
+    <td>currently waiting</td><td><b><%= imageProcessor.getWaitingJobs() %></b></td>
     <td></td>
   </tr>
   <tr>
-    <td>currently running</td><td><b><%= digilib.servlet.DigilibWorker.getNumRunning() %></b></td>
+    <td>currently running</td><td><b><%= imageProcessor.getRunningJobs() %></b></td>
     <td></td>
   </tr>
 </table>
--- a/client/digitallibrary/server/dlRequest.jsp	Wed Jul 14 16:36:42 2010 +0200
+++ b/client/digitallibrary/server/dlRequest.jsp	Tue Dec 21 09:52:16 2010 +0100
@@ -93,7 +93,7 @@
 
 <table>
 <%
-    Object[] keys = dlRequest.keySet().toArray();
+    Object[] keys = dlRequest.getParams().keySet().toArray();
     java.util.Arrays.sort(keys);
     int l = keys.length;
     for (int i = 0; i < l; i++) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/.classpath	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v7.0"/>
+	<classpathentry kind="con" path="org.eclipse.jst.j2ee.internal.module.container"/>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/ImageInfo">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/iText">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JAI">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JAI-ImageIO">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/JDOM">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry exported="true" kind="con" path="org.eclipse.jdt.USER_LIBRARY/Log4J">
+		<attributes>
+			<attribute name="org.eclipse.jst.component.dependency" value="../"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/.project	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>digilib-servlet</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+	</natures>
+</projectDescription>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/.settings/org.eclipse.jdt.core.prefs	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,12 @@
+#Tue Oct 12 18:02:03 CEST 2010
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.6
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/.settings/org.eclipse.wst.common.component	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-modules id="moduleCoreId" project-version="1.5.0">
+    <wb-module deploy-name="digilib-servlet">
+        <wb-resource deploy-path="/" source-path="/src"/>
+    </wb-module>
+</project-modules>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/.settings/org.eclipse.wst.common.project.facet.core.xml	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<faceted-project>
+  <fixed facet="jst.utility"/>
+  <fixed facet="java"/>
+  <installed facet="java" version="1.6"/>
+  <installed facet="jst.utility" version="1.0"/>
+</faceted-project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/META-INF/MANIFEST.MF	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Class-Path: 
+
--- a/servlet/src/digilib/auth/AuthOps.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/auth/AuthOps.java	Tue Dec 21 09:52:16 2010 +0100
@@ -81,7 +81,7 @@
 	 * @throws AuthOpException Exception thrown on error.
 	 * @return List of Strings with role names.
 	 */
-	public List rolesForPath(String filepath, HttpServletRequest request)
+	public List<String> rolesForPath(String filepath, HttpServletRequest request)
 		throws AuthOpException;
 
 	/** Authorization roles needed for request.
@@ -95,7 +95,7 @@
 	 * @throws AuthOpException Exception thrown on error.
 	 * @return List of Strings with role names.
 	 */
-	public List rolesForPath(DigilibRequest request)
+	public List<String> rolesForPath(DigilibRequest request)
 		throws AuthOpException;
 
 	/** Test request authorization against a list of roles.
@@ -103,13 +103,13 @@
 	 * @param request ServletRequest with address information.
 	 * @return true if the user information in the request authorizes one of the roles.
 	 */
-	public boolean isRoleAuthorized(List roles, HttpServletRequest request);
+	public boolean isRoleAuthorized(List<String> roles, HttpServletRequest request);
 
 	/** Test request authorization against a list of roles.
 	 * @param roles List of Strings with role names.
 	 * @param request ServletRequest with address information.
 	 * @return true if the user information in the request authorizes one of the roles.
 	 */
-	public boolean isRoleAuthorized(List roles, DigilibRequest request);
+	public boolean isRoleAuthorized(List<String> roles, DigilibRequest request);
 
 }
--- a/servlet/src/digilib/auth/AuthOpsImpl.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/auth/AuthOpsImpl.java	Tue Dec 21 09:52:16 2010 +0100
@@ -21,7 +21,6 @@
 package digilib.auth;
 
 import java.util.List;
-import java.util.ListIterator;
 
 import javax.servlet.http.HttpServletRequest;
 
@@ -56,7 +55,7 @@
    */
   public boolean isAuthRequired(String filepath, HttpServletRequest request) throws AuthOpException {
     // check permissions
-    List rolesRequired = rolesForPath(filepath, request);
+    List<String> rolesRequired = rolesForPath(filepath, request);
     return (rolesRequired != null);
   }
 
@@ -66,7 +65,7 @@
   public boolean isAuthRequired(DigilibRequest request)
 	  throws AuthOpException {
 		// check permissions
-		List rolesRequired = rolesForPath(request);
+		List<String> rolesRequired = rolesForPath(request);
 		return (rolesRequired != null);
   }
 
@@ -80,7 +79,7 @@
    * @return List of Strings with role names.
    */
   public boolean isAuthorized(String filepath, HttpServletRequest request) throws AuthOpException {
-    List rolesAllowed = rolesForPath(filepath, request);
+    List<String> rolesAllowed = rolesForPath(filepath, request);
     return isRoleAuthorized(rolesAllowed, request);
   }
 
@@ -89,7 +88,7 @@
    */
   public boolean isAuthorized(DigilibRequest request)
 	  throws AuthOpException {
-		List rolesAllowed = rolesForPath(request);
+		List<String> rolesAllowed = rolesForPath(request);
 		return isRoleAuthorized(rolesAllowed, request);
   }
 
@@ -98,11 +97,8 @@
    * @param request ServletRequest with address information.
    * @return true if the user information in the request authorizes one of the roles.
    */
-  public boolean isRoleAuthorized(List roles, HttpServletRequest request) {
-    ListIterator r = roles.listIterator();
-    String s = "";
-    while (r.hasNext()) {
-      s = (String)r.next();
+  public boolean isRoleAuthorized(List<String> roles, HttpServletRequest request) {
+    for (String s: roles) {
       logger.debug("Testing role: "+s);
       if (request.isUserInRole(s)) {
       	logger.debug("Role Authorized");
@@ -115,11 +111,8 @@
   /**
    * @see digilib.auth.AuthOps#isRoleAuthorized(java.util.List, digilib.servlet.DigilibRequest)
    */
-  public boolean isRoleAuthorized(List roles, DigilibRequest request) {
-	ListIterator r = roles.listIterator();
-	String s = "";
-	while (r.hasNext()) {
-	  s = (String)r.next();
+  public boolean isRoleAuthorized(List<String> roles, DigilibRequest request) {
+	for (String s: roles) {
 	  logger.debug("Testing role: "+s);
 	  if (((HttpServletRequest)request.getServletRequest()).isUserInRole(s)) {
 	  	logger.debug("Role Authorized");
@@ -131,8 +124,8 @@
 
   public abstract void init() throws AuthOpException;
 
-  public abstract List rolesForPath(String filepath, HttpServletRequest request) throws AuthOpException;
+  public abstract List<String> rolesForPath(String filepath, HttpServletRequest request) throws AuthOpException;
 
-  public abstract List rolesForPath(DigilibRequest request) throws AuthOpException;
+  public abstract List<String> rolesForPath(DigilibRequest request) throws AuthOpException;
 
 }
--- a/servlet/src/digilib/auth/HashTree.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/auth/HashTree.java	Tue Dec 21 09:52:16 2010 +0100
@@ -20,7 +20,10 @@
 
 package digilib.auth;
 
-import java.util.*;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
 
 /**
  * Tree representation wrapper for a HashMap.
@@ -37,7 +40,7 @@
  */
 public class HashTree {
 
-    private Map table;
+    private Map<String, String> table;
 
     private String twigSep = "/";
 
@@ -53,7 +56,7 @@
      * @param twig_separator
      * @param leaf_separator
      */
-    public HashTree(Map t, String twig_separator, String leaf_separator) {
+    public HashTree(Map<String, String> t, String twig_separator, String leaf_separator) {
         table = t;
         twigSep = twig_separator;
         leafSep = leaf_separator;
@@ -73,10 +76,10 @@
      * @param branch
      * @return
      */
-    List match(String branch) {
+    List<String> match(String branch) {
         String b = "";
         String m;
-        LinkedList matches = new LinkedList();
+        LinkedList<String> matches = new LinkedList<String>();
 
         // split branch
         StringTokenizer twig = new StringTokenizer(branch, twigSep);
@@ -87,7 +90,7 @@
             } else {
                 b += twigSep + twig.nextToken();
             }
-            m = (String) table.get(b);
+            m = table.get(b);
             if (m != null) {
                 if (m.indexOf(leafSep) < 0) {
                     // single leaf
--- a/servlet/src/digilib/auth/XMLAuthOps.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/auth/XMLAuthOps.java	Tue Dec 21 09:52:16 2010 +0100
@@ -69,8 +69,8 @@
 	 */
 	public void init() throws AuthOpException {
 		logger.debug("xmlauthops.init (" + configFile + ")");
-		Map pathList = null;
-		Map ipList = null;
+		Map<String, String> pathList = null;
+		Map<String, String> ipList = null;
 		try {
 			// load authPaths
 			XMLListLoader pathLoader =
@@ -105,7 +105,7 @@
 	 * @throws AuthOpException Exception thrown on error.
 	 * @return List of Strings with role names.
 	 */
-	public List rolesForPath(String filepath, HttpServletRequest request)
+	public List<String> rolesForPath(String filepath, HttpServletRequest request)
 		throws digilib.auth.AuthOpException {
 		logger.debug("rolesForPath ("
 				+ filepath
@@ -114,13 +114,13 @@
 				+ "]");
 
 		// check if the requests address provides a role
-		List provided = authIPs.match(request.getRemoteAddr());
+		List<String> provided = authIPs.match(request.getRemoteAddr());
 		if ((provided != null) && (provided.contains("ALL"))) {
 			// ALL switches off checking;
 			return null;
 		}
 		// which roles are required?
-		List required = authPaths.match(filepath);
+		List<String> required = authPaths.match(filepath);
 		// do any provided roles match?
 		if ((provided != null) && (required != null)) {
 			for (int i = 0; i < provided.size(); i++) {
@@ -136,7 +136,7 @@
 	/**
 	 * @see digilib.auth.AuthOps#rolesForPath(digilib.servlet.DigilibRequest)
 	 */
-	public List rolesForPath(DigilibRequest request) throws AuthOpException {
+	public List<String> rolesForPath(DigilibRequest request) throws AuthOpException {
 		logger.debug("rolesForPath ("
 				+ request.getFilePath()
 				+ ") by ["
@@ -144,14 +144,14 @@
 				+ "]");
 
 		// check if the requests address provides a role
-		List provided =
+		List<String> provided =
 			authIPs.match(request.getServletRequest().getRemoteAddr());
 		if ((provided != null) && (provided.contains("ALL"))) {
 			// ALL switches off checking;
 			return null;
 		}
 		// which roles are required?
-		List required = authPaths.match(request.getFilePath());
+		List<String> required = authPaths.match(request.getFilePath());
 		// do any provided roles match?
 		if ((provided != null) && (required != null)) {
 			for (int i = 0; i < provided.size(); i++) {
--- a/servlet/src/digilib/image/DocuImage.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/image/DocuImage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -214,11 +214,17 @@
     /**
      * Check image size and type and store in ImageFile f
      */
-    public boolean identify(ImageFile imgf) throws IOException;
+    public ImageFile identify(ImageFile imgf) throws IOException;
 
     /**
      * Returns a list of supported image formats
      */
-	public Iterator getSupportedFormats();
+	public Iterator<String> getSupportedFormats();
+	
+	/**
+	 * returns the underlying image as java.awt.Image (if possible, or null)
+	 * @return
+	 */
+	public java.awt.Image getAwtImage();
 
 }
--- a/servlet/src/digilib/image/DocuImageImpl.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/image/DocuImageImpl.java	Tue Dec 21 09:52:16 2010 +0100
@@ -20,16 +20,15 @@
 
 package digilib.image;
 
+import java.awt.Image;
 import java.awt.Rectangle;
-import java.io.File;
 import java.io.IOException;
-import java.io.RandomAccessFile;
+import java.io.OutputStream;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 
 import org.apache.log4j.Logger;
-import org.marcoschmidt.image.ImageInfo;
 
 import digilib.io.FileOpException;
 import digilib.io.ImageFile;
@@ -37,7 +36,7 @@
 /** Simple abstract implementation of the <code>DocuImage</code> interface.
  *
  * This implementation provides basic functionality for the utility methods like
- * <code>SetUtils</code>, and <code>getKnownFileTypes</code>. Image methods like
+ * <code>getKnownFileTypes</code>. Image methods like
  * <code>loadImage</code>, <code>writeImage</code>, <code>getWidth</code>,
  * <code>getHeight</code>, <code>crop</code> and <code>scale</code> must be
  * implemented by derived classes.
@@ -72,34 +71,7 @@
 		this.quality = quality;
 	}
 
-    /** Check image size and type and store in ImageFile f */
-    public boolean identify(ImageFile imgf) throws IOException {
-        // fileset to store the information
-        File f = imgf.getFile();
-        if (f == null) {
-            throw new IOException("File not found!");
-        }
-        RandomAccessFile raf = new RandomAccessFile(f, "r");
-        // set up ImageInfo object
-        ImageInfo iif = new ImageInfo();
-        iif.setInput(raf);
-        iif.setCollectComments(false);
-        iif.setDetermineImageNumber(false);
-        logger.debug("identifying (ImageInfo) " + f);
-        // try with ImageInfo first
-        if (iif.check()) {
-            ImageSize d = new ImageSize(iif.getWidth(), iif.getHeight());
-            imgf.setSize(d);
-            imgf.setMimetype(iif.getMimeType());
-            //logger.debug("  format:"+iif.getFormatName());
-            raf.close();
-            logger.debug("image size: " + imgf.getSize());
-            return true;
-        }
-        return false;
-    }
-        
-    /** Crop and scale the current image.
+	/** Crop and scale the current image.
 	 *
 	 * The current image is cropped to a rectangle of width, height at position
 	 * x_off, y_off. The resulting image is scaled by the factor scale using the
@@ -116,7 +88,7 @@
 	public void cropAndScale(
 		int x_off, int y_off, int width, int height, double scale, int qual) 
 		throws ImageOpException {
-
+		// default implementation: first crop, then scale
 		setQuality(qual);
 		crop(x_off, y_off, width, height);
 		scale(scale, scale);
@@ -126,6 +98,14 @@
 		return mimeType;
 	}
 
+    /* (non-Javadoc)
+     * @see digilib.image.DocuImage#identify(digilib.io.ImageFile)
+     */
+    public ImageFile identify(ImageFile imgf) throws IOException {
+        // just a do-nothing implementation
+        return null;
+    }
+
 	public void rotate(double angle) throws ImageOpException {
 		// just a do-nothing implementation
 	}
@@ -157,10 +137,44 @@
 		// emtpy implementation
 	}
 
-	public Iterator getSupportedFormats() {
-		List empty = new LinkedList();
+	public Iterator<String> getSupportedFormats() {
+		List<String> empty = new LinkedList<String>();
 		return empty.iterator();
 	}
-	
+
+    public void crop(int xoff, int yoff, int width, int height)
+            throws ImageOpException {
+        // TODO Auto-generated method stub
+    }
+
+    public Image getAwtImage() {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public int getHeight() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
 
+    public int getWidth() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    public void loadImage(ImageFile f) throws FileOpException {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void scale(double scaleX, double scaleY) throws ImageOpException {
+        // TODO Auto-generated method stub
+        
+    }
+
+    public void writeImage(String mt, OutputStream ostream)
+            throws FileOpException {
+        // TODO Auto-generated method stub
+    }
+	
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageInfoDocuImage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,51 @@
+/**
+ * 
+ */
+package digilib.image;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import org.marcoschmidt.image.ImageInfo;
+
+import digilib.io.ImageFile;
+
+/** Simple abstract implementation of the <code>DocuImage</code> interface.
+ * Implements only the identify method using the ImageInfo class.
+ * @author casties
+ *
+ */
+public abstract class ImageInfoDocuImage extends DocuImageImpl {
+
+    /** Check image size and type and store in ImageFile f */
+    public ImageFile identify(ImageFile imgf) throws IOException {
+        // fileset to store the information
+        File f = imgf.getFile();
+        if (f == null) {
+            throw new IOException("File not found!");
+        }
+        RandomAccessFile raf = new RandomAccessFile(f, "r");
+        // set up ImageInfo object
+        ImageInfo iif = new ImageInfo();
+        iif.setInput(raf);
+        iif.setCollectComments(false);
+        iif.setDetermineImageNumber(false);
+        logger.debug("identifying (ImageInfo) " + f);
+        // try with ImageInfo first
+        if (iif.check()) {
+            ImageSize d = new ImageSize(iif.getWidth(), iif.getHeight());
+            imgf.setSize(d);
+            imgf.setMimetype(iif.getMimeType());
+            //logger.debug("  format:"+iif.getFormatName());
+            raf.close();
+            logger.debug("image size: " + imgf.getSize());
+            return imgf;
+        } else {
+            raf.close();
+        }
+        return null;
+    }
+        
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageJobDescription.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,492 @@
+package digilib.image;
+
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Rectangle2D;
+import java.io.IOException;
+
+import org.apache.log4j.Logger;
+
+import digilib.io.DocuDirCache;
+import digilib.io.DocuDirectory;
+import digilib.io.FileOpException;
+import digilib.io.FileOps;
+import digilib.io.FileOps.FileClass;
+import digilib.io.ImageFile;
+import digilib.io.ImageFileset;
+import digilib.servlet.DigilibConfiguration;
+import digilib.util.OptionsSet;
+import digilib.util.Parameter;
+import digilib.util.ParameterMap;
+
+
+/** 
+ * A container class for storing a set of instructional parameters 
+ * used for content generating classes like MakePDF.  
+ * 
+ * This contains the functionality formerly found in Scaler, processRequest, only factorized.
+ * 
+ * TODO clean up...
+ * 
+ * @author cmielack, casties
+ *
+ */
+
+public class ImageJobDescription extends ParameterMap {
+	
+	DigilibConfiguration dlConfig = null;
+	protected static Logger logger = Logger.getLogger("digilib.servlet");
+
+	ImageFile fileToLoad = null;
+	ImageFileset fileset = null;
+	DocuDirectory fileDir = null;
+	String filePath = null;
+	ImageSize expectedSourceSize = null;
+	Float scaleXY = null;
+	Rectangle2D userImgArea = null;
+	Rectangle2D outerUserImgArea= null;
+	Boolean imageSendable = null;
+	String mimeType;
+	Integer paramDW;
+	Integer paramDH;
+
+	/** create empty ImageJobDescription.
+	 * @param dlcfg
+	 */
+	public ImageJobDescription(DigilibConfiguration dlcfg) {
+		super(30);
+		dlConfig = dlcfg;
+	}
+
+
+	/** set up Parameters
+	 * @see digilib.util.ParameterMap#initParams()
+	 */
+	@Override
+	protected void initParams() {
+		// url of the page/document (second part)
+		newParameter("fn", "", null, 's');
+		// page number
+		newParameter("pn", new Integer(1), null, 's');
+		// width of client in pixels
+		newParameter("dw", new Integer(0), null, 's');
+		// height of client in pixels
+		newParameter("dh", new Integer(0), null, 's');
+		// left edge of image (float from 0 to 1)
+		newParameter("wx", new Float(0), null, 's');
+		// top edge in image (float from 0 to 1)
+		newParameter("wy", new Float(0), null, 's');
+		// width of image (float from 0 to 1)
+		newParameter("ww", new Float(1), null, 's');
+		// height of image (float from 0 to 1)
+		newParameter("wh", new Float(1), null, 's');
+		// scale factor
+		newParameter("ws", new Float(1), null, 's');
+		// special options like 'fit' for gifs
+		newParameter("mo", this.options, null, 's');
+		// rotation angle (degree)
+		newParameter("rot", new Float(0), null, 's');
+		// contrast enhancement factor
+		newParameter("cont", new Float(0), null, 's');
+		// brightness enhancement factor
+		newParameter("brgt", new Float(0), null, 's');
+		// color multiplicative factors
+		newParameter("rgbm", "0/0/0", null, 's');
+		// color additive factors
+		newParameter("rgba", "0/0/0", null, 's');
+		// display dpi resolution (total)
+		newParameter("ddpi", new Float(0), null, 's');
+		// display dpi X resolution
+		newParameter("ddpix", new Float(0), null, 's');
+		// display dpi Y resolution
+		newParameter("ddpiy", new Float(0), null, 's');
+		// scale factor for mo=ascale
+		newParameter("scale", new Float(1), null, 's');
+	}
+
+
+	/* (non-Javadoc)
+	 * @see digilib.servlet.ParameterMap#initOptions()
+	 */
+	@Override
+	protected void initOptions() {
+		String s = this.getAsString("mo");
+		options = new OptionsSet(s);
+	}
+
+
+	/** Creates new ImageJobDescription by merging Parameters from another ParameterMap.
+	 * @param pm
+	 * @param dlcfg
+	 * @return
+	 */
+	public static ImageJobDescription getInstance(ParameterMap pm, DigilibConfiguration dlcfg) {
+		ImageJobDescription newMap = new ImageJobDescription(dlcfg);
+		// add all params to this map
+		newMap.params.putAll(pm.getParams());
+		newMap.initOptions();
+		return newMap;
+	}
+
+	
+	public String getMimeType() throws IOException {
+		if (mimeType == null) {
+			fileToLoad = getFileToLoad();
+			if(! fileToLoad.isChecked()){
+				DigilibConfiguration.docuImageIdentify(fileToLoad);
+			}
+			mimeType = fileToLoad.getMimetype();
+		}
+		return mimeType;
+	}
+	
+	public ImageFile getFileToLoad() throws IOException {
+		
+		if(fileToLoad == null){
+			fileset = getFileset();
+			
+			/* select a resolution */
+			if (getHiresOnly()) {
+				// get first element (= highest resolution)
+				fileToLoad = fileset.getBiggest();
+			} else if (getLoresOnly()) {
+				// enforced lores uses next smaller resolution
+				fileToLoad = fileset.getNextSmaller(getExpectedSourceSize());
+				if (fileToLoad == null) {
+					// this is the smallest we have
+					fileToLoad = fileset.getSmallest();
+				}
+			} else {
+				// autores: use next higher resolution
+				fileToLoad = fileset.getNextBigger(getExpectedSourceSize());
+				if (fileToLoad == null) {
+					// this is the highest we have
+					fileToLoad = fileset.getBiggest();
+				}
+			}
+			logger.info("Planning to load: " + fileToLoad.getFile());
+		}
+		
+		return fileToLoad;
+
+	}
+	
+	public DocuDirectory getFileDirectory() throws FileOpException {
+		if(fileDir == null){
+			DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+			String fp = getFilePath();
+			fileDir = dirCache.getDirectory(fp);
+			if (fileDir == null) {
+				throw new FileOpException("Directory " + getFilePath() + " not found.");
+			}
+		}
+		return fileDir;
+	}
+	
+    public ImageFileset getFileset() throws FileOpException {
+        if(fileset==null){
+            DocuDirCache dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+    
+            fileset = (ImageFileset) dirCache.getFile(getFilePath(), getAsInt("pn"), FileClass.IMAGE);
+            if (fileset == null) {
+                throw new FileOpException("File " + getFilePath() + "("
+                        + getAsInt("pn") + ") not found.");
+            }
+        }
+        return fileset;
+    }
+    
+	public String getFilePath() {
+		if(filePath == null){
+			String s = this.getAsString("request.path");
+			s += this.getAsString("fn");
+			filePath = FileOps.normalName(s);
+		}
+		return filePath;
+	}
+
+	public boolean getHiresOnly(){
+		return hasOption("clip") || hasOption("hires");
+	}
+	
+	public boolean getLoresOnly(){
+		return hasOption("lores");
+	}
+
+	public boolean getScaleToFit() {
+		return !(hasOption("clip") || hasOption("osize") || hasOption("ascale"));
+	}
+
+	public boolean getAbsoluteScale(){
+		return hasOption("osize") || hasOption("ascale");
+	}
+	
+	
+	public ImageSize getExpectedSourceSize() throws IOException {
+		if (expectedSourceSize == null){
+			expectedSourceSize = new ImageSize();
+			if (getScaleToFit()) {
+				// scale to fit -- calculate minimum source size
+				float scale = (1 / Math.min(getAsFloat("ww"), getAsFloat("wh"))) * getAsFloat("ws");
+				expectedSourceSize.setSize((int) (getDw() * scale),
+						(int) (getDh() * scale));
+			} else if (getAbsoluteScale() && hasOption("ascale")) {
+				// absolute scale -- apply scale to hires size
+				expectedSourceSize = getHiresSize().getScaled(getAsFloat("scale"));
+			} else {
+				// clip to fit -- source = destination size
+				expectedSourceSize.setSize((int) (getDw() * getAsFloat("ws")),
+						(int) (getDh() * getAsFloat("ws")));
+			}
+		}
+		return expectedSourceSize;
+	}
+	
+	public ImageSize getHiresSize() throws IOException {
+		logger.debug("get_hiresSize()");
+
+		ImageSize hiresSize = null;
+		ImageFileset fileset = getFileset();
+		if (getAbsoluteScale()) {
+			ImageFile hiresFile = fileset.getBiggest();
+			if (!hiresFile.isChecked()) {
+				DigilibConfiguration.docuImageIdentify(hiresFile);
+			}
+			hiresSize = hiresFile.getSize();
+		}
+		return hiresSize;
+	}
+	
+	/** Returns image scaling factor.
+	 * Uses image size and user parameters.
+	 * Modifies scaleXY, userImgArea. 
+	 * @return
+	 * @throws IOException
+	 * @throws ImageOpException
+	 */
+	public float getScaleXY() throws IOException, ImageOpException {
+		//logger.debug("get_scaleXY()");
+		if(scaleXY == null){
+			// coordinates and scaling
+			float areaWidth;
+			float areaHeight;
+			float ws = getAsFloat("ws");
+			ImageSize imgSize = getFileToLoad().getSize();
+			// user window area in [0,1] coordinates
+			Rectangle2D relUserArea = new Rectangle2D.Float(getAsFloat("wx"), getAsFloat("wy"),
+					getAsFloat("ww"), getAsFloat("wh"));
+			// transform from relative [0,1] to image coordinates.
+			AffineTransform imgTrafo = AffineTransform.getScaleInstance(imgSize
+					.getWidth(), imgSize.getHeight());
+			// transform user coordinate area to image coordinate area
+			userImgArea = imgTrafo.createTransformedShape(
+					relUserArea).getBounds2D();
+	
+			if (getScaleToFit()) {
+				// calculate scaling factors based on inner user area
+				areaWidth = (float) userImgArea.getWidth();
+				areaHeight = (float) userImgArea.getHeight();
+				float scaleX = getDw() / areaWidth * ws;
+				float scaleY = getDh() / areaHeight * ws;
+				scaleXY = (scaleX > scaleY) ? scaleY : scaleX;
+			} else if (getAbsoluteScale()) {
+				// absolute scaling factor
+				if (hasOption("osize")) {
+					// get original resolution from metadata
+					fileset.checkMeta();
+					float origResX = fileset.getResX();
+					float origResY = fileset.getResY();
+					if ((origResX == 0) || (origResY == 0)) {
+						throw new ImageOpException("Missing image DPI information!");
+					}
+					if ((getAsFloat("ddpix") == 0) || (getAsFloat("ddpiy") == 0)) {
+						throw new ImageOpException("Missing display DPI information!");
+					}
+					// calculate absolute scale factor
+					float sx = getAsFloat("ddpix") / origResX;
+					float sy = getAsFloat("ddpiy") / origResY;
+					// currently only same scale -- mean value
+					scaleXY = (sx + sy) / 2f;
+				} else {
+					scaleXY = getAsFloat("scale");
+				}
+				// we need to correct the factor if we use a pre-scaled image
+				ImageSize hiresSize = getHiresSize();
+				if (imgSize.getWidth() != hiresSize.getWidth()) {
+					scaleXY *= (float)hiresSize.getWidth() / (float)imgSize.getWidth();
+				}
+				areaWidth = getDw() / scaleXY * ws;
+				areaHeight = getDh() / scaleXY * ws;
+				// reset user area size
+				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
+						areaWidth, areaHeight);
+			} else {
+				// crop to fit -- don't scale
+				areaWidth = getDw() * ws;
+				areaHeight = getDh() * ws;
+				// reset user area size
+				userImgArea.setRect(userImgArea.getX(), userImgArea.getY(),
+						areaWidth, areaHeight);
+				scaleXY = 1f;
+			}
+		}
+		return (float) scaleXY;
+	}
+	
+	public int getDw() throws IOException {
+		logger.debug("get_paramDW()");
+		if (paramDW == null) {
+
+			paramDW = getAsInt("dw");
+			paramDH = getAsInt("dh");
+
+			float imgAspect = getFileToLoad().getAspect();
+			if (paramDW == 0) {
+				// calculate dw
+				paramDW = Math.round(paramDH * imgAspect);
+				setValue("dw", paramDW);
+			} else if (paramDH == 0) {
+				// calculate dh
+				paramDH = Math.round(paramDW / imgAspect);
+				setValue("dh", paramDH);
+			}
+		}
+		return paramDW;
+	}
+	
+	public int getDh() throws IOException {
+		logger.debug("get_paramDH()");
+		if (paramDH == null) {
+			
+			paramDW = getAsInt("dw");
+			paramDH = getAsInt("dh");
+
+			float imgAspect = getFileToLoad().getAspect();
+			if (paramDW == 0) {
+				// calculate dw
+				paramDW = Math.round(paramDH * imgAspect);
+				setValue("dw", paramDW);
+			} else if (paramDH == 0) {
+				// calculate dh
+				paramDH = Math.round(paramDW / imgAspect);
+				setValue("dh", paramDH);
+			}
+		}
+		return paramDH;
+	}
+	
+	public Integer getScaleQual(){
+		logger.debug("get_scaleQual()");
+		Integer qual = dlConfig.getAsInt("default-quality");
+		if(hasOption("q0"))
+			qual = 0;
+		else if(hasOption("q1"))
+			qual = 1;
+		else if(hasOption("q2"))
+			qual = 2;
+		return qual;
+	}
+
+	
+	public Rectangle2D getUserImgArea() throws IOException, ImageOpException{
+		if(userImgArea == null) {
+			// getScaleXY sets userImgArea
+			getScaleXY();
+		}
+		return userImgArea;		
+		
+	}
+	
+	public Rectangle2D getOuterUserImgArea() throws IOException, ImageOpException {
+		if(outerUserImgArea == null){
+			outerUserImgArea = getUserImgArea();
+			
+			// image size in pixels
+			ImageSize imgSize = getFileToLoad().getSize();
+			Rectangle2D imgBounds = new Rectangle2D.Float(0, 0, imgSize.getWidth(), 
+					imgSize.getHeight());
+			
+			// clip area at the image border
+			outerUserImgArea = outerUserImgArea.createIntersection(imgBounds);
+	
+			// check image parameters sanity
+			scaleXY = getScaleXY();
+			logger.debug("outerUserImgArea.getWidth()=" + outerUserImgArea.getWidth());
+			logger.debug("get_scaleXY() * outerUserImgArea.getWidth() = " + (scaleXY * outerUserImgArea.getWidth()));
+			
+			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!");
+			}
+		}
+		return outerUserImgArea;
+	}
+	
+	
+	public float[] getRGBM(){
+		float[] paramRGBM = null;//{0f,0f,0f};
+		Parameter p = params.get("rgbm");
+		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+			return p.parseAsFloatArray("/");
+		}	
+		return paramRGBM;
+	}
+	
+	public float[] getRGBA(){
+		float[] paramRGBA =  null;//{0f,0f,0f};
+		Parameter p = params.get("rgba");
+		if (p.hasValue() && (!p.getAsString().equals("0/0/0"))) {
+			paramRGBA = p.parseAsFloatArray("/");
+		}
+		return paramRGBA;
+	}
+	
+	/** Has send-as-file been requested?
+	 * @return
+	 */
+	public boolean getSendAsFile(){
+		return hasOption("file")
+		|| hasOption("rawfile");
+	}
+	
+	/** Could the image be sent without processing?
+	 * Takes image type and additional image operations into account. 
+	 * Does not check requested size transformation.
+	 * @return
+	 * @throws IOException 
+	 */
+	public boolean isImageSendable() throws IOException {
+		// cached result?
+		if (imageSendable == null) {
+			String mimeType = getMimeType();
+			imageSendable = ( (mimeType.equals("image/jpeg")
+				        	|| mimeType.equals("image/png")
+				        	|| mimeType.equals("image/gif") )
+				        	&& 
+				        	!(hasOption("hmir")
+							|| hasOption("vmir") 
+							|| (getAsFloat("rot") != 0.0)
+							|| (getRGBM() != null) 
+							|| (getRGBA() != null)
+							|| (getAsFloat("cont") != 0.0) 
+							|| (getAsFloat("brgt") != 0.0)));
+		}
+		
+		return imageSendable;
+	}
+	
+	
+	public boolean isTransformRequired() throws IOException {
+		ImageSize is = getFileToLoad().getSize();
+		ImageSize ess = getExpectedSourceSize();
+		// nt = no transform required
+		boolean nt = isImageSendable() && (
+			// lores: send if smaller
+			(getLoresOnly() && is.isSmallerThan(ess))
+			// else send if it fits
+			|| (!(getLoresOnly() || getHiresOnly()) && is.fitsIn(ess)));
+		return ! nt;
+	}
+}
\ No newline at end of file
--- a/servlet/src/digilib/image/ImageLoaderDocuImage.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/image/ImageLoaderDocuImage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -52,7 +52,7 @@
 import digilib.io.ImageFileset;
 
 /** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
-public class ImageLoaderDocuImage extends DocuImageImpl {
+public class ImageLoaderDocuImage extends ImageInfoDocuImage {
 
 	/** image object */
 	protected BufferedImage img;
@@ -117,20 +117,21 @@
 	}
 
 	/* returns a list of supported image formats */
-	public Iterator getSupportedFormats() {
+	public Iterator<String> getSupportedFormats() {
 		String[] formats = ImageIO.getReaderFormatNames();
 		return Arrays.asList(formats).iterator();
 	}
 
     /** Check image size and type and store in ImageFile f */
-    public boolean identify(ImageFile imgf) throws IOException {
+    public ImageFile identify(ImageFile imageFile) throws IOException {
         // try parent method first
-        if (super.identify(imgf)) {
-            return true;
+        ImageFile imf = super.identify(imageFile);
+        if (imf != null) {
+            return imf;
         }
         // fileset to store the information
-        ImageFileset imgfs = imgf.getParent();
-        File f = imgf.getFile();
+        ImageFileset imgfs = imageFile.getParent();
+        File f = imageFile.getFile();
         if (f == null) {
             throw new IOException("File not found!");
         }
@@ -138,47 +139,27 @@
         /*
          * try ImageReader
          */
-        RandomAccessFile raf = new RandomAccessFile(f, "r");
-        ImageInputStream istream = ImageIO.createImageInputStream(raf);
-        Iterator readers = ImageIO.getImageReaders(istream);
-        if (readers.hasNext()) {
-            ImageReader 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());
-            }
-            try {
-                reader.setInput(istream);
-                ImageSize d = new ImageSize(reader.getWidth(0), reader.getHeight(0));
-                imgf.setSize(d);
-                //String t = reader.getFormatName();
-                String t = FileOps.mimeForFile(f);
-                imgf.setMimetype(t);
-                //logger.debug("  format:"+t);
-                if (imgfs != null) {
-                    imgfs.setAspect(d);
-                }
-                return true;
-            } finally {
-                // dispose the reader to free resources
-                reader.dispose();
-                raf.close();
-            }
+        if ((reader == null) || (imgFile != imageFile.getFile())) {
+            getReader(imageFile);
         }
-        throw new FileOpException("ERROR: unknown image file format!");
+        ImageSize d = new ImageSize(reader.getWidth(0), reader.getHeight(0));
+        imageFile.setSize(d);
+        // String t = reader.getFormatName();
+        String t = FileOps.mimeForFile(f);
+        imageFile.setMimetype(t);
+        // logger.debug("  format:"+t);
+        if (imgfs != null) {
+            imgfs.setAspect(d);
+        }
+        return imageFile;
     }
-
     
     /* load image file */
 	public void loadImage(ImageFile f) throws FileOpException {
 		logger.debug("loadImage " + f.getFile());
 		try {
 			img = ImageIO.read(f.getFile());
-			if (img == null) {
-				throw new FileOpException("Unable to load File!");
-			}
+            mimeType = f.getMimetype();
 		} catch (IOException e) {
 			throw new FileOpException("Error reading image.");
 		}
@@ -196,23 +177,27 @@
 			// clean up old reader
 			dispose();
 		}
-		// System.gc();
 		RandomAccessFile rf = new RandomAccessFile(f.getFile(), "r");
 		ImageInputStream istream = new FileImageInputStream(rf);
-		// Iterator readers = ImageIO.getImageReaders(istream);
+		Iterator<ImageReader> readers;
 		String mt = f.getMimetype();
-		logger.debug("File type:" + mt);
-		Iterator readers = ImageIO.getImageReadersByMIMEType(mt);
+		if (mt == null) {
+			logger.debug("No mime-type. Trying automagic.");
+			readers = ImageIO.getImageReaders(istream);
+		} else {
+			logger.debug("File type:" + mt);
+			readers = ImageIO.getImageReadersByMIMEType(mt);
+		}
 		if (!readers.hasNext()) {
-			throw new FileOpException("Unable to load File!");
+		    rf.close();
+			throw new FileOpException("Can't find Reader to load File!");
 		}
-		reader = (ImageReader) readers.next();
+		reader = readers.next();
 		/* are there more readers? */
 		logger.debug("ImageIO: this reader: " + reader.getClass());
-		while (readers.hasNext()) {
+		/* while (readers.hasNext()) {
 			logger.debug("ImageIO: next reader: " + readers.next().getClass());
-		}
-		// */
+		} */
 		reader.setInput(istream);
 		imgFile = f.getFile();
 		return reader;
@@ -222,7 +207,6 @@
 	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);
@@ -236,13 +220,11 @@
 			// read image
 			logger.debug("loading..");
 			img = reader.read(0, readParam);
+			mimeType = f.getMimetype();
 			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 */
@@ -325,15 +307,14 @@
 		AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform
 				.getScaleInstance(scale, scale), renderHint);
 		BufferedImage scaledImg = null;
-		// enforce destination image type (*Java2D BUG*)
+		/* 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);
 		if (scaledImg == null) {
@@ -344,7 +325,6 @@
 		logger.debug("SCALE: " + scale + " ->" + scaledImg.getWidth() + "x"
 				+ scaledImg.getHeight());
 		img = scaledImg;
-		scaledImg = null;
 	}
 
 	public void blur(int radius) throws ImageOpException {
@@ -364,16 +344,14 @@
 		// blur with convolve operation
 		ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP,
 				renderHint);
+		BufferedImage blurredImg = null;
 		// blur needs explicit destination image type for color *Java2D BUG*
-		BufferedImage blurredImg = null;
 		if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
+			logger.debug("blur: fixing destination image type");
 			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;
 	}
 
@@ -381,9 +359,6 @@
 			throws ImageOpException {
 		// setup Crop
 		BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
-		// DEBUG
-		// util.dprintln(2, " time
-		// "+(System.currentTimeMillis()-startTime)+"ms");
 		if (croppedImg == null) {
 			throw new ImageOpException("Unable to crop");
 		}
@@ -413,12 +388,10 @@
 	}
 
 	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
@@ -491,17 +464,12 @@
 		double xoff = rotbounds.getX();
 		double yoff = rotbounds.getY();
 		// move image back in line
-		trafo
-				.preConcatenate(AffineTransform.getTranslateInstance(-xoff,
-						-yoff));
+		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)
 		/*
@@ -536,9 +504,6 @@
 		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;
 	}
 
@@ -562,7 +527,7 @@
 		img = null;
 	}
 
-	public Image getImage(){
+	public Image getAwtImage(){
 		return (Image) img;
 	}
 	
--- a/servlet/src/digilib/image/ImageOps.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-/* ImageOps -- convenience methods for images
- 
- 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 13.10.2004
- */
-package digilib.image;
-
-import java.io.IOException;
-
-import digilib.io.ImageFile;
-
-/**
- * convenience methods for images
- *
- * @author casties
- */
-public class ImageOps {
-    
-    public static final int TYPE_AUTO = 0;
-    public static final int TYPE_JPEG = 1;
-    public static final int TYPE_PNG = 2;
-    
-    private static DocuImage docuImg;
-    
-    public static boolean checkFile(ImageFile imgf) throws IOException {
-        return docuImg.identify(imgf);
-    }
-    
-    public static void setDocuImage(DocuImage di) {
-        docuImg = di;
-    }
-    
-    public static DocuImage getDocuImage() {
-        return docuImg;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageWorker.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,154 @@
+/** Worker (Callable) that renders an image.
+ * 
+ */
+package digilib.image;
+
+import java.awt.Rectangle;
+import java.io.IOException;
+import java.util.concurrent.Callable;
+
+import org.apache.log4j.Logger;
+
+import digilib.io.FileOpException;
+import digilib.servlet.DigilibConfiguration;
+
+/** Worker that renders an image.
+ * 
+ * @author casties
+ *
+ */
+public class ImageWorker implements Callable<DocuImage> {
+
+    
+    protected static Logger logger = Logger.getLogger(ImageWorker.class);
+    private DigilibConfiguration dlConfig;
+    private ImageJobDescription jobinfo;
+
+    public ImageWorker(DigilibConfiguration dlConfig, ImageJobDescription jobinfo) {
+        super();
+        this.dlConfig = dlConfig;
+        this.jobinfo = jobinfo;
+    }
+
+    /**
+     * render and return the image
+     */
+    @Override
+    public DocuImage call() throws FileOpException, IOException, ImageOpException {
+        
+        logger.debug("image worker starting");
+        long startTime = System.currentTimeMillis();
+
+        /* crop and scale image */
+
+        // new DocuImage instance
+        DocuImage docuImage = DigilibConfiguration.getDocuImageInstance();
+        if (docuImage == null) {
+            throw new ImageOpException("Unable to load DocuImage class!");
+        }
+
+        // set interpolation quality
+        docuImage.setQuality(jobinfo.getScaleQual());
+
+        Rectangle loadRect = jobinfo.getOuterUserImgArea().getBounds();
+        float scaleXY = jobinfo.getScaleXY();
+        
+        // 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 (jobinfo.getScaleQual() > 0) {
+                    subsamp = (float) Math.max(Math.floor(subf / dlConfig.getAsFloat("subsample-minimum")), 1d);
+                } else {
+                    subsamp = (float) Math.floor(subf);
+                }
+                scaleXY = subsamp / subf;
+                logger.debug("Using subsampling: " + subsamp + " rest "
+                        + scaleXY);
+            }
+
+            docuImage.loadSubimage(jobinfo.getFileToLoad(), loadRect, (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(jobinfo.getFileToLoad());
+            docuImage.crop((int) loadRect.getX(), (int) loadRect.getY(),
+                    (int) loadRect.getWidth(), (int) loadRect.getHeight());
+
+            docuImage.scale(scaleXY, scaleXY);
+        }
+
+        // mirror image
+        // operation mode: "hmir": mirror horizontally, "vmir": mirror
+        // vertically
+        if (jobinfo.hasOption("hmir")) {
+            docuImage.mirror(0);
+        }
+        if (jobinfo.hasOption("vmir")) {
+            docuImage.mirror(90);
+        }
+
+        // rotate image
+        if (jobinfo.getAsFloat("rot") != 0d) {
+            docuImage.rotate(jobinfo.getAsFloat("rot"));
+            /* if (jobinfo.get_wholeRotArea()) {
+                // crop to the inner bounding box
+                float xcrop = (float) (docuImage.getWidth() - jobinfo.get_innerUserImgArea().getWidth()
+                        * scaleXY);
+                float ycrop = (float) (docuImage.getHeight() - jobinfo.get_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
+        float[] paramRGBM = jobinfo.getRGBM();
+        float[] paramRGBA = jobinfo.getRGBA();
+        if ((paramRGBM != null) || (paramRGBA != null)) {
+            // make sure 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
+        float paramCONT = jobinfo.getAsFloat("cont");
+        float paramBRGT = jobinfo.getAsFloat("brgt");
+        if ((paramCONT != 0f) || (paramBRGT != 0f)) {
+            float mult = (float) Math.pow(2, paramCONT);
+            docuImage.enhance(mult, paramBRGT);
+        }
+
+        logger.debug("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
+
+        return docuImage;
+    }
+
+}
--- a/servlet/src/digilib/image/JAIDocuImage.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/image/JAIDocuImage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -49,7 +49,7 @@
 import digilib.io.ImageFileset;
 
 /** A DocuImage implementation using Java Advanced Imaging Library. */
-public class JAIDocuImage extends DocuImageImpl {
+public class JAIDocuImage extends ImageInfoDocuImage {
 
 	protected RenderedImage img;
 
@@ -84,30 +84,30 @@
 	}
 
 	/* returns a list of supported image formats */
-	public Iterator getSupportedFormats() {
-		Enumeration codecs = ImageCodec.getCodecs();
-		List formats = new ArrayList(5);
-		for (Object codec = codecs.nextElement(); codecs.hasMoreElements(); codec = codecs
-				.nextElement()) {
-			logger
-					.debug("known format:"
-							+ ((ImageCodec) codec).getFormatName());
-			formats.add(((ImageCodec) codec).getFormatName());
-		}
-		logger.debug("tilecachesize:"
-				+ JAI.getDefaultInstance().getTileCache().getMemoryCapacity());
-		return formats.iterator();
-	}
+    @SuppressWarnings("unchecked") // ImageCodec.getCodecs() returns a naked Enumeration
+    public Iterator<String> getSupportedFormats() {
+        Enumeration<ImageCodec> codecs = ImageCodec.getCodecs();
+        List<String> formats = new ArrayList<String>();
+        for (ImageCodec codec = codecs.nextElement(); codecs.hasMoreElements(); codec = codecs
+                .nextElement()) {
+            logger.debug("known format:"+codec.getFormatName());
+            formats.add(codec.getFormatName());
+        }
+        logger.debug("tilecachesize:"
+                + JAI.getDefaultInstance().getTileCache().getMemoryCapacity());
+        return formats.iterator();
+    }
 
 	/* Check image size and type and store in ImageFile f */
-	public boolean identify(ImageFile imgf) throws IOException {
-		// try parent method first
-		if (super.identify(imgf)) {
-			return true;
+	public ImageFile identify(ImageFile imageFile) throws IOException {
+        // try parent method first
+	    ImageFile imf = super.identify(imageFile);
+		if (imf != null) {
+			return imf;
 		}
 		// fileset to store the information
-		ImageFileset imgfs = imgf.getParent();
-		File f = imgf.getFile();
+		ImageFileset imgfs = imageFile.getParent();
+		File f = imageFile.getFile();
 		if (f == null) {
 			throw new IOException("File not found!");
 		}
@@ -118,15 +118,15 @@
 		try {
 			RenderedOp img = JAI.create("fileload", f.getAbsolutePath());
 			ImageSize d = new ImageSize(img.getWidth(), img.getHeight());
-			imgf.setSize(d);
+			imageFile.setSize(d);
 			String t = FileOps.mimeForFile(f);
-			imgf.setMimetype(t);
+			imageFile.setMimetype(t);
 			// logger.debug(" format:"+t);
 			if (imgfs != null) {
 				imgfs.setAspect(d);
 			}
-			logger.debug("image size: " + imgf.getSize());
-			return true;
+			logger.debug("image size: " + imageFile.getSize());
+			return imageFile;
 		} catch (Exception e) {
 			throw new FileOpException("ERROR: unknown image file format!");
 		}
@@ -138,6 +138,7 @@
 		if (img == null) {
 			throw new FileOpException("Unable to load File!");
 		}
+        mimeType = f.getMimetype();
 	}
 
 	/* Load an image file into the Object. */
@@ -168,6 +169,7 @@
 			// scale
 			logger.debug("loadSubimage: scale");
 			img = JAI.create("scale", sp);
+            mimeType = f.getMimetype();
 		}
 	}
 
@@ -330,11 +332,6 @@
 		logger.debug("CROP: " + x_off + "," + y_off + ", " + width + ","
 				+ height + " ->" + croppedImg.getWidth() + "x"
 				+ croppedImg.getHeight());
-		// DEBUG
-
-		if (croppedImg == null) {
-			throw new ImageOpException("Unable to crop");
-		}
 		img = croppedImg;
 	}
 
@@ -384,11 +381,6 @@
 
 		logger.debug("ROTATE: " + x + "," + y + ", " + angle + " (" + rangle
 				+ ")" + " ->" + rotImg.getWidth() + "x" + rotImg.getHeight());
-		// DEBUG
-
-		if (rotImg == null) {
-			throw new ImageOpException("Unable to rotate");
-		}
 		img = rotImg;
 	}
 
@@ -442,10 +434,6 @@
 		logger.debug("ENHANCE: *" + mult + ", +" + add + " ->"
 				+ enhImg.getWidth() + "x" + enhImg.getHeight());
 		// DEBUG
-
-		if (enhImg == null) {
-			throw new ImageOpException("Unable to enhance");
-		}
 		img = enhImg;
 	}
 
@@ -472,11 +460,6 @@
 
 		logger.debug("ENHANCE_RGB: *" + rgbm + ", +" + rgba + " ->"
 				+ enhImg.getWidth() + "x" + enhImg.getHeight());
-		// DEBUG
-
-		if (enhImg == null) {
-			throw new ImageOpException("Unable to enhanceRGB");
-		}
 		img = enhImg;
 	}
 
--- a/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/image/JAIImageLoaderDocuImage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -20,6 +20,7 @@
 
 package digilib.image;
 
+import java.awt.Image;
 import java.awt.Rectangle;
 import java.awt.image.renderable.ParameterBlock;
 import java.io.File;
@@ -89,6 +90,7 @@
 		if (img == null) {
 			throw new FileOpException("Unable to load File!");
 		}
+        mimeType = f.getMimetype();
 	}
 
 	/* Get an ImageReader for the image file. */
@@ -98,11 +100,11 @@
 		RandomAccessFile rf = new RandomAccessFile(f.getFile(), "r");
 		ImageInputStream istream = new FileImageInputStream(rf);
 		//Iterator readers = ImageIO.getImageReaders(istream);
-		Iterator readers = ImageIO.getImageReadersByMIMEType(f.getMimetype());
+		Iterator<ImageReader> readers = ImageIO.getImageReadersByMIMEType(f.getMimetype());
 		if (! readers.hasNext()) {
 			throw new FileOpException("Unable to load File!");
 		}
-		reader = (ImageReader) readers.next();
+		reader = readers.next();
 		logger.debug("JAIImageIO: this reader: " + reader.getClass());
 		while (readers.hasNext()) {
 			logger.debug("  next reader: " + readers.next().getClass());
@@ -139,6 +141,7 @@
 			throw new FileOpException("Unable to load File!");
 		}
 		imgFile = f.getFile();
+        mimeType = f.getMimetype();
 	}
 
 
@@ -166,7 +169,13 @@
 		}
 	}
 
-	/* (non-Javadoc)
+	@Override
+    public Image getAwtImage() {
+        // TODO Auto-generated method stub
+        return (Image) img;
+    }
+
+    /* (non-Javadoc)
 	 * @see java.lang.Object#finalize()
 	 */
 	protected void finalize() throws Throwable {
--- a/servlet/src/digilib/io/AliasingDocuDirCache.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/AliasingDocuDirCache.java	Tue Dec 21 09:52:16 2010 +0100
@@ -23,9 +23,10 @@
 package digilib.io;
 
 import java.io.File;
-import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 
+import digilib.io.FileOps.FileClass;
 import digilib.servlet.DigilibConfiguration;
 
 /**
@@ -36,16 +37,16 @@
 
 	/**
 	 * @param baseDirs
-	 * @param fileClasses
+	 * @param fcs
 	 * @param confFileName
 	 * @throws FileOpException
 	 */
-	public AliasingDocuDirCache(String[] baseDirs, int[] fileClasses,
+	public AliasingDocuDirCache(String[] baseDirs, FileClass[] fcs,
 			File confFile, DigilibConfiguration dlConfig)
 			throws FileOpException {
 		// create standard DocuDirCache
-		super(baseDirs, fileClasses, dlConfig);
-		Map pathMap = null;
+		super(baseDirs, fcs, dlConfig);
+		Map<String,String> pathMap = null;
 		// read alias config file
 		try {
 			// load into pathMap
@@ -63,18 +64,16 @@
 		 * load map entries into cache
 		 */
 
-		for (Iterator i = pathMap.keySet().iterator(); i.hasNext();) {
-			String link = (String) i.next();
-			String dir = (String) pathMap.get(link);
-			if (dir == null) {
+		for (Entry<String, String> linkdir: pathMap.entrySet()) {
+			if (linkdir.getValue() == null) {
 				logger.error("Key mismatch in mapping file!");
 				break;	
 			}
-			DocuDirectory destDir = new DocuDirectory(dir, this);
+			DocuDirectory destDir = new DocuDirectory(linkdir.getValue(), this);
 			if (destDir.isValid()) {
-				logger.debug("Aliasing dir: " + link);
+				logger.debug("Aliasing dir: " + linkdir.getKey());
 				// add the alias name
-				putName(FileOps.normalName(link), destDir);
+				putName(FileOps.normalName(linkdir.getKey()), destDir);
 				// add the real dir
 				putDir(destDir);
 			}
@@ -87,13 +86,12 @@
 	 * @param name
 	 * @param newdir
 	 */
-	public void putName(String name, DocuDirectory newdir) {
-		if (map.containsKey(name)) {
-			logger
-					.warn("Duplicate key in AliasingDocuDirCache.put -- ignored!");
-		} else {
-			map.put(name, newdir);
-		}
-	}
+    public void putName(String name, DocuDirectory newdir) {
+        if (map.containsKey(name)) {
+            logger.warn("Duplicate key in AliasingDocuDirCache.put -- ignored!");
+        } else {
+            map.put(name, newdir);
+        }
+    }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/DigilibInfoReader.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,76 @@
+package digilib.io;
+
+/** DigilibInfoReader 
+ * A class for reading the information from info.xml files used in digilib image directories.
+ *
+ */
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+
+
+
+public class DigilibInfoReader {
+
+	/** gengeral logger for this class */
+	protected static Logger logger = Logger.getLogger("digilib.servlet");
+	
+	private String filename = null;
+	//private static String base_element = "info";
+	
+	public DigilibInfoReader(String fn){
+		filename = fn;
+	}
+
+	/**
+	 * Returns the attribute defined by 'attr' as a String.
+	 * 
+	 * @param attr
+	 * @return
+	 */
+	@SuppressWarnings("unchecked") // Element.getChildren() returns naked List
+    public String getAsString(String attr){
+		try{
+			SAXBuilder builder = new SAXBuilder();
+			Document doc = builder.build(new File(filename));
+			Element root = doc.getRootElement();
+			List<Element> mainElements = root.getChildren();
+			// logger.debug("XML mainElements:"+mainElements.toString());
+
+			for(int i=0; i<mainElements.size(); i++){
+				Element elem = mainElements.get(i);
+				if(elem.getName()==attr){
+					// logger.debug(attr+" == "+(String)elem.getTextTrim());
+					return (String)elem.getTextTrim();
+				}
+			}
+
+		}
+		catch(Exception e){
+			logger.error(e.getMessage());
+		}
+		return null;
+	}
+	
+	
+	/**
+	 * Find out if the info.xml exists
+	 * @return
+	 */
+	public boolean hasInfo(){
+		try {
+			SAXBuilder builder = new SAXBuilder();
+			builder.build(new File(filename));
+			return true;
+		}
+		catch(Exception e){
+			return false;
+		}
+	}
+	
+}
--- a/servlet/src/digilib/io/DocuDirCache.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/DocuDirCache.java	Tue Dec 21 09:52:16 2010 +0100
@@ -24,13 +24,13 @@
 
 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.io.FileOps.FileClass;
 import digilib.servlet.DigilibConfiguration;
 
 /**
@@ -42,13 +42,13 @@
 	Logger logger = Logger.getLogger(this.getClass());
 
 	/** HashMap of directories */
-	Map map = null;
+	Map<String, DocuDirectory> map = null;
 
 	/** names of base directories */
 	String[] baseDirNames = null;
 
 	/** array of allowed file classes (image/text) */
-	private int[] fileClasses = null;
+	private FileClass[] fileClasses = null;
 
 	/** number of files in the whole cache (approximate) */
 	long numFiles = 0;
@@ -59,9 +59,6 @@
 	/** number of cache misses */
 	long misses = 0;
 
-	/** use safe (but slow) indexing */
-	boolean safeDirIndex = false;
-
 	/** the root directory element */
 	public static Directory ROOT = null;
 
@@ -71,12 +68,11 @@
 	 * @param bd
 	 *            base directory names
 	 */
-	public DocuDirCache(String[] bd, int[] fileClasses,
+	public DocuDirCache(String[] bd, FileClass[] fcs,
 			DigilibConfiguration dlConfig) {
 		baseDirNames = bd;
-		map = new HashMap();
-		this.fileClasses = fileClasses;
-		safeDirIndex = dlConfig.getAsBoolean("safe-dir-index");
+		map = new HashMap<String, DocuDirectory>();
+		this.fileClasses = fcs;
 	}
 
 	/**
@@ -87,10 +83,9 @@
 	 */
 	public DocuDirCache(String[] bd) {
 		baseDirNames = bd;
-		map = new HashMap();
+		map = new HashMap<String, DocuDirectory>();
 		// default file class is CLASS_IMAGE
-		fileClasses = new int[1];
-		fileClasses[0] = FileOps.CLASS_IMAGE;
+		fileClasses = new FileClass[] { FileClass.IMAGE };
 	}
 
 	/**
@@ -127,7 +122,7 @@
 		String parent = FileOps.parent(newDir.getDirName());
 		if (parent != "") {
 			// check the parent in the cache
-			DocuDirectory pd = (DocuDirectory) map.get(parent);
+			DocuDirectory pd = map.get(parent);
 			if (pd == null) {
 				// the parent is unknown
 				pd = new DocuDirectory(parent, this);
@@ -149,11 +144,9 @@
 	 *            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);
+	public List<DocuDirectory> getChildren(String dirname, boolean recurse) {
+		List<DocuDirectory> l = new LinkedList<DocuDirectory>();
+		for (DocuDirectory dd: map.values()) {
 			if (recurse) {
 				if (dd.getDirName().startsWith(dirname)) {
 					l.add(dd);
@@ -182,12 +175,12 @@
 	 *            file class
 	 * @return
 	 */
-	public DocuDirent getFile(String fn, int in, int fc) {
+	public DocuDirent getFile(String fn, int in, FileClass 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);
+		dd = map.get(fn);
         // logger.debug("fn: " + fn);
         // logger.debug("dd: " + dd);
 		if (dd == null) {
@@ -213,7 +206,7 @@
 				String d = FileOps.parent(fn);
 				// try it in the cache
                 // logger.debug(fn + " is a file in dir " + d);
-				dd = (DocuDirectory) map.get(d);
+				dd = map.get(d);
 				if (dd == null) {
 					// try to read from disk
 					dd = new DocuDirectory(d, this);
@@ -262,7 +255,7 @@
 	public DocuDirectory getDirectory(String fn) {
 		DocuDirectory dd;
 		// first, assume fn is a directory and look in the cache
-		dd = (DocuDirectory) map.get(fn);
+		dd = map.get(fn);
 		if (dd == null) {
 			// cache miss
 			misses++;
@@ -278,7 +271,7 @@
 				// maybe it's a file
 				if (f.canRead()) {
 					// try the parent directory in the cache
-					dd = (DocuDirectory) map.get(f.getParent());
+					dd = map.get(f.getParent());
 					if (dd == null) {
 						// try to read from disk
 						dd = new DocuDirectory(f.getParent(), this);
@@ -350,14 +343,14 @@
 	/**
 	 * @return
 	 */
-	public int[] getFileClasses() {
+	public FileClass[] getFileClasses() {
 		return fileClasses;
 	}
 
 	/**
 	 * @param fileClasses
 	 */
-	public void setFileClasses(int[] fileClasses) {
+	public void setFileClasses(FileClass[] fileClasses) {
 		this.fileClasses = fileClasses;
 	}
 
--- a/servlet/src/digilib/io/DocuDirectory.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/DocuDirectory.java	Tue Dec 21 09:52:16 2010 +0100
@@ -25,19 +25,20 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
 import org.xml.sax.SAXException;
 
+import digilib.io.FileOps.FileClass;
+
 /**
  * @author casties
  */
 public class DocuDirectory extends Directory {
 
 	/** list of files (DocuDirent) */
-	private ArrayList[] list = null;
+	private List<List<DocuDirent>> list = null;
 
 	/** directory object is valid (exists on disk) */
 	private boolean isValid = false;
@@ -49,13 +50,13 @@
 	private String dirName = null;
 
 	/** directory metadata */
-	private Map dirMeta = null;
+	private MetadataMap dirMeta = null;
 
 	/** state of metadata is valid */
 	private boolean metaChecked = false;
 
 	/** unresolved file metadata */
-	private Map unresolvedFileMeta = null;
+	private Map<String, MetadataMap> unresolvedFileMeta = null;
 
 	/** time of last access of this object (not the filesystem) */
 	private long objectATime = 0;
@@ -89,8 +90,13 @@
 	 */
 	protected void initDir() {
 		String baseDirName = cache.getBaseDirNames()[0];
-		// clear directory first
-		list = new ArrayList[FileOps.NUM_CLASSES];
+		// clear directory list
+		FileClass[] fcs = FileClass.values();
+		list = new ArrayList<List<DocuDirent>>(fcs.length);
+		// create empty list for all classes
+		for (@SuppressWarnings("unused") FileClass fc: fcs) {
+		    list.add(null);
+		}
 		isValid = false;
 		dirMTime = 0;
 		// the first directory has to exist
@@ -102,7 +108,7 @@
 	 *  
 	 */
 	public int size() {
-		return ((list != null) && (list[0] != null)) ? list[0].size() : 0;
+		return ((list != null) && (list.get(0) != null)) ? list.get(0).size() : 0;
 	}
 
 	/**
@@ -111,21 +117,21 @@
 	 * @param fc
 	 *            fileClass
 	 */
-	public int size(int fc) {
-		return ((list != null) && (list[fc] != null)) ? list[fc].size() : 0;
+	public int size(FileClass fc) {
+		return ((list != null) && (list.get(fc.ordinal()) != null)) ? list.get(fc.ordinal()).size() : 0;
 	}
 
 	/**
-	 * Returns the ImageFile at the index.
+	 * Returns the ImageFileSet at the index.
 	 * 
 	 * @param index
 	 * @return
 	 */
 	public ImageFileset get(int index) {
-		if ((list == null) || (list[0] == null) || (index >= list[0].size())) {
+		if ((list == null) || (list.get(0) == null) || (index >= list.get(0).size())) {
 			return null;
 		}
-		return (ImageFileset) list[0].get(index);
+		return (ImageFileset) list.get(0).get(index);
 	}
 
 	/**
@@ -136,11 +142,11 @@
 	 *            fileClass
 	 * @return
 	 */
-	public DocuDirent get(int index, int fc) {
-		if ((list == null) || (list[fc] == null) || (index >= list[fc].size())) {
+	public DocuDirent get(int index, FileClass fc) {
+		if ((list == null) || (list.get(fc.ordinal()) == null) || (index >= list.get(fc.ordinal()).size())) {
 			return null;
 		}
-		return (DocuDirent) list[fc].get(index);
+		return (DocuDirent) list.get(fc.ordinal()).get(index);
 	}
 
 	/**
@@ -180,11 +186,8 @@
 		 * extensions) but slower.
 		 */
 		File[] allFiles = null;
-		if (cache.safeDirIndex) {
-			allFiles = dir.listFiles(new FileOps.ReadableFileFilter());
-		} else {
-			allFiles = dir.listFiles();
-		}
+		//	allFiles = dir.listFiles(new FileOps.ReadableFileFilter());
+		allFiles = dir.listFiles();
 		//logger.debug("  done");
 		if (allFiles == null) {
 			// not a directory
@@ -210,10 +213,9 @@
 		}
 
 		// 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);
+        //for (int classIdx = 0; classIdx < FileOps.NUM_CLASSES; classIdx++) {
+		for (FileClass fileClass: cache.getFileClasses()) {
+			//fileClass = cache.getFileClasses()[classIdx];
 			File[] fileList = FileOps.listFiles(allFiles, FileOps
 					.filterForClass(fileClass));
 			//logger.debug(" done");
@@ -221,10 +223,10 @@
 			int numFiles = fileList.length;
 			if (numFiles > 0) {
 				// create new list
-				list[fileClass] = new ArrayList(numFiles);
+				list.set(fileClass.ordinal(), new ArrayList<DocuDirent>(numFiles));
 				// sort the file names alphabetically and iterate the list
 				// Arrays.sort(fileList); // not needed <hertzhaft>
-				Map hints = FileOps.newHints(FileOps.HINT_BASEDIRS, dirs);
+				Map<Integer, Object> 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],
@@ -232,11 +234,12 @@
 					// add the file to our list
                     // logger.debug(f.getName());
 
-					list[fileClass].add(f);
+					list.get(fileClass.ordinal()).add(f);
 					f.setParent(this);
 				}
-                // we sort the ArrayList, not the Array, for binarySearch to work
-                Collections.sort(list[fileClass]);
+                // we sort the inner ArrayList (the list of files not the list of file types)
+				// for binarySearch to work (DocuDirent's natural sort order is by filename)
+                Collections.sort(list.get(fileClass.ordinal()));
 			}
 		}
 		// clear the scaled directories
@@ -282,12 +285,12 @@
 			XMLMetaLoader ml = new XMLMetaLoader();
 			try {
 				// read directory meta file
-				Map fileMeta = ml.loadURL(mf.getAbsolutePath());
+				Map<String, MetadataMap> fileMeta = ml.loadURL(mf.getAbsolutePath());
 				if (fileMeta == null) {
 					return;
 				}
 				// meta for the directory itself is in the "" bin
-				dirMeta = (Map) fileMeta.remove("");
+				dirMeta = fileMeta.remove("");
 				// read meta for files in this directory
 				readFileMeta(fileMeta, null);
 				// is there meta for other files left?
@@ -334,25 +337,23 @@
 	 * @param fc
 	 *            fileClass
 	 */
-	protected void readFileMeta(Map fileMeta, String relPath) {
+	protected void readFileMeta(Map<String,MetadataMap> 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) {
+		for (FileClass fc: FileClass.values()) {
+			if (list.get(fc.ordinal()) == null) {
 				continue;
 			}
 			// iterate through the list of files in this directory
-			for (Iterator i = list[fc].iterator(); i.hasNext();) {
-				DocuDirent f = (DocuDirent) i.next();
+			for (DocuDirent f: list.get(fc.ordinal())) {
 				// 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);
+				MetadataMap meta = fileMeta.remove(fn);
 				if (meta != null) {
 					// store meta in file
 					f.setFileMeta(meta);
@@ -361,13 +362,13 @@
 		}
 	}
 
-	protected void notifyChildMeta(Map childmeta) {
-		List children = cache.getChildren(this.dirName, true);
+	protected void notifyChildMeta(MetadataMap childmeta) {
+		List<DocuDirectory> children = cache.getChildren(this.dirName, true);
 		if (children.size() > 0) {
-			for (Iterator i = children.iterator(); i.hasNext();) {
+			/*for (DocuDirectory d: children) {
 				// TODO: finish this!
 				//((DocuDirectory) i.next()).readFileMeta()
-			}
+			}*/
 		}
 	}
 
@@ -395,7 +396,7 @@
 	 * @return int index of file <code>fn</code>
 	 */
 	public int indexOf(String fn) {
-		int fc = FileOps.classForFilename(fn);
+		FileClass fc = FileOps.classForFilename(fn);
 		return indexOf(fn, fc);
 	}
 
@@ -409,20 +410,20 @@
 	 *            filename
 	 * @return int index of file <code>fn</code>
 	 */
-	public int indexOf(String fn, int fc) {
+	public int indexOf(String fn, FileClass fc) {
 		if (!isRead()) {
 			// read directory now
 			if (!readDir()) {
 				return -1;
 			}
 		}
-		List fileList = list[fc];
+		List<DocuDirent> fileList = list.get(fc.ordinal());
 		// empty directory?
 		if (fileList == null) {
 			return -1;
 		}
         
-		// search for exact match
+		// search for exact match (DocuDirent does compareTo<String>)
         // OBS: fileList needs to be sorted first (see )! <hertzhaft>
 		int idx = Collections.binarySearch(fileList, fn);
 		if (idx >= 0) {
@@ -432,15 +433,15 @@
 			// try closest matches without extension
 			idx = -idx - 1;
 			if ((idx < fileList.size())
-					&& isBaseInList(fileList, idx, fn)) {
+					&& isBasenameInList(fileList, idx, fn)) {
 				// idx matches
 				return idx;
 			} else if ((idx > 0)
-					&& isBaseInList(fileList, idx-1, fn)) {
+					&& isBasenameInList(fileList, idx-1, fn)) {
 				// idx-1 matches
 				return idx - 1;
 			} else if ((idx + 1 < fileList.size())
-					&& isBaseInList(fileList, idx+1, fn)) {
+					&& isBasenameInList(fileList, idx+1, fn)) {
 				// idx+1 matches
 				return idx + 1;
 			}
@@ -449,8 +450,8 @@
 		return -1;
 	}
 
-	private boolean isBaseInList(List fl, int idx, String fn) {
-		String dfn = FileOps.basename(((DocuDirent) fl.get(idx))
+	private boolean isBasenameInList(List<DocuDirent> fl, int idx, String fn) {
+		String dfn = FileOps.basename((fl.get(idx))
 				.getName());
 		return (dfn.equals(fn)||dfn.equals(FileOps.basename(fn))); 
 	}
@@ -467,10 +468,10 @@
 	 * @return DocuDirent
 	 */
 	public DocuDirent find(String fn) {
-		int fc = FileOps.classForFilename(fn);
+		FileClass fc = FileOps.classForFilename(fn);
 		int i = indexOf(fn, fc);
 		if (i >= 0) {
-			return (DocuDirent) list[0].get(i);
+			return (DocuDirent) list.get(0).get(i);
 		}
 		return null;
 	}
@@ -486,10 +487,10 @@
 	 *            filename
 	 * @return DocuDirent
 	 */
-	public DocuDirent find(String fn, int fc) {
+	public DocuDirent find(String fn, FileClass fc) {
 		int i = indexOf(fn, fc);
 		if (i >= 0) {
-			return (DocuDirent) list[fc].get(i);
+			return (DocuDirent) list.get(fc.ordinal()).get(i);
 		}
 		return null;
 	}
@@ -531,7 +532,7 @@
 	/**
 	 * @return Hashtable
 	 */
-	public Map getDirMeta() {
+	public MetadataMap getDirMeta() {
 		return dirMeta;
 	}
 
@@ -560,7 +561,7 @@
 	 * @param dirMeta
 	 *            The dirMeta to set
 	 */
-	public void setDirMeta(Map dirMeta) {
+	public void setDirMeta(MetadataMap dirMeta) {
 		this.dirMeta = dirMeta;
 	}
 
--- a/servlet/src/digilib/io/DocuDirent.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/DocuDirent.java	Tue Dec 21 09:52:16 2010 +0100
@@ -27,18 +27,20 @@
 
 import org.apache.log4j.Logger;
 
+import digilib.io.FileOps.FileClass;
+
 /**
  * Abstract directory entry in a DocuDirectory.
  * 
  * @author casties
  *  
  */
-public abstract class DocuDirent implements Comparable {
+public abstract class DocuDirent implements Comparable<Object> {
 
 	/** the file class of this file */
-	protected static int fileClass = FileOps.CLASS_NONE;
+	protected static FileClass fileClass = FileClass.NONE;
 	/** HashMap with metadata */
-	protected Map fileMeta = null;
+	protected MetadataMap fileMeta = null;
 	/** Is the Metadata valid */
 	protected boolean metaChecked = false;
 	/** the parent directory */
@@ -73,11 +75,11 @@
 			XMLMetaLoader ml = new XMLMetaLoader();
 			try {
 				// read meta file
-				Map meta = ml.loadURL(mf.getAbsolutePath());
+				Map<String, MetadataMap> meta = ml.loadURL(mf.getAbsolutePath());
 				if (meta == null) {
 					return;
 				}
-				fileMeta = (Map) meta.get(getName());
+				fileMeta = meta.get(getName());
 			} catch (Exception e) {
 				Logger.getLogger(this.getClass()).warn("error reading file .meta", e);
 			}
@@ -121,7 +123,7 @@
 	 * 
 	 * @return HashMap
 	 */
-	public Map getFileMeta() {
+	public MetadataMap getFileMeta() {
 		return fileMeta;
 	} 
 	
@@ -131,7 +133,7 @@
 	 * @param fileMeta
 	 *            The fileMeta to set
 	 */
-	public void setFileMeta(Map fileMeta) {
+	public void setFileMeta(MetadataMap fileMeta) {
 		this.fileMeta = fileMeta;
 	} 
 	
@@ -145,7 +147,7 @@
 	/**
 	 * @return
 	 */
-	public static int getFileClass() {
+	public static FileClass getFileClass() {
 		return fileClass;
 	}
 
@@ -156,9 +158,11 @@
 	 * @see java.lang.Comparable#compareTo(java.lang.Object)
 	 */
 	public int compareTo(Object arg0) {
-		return (arg0 instanceof DocuDirent)
-            ? getName().compareTo(((DocuDirent) arg0).getName())
-            : getName().compareTo((String) arg0);
+		if (arg0 instanceof DocuDirent) {
+		    return getName().compareTo(((DocuDirent) arg0).getName());
+		} else {
+		    return getName().compareTo((String) arg0);
+		}
 	}
 
 	
--- a/servlet/src/digilib/io/FileOps.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/FileOps.java	Tue Dec 21 09:52:16 2010 +0100
@@ -44,23 +44,15 @@
 			{ "htm", "text/html" }, { "xml", "text/xml" },
 			{ "svg", "image/svg+xml" }, { "meta", "text/xml" } };
 
-	public static Map fileTypes;
-
-	public static List imageExtensions;
+	public static Map<String, String> fileTypes;
 
-	public static List textExtensions;
-
-	public static List svgExtensions;
+	public static List<String> imageExtensions;
 
-	public static final int CLASS_NONE = -1;
-
-	public static final int CLASS_IMAGE = 0;
+	public static List<String> textExtensions;
 
-	public static final int CLASS_TEXT = 1;
+	public static List<String> svgExtensions;
 
-	public static final int CLASS_SVG = 2;
-
-	public static final int NUM_CLASSES = 3;
+	public static enum FileClass {NONE, IMAGE, TEXT, SVG}
 
 	public static final Integer HINT_BASEDIRS = new Integer(1);
 
@@ -72,20 +64,20 @@
 	 * static initializer for FileOps
 	 */
 	static {
-		fileTypes = new HashMap();
-		imageExtensions = new ArrayList();
-		textExtensions = new ArrayList();
-		svgExtensions = new ArrayList();
+		fileTypes = new HashMap<String, String>();
+		imageExtensions = new ArrayList<String>();
+		textExtensions = new ArrayList<String>();
+		svgExtensions = new ArrayList<String>();
 		// iterate through file types in ft and fill the Map and Lists
 		for (int i = 0; i < ft.length; i++) {
 			String ext = ft[i][0];
 			String mt = ft[i][1];
 			fileTypes.put(ext, mt);
-			if (classForMimetype(mt) == CLASS_IMAGE) {
+			if (classForMimetype(mt) == FileClass.IMAGE) {
 				imageExtensions.add(ext);
-			} else if (classForMimetype(mt) == CLASS_TEXT) {
+			} else if (classForMimetype(mt) == FileClass.TEXT) {
 				textExtensions.add(ext);
-			} else if (classForMimetype(mt) == CLASS_SVG) {
+			} else if (classForMimetype(mt) == FileClass.SVG) {
 				svgExtensions.add(ext);
 			}
 		}
@@ -97,18 +89,18 @@
 	 * @param mt
 	 * @return
 	 */
-	public static int classForMimetype(String mt) {
+	public static FileClass classForMimetype(String mt) {
 		if (mt == null) {
-			return CLASS_NONE;
+			return FileClass.NONE;
 		}
 		if (mt.startsWith("image/svg")) {
-			return CLASS_SVG;
+			return FileClass.SVG;
 		} else if (mt.startsWith("image")) {
-			return CLASS_IMAGE;
+			return FileClass.IMAGE;
 		} else if (mt.startsWith("text")) {
-			return CLASS_TEXT;
+			return FileClass.TEXT;
 		}
-		return CLASS_NONE;
+		return FileClass.NONE;
 	}
 
 	/**
@@ -124,23 +116,36 @@
 	 * @param fn
 	 * @return
 	 */
-	public static int classForFilename(String fn) {
+	public static FileClass classForFilename(String fn) {
 		String mt = (String) fileTypes.get(extname(fn).toLowerCase());
 		return classForMimetype(mt);
 	}
 
-	public static Iterator getImageExtensionIterator() {
+	public static Iterator<String> getImageExtensionIterator() {
 		return imageExtensions.iterator();
 	}
 
-	public static Iterator getTextExtensionIterator() {
+    public static List<String> getImageExtensions() {
+        return imageExtensions;
+    }
+
+    public static Iterator<String> getTextExtensionIterator() {
 		return textExtensions.iterator();
 	}
 
-	public static Iterator getSVGExtensionIterator() {
+	public static List<String> getTextExtensions() {
+        return textExtensions;
+    }
+
+    public static Iterator<String> getSVGExtensionIterator() {
 		return svgExtensions.iterator();
 	}
 
+    public static List<String> getSvgExtensions() {
+        return svgExtensions;
+    }
+
+
 	/**
 	 * convert a string with a list of pathnames into an array of strings using
 	 * the system's path separator string
@@ -277,7 +282,7 @@
 	static class ImageFileFilter implements FileFilter {
 
 		public boolean accept(File f) {
-			return (classForFilename(f.getName()) == CLASS_IMAGE);
+			return (classForFilename(f.getName()) == FileClass.IMAGE);
 		}
 	}
 
@@ -287,7 +292,7 @@
 	static class TextFileFilter implements FileFilter {
 
 		public boolean accept(File f) {
-			return (classForFilename(f.getName()) == CLASS_TEXT);
+			return (classForFilename(f.getName()) == FileClass.TEXT);
 		}
 	}
 
@@ -298,7 +303,7 @@
 	static class SVGFileFilter implements FileFilter {
 
 		public boolean accept(File f) {
-			return (classForFilename(f.getName()) == CLASS_SVG);
+			return (classForFilename(f.getName()) == FileClass.SVG);
 		}
 	}
 
@@ -308,14 +313,14 @@
 	 * @param fileClass
 	 * @return
 	 */
-	public static FileFilter filterForClass(int fileClass) {
-		if (fileClass == CLASS_IMAGE) {
+	public static FileFilter filterForClass(FileClass fileClass) {
+		if (fileClass == FileClass.IMAGE) {
 			return new ImageFileFilter();
 		}
-		if (fileClass == CLASS_TEXT) {
+		if (fileClass == FileClass.TEXT) {
 			return new TextFileFilter();
 		}
-		if (fileClass == CLASS_SVG) {
+		if (fileClass == FileClass.SVG) {
 			return new SVGFileFilter();
 		}
 		return null;
@@ -333,15 +338,15 @@
 	 *            optional additional parameters
 	 * @return
 	 */
-	public static DocuDirent fileForClass(int fileClass, File file, Map hints) {
+	public static DocuDirent fileForClass(FileClass fileClass, File file, Map<Integer,Object> hints) {
 		// what class of file do we have?
-		if (fileClass == CLASS_IMAGE) {
+		if (fileClass == FileClass.IMAGE) {
 			// image file
 			return new ImageFileset(file, hints);
-		} else if (fileClass == CLASS_TEXT) {
+		} else if (fileClass == FileClass.TEXT) {
 			// text file
 			return new TextFile(file);
-		} else if (fileClass == CLASS_SVG) {
+		} else if (fileClass == FileClass.SVG) {
 			// text file
 			return new SVGFile(file);
 		}
@@ -379,8 +384,8 @@
 	 * @param value
 	 * @return
 	 */
-	public static Map newHints(Integer type, Object value) {
-		Map m = new HashMap();
+	public static Map<Integer, Object> newHints(Integer type, Object value) {
+		Map<Integer, Object> m = new HashMap<Integer, Object>();
 		if (type != null) {
 			m.put(type, value);
 		}
--- a/servlet/src/digilib/io/ImageFileset.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/ImageFileset.java	Tue Dec 21 09:52:16 2010 +0100
@@ -22,11 +22,13 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 
-import digilib.image.ImageOps;
 import digilib.image.ImageSize;
+import digilib.io.FileOps.FileClass;
+import digilib.servlet.DigilibConfiguration;
 
 /**
  * @author casties
@@ -34,10 +36,10 @@
 public class ImageFileset extends DocuDirent {
 
 	/** this is an image file */
-	protected static int fileClass = FileOps.CLASS_IMAGE;
+	protected static FileClass fileClass = FileClass.IMAGE;
 	
 	/** list of files (ImageFile) */
-	private ArrayList list = null;
+	private List<ImageFile> list = null;
 
 	/** aspect ratio (width/height) */
 	private float aspect = 0;
@@ -55,7 +57,7 @@
 	 * @param initialCapacity
 	 */
 	public ImageFileset() {
-		list = new ArrayList();
+		list = new ArrayList<ImageFile>();
 	}
 
 	/**
@@ -66,10 +68,10 @@
 	 * @param file
 	 * @param hints
 	 */
-	public ImageFileset(File file, Map hints) {
+	public ImageFileset(File file, Map<Integer,Object> hints) {
 		Directory[] dirs = (Directory[]) hints.get(FileOps.HINT_BASEDIRS);
 		int nb = dirs.length;
-		list = new ArrayList(nb);
+		list = new ArrayList<ImageFile>(nb);
 		parent = dirs[0];
 		fill(dirs, file, hints);
 	}
@@ -105,7 +107,7 @@
 	 *  
 	 */
 	public File getFile() {
-		return (list != null) ? ((ImageFile) list.get(0)).getFile() : null;
+		return (list != null) ? list.get(0).getFile() : null;
 	}
 
 	/**
@@ -116,7 +118,7 @@
 	 * @return
 	 */
 	public ImageFile get(int index) {
-		return (ImageFile) list.get(index);
+		return list.get(index);
 	}
 
 	/**
@@ -132,11 +134,11 @@
 	 * @return
 	 */
 	public ImageFile getNextSmaller(ImageSize size) {
-		for (Iterator i = getHiresIterator(); i.hasNext();) {
-			ImageFile f = (ImageFile) i.next();
+		for (Iterator<ImageFile> i = getHiresIterator(); i.hasNext();) {
+			ImageFile f = i.next();
 			try {
 				if (!f.isChecked()) {
-					ImageOps.checkFile(f);
+					DigilibConfiguration.docuImageIdentify(f);
 				}
 				if (f.getSize().isTotallySmallerThan(size)) {
 					return f;
@@ -160,11 +162,11 @@
 	 * @return
 	 */
 	public ImageFile getNextBigger(ImageSize size) {
-		for (ListIterator i = getLoresIterator(); i.hasPrevious();) {
-			ImageFile f = (ImageFile) i.previous();
+		for (ListIterator<ImageFile> i = getLoresIterator(); i.hasPrevious();) {
+			ImageFile f = i.previous();
 			try {
 				if (!f.isChecked()) {
-					ImageOps.checkFile(f);
+					DigilibConfiguration.docuImageIdentify(f);
 				}
 				if (f.getSize().isBiggerThan(size)) {
 					return f;
@@ -202,7 +204,7 @@
 	 * 
 	 * @return
 	 */
-	public ListIterator getHiresIterator() {
+	public ListIterator<ImageFile> getHiresIterator() {
 		return list.listIterator();
 	}
 
@@ -216,7 +218,7 @@
 	 * 
 	 * @return
 	 */
-	public ListIterator getLoresIterator() {
+	public ListIterator<ImageFile> getLoresIterator() {
 		return list.listIterator(list.size());
 	}
 
@@ -231,7 +233,7 @@
 	 * @param hints
 	 *  
 	 */
-	void fill(Directory[] dirs, File fl, Map hints) {
+	void fill(Directory[] dirs, File fl, Map<Integer,Object> hints) {
 		int nb = dirs.length;
 		String fn = fl.getName();
 		String baseFn = FileOps.basename(fn);
@@ -271,7 +273,7 @@
 					continue;
 				}
 			}
-			if (FileOps.classForFilename(dirFiles[fileIdx]) == FileOps.CLASS_IMAGE) {
+			if (FileOps.classForFilename(dirFiles[fileIdx]) == fileClass) {
 				/* logger.debug("adding file " + dirFiles[fileIdx]
 						+ " to Fileset " + this.getName()); */
 				add(new ImageFile(dirFiles[fileIdx], this, dirs[dirIdx]));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/MetadataMap.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,14 @@
+/**
+ * 
+ */
+package digilib.io;
+
+import java.util.HashMap;
+
+/** Map for metadata related to files.
+ * @author casties
+ *
+ */
+public class MetadataMap extends HashMap<String, String> {
+
+}
--- a/servlet/src/digilib/io/SVGFile.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/SVGFile.java	Tue Dec 21 09:52:16 2010 +0100
@@ -23,6 +23,8 @@
 
 import java.io.File;
 
+import digilib.io.FileOps.FileClass;
+
 /** Class for SVG files.
  * 
  * @author casties
@@ -30,7 +32,7 @@
  */
 public class SVGFile extends DocuDirent {
 	/** this is a text file */
-	protected static int fileClass = FileOps.CLASS_SVG;
+	protected static FileClass fileClass = FileClass.SVG;
 	/** our File instance */
 	protected File file = null;
 	
--- a/servlet/src/digilib/io/TextFile.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/TextFile.java	Tue Dec 21 09:52:16 2010 +0100
@@ -23,6 +23,8 @@
 
 import java.io.File;
 
+import digilib.io.FileOps.FileClass;
+
 /** Class for text files.
  * 
  * @author casties
@@ -30,7 +32,7 @@
  */
 public class TextFile extends DocuDirent {
 	/** this is a text file */
-	protected static int fileClass = FileOps.CLASS_TEXT;
+	protected static FileClass fileClass = FileClass.TEXT;
 	/** our File instance */
 	protected File file = null;
 	
--- a/servlet/src/digilib/io/XMLListLoader.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/XMLListLoader.java	Tue Dec 21 09:52:16 2010 +0100
@@ -75,17 +75,17 @@
 	 */
 	private class XMLListParser extends DefaultHandler {
 
-		private Map listData;
-		private LinkedList tagSpace;
+		private Map<String, String> listData;
+		private LinkedList<String> tagSpace;
 
-		public Map getData() {
+		public Map<String, String> getData() {
 			return listData;
 		}
 
 		// Parser calls this once at the beginning of a document
 		public void startDocument() throws SAXException {
-			listData = new HashMap();
-			tagSpace = new LinkedList();
+			listData = new HashMap<String, String>();
+			tagSpace = new LinkedList<String>();
 		}
 
 		// Parser calls this for each element in a document
@@ -151,7 +151,7 @@
 	 *  load and parse a file (as URL)
 	 *    returns HashMap with list data
 	 */
-	public Map loadURL(String path) throws SAXException, IOException {
+	public Map<String, String> loadURL(String path) throws SAXException, IOException {
 		//System.out.println("loadurl ("+path+")");
 		// Create a JAXP SAXParserFactory and configure it
 		SAXParserFactory spf = SAXParserFactory.newInstance();
--- a/servlet/src/digilib/io/XMLMetaLoader.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/io/XMLMetaLoader.java	Tue Dec 21 09:52:16 2010 +0100
@@ -52,9 +52,9 @@
 	 */
 	private class XMLMetaParser extends DefaultHandler {
 
-		private LinkedList tags;
-		private Map files;
-		private Map meta;
+		private LinkedList<String> tags;
+		private Map<String, MetadataMap> files;
+		private MetadataMap meta;
 		private StringBuffer content;
 		private boolean collecting;
 		private StringBuffer collectedContent;
@@ -96,8 +96,8 @@
 			
 		// Parser calls this once at the beginning of a document
 		public void startDocument() throws SAXException {
-			tags = new LinkedList();
-			files = new HashMap();
+			tags = new LinkedList<String>();
+			files = new HashMap<String, MetadataMap>();
 			collecting = false;
 			collectedContent = null;
 		}
@@ -118,13 +118,13 @@
 
 			if (name.equals(metaTag)) {
 				// new meta tag
-				meta = new HashMap();
+				meta = new MetadataMap();
 				collectedContent = new StringBuffer();
 			} else if (name.equals(fileTag)) {
 				// new file tag
 				fileName = null;
 				filePath = null;
-				meta = new HashMap();
+				meta = new MetadataMap();
 				collectedContent = new StringBuffer();
 			} else if (name.equals(collectTag)) {
 				// start collecting
@@ -162,7 +162,7 @@
 			String name = getName(localName, qName);
 			// exit the tag
 			tags.removeLast();
-			String lastTag = (tags.isEmpty()) ? "" : (String) tags.getLast();
+			String lastTag = (tags.isEmpty()) ? "" : tags.getLast();
 
 			// was it a file/name tag?
 			if (name.equals(fileNameTag) && lastTag.equals(fileTag)) {
@@ -260,7 +260,7 @@
 	 *  load and parse a file (as URL)
 	 *    returns HashMap with list data
 	 */
-	public Map loadURL(String path) throws SAXException, IOException {
+	public Map<String, MetadataMap> loadURL(String path) throws SAXException, IOException {
 		logger.debug("loading meta: "+path);
 		// Create a JAXP SAXParserFactory and configure it
 		SAXParserFactory spf = SAXParserFactory.newInstance();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/pdf/PDFFileWorker.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,58 @@
+/**
+ * 
+ */
+package digilib.pdf;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+
+import digilib.image.DocuImage;
+import digilib.servlet.DigilibConfiguration;
+import digilib.servlet.PDFRequest;
+import digilib.util.DigilibJobCenter;
+
+/**
+ * @author casties
+ *
+ */
+public class PDFFileWorker implements Callable<File> {
+	/** the wrapped PDFStreamWorker */
+    protected PDFStreamWorker streamWorker;
+    
+    /** the temporary output file */
+    protected File tempFile;
+
+    /** the final output file */
+    protected File finalFile;
+
+    /** Create new PDFFileWorker.
+     * @param dlConfig
+     * @param tempFile
+     * @param job_info
+     * @param imageJobCenter
+     * @throws FileNotFoundException
+     */
+    public PDFFileWorker(DigilibConfiguration dlConfig, 
+    		File tempFile, File finalFile,
+			PDFRequest job_info,
+			DigilibJobCenter<DocuImage> imageJobCenter) throws FileNotFoundException {
+        this.tempFile = tempFile; 
+    	OutputStream outstream = new FileOutputStream(tempFile);
+    	this.finalFile = finalFile;
+    	this.streamWorker = new PDFStreamWorker(dlConfig, outstream, job_info, imageJobCenter);
+    }
+    
+    @Override
+    public File call() throws Exception {
+    	OutputStream outstream = streamWorker.call();
+    	outstream.flush();
+    	// move temporary to final file
+    	tempFile.renameTo(finalFile);
+        return finalFile;
+    }
+    
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/pdf/PDFStreamWorker.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,162 @@
+package digilib.pdf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+import org.apache.log4j.Logger;
+
+import com.itextpdf.text.Document;
+import com.itextpdf.text.DocumentException;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.PageSize;
+import com.itextpdf.text.pdf.PdfWriter;
+
+import digilib.image.DocuImage;
+import digilib.image.ImageJobDescription;
+import digilib.image.ImageWorker;
+import digilib.servlet.DigilibConfiguration;
+import digilib.servlet.PDFRequest;
+import digilib.util.DigilibJobCenter;
+import digilib.util.NumRange;
+
+public class PDFStreamWorker implements Callable<OutputStream> {
+
+	protected static Logger logger = Logger.getLogger(PDFStreamWorker.class);
+
+	protected DigilibConfiguration dlConfig = null;
+
+	protected Document doc = null;
+
+	protected OutputStream outstream = null;
+
+	protected PDFRequest job_info = null;
+
+	protected DigilibJobCenter<DocuImage> imageJobCenter = null;
+
+	/**
+	 * @param dlConfig
+	 * @param outputfile
+	 * @param job_info
+	 */
+	public PDFStreamWorker(DigilibConfiguration dlConfig, OutputStream outputfile,
+			PDFRequest job_info,
+			DigilibJobCenter<DocuImage> imageJobCenter) {
+		super();
+		this.dlConfig = dlConfig;
+		this.outstream = outputfile;
+		this.job_info = job_info;
+		this.imageJobCenter = imageJobCenter;
+	}
+
+	public OutputStream call() throws Exception {
+		outstream = renderPDF();
+		return outstream;
+	}
+
+	/**
+	 * @throws DocumentException
+	 * @throws InterruptedException
+	 * @throws ExecutionException
+	 * @throws IOException
+	 */
+	protected OutputStream renderPDF() throws DocumentException, InterruptedException,
+			ExecutionException, IOException {
+		// create document object
+		doc = new Document(PageSize.A4, 0, 0, 0, 0);
+		PdfWriter docwriter = null;
+
+		long start_time = System.currentTimeMillis();
+
+		docwriter = PdfWriter.getInstance(doc, outstream);
+
+		setPDFProperties(doc);
+
+		doc.open();
+
+		addTitlePage(doc);
+
+		logger.debug("- " + outstream + " doc.open()ed ("
+				+ (System.currentTimeMillis() - start_time) + "ms)");
+		start_time = System.currentTimeMillis();
+
+		NumRange pgs = job_info.getPages();
+
+		for (int p : pgs) {
+			logger.debug(" - adding Image " + p + " to " + outstream);
+			// create ImageJobInformation
+			ImageJobDescription iji = ImageJobDescription.getInstance(job_info, job_info.getDlConfig());
+			iji.setValue("pn", p);
+			addImage(doc, iji);
+			logger.debug(" - done adding Image " + p + " to " + outstream);
+		}
+
+		logger.debug(" - done adding all Images to " + outstream);
+
+		doc.close();
+		logger.debug("- " + outstream + " doc.close() ("
+				+ (System.currentTimeMillis() - start_time) + "ms)");
+		docwriter.close();
+		return outstream;
+	}
+
+	/**
+	 * Set PDF-Meta-Attributes.
+	 */
+	public Document setPDFProperties(Document doc) {
+		// TODO get proper Information from dlConfig
+		doc.addAuthor(this.getClass().getName());
+		doc.addCreationDate();
+		doc.addKeywords("digilib");
+		doc.addTitle("digilib PDF");
+		doc.addCreator(this.getClass().getName());
+		return doc;
+	}
+
+	/**
+	 * Create a title page and append it to the document (should, of course, be
+	 * called first)
+	 * 
+	 * @throws DocumentException
+	 */
+	public Document addTitlePage(Document doc) throws DocumentException {
+		PDFTitlePage titlepage = new PDFTitlePage(job_info);
+		doc.add(titlepage.getPageContents());
+		doc.newPage();
+		return doc;
+	}
+
+	/**
+	 * adds an image to the document.
+	 * 
+	 * @param doc
+	 * @param iji
+	 * @return
+	 * @throws InterruptedException
+	 * @throws ExecutionException
+	 * @throws IOException
+	 * @throws DocumentException
+	 */
+	public Document addImage(Document doc, ImageJobDescription iji)
+			throws InterruptedException, ExecutionException, IOException,
+			DocumentException {
+		// create image worker
+		ImageWorker job = new ImageWorker(dlConfig, iji);
+		// submit
+		Future<DocuImage> jobTicket = imageJobCenter.submit(job);
+		// wait for result
+		DocuImage img = jobTicket.get();
+		// scale the image
+		Image pdfimg = Image.getInstance(img.getAwtImage(), null);
+		float docW = PageSize.A4.getWidth() - 2 * PageSize.A4.getBorder();
+		float docH = PageSize.A4.getHeight() - 2 * PageSize.A4.getBorder();
+		// TODO: do we really scale this again?
+		pdfimg.scaleToFit(docW, docH);
+		// add to PDF
+		doc.add(pdfimg);
+		return doc;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/pdf/PDFTitlePage.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,184 @@
+package digilib.pdf;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.log4j.Logger;
+
+import com.itextpdf.text.Anchor;
+import com.itextpdf.text.BadElementException;
+import com.itextpdf.text.Chunk;
+import com.itextpdf.text.Element;
+import com.itextpdf.text.FontFactory;
+import com.itextpdf.text.Image;
+import com.itextpdf.text.Paragraph;
+
+
+import digilib.io.DigilibInfoReader;
+import digilib.io.DocuDirCache;
+import digilib.servlet.PDFCache;
+import digilib.servlet.PDFRequest;
+
+/** A class for the generation of title pages for the generated pdf documents.
+ * 
+ * 
+ */
+public class PDFTitlePage {
+	
+	private PDFRequest job_info = null;
+	private DigilibInfoReader info_reader= null;
+	private DocuDirCache dirCache = null;
+	protected static Logger logger = Logger.getLogger("digilib.servlet");
+
+	
+	/**
+	 * Initialize a TitlePage
+	 * @param pdfji
+	 */
+	public PDFTitlePage(PDFRequest pdfji){
+		job_info = pdfji;
+		dirCache = (DocuDirCache) job_info.getDlConfig().getValue("servlet.dir.cache");
+
+		String fn = getBase(dirCache.getDirectory(pdfji.getImageJobInformation().getAsString("fn")).getDir().getPath()) + "presentation/info.xml";
+		
+		info_reader = new DigilibInfoReader(fn);
+	}
+	
+	/**
+	 * generate iText-PDF-Contents for the title page
+	 * 
+	 * @return
+	 */
+	public Element getPageContents(){
+		Paragraph content = new Paragraph();
+		content.setAlignment(Element.ALIGN_CENTER);
+
+		// add vertical whitespace
+		for(int i=0; i<8; i++){
+			content.add(Chunk.NEWLINE);
+		}
+		
+		
+		// add logo
+		content.add(getLogo());
+		content.add(Chunk.NEWLINE);
+		content.add(Chunk.NEWLINE);
+
+		// add title
+		Anchor title = new Anchor(new Paragraph(getTitle(),FontFactory.getFont(FontFactory.HELVETICA,16)));
+		String burl = job_info.getImageJobInformation().getAsString("base.url");
+		
+		title.setReference(burl+"digilib.jsp?fn="+job_info.getImageJobInformation().getAsString("fn"));
+		content.add(title);		
+		content.add(Chunk.NEWLINE);
+
+		// add author
+		if(getDate()!=" ")
+			content.add(new Paragraph(getAuthor()+" ("+getDate()+")",FontFactory.getFont(FontFactory.HELVETICA,14)));
+		else
+			content.add(new Paragraph(getAuthor(),FontFactory.getFont(FontFactory.HELVETICA,14)));
+		
+		content.add(Chunk.NEWLINE);
+		
+		// add page numbers
+		content.add(new Paragraph(getPages(), FontFactory.getFont(FontFactory.HELVETICA, 12)));
+
+
+		content.add(Chunk.NEWLINE);
+		content.add(Chunk.NEWLINE);
+		content.add(Chunk.NEWLINE);
+
+		// add credits
+		content.add(new Paragraph("MPIWG Berlin 2009", FontFactory.getFont(FontFactory.HELVETICA,10)));
+
+		// add digilib version
+		content.add(new Paragraph(getDigilibVersion(),FontFactory.getFont(FontFactory.HELVETICA,10)));
+
+		for(int i=0; i<8; i++){
+			content.add(Chunk.NEWLINE);
+		}
+		Anchor address = new Anchor(
+				new Paragraph(burl+"digilib.jsp?fn="+job_info.getImageJobInformation().getAsString("fn"), FontFactory.getFont(FontFactory.COURIER, 9))
+									);
+		address.setReference(burl+"digilib.jsp?fn="+job_info.getImageJobInformation().getAsString("fn"));
+		
+		content.add(address);
+
+		
+		return content;
+	}
+	
+	/**
+	 * return base directory of an image directory
+	 * 
+	 * @param path
+	 * @return
+	 */
+	private String getBase(String path){
+		if(path.contains("/")){
+			String[] x = path.split("/");
+			String newpath = "";
+			for(int i=0; i<x.length-1; i++){
+				newpath += x[i]+"/";
+			}
+			return newpath;
+		}
+		else
+			return "";
+	}
+	
+
+	/**
+	 * Methods for the different attributes.
+	 * 
+	 */
+	
+	
+	private Image getLogo(){
+		try {
+			URL url = new URL(job_info.getDlConfig().getAsString("pdf-logo"));
+			if(url!=null && !url.equals("")){
+				Image logo = Image.getInstance(url);
+				logo.setAlignment(Element.ALIGN_CENTER);
+				return logo;
+			}
+		} catch (BadElementException e) {
+			logger.error(e.getMessage());
+			e.printStackTrace();
+		} catch (MalformedURLException e) {
+			logger.error(e.getMessage());
+			e.printStackTrace();
+		} catch (IOException e) {
+			logger.error(e.getMessage());
+			e.printStackTrace();
+		}
+		return null;
+	}
+	private String getTitle(){
+		if(info_reader.hasInfo())
+			return info_reader.getAsString("title");
+		else
+			return job_info.getImageJobInformation().getAsString("fn");
+	}
+	private String getAuthor(){
+		if(info_reader.hasInfo())
+			return info_reader.getAsString("author");
+		else
+			return " ";
+	}
+	private String getDate(){
+		if(info_reader.hasInfo())
+			return info_reader.getAsString("date");
+		else
+			return " ";
+	}
+	private String getPages(){
+		return "Pages "+job_info.getAsString("pgs") + " (scan page numbers)";
+	}
+
+	private String getDigilibVersion(){
+		return "Digilib PDFMaker v."+PDFCache.version;
+	}
+	
+}
--- a/servlet/src/digilib/servlet/DigilibConfiguration.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/DigilibConfiguration.java	Tue Dec 21 09:52:16 2010 +0100
@@ -22,8 +22,9 @@
 package digilib.servlet;
 
 import java.io.File;
-import java.util.Iterator;
+import java.io.IOException;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
@@ -34,7 +35,10 @@
 import digilib.image.DocuImage;
 import digilib.image.DocuImageImpl;
 import digilib.io.FileOps;
+import digilib.io.ImageFile;
 import digilib.io.XMLListLoader;
+import digilib.util.Parameter;
+import digilib.util.ParameterMap;
 
 /**
  * Class to hold the digilib servlet configuration parameters. The parameters
@@ -50,10 +54,8 @@
  */
 public class DigilibConfiguration extends ParameterMap {
 
-	private static final long serialVersionUID = -6630487070791637120L;
-
 	/** DocuImage class instance */
-	private Class docuImageClass = null;
+	private static Class<DocuImageImpl> docuImageClass = null;
 
 	/** Log4J logger */
 	private Logger logger = Logger.getLogger("digilib.config");
@@ -63,11 +65,16 @@
 	 *  
 	 */
 	public DigilibConfiguration() {
-		// create HashMap(20)
 		super(20);
 		// we start with a default logger config
 		BasicConfigurator.configure();
+		initParams();
+	}
 
+	/**
+	 * 
+	 */
+	protected void initParams() {
 		/*
 		 * Definition of parameters and default values. System parameters that
 		 * are not read from config file have a type 's'.
@@ -91,6 +98,12 @@
 			's');
 		// AuthOps instance for authentication
 		newParameter("servlet.auth.op", null, null, 's');
+        // Executor for image operations
+        newParameter("servlet.worker.imageexecutor", null, null, 's');
+        // Executor for PDF operations
+        newParameter("servlet.worker.pdfexecutor", null, null, 's');
+        // Executor for PDF-image operations
+        newParameter("servlet.worker.pdfimageexecutor", null, null, 's');
 
 		/*
 		 * parameters that can be read from config file have a type 'f'
@@ -123,8 +136,6 @@
 		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",
@@ -140,18 +151,29 @@
 		// use mapping file to translate paths
 		newParameter("use-mapping", Boolean.FALSE, null, 'f');
 		// mapping file location
-		newParameter("mapping-file", new File("WEB-INF/digilib-map.xml"), null, 'f');
+		newParameter("mapping-file", new File("digilib-map.xml"), null, 'f');
 		// log4j config file location
-		newParameter("log-config-file", new File("WEB-INF/log4j-config.xml"), null, 'f');
+		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 working threads
 		newParameter("worker-threads", new Integer(1), null, 'f');
 		// max number of waiting threads
-		newParameter("max-waiting-threads", new Integer(0), null, 'f');
-
+		newParameter("max-waiting-threads", new Integer(20), null, 'f');
+		// number of pdf-generation threads
+		newParameter("pdf-worker-threads", new Integer(1), null, 'f');
+		// max number of waiting pdf-generation threads
+		newParameter("pdf-max-waiting-threads", new Integer(20), null, 'f');
+		// number of pdf-image generation threads
+		newParameter("pdf-image-worker-threads", new Integer(1), null, 'f');
+		// max number of waiting pdf-image generation threads
+		newParameter("pdf-image-max-waiting-threads", new Integer(10), null, 'f');
+        // PDF generation temp directory
+        newParameter("pdf-temp-dir", "pdf_temp", null, 'f');
+        // PDF generation cache directory
+        newParameter("pdf-cache-dir", "pdf_cache", null, 'f');
+        // PDF generation cache directory
+        newParameter("pdf-cache-dir", "pdf_cache", null, 'f');
 	}
 
 	/**
@@ -167,127 +189,111 @@
 
 	/**
 	 * read parameter list from the XML file in init parameter "config-file"
+	 * or file digilib-config.xml
 	 */
-	public void readConfig(ServletConfig config) throws Exception {
+	@SuppressWarnings("unchecked")
+    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 (config == null) {
+		if (c == null) {
 			// no config no file...
 			return;
 		}
-		String fn = config.getInitParameter("config-file");
+		String fn = c.getInitParameter("config-file");
 		if (fn == null) {
-			fn = ServletOps.getFile("WEB-INF/digilib-config.xml", config);
+			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 configFile = new File(fn);
+		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(configFile.toURL().toString());
+		Map<String,String> confTable = lilo.loadURL(f.toURL().toString());
 
 		// set config file path parameter
-		setValue("servlet.config.file", configFile.getCanonicalPath());
+		setValue("servlet.config.file", f.getCanonicalPath());
 
 		/*
-		 * process parameters from file
+		 * 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);
+		for (Entry<String, String> confEntry: confTable.entrySet()) {
+			Parameter p = get(confEntry.getKey());
 			if (p != null) {
 				if (p.getType() == 's') {
 					// type 's' Parameters are not overwritten.
 					continue;
 				}
-				if (!p.setValueFromString(val)) {
+				if (!p.setValueFromString(confEntry.getValue())) {
 					/*
 					 * automatic conversion failed -- try special cases
 					 */
 
 					// basedir-list
-					if (key.equals("basedir-list")) {
+					if (confEntry.getKey().equals("basedir-list")) {
 						// split list into directories
-						String[] sa = FileOps.pathToArray(val);
+						String[] sa = FileOps.pathToArray(confEntry.getValue());
 						if (sa != null) {
 							p.setValue(sa);
 						}
 					}
-
 				}
 			} else {
 				// parameter unknown -- just add
-				newParameter(key, null, val, 'f');
+				newParameter(confEntry.getKey(), null, confEntry.getValue(), 'f');
 			}
 		}
-		
-		/*
-		 * process all parameters
-		 */
-        for (Iterator i = this.keySet().iterator(); i.hasNext();) {
-            String key = (String) i.next();
-            Parameter para = (Parameter) this.get(key);
-
-            // basedir-list
-            if (key.equals("basedir-list")) {
-                // split list into directories
-                String[] dirs = (String[]) para.getValue();
-                for (int j = 0; j < dirs.length; j++) {
-                    // make relative directory paths be inside the webapp
-                    dirs[j] = ServletOps.getFile(dirs[j], config);
-                }
-                para.setValue(dirs);
-            }
-            
-            // File types
-            if (para.getValue() instanceof File) {
-               File pf = (File) para.getValue();
-               // make relative paths be inside the webapp
-               para.setValue(ServletOps.getFile(pf, config));
-            }
-            
-        }
-
+		// initialise static DocuImage class instance
+		DigilibConfiguration.docuImageClass = (Class<DocuImageImpl>) Class.forName(getAsString("docuimage-class"));
 	}
 
 	/**
 	 * Creates a new DocuImage instance.
 	 * 
-	 * The type of DocuImage is specified by docuImageType.
+	 * The type of DocuImage is specified by docuimage-class.
 	 * 
 	 * @return DocuImage
 	 */
-	public DocuImage getDocuImageInstance() {
+    public static DocuImage getDocuImageInstance() {
 		DocuImageImpl di = null;
 		try {
-			if (docuImageClass == null) {
-				docuImageClass = Class.forName(getAsString("docuimage-class"));
-			}
-			di = (DocuImageImpl) docuImageClass.newInstance();
+			di = docuImageClass.newInstance();
 		} catch (Exception e) {
 		}
 		return di;
 	}
 
 	/**
+     * Check image size and type and store in ImageFile imgf
+     * 
+	 * @param imgf
+	 * @return
+	 * @throws IOException
+	 */
+	public static ImageFile docuImageIdentify(ImageFile imgf) throws IOException {
+	    // use fresh DocuImage instance
+	    DocuImage di = getDocuImageInstance();
+		return di.identify(imgf);
+	}
+	
+	/**
 	 * @return Returns the docuImageClass.
 	 */
-	public Class getDocuImageClass() {
+	public static Class<DocuImageImpl> getDocuImageClass() {
 		return docuImageClass;
 	}
+
 	/**
 	 * @param docuImageClass The docuImageClass to set.
 	 */
-	public void setDocuImageClass(Class docuImageClass) {
-		this.docuImageClass = docuImageClass;
+	public static void setDocuImageClass(Class<DocuImageImpl> dic) {
+		docuImageClass = dic;
 	}
 }
--- a/servlet/src/digilib/servlet/DigilibImageWorker.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,281 +0,0 @@
-/* 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.Rectangle;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
-
-import javax.servlet.http.HttpServletResponse;
-
-import digilib.image.DocuImage;
-import digilib.image.ImageOpException;
-import digilib.image.ImageOps;
-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;
-
-	int forceType;
-
-	/**
-	 * @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
-	 * @param forceType
-	 */
-	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, int forceType) {
-		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;
-		this.forceType = forceType;
-	}
-
-	/*
-	 * do the work
-	 */
-	public DocuImage render() 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);
-
-		Rectangle loadRect = outerUserImgArea.getBounds();
-		// 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, loadRect, (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) loadRect.getX(), (int) loadRect.getY(),
-					(int) loadRect.getWidth(), (int) loadRect.getHeight());
-
-			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("rendered in " + (System.currentTimeMillis() - startTime) + "ms");
-
-		return docuImage;
-	}
-
-	public void write(DocuImage img) throws FileOpException, IOException {
-		/* write the resulting image */
-
-		// setup output -- if output type is forced use that otherwise
-		// if source is JPG then dest will be JPG else it's PNG
-		if (forceType != ImageOps.TYPE_AUTO) {
-			if (forceType == ImageOps.TYPE_JPEG) {
-				mimeType = "image/jpeg";
-			}
-			if (forceType == ImageOps.TYPE_PNG) {
-				mimeType = "image/png";
-			}
-		} else if ((mimeType.equals("image/jpeg")
-				|| mimeType.equals("image/jp2") || mimeType.equals("image/fpx"))) {
-			mimeType = "image/jpeg";
-		} else {
-			mimeType = "image/png";
-		}
-		response.setContentType(mimeType);
-
-		// write the image
-		img.writeImage(mimeType, response.getOutputStream());
-		response.flushBuffer();
-
-		logger.info("image worker " + this.getName() + " done in "
-				+ (System.currentTimeMillis() - startTime));
-
-		img.dispose();
-	}
-}
--- a/servlet/src/digilib/servlet/DigilibRequest.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/DigilibRequest.java	Tue Dec 21 09:52:16 2010 +0100
@@ -26,29 +26,19 @@
 
 package digilib.servlet;
 
-import java.io.StringReader;
 import java.io.UnsupportedEncodingException;
 import java.net.URLDecoder;
 import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Iterator;
 import java.util.StringTokenizer;
 
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 
-import org.apache.log4j.Logger;
-
-import com.hp.hpl.mesa.rdf.jena.mem.ModelMem;
-import com.hp.hpl.mesa.rdf.jena.model.Model;
-import com.hp.hpl.mesa.rdf.jena.model.Property;
-import com.hp.hpl.mesa.rdf.jena.model.ResIterator;
-import com.hp.hpl.mesa.rdf.jena.model.Resource;
-import com.hp.hpl.mesa.rdf.jena.model.Statement;
-import com.hp.hpl.mesa.rdf.jena.model.StmtIterator;
-
 import digilib.image.DocuImage;
 import digilib.io.FileOps;
+import digilib.util.OptionsSet;
+import digilib.util.Parameter;
+import digilib.util.ParameterMap;
 
 /**
  * Class holding the parameters of a digilib user request. The parameters are
@@ -74,25 +64,30 @@
  */
 public class DigilibRequest extends ParameterMap {
 
-	private static final long serialVersionUID = -4707707539569977901L;
+	protected DocuImage image; // internal DocuImage instance for this request
 
-	private final static String ECHO = "http://echo.unibe.ch/digilib/rdf#";
+	protected ServletRequest servletRequest; // internal ServletRequest
 
-	private final static String DIGILIB = "Digilib";
-
-	private Logger logger = Logger.getLogger(this.getClass());
-
-	private boolean boolRDF = false; // use RDF Parameters
+	public DigilibRequest() {
+		super(30);
+	}
 
-	private DocuImage image; // internal DocuImage instance for this request
-
-	private ServletRequest servletRequest; // internal ServletRequest
+    /**
+     * Creates a new instance of DigilibRequest with parameters from a
+     * ServletRequest. All undefined parameters are set to default values.
+     * 
+     * @param request
+     */
+    public DigilibRequest(ServletRequest request) {
+        super(30);
+        setWithRequest(request);
+        initOptions();
+    }
 
-	/** Creates a new instance of DigilibRequest and sets default values. */
-	public DigilibRequest() {
-		// create HashMap(20)
-		super(30);
-
+	/** set up parameters.
+	 * 
+	 */
+	protected void initParams() {
 		/*
 		 * Definition of parameters and default values. Parameter of type 's'
 		 * are for the servlet.
@@ -117,7 +112,7 @@
 		// scale factor
 		newParameter("ws", new Float(1), null, 's');
 		// special options like 'fit' for gifs
-		newParameter("mo", "", null, 's');
+		newParameter("mo", this.options, null, 's');
 		// rotation angle (degree)
 		newParameter("rot", new Float(0), null, 's');
 		// contrast enhancement factor
@@ -175,19 +170,15 @@
 		newParameter("lv", new Integer(2), null, 'c');
 		// marks
 		newParameter("mk", "", null, 'c');
-
 	}
 
-	/**
-	 * Creates a new instance of DigilibRequest with parameters from a
-	 * ServletRequest. All undefined parameters are set to default values.
-	 * 
-	 * @param request
-	 */
-	public DigilibRequest(ServletRequest request) {
-		this();
-		setWithRequest(request);
-	}
+    /* (non-Javadoc)
+     * @see digilib.servlet.ParameterMap#initOptions()
+     */
+    @Override
+    protected void initOptions() {
+        options = (OptionsSet) getValue("mo");
+    }
 
 	/**
 	 * Populate the request object with data from a ServletRequest.
@@ -199,11 +190,7 @@
 		servletRequest = request;
 		// decide if it's old-style or new-style
 		String qs = ((HttpServletRequest) request).getQueryString();
-		if (request.getParameter("rdf") != null) {
-			// RDF stuff
-			boolRDF = true;
-			setWithRDF(request);
-		} else if (qs != null) {
+		if (qs != null) {
 			if (qs.indexOf("&amp;") > -1) {
 				// &amp; separator
 				setWithParamString(qs, "&amp;");
@@ -349,8 +336,7 @@
 	public String getAsString(int type) {
 		StringBuffer s = new StringBuffer(50);
 		// go through all values
-		for (Iterator i = this.values().iterator(); i.hasNext();) {
-			Parameter p = (Parameter) i.next();
+		for (Parameter p: params.values()) {
 			if ((type > 0) && (p.getType() != type)) {
 				// skip the wrong types
 				continue;
@@ -406,13 +392,14 @@
 	 * @param request
 	 *            ServletRequest to get parameters from.
 	 */
-	public void setWithParamRequest(ServletRequest request) {
+	//@SuppressWarnings("unchecked") // ServletRequest.getParameterNames() returns naked Enumeration
+    public void setWithParamRequest(ServletRequest request) {
 		setValue("servlet.request", request);
 		// go through all request parameters
-		for (Enumeration i = request.getParameterNames(); i.hasMoreElements();) {
+		for (Enumeration<String> i = request.getParameterNames(); i.hasMoreElements();) {
 			String name = (String) i.nextElement();
 			// is this a known parameter?
-			if (this.containsKey(name)) {
+			if (params.containsKey(name)) {
 				Parameter p = (Parameter) this.get(name);
 				// internal parameters are not set
 				if (p.getType() == 'i') {
@@ -447,7 +434,7 @@
 				String name = URLDecoder.decode(nv[0], "UTF-8");
 				String val = URLDecoder.decode(nv[1], "UTF-8");
 				// is this a known parameter?
-				if (this.containsKey(name)) {
+				if (params.containsKey(name)) {
 					Parameter p = (Parameter) this.get(name);
 					// internal parameters are not set
 					if (p.getType() == 'i') {
@@ -466,78 +453,9 @@
 	}
 
 	/**
-	 * 
-	 * 
-	 */
-	public void setWithRDF(ServletRequest request) {
-		String strRDF;
-		strRDF = request.getParameter("rdf");
-		if (strRDF != null) {
-			Hashtable hashRDF = rdf2hash(strRDF);
-			// go through all parameters
-			for (Iterator i = hashRDF.keySet().iterator(); i.hasNext();) {
-				String name = (String) i.next();
-				// is this a known parameter?
-				if (this.containsKey(name)) {
-					Parameter p = (Parameter) this.get(name);
-					// internal parameters are not set
-					if (p.getType() == 'i') {
-						continue;
-					}
-					p.setValueFromString((String) hashRDF.get(name));
-				}
-			}
-		}
-		// add path from request
-		setValue("request.path", ((HttpServletRequest) request).getPathInfo());
-	}
-
-	private Hashtable rdf2hash(String strRDF) {
-		Hashtable hashParams = new Hashtable();
-		try {
-			// create an empty model
-			Model model = new ModelMem();
-			StringReader sr = new StringReader(strRDF);
-			model.read(sr, "");
-			// get Property type -> digilib
-			Property property = model.getProperty(DigilibRequest.ECHO, "type");
-			if (property != null) {
-				ResIterator resourceIterator = model
-						.listSubjectsWithProperty(property);
-				while (resourceIterator.hasNext()) {
-					Resource resource = resourceIterator.next();
-					StmtIterator statementIterator = resource.listProperties();
-					String type = resource.getProperty(property).getResource()
-							.getURI();
-					if (type == null) {
-						continue;
-					}
-					if (type.equals(DigilibRequest.ECHO
-							+ DigilibRequest.DIGILIB)) {
-						while (statementIterator.hasNext()) {
-							Statement statement = statementIterator.next();
-							Property predicate = statement.getPredicate();
-							if (predicate.getNameSpace().equals(
-									DigilibRequest.ECHO)) {
-								hashParams.put(predicate.getLocalName(),
-										statement.getObject().toString());
-							}
-						}
-					}
-				}
-			} else {
-				logger.warn("The type property was null! So the rdf-model"
-						+ " sent to Digilib was probably incorrect!");
-			}
-		} catch (Exception e) {
-			logger.warn("rdf2hash function caused an error: ", e);
-		}
-		return hashParams;
-	}
-
-	/**
 	 * Test if option string <code>opt</code> is set. Checks if the substring
 	 * <code>opt</code> is contained in the options string <code>param</code>.
+	 * Deprecated! use hasOption(String opt) for "mo"-options.
 	 * 
 	 * @param opt
 	 *            Option string to be tested.
@@ -614,10 +532,6 @@
 		setValue("docu.image", image);
 	}
 
-	public boolean isRDF() {
-		return boolRDF;
-	}
-
 	/**
 	 * @return
 	 */
--- a/servlet/src/digilib/servlet/DigilibWorker.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,195 +0,0 @@
-	/* 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 java.util.concurrent.Semaphore;
-
-import org.apache.log4j.Logger;
-
-import digilib.image.DocuImage;
-
-/**
- * image operation worker.
- * 
- * @author casties
- */
-public abstract class DigilibWorker {
-
-	protected static Logger logger = Logger.getLogger(DigilibWorker.class);
-
-	private static int maxRunningThreads = 0;
-
-	private static int runningThreads = 0;
-
-	private static int waitingThreads = 0;
-
-	private static int maxWaitingThreads = 0;
-
-	public static Semaphore sem = new Semaphore(2, true);
-
-	protected Throwable error;
-
-	/**
-	 * @param job
-	 */
-	public DigilibWorker() {
-		super();
-		error = null;
-	}
-
-	public abstract DocuImage render() throws Exception;
-
-	public abstract void write(DocuImage img) throws Exception;
-
-	/**
-	 * Do the work.
-	 */
-	public void run() {
-		logger.debug((++waitingThreads) + " waiting threads");
-		DocuImage img = null;
-		try {
-			sem.acquire();
-			waitingThreads--;
-		} catch (InterruptedException e) {
-			error = e;
-			waitingThreads--;
-			// should we reinterrupt?
-			return;
-		}
-		logger.debug((++runningThreads) + " running threads");
-		try {
-			/* 
-			 * do rendering under the semaphore 
-			 */
-			img = render();
-		} catch (Throwable e) {
-			error = e;
-			logger.error(e);
-		} finally {
-			runningThreads--;
-			sem.release();
-		}
-		/* 
-		 * write the result without semaphore
-		 */
-		if (!hasError()) {
-			try{
-				write(img);
-			} catch (Throwable e) {
-				error = e;
-				logger.error(e);
-			}
-		}
-	}
-
-	/**
-	 * Returns the name of this thread.
-	 * 
-	 * @return
-	 */
-	public String getName() {
-		return Thread.currentThread().getName();
-	}
-
-	/** Returns if the worker could run (i.e. is not overloaded).
-	 * 
-	 * @return
-	 */
-	public static boolean canRun() {
-		return ((DigilibWorker.maxWaitingThreads == 0) || (DigilibWorker.getNumWaiting() <= DigilibWorker.maxWaitingThreads));
-	}
-
-	/**
-	 * returns if an error occurred.
-	 * 
-	 * @return
-	 */
-	public boolean hasError() {
-		return (error != null);
-	}
-
-	/**
-	 * @return Returns the error.
-	 */
-	public Throwable getError() {
-		return error;
-	}
-
-	/**
-	 * @return Returns the semaphore.
-	 */
-	public static Semaphore getSemaphore() {
-		return sem;
-	}
-
-	/**
-	 * @param sem
-	 *            The semaphore to set.
-	 */
-	public static void setSemaphore(Semaphore sem) {
-		DigilibWorker.sem = sem;
-	}
-
-	public static void setSemaphore(int maxrun, boolean fair) {
-		sem = new Semaphore(maxrun, fair);
-		maxRunningThreads = maxrun;
-	}
-
-	/**
-	 * The number of currently running threads (approximate).
-	 * 
-	 * @return
-	 */
-	public static int getNumRunning() {
-		return (maxRunningThreads - sem.availablePermits());
-	}
-
-	/**
-	 * The number of currently waiting threads (approximate).
-	 * 
-	 * @return
-	 */
-	public static int getNumWaiting() {
-		return sem.getQueueLength();
-	}
-
-	/**
-	 * @return Returns the maxWaitingThreads.
-	 */
-	public static int getMaxWaitingThreads() {
-		return maxWaitingThreads;
-	}
-
-	/**
-	 * @param maxWaitingThreads The maxWaitingThreads to set.
-	 */
-	public static void setMaxWaitingThreads(int maxWaitingThreads) {
-		DigilibWorker.maxWaitingThreads = maxWaitingThreads;
-	}
-
-	public static int getMaxRunningThreads() {
-		return maxRunningThreads;
-	}
-
-	public static void setMaxRunningThreads(int maxRunningThreads) {
-		DigilibWorker.maxRunningThreads = maxRunningThreads;
-	}
-}
--- a/servlet/src/digilib/servlet/DocumentBean.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/DocumentBean.java	Tue Dec 21 09:52:16 2010 +0100
@@ -33,11 +33,10 @@
 
 import digilib.auth.AuthOpException;
 import digilib.auth.AuthOps;
-import digilib.image.ImageOps;
 import digilib.image.ImageSize;
 import digilib.io.DocuDirCache;
 import digilib.io.DocuDirectory;
-import digilib.io.FileOps;
+import digilib.io.FileOps.FileClass;
 import digilib.io.ImageFile;
 import digilib.io.ImageFileset;
 
@@ -128,7 +127,7 @@
 	 * return a list of authorization roles needed for request to access the
 	 * specified path
 	 */
-	public List rolesForPath(DigilibRequest request) throws AuthOpException {
+	public List<String> rolesForPath(DigilibRequest request) throws AuthOpException {
 		logger.debug("rolesForPath");
 		return useAuthentication ? authOp.rolesForPath(request) : null;
 	}
@@ -136,7 +135,7 @@
 	/**
 	 * check request authorization against a list of roles
 	 */
-	public boolean isRoleAuthorized(List roles, DigilibRequest request) {
+	public boolean isRoleAuthorized(List<String> roles, DigilibRequest request) {
 		logger.debug("isRoleAuthorized");
 		return useAuthentication ? authOp.isRoleAuthorized(roles, request)
 				: true;
@@ -193,7 +192,7 @@
 		String fn = dlRequest.getFilePath();
 		// get information about the file
 		ImageFileset fileset = (ImageFileset) dirCache.getFile(fn, dlRequest
-				.getAsInt("pn"), FileOps.CLASS_IMAGE);
+				.getAsInt("pn"), FileClass.IMAGE);
 		if (fileset == null) {
 			return;
 		}
@@ -211,9 +210,9 @@
 		// get original pixel size
 		ImageFile origfile = fileset.getBiggest();
 		// check image for size if mo=hires
-		if ((! origfile.isChecked())&&dlRequest.hasOption("mo", "hires")) {
+		if ((! origfile.isChecked())&&dlRequest.hasOption("hires")) {
 			logger.debug("pre-checking image!");
-			ImageOps.checkFile(origfile);
+			DigilibConfiguration.docuImageIdentify(origfile);
 		}
 		ImageSize pixsize = origfile.getSize();
 		if (pixsize != null) {
@@ -238,15 +237,22 @@
 		return getNumPages(dlRequest);
 	}
 
+    /**
+     * get the number of image pages/files in the directory
+     */
+    public int getNumPages(DigilibRequest request) throws Exception {
+        return getNumPages(request, FileClass.IMAGE);
+    }
+
 	/**
-	 * get the number of pages/files in the directory
+	 * get the number of pages/files of type fc in the directory
 	 */
-	public int getNumPages(DigilibRequest request) throws Exception {
+	public int getNumPages(DigilibRequest request, FileClass fc) throws Exception {
 		logger.debug("getNumPages");
 		DocuDirectory dd = (dirCache != null) ? dirCache.getDirectory(request
 				.getFilePath()) : null;
 		if (dd != null) {
-			return dd.size();
+			return dd.size(fc);
 		}
 		return 0;
 	}
--- a/servlet/src/digilib/servlet/Initialiser.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/Initialiser.java	Tue Dec 21 09:52:16 2010 +0100
@@ -21,6 +21,8 @@
 package digilib.servlet;
 
 import java.io.File;
+import java.io.OutputStream;
+import java.util.List;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
@@ -32,39 +34,42 @@
 
 import digilib.auth.AuthOps;
 import digilib.auth.XMLAuthOps;
-import digilib.image.ImageOps;
+import digilib.image.DocuImage;
 import digilib.io.AliasingDocuDirCache;
 import digilib.io.DocuDirCache;
-import digilib.io.FileOps;
+import digilib.io.FileOps.FileClass;
+import digilib.util.DigilibJobCenter;
 
 /**
- * Initalisation servlet for setup tasks.
+ * Singleton initalisation servlet for setup tasks and resources.
  * 
  * @author casties
  *  
  */
+@SuppressWarnings("serial")
 public class Initialiser extends HttpServlet {
 
-	private static final long serialVersionUID = -5126621114382549343L;
-
 	/** servlet version */
-	public static final String iniVersion = "0.2";
+	public static final String version = "0.2";
 
 	/** 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;
-
+	/** Executor for digilib image jobs */
+	DigilibJobCenter<DocuImage> imageEx;
+	
+	/** Executor for PDF jobs */
+	DigilibJobCenter<OutputStream> pdfEx;
+	
+	/** Executor for PDF image jobs */
+	DigilibJobCenter<DocuImage> pdfImageEx;
+	
 	/**
 	 * Initialisation on first run.
 	 * 
@@ -75,13 +80,12 @@
 
 		System.out
 				.println("***** Digital Image Library Initialisation Servlet (version "
-						+ iniVersion + ") *****");
+						+ version + ") *****");
 
 		// get our ServletContext
 		ServletContext context = config.getServletContext();
 		// see if there is a Configuration instance
-		dlConfig = (DigilibConfiguration) context
-				.getAttribute("digilib.servlet.configuration");
+		dlConfig = (DigilibConfiguration) context.getAttribute("digilib.servlet.configuration");
 		if (dlConfig == null) {
 			// create new Configuration
 			try {
@@ -99,11 +103,10 @@
 				// say hello in the log file
 				logger
 						.info("***** Digital Image Library Initialisation Servlet (version "
-								+ iniVersion + ") *****");
+								+ version + ") *****");
 				// directory cache
 				String[] bd = (String[]) dlConfig.getValue("basedir-list");
-				int[] fcs = { FileOps.CLASS_IMAGE, FileOps.CLASS_TEXT,
-						FileOps.CLASS_SVG };
+				FileClass[] fcs = { FileClass.IMAGE, FileClass.TEXT };
 				if (dlConfig.getAsBoolean("use-mapping")) {
 					// with mapping file
 					File mapConf = ServletOps.getConfigFile((File) dlConfig
@@ -123,21 +126,28 @@
 					// XML version
 					File authConf = ServletOps.getConfigFile((File) dlConfig
 							.getValue("auth-file"), config);
-					authOp = new XMLAuthOps(authConf);
+					AuthOps 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());
-                ImageOps.setDocuImage(dlConfig.getDocuImageInstance());
-				// worker threads
+				DocuImage di = DigilibConfiguration.getDocuImageInstance();
+				dlConfig.setValue("servlet.docuimage.class", di.getClass().getName());
+				// digilib worker threads
 				int nt = dlConfig.getAsInt("worker-threads");
-				DigilibWorker.setSemaphore(nt, true);
-				int mt = dlConfig.getAsInt("max-waiting-threads");
-				DigilibWorker.setMaxWaitingThreads(mt);
+                int mt = dlConfig.getAsInt("max-waiting-threads");
+				imageEx = new DigilibJobCenter<DocuImage>(nt, mt, true, "servlet.worker.imageexecutor");
+                dlConfig.setValue("servlet.worker.imageexecutor", imageEx);				
+				// PDF worker threads
+				int pnt = dlConfig.getAsInt("pdf-worker-threads");
+                int pmt = dlConfig.getAsInt("pdf-max-waiting-threads");
+				pdfEx = new DigilibJobCenter<OutputStream>(pnt, pmt, true, "servlet.worker.pdfexecutor");
+                dlConfig.setValue("servlet.worker.pdfexecutor", pdfEx);				
+				// PDF image worker threads
+				int pint = dlConfig.getAsInt("pdf-image-worker-threads");
+                int pimt = dlConfig.getAsInt("pdf-image-max-waiting-threads");
+				pdfImageEx = new DigilibJobCenter<DocuImage>(pint, pimt, true, "servlet.worker.pdfimageexecutor");
+                dlConfig.setValue("servlet.worker.pdfimageexecutor", pdfImageEx);				
 				// set as the servlets main config
 				context.setAttribute("digilib.servlet.configuration", dlConfig);
 
@@ -148,15 +158,45 @@
 			// 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");
+							+ version + ") *****");
+			logger.warn("Already initialised!");
 		}
 	}
 
+    /** clean up local resources
+     * @see javax.servlet.GenericServlet#destroy()
+     */
+    @Override
+    public void destroy() {
+        if (dirCache != null) {
+            // shut down dirCache?
+            dirCache = null;
+        }
+        if (imageEx != null) {
+            // shut down image thread pool
+            List<Runnable> rj = imageEx.shutdownNow();
+            int nrj = rj.size();
+            if (nrj > 0) {
+                logger.error("Still running threads when shutting down image job queue: "+nrj);
+            }
+        }
+        if (pdfEx != null) {
+            // shut down pdf thread pool
+            List<Runnable> rj = pdfEx.shutdownNow();
+            int nrj = rj.size();
+            if (nrj > 0) {
+                logger.error("Still running threads when shutting down PDF job queue: "+nrj);
+            }
+        }
+        if (pdfImageEx != null) {
+            // shut down pdf image thread pool
+            List<Runnable> rj = pdfImageEx.shutdownNow();
+            int nrj = rj.size();
+            if (nrj > 0) {
+                logger.error("Still running threads when shutting down PDF-image job queue: "+nrj);
+            }
+        }
+        super.destroy();
+    }
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/PDFCache.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,371 @@
+package digilib.servlet;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+
+import digilib.image.DocuImage;
+import digilib.pdf.PDFFileWorker;
+import digilib.util.DigilibJobCenter;
+
+/**
+ * A class for handling user requests for pdf documents from digilib images.  
+ * 
+ * If a document does not already exist, it will be enqueued for generation; if it does exist, it is sent
+ * to the user.
+ * 
+ * @author cmielack
+ *
+ */
+
+@SuppressWarnings("serial")
+public class PDFCache extends HttpServlet {
+
+    public static String version = "0.3a";
+
+    /** logger for accounting requests */
+    protected static Logger accountlog = Logger.getLogger("account.pdf.request");
+
+    /** gengeral logger for this class */
+    protected static Logger logger = Logger.getLogger("digilib.pdfcache");
+
+    /** logger for authentication related */
+    protected static Logger authlog = Logger.getLogger("digilib.pdf.auth");
+
+	private DigilibConfiguration dlConfig = null;
+	
+	public static String instanceKey = "digilib.servlet.PDFCache";
+	
+	private DigilibJobCenter<File> pdfJobCenter = null;
+	
+	private DigilibJobCenter<DocuImage> pdfImageJobCenter = null;
+	
+	private File cache_directory = new File("cache");  
+	
+	private File temp_directory = new File("pdf_temp");
+	
+	private static String JSP_WIP = "/pdf/wip.jsp";
+	
+	private static String JSP_ERROR = "/pdf/error.jsp";
+	
+	/** document status.
+	 *  DONE: document exists in cache
+	 *  WIP: document is "work in progress"
+	 *  NONEXISTENT: document does not exist in cache and is not in progress
+	 *  ERROR: an error occurred while processing the request
+	 */
+	public static enum PDFStatus {DONE, WIP, NONEXISTENT, ERROR};
+
+	
+	public void init(ServletConfig config) throws ServletException {
+		super.init(config);
+		
+        System.out.println("***** Digital Image Library Image PDF-Cache Servlet (version "
+                + version + ") *****");
+        // say hello in the log file
+        logger.info("***** Digital Image Library Image PDF-Cache Servlet (version "
+                + version + ") *****");
+
+		ServletContext context = getServletContext();
+		dlConfig = (DigilibConfiguration) context.getAttribute("digilib.servlet.configuration");
+		if (dlConfig == null) {
+			// no Configuration
+			throw new ServletException("No Configuration!");
+		}
+	
+		String temp_fn = dlConfig.getAsString("pdf-temp-dir");
+		temp_directory = new File(temp_fn);
+		if (!temp_directory.exists()) {
+			// try to create
+			temp_directory.mkdirs();
+		} else {
+	        // rid the temporary directory of possible incomplete document files
+	        emptyDirectory(temp_directory);
+		}
+		if (!temp_directory.isDirectory()) {
+		    throw new ServletException("Configuration error: problem with pdf-temp-dir="+temp_fn);
+		}
+        
+		String cache_fn = dlConfig.getAsString("pdf-cache-dir");
+       	cache_directory = new File(cache_fn);
+		if (!cache_directory.exists()) {
+			// try to create
+			cache_directory.mkdirs();
+		}
+        if (!cache_directory.isDirectory()) {
+            throw new ServletException("Configuration error: problem with pdf-cache-dir="+cache_fn);
+        }
+
+        pdfJobCenter = (DigilibJobCenter<File>) dlConfig.getValue("servlet.worker.pdfexecutor");
+        pdfImageJobCenter = (DigilibJobCenter<DocuImage>) dlConfig.getValue("servlet.worker.pdfimageexecutor");
+        
+		// register this instance globally
+		context.setAttribute(instanceKey, this);
+		
+	}
+	
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
+        accountlog.info("GET from " + request.getRemoteAddr());
+        this.processRequest(request, response);
+    }
+
+
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
+        accountlog.info("POST from " + request.getRemoteAddr());
+        this.processRequest(request, response);
+    }
+    
+	/** 
+	 * clean up any broken and unfinished files from the temporary directory.
+	 */
+	public void emptyDirectory(File dir){
+		File[] temp_files = dir.listFiles();
+		for (File f: temp_files){
+			f.delete();
+		}
+	}
+	
+	
+	public void processRequest(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException {
+
+        if (dlConfig == null) {
+            logger.error("ERROR: No Configuration!");
+            throw new ServletException("NO VALID digilib CONFIGURATION!");
+        }
+
+        String docid = "";
+	    try {
+		// evaluate request ( make a PDFJobDeclaration , get the DocumentId)
+		PDFRequest pdfji = new PDFRequest(request, dlConfig); 
+		
+		docid = pdfji.getDocumentId();
+		
+		// if some invalid data has been entered ...
+		if(!pdfji.checkValidity()) {
+			notifyUser(PDFStatus.ERROR, docid, request, response);
+			return;
+		}
+		
+		PDFStatus status = getStatus(docid);
+		
+        if (status == PDFStatus.NONEXISTENT) {
+        	// not there -- start creation
+            try {
+				createNewPdfDocument(pdfji, docid);
+	            notifyUser(status, docid, request, response);
+			} catch (FileNotFoundException e) {
+				// error in pdf creation
+                logger.error(e.getMessage());
+				notifyUser(PDFStatus.ERROR, docid, request, response);
+			}
+        } else if (status == PDFStatus.DONE) {
+        	// pdf created -- send it
+            try {
+                ServletOps.sendFile(getCacheFile(docid), "application/pdf", getDownloadFilename(pdfji), response);
+                //sendFile(docid, getDownloadFilename(pdfji), response);
+            } catch (IOException e) {
+            	// sending didn't work
+                logger.error(e.getMessage());
+            }
+        } else {
+        	// should be work in progress
+            notifyUser(status, docid, request, response);
+        }
+	    } catch (Exception e) {
+            // error in pdf creation
+            logger.error(e.getMessage());
+            notifyUser(PDFStatus.ERROR, docid, request, response);
+	    }
+	}
+
+	/**
+	 * depending on the documents status, redirect the user to the appropriate waiting or download page.
+	 * 
+	 * @param status
+	 * @param documentid
+	 * @param request
+	 * @param response
+	 */
+	public void notifyUser(PDFStatus status, String documentid, HttpServletRequest request, HttpServletResponse response){
+		
+		String jsp=null;
+		
+		if(status == PDFStatus.NONEXISTENT){
+			// tell the user that the document has to be created before he/she can download it
+			logger.debug("PDFCache: "+documentid+" has STATUS_NONEXISTENT.");
+			jsp = JSP_WIP;
+		} else if(status == PDFStatus.WIP){
+			logger.debug("PDFCache: "+documentid+" has STATUS_WIP.");
+			jsp = JSP_WIP;
+
+			// TODO: estimate remaining work time
+			// TODO: tell the user he/she has to wait
+		} else if(status == PDFStatus.DONE){
+			logger.debug("PDFCache: "+documentid+" has STATUS_DONE.");
+		} else {
+			logger.debug("PDFCache: "+documentid+" has STATUS_ERROR.");
+			jsp = JSP_ERROR;
+		}
+
+		try {
+			// forward to the relevant jsp
+			ServletContext context = getServletContext();
+			RequestDispatcher dispatch = context.getRequestDispatcher(jsp);
+			dispatch.forward(request, response);
+		} catch (ServletException e) {
+			logger.debug(e.getMessage());
+			e.printStackTrace();
+		} catch (IOException e) {
+			logger.debug(e.getMessage());
+			e.printStackTrace();
+		}
+		
+	}
+
+	
+	/** check the status of the document corresponding to the documentid */
+	public PDFStatus getStatus(String documentid){
+		// looks into the cache and temp directory in order to find out the status of the document
+		File cached = getCacheFile(documentid);
+		File wip = getTempFile(documentid);
+		if(cached.exists()){
+			return PDFStatus.DONE;
+		} else if (wip.exists()){
+			return PDFStatus.WIP;
+		} else {
+			return PDFStatus.NONEXISTENT;
+		}
+	}
+
+	/** 
+	 * create new thread for pdf generation.
+	 * 
+	 * @param pdfji
+	 * @param filename
+	 * @return 
+	 * @throws FileNotFoundException 
+	 */
+	public Future<File> createNewPdfDocument(PDFRequest pdfji, String filename) throws FileNotFoundException{
+		// start new worker
+		File tempf = this.getTempFile(filename);
+		File finalf = this.getCacheFile(filename);
+		PDFFileWorker job = new PDFFileWorker(dlConfig, tempf, finalf, pdfji, pdfImageJobCenter);
+		// start job
+		Future<File> jobTicket = pdfJobCenter.submit(job);
+		return jobTicket;
+	}
+	
+	
+	/**
+	 * generate the filename the user is going to receive the pdf as
+	 * 
+	 * @param pdfji
+	 * @return
+	 */
+	public String getDownloadFilename(PDFRequest pdfji){
+		// filename example: digilib_example_pgs1-3.pdf
+		String filename;
+		filename =  "digilib_";
+		filename += pdfji.getAsString("fn");
+		filename += "_pgs" + pdfji.getAsString("pgs");
+		filename += ".pdf";
+		
+		return filename;
+	}
+	
+	/**
+	 *  sends a document to the user
+	 * 
+	 * @param cachefile  The filename of the  document in cache.
+	 * @param filename  The filename used for downloading.
+	 * @param response  
+	 * @throws IOException
+	 */
+	public void sendFile(String cachefile, String filename, HttpServletResponse response) throws IOException{
+		File cached_file = null;
+		FileInputStream fis = null;
+		ServletOutputStream sos = null;
+		BufferedInputStream bis = null;
+
+		try {
+			// get file handle
+			cached_file = new File(cache_directory, cachefile);
+			// create necessary streams
+			fis = new FileInputStream(cached_file);
+			sos = response.getOutputStream();
+			bis = new BufferedInputStream(fis);
+
+			int bytes = 0;
+
+			// set http headers
+			response.setContentType("application/pdf");
+			response.addHeader("Content-Disposition", "attachment; filename="+filename);
+			response.setContentLength( (int) cached_file.length());
+
+			// send the bytes
+			while ((bytes = bis.read()) != -1){ 
+				sos.write(bytes);
+			}
+			}
+			catch(Exception e){
+				logger.error(e.getMessage());
+			}
+			finally{
+				// close all streams
+				if (fis != null)
+					fis.close();
+				if (bis != null)
+					bis.close();
+				if (sos != null)
+					sos.close();
+			}
+
+	}
+	
+	public File getCacheDirectory(){
+		return cache_directory;
+	}
+	
+	public File getTempDirectory(){
+		return temp_directory;
+	}
+	
+	/** 
+	 * returns a File object based on filename in the temp directory.
+	 * @param filename
+	 * @return
+	 */
+	public File getTempFile(String filename) {
+	    return new File(temp_directory, filename);
+	}
+
+	/** 
+     * returns a File object based on filename in the cache directory.
+     * @param filename
+     * @return
+     */
+    public File getCacheFile(String filename) {
+        return new File(cache_directory, filename);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/PDFRequest.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,150 @@
+package digilib.servlet;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.log4j.Logger;
+
+import digilib.image.ImageJobDescription;
+import digilib.io.DocuDirectory;
+import digilib.io.FileOpException;
+import digilib.io.FileOps.FileClass;
+import digilib.util.NumRange;
+import digilib.util.OptionsSet;
+import digilib.util.ParameterMap;
+
+
+/** 
+ * A container class for storing a set of instruction parameters 
+ * used for content generator classes like MakePDF.  
+ * 
+ * 
+ * @author cmielack, casties
+ *
+ */
+public class PDFRequest extends ParameterMap {
+
+	DigilibConfiguration dlConfig = null;
+	NumRange pages = null;
+	/** general logger for this class */
+	protected static Logger logger = Logger.getLogger("digilib.servlet");
+
+	
+	/**
+	 * Initialize the PDFJobInformation
+	 * 
+	 * @param dlcfg			
+	 * 						The DigilibConfiguration. 
+	 */
+	public PDFRequest(DigilibConfiguration dlcfg) {
+		super(30);
+		dlConfig = dlcfg;
+	}
+
+	/**
+	 * Initialize the PDFJobInformation with a request.
+	 * 
+	 * @param dlcfg		The DigilibConfiguration. 		
+	 * @param request
+	 * @throws FileOpException 
+	 */
+	public PDFRequest(HttpServletRequest request, DigilibConfiguration dlcfg) throws FileOpException {
+		super(30);
+		dlConfig = dlcfg;
+		this.setWithRequest(request);
+	}
+
+	
+	protected void initParams() {
+		// page numbers
+		newParameter("pgs", "", null, 's');
+		// url of the page/document (second part)
+		newParameter("fn", "", null, 's');
+		// width of client in pixels
+		newParameter("dw", new Integer(0), null, 's');
+		// height of client in pixels
+		newParameter("dh", new Integer(500), null, 's');
+	}
+	
+	/* (non-Javadoc)
+     * @see digilib.servlet.ParameterMap#initOptions()
+     */
+    @Override
+    protected void initOptions() {
+        options = (OptionsSet) getValue("mo");
+    }
+
+    /**
+	 * Read the request object.
+	 * 
+	 * @param request
+	 * @throws FileOpException 
+	 */
+	public void setWithRequest(HttpServletRequest request) throws FileOpException {
+	    // read matching request parameters for the parameters in this map 
+		for (String k : params.keySet()) {
+			if (request.getParameterMap().containsKey(k)) {
+				setValueFromString(k, request.getParameter(k));
+			}
+		}
+		// process parameters
+		pages = new NumRange(getAsString("pgs"));
+        ImageJobDescription ij = ImageJobDescription.getInstance(this, dlConfig);
+        DocuDirectory dir = ij.getFileDirectory();
+        int dirsize = dir.size(FileClass.IMAGE);
+        pages.setMaxnum(dirsize);
+	}
+	
+	
+	/**
+	 * Generate a filename for the pdf to be created.
+	 * 
+	 * @return
+	 */
+	public String getDocumentId(){
+		String fn = getAsString("fn");
+		String dh = getAsString("dh");
+		String dw = getAsString("dw");
+		String pgs = getAsString("pgs");
+			
+		String id = "fn=" + fn + "&dw=" + dw + "&dh=" + dh + "&pgs=" + pgs + ".pdf";
+		// make safe to use as filename by urlencoding
+		try {
+			id = URLEncoder.encode(id, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			// this shouldn't happen
+		}
+		return id;
+	}
+
+	
+	public ImageJobDescription getImageJobInformation(){
+		return ImageJobDescription.getInstance(this, dlConfig);
+	}
+	
+	
+	public NumRange getPages() {
+	    return pages;
+	}
+	
+	
+	/**
+	 * Check parameters for validity.
+	 * Returns true if no errors are found.
+	 * 
+	 * @return
+	 */
+	public boolean checkValidity(){
+	    if (pages != null) {
+	        return true;
+	    }
+	    return false;
+	} 
+	
+	public DigilibConfiguration getDlConfig(){
+		return dlConfig;
+	}
+	
+}
\ No newline at end of file
--- a/servlet/src/digilib/servlet/Parameter.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,275 +0,0 @@
-/* Parameter -- General digilib parameter 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 02.09.2003 by casties
- * 
- */
-package digilib.servlet;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * General digilib parameter class.
- * 
- * @author casties
- *  
- */
-public class Parameter {
-	/** real value */
-	protected Object value = null;
-
-	/** default value */
-	protected Object defval = null;
-
-	/** parameter name (e.g. in config file) */
-	protected String name = null;
-
-	/** parameter type */
-	protected int type = 0;
-
-	/**
-	 * Default constructor.
-	 *  
-	 */
-	public Parameter() {
-		super();
-	}
-
-	/**
-	 * Constructor with name, default, and value.
-	 * 
-	 * @param value
-	 * @param defval
-	 */
-	public Parameter(String name, Object defval, Object value) {
-		this.name = name;
-		this.value = value;
-		this.defval = defval;
-	}
-
-	/**
-	 * Constructor with name, default, value, and type.
-	 * 
-	 * @param value
-	 * @param defval
-	 */
-	public Parameter(String name, Object defval, Object value, int type) {
-		this.name = name;
-		this.value = value;
-		this.defval = defval;
-		this.type = type;
-	}
-
-	/**
-	 * Is the value valid.
-	 * 
-	 * @return
-	 */
-	public boolean hasValue() {
-		return (value != null);
-	}
-
-	/**
-	 * Try to set the value from a String.
-	 * 
-	 * Tries to convert the String to the same type as the default value. Sets
-	 * the value anyway if the default is null. Returns if the value could be
-	 * set.
-	 * 
-	 * @param val
-	 * @return
-	 */
-	public boolean setValueFromString(String val) {
-		// no default matches all
-		if (defval == null) {
-			this.value = val;
-			return true;
-		}
-		Class c = defval.getClass();
-		// take String as is
-		if (c == String.class) {
-			this.value = val;
-			return true;
-		}
-		// set File
-		if (c == File.class) {
-			this.value = new File(val);
-			return true;
-		}
-		// set Boolean if string == "true"
-		if (c == Boolean.class) {
-			this.value = new Boolean(val.compareToIgnoreCase("true") == 0);
-			return true;
-		}
-		try {
-			// set Integer
-			if (c == Integer.class) {
-				this.value = new Integer(Integer.parseInt(val));
-				return true;
-			}
-			// set Float
-			if (c == Float.class) {
-				this.value = new Float(Float.parseFloat(val));
-				return true;
-			}
-		} catch (NumberFormatException e) {
-		}
-		// then it's unknown
-		return false;
-	}
-
-	/**
-	 * Get the default as Object.
-	 * 
-	 * @return
-	 */
-	public Object getDefault() {
-		return defval;
-	}
-
-	/**
-	 * Set the default.
-	 * 
-	 * @param defval
-	 */
-	public void setDefault(Object defval) {
-		this.defval = defval;
-	}
-
-	/**
-	 * Get the value as Object.
-	 * 
-	 * Returns the default if the value is not set.
-	 * 
-	 * @return
-	 */
-	public Object getValue() {
-		return (value != null) ? value : defval;
-	}
-
-	public int getAsInt() {
-		Integer i = (Integer) getValue();
-		return (i != null) ? i.intValue() : 0;
-	}
-
-	public float getAsFloat() {
-		Float f = (Float) getValue();
-		return (f != null) ? f.floatValue() : 0f;
-	}
-
-	public String getAsString() {
-		Object s = getValue();
-		if (s == null) {
-			return "";
-		}
-		if (s.getClass() == File.class) {
-			try {
-				return ((File) s).getCanonicalPath();
-			} catch (IOException e) {
-				return "ERR: " + s.toString();
-			}
-		}
-		return s.toString();
-	}
-
-	public boolean getAsBoolean() {
-		Boolean b = (Boolean) getValue();
-		return (b != null) ? b.booleanValue() : false;
-	}
-
-	public String[] parseAsArray(String separator) {
-		String s = getAsString();
-		String[] sa = s.split(separator);
-		return sa;
-	}
-
-	public float[] parseAsFloatArray(String separator) {
-		String s = getAsString();
-		String[] sa = s.split(separator);
-		float[] fa = null;
-		try {
-			int n = sa.length;
-			fa = new float[n];
-			for (int i = 0; i < n; i++) {
-				float f = Float.parseFloat(sa[i]);
-				fa[i] = f;
-			}
-		} catch (Exception e) {
-		}
-
-		return fa;
-	}
-
-	/**
-	 * Set the value.
-	 * 
-	 * @param value
-	 */
-	public void setValue(Object value) {
-		this.value = value;
-	}
-
-	/**
-	 * Set the value.
-	 * 
-	 * @param value
-	 */
-	public void setValue(int value) {
-		this.value = new Integer(value);
-	}
-
-	/**
-	 * Set the value.
-	 * 
-	 * @param value
-	 */
-	public void setValue(float value) {
-		this.value = new Float(value);
-	}
-
-	/**
-	 * @return
-	 */
-	public String getName() {
-		return name;
-	}
-
-	/**
-	 * @param name
-	 */
-	public void setName(String name) {
-		this.name = name;
-	}
-
-	/**
-	 * @return
-	 */
-	public int getType() {
-		return type;
-	}
-
-	/**
-	 * @param type
-	 */
-	public void setType(int type) {
-		this.type = type;
-	}
-
-}
--- a/servlet/src/digilib/servlet/ParameterMap.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,251 +0,0 @@
-/* ParameterMap.java -- HashMap of Parameters.
-
-  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 02.09.2003 by casties
- *
- */
-package digilib.servlet;
-
-import java.util.HashMap;
-
-/** HashMap of digilib.servlet.Parameter's.
- * 
- * Keys are Strings. Values are Parameters.
- * 
- * @author casties
- *
- */
-public class ParameterMap extends HashMap {
-
-	private static final long serialVersionUID = 1530820988748391313L;
-
-	/** Default constructor.
-	 * 
-	 */
-	public ParameterMap() {
-		super();
-	}
-
-	/** Construcotr with initial size.
-	 * @param arg0
-	 */
-	public ParameterMap(int arg0) {
-		super(arg0);
-	}
-
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public Parameter get(String key) {
-		return (Parameter) super.get(key);
-	}
-
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public Object getValue(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.getValue() : null;
-	}
-	
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public String getAsString(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.getAsString() : null;
-	}
-
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public int getAsInt(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.getAsInt() : 0;
-	}
-
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public float getAsFloat(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.getAsFloat() : 0f;
-	}
-
-	/** Get the Parameter with the corresponding key.
-	 * 
-	 * Returns null if no element is associated with key.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public boolean getAsBoolean(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.getAsBoolean() : false;
-	}
-
-	/** Returns if the Parameter's value has been set.
-	 * 
-	 * @param key
-	 * @return
-	 */
-	public boolean hasValue(String key) {
-		Parameter p = (Parameter) super.get(key);
-		return (p != null) ? p.hasValue() : false;
-	}
-	
-	/** Add the Parameter to the map with a certain key.
-	 * 
-	 * Returns the value that was previously associated with key. 
-	 * 
-	 * @param key
-	 * @param val
-	 * @return
-	 */
-	public Parameter put(String key, Parameter val) {
-		return (Parameter) super.put(key, val);
-	}
-
-	/** Add the Parameter val to the map, using val's name.
-	 * 
-	 * Returns the value that was previously associated with val's name. 
-	 * 
-	 * @param val
-	 * @return
-	 */
-	public Parameter put(Parameter val) {
-		return (Parameter) super.put(val.getName(), val);
-	}
-	
-	/** Add a new Parameter with name, default and value.
-	 * 
-	 * Returns the key that was previously associated with name. 
-	 * 
-	 * @param name
-	 * @param def
-	 * @param val
-	 * @return
-	 */
-	public Parameter newParameter(String name, Object def, Object val) {
-		Parameter p = new Parameter(name, def, val);
-		return (Parameter) super.put(name, p);
-	}
-
-	/** Add a new Parameter with name, default, value and type.
-	 * 
-	 * Returns the key that was previously associated with name. 
-	 * 
-	 * @param name
-	 * @param def
-	 * @param val
-	 * @param type
-	 * @return
-	 */
-	public Parameter newParameter(String name, Object def, Object val, int type) {
-		Parameter p = new Parameter(name, def, val, type);
-		return (Parameter) super.put(name, p);
-	}
-
-	/** Set the value of an existing parameter.
-	 * 
-	 * Sets the value and returns true if the parameter exists.
-	 * 
-	 * @param key
-	 * @param val
-	 * @return
-	 */
-	public boolean setValue(String key, Object val) {
-		Parameter p = get(key);
-		if (p != null) {
-			p.setValue(val);
-			return true;
-		}
-		return false;
-	}
-
-	/** Set the value of an existing parameter.
-	 * 
-	 * Sets the value and returns true if the parameter exists.
-	 * 
-	 * @param key
-	 * @param val
-	 * @return
-	 */
-	public boolean setValue(String key, int val) {
-		Parameter p = get(key);
-		if (p != null) {
-			p.setValue(val);
-			return true;
-		}
-		return false;
-	}
-
-	/** Set the value of an existing parameter.
-	 * 
-	 * Sets the value and returns true if the parameter exists.
-	 * 
-	 * @param key
-	 * @param val
-	 * @return
-	 */
-	public boolean setValue(String key, float val) {
-		Parameter p = get(key);
-		if (p != null) {
-			p.setValue(val);
-			return true;
-		}
-		return false;
-	}
-
-	/** Set the value of an existing parameter.
-	 * 
-	 * Sets the value and returns true if the parameter exists.
-	 * 
-	 * @param key
-	 * @param val
-	 * @return
-	 */
-	public boolean setValueFromString(String key, String val) {
-		Parameter p = get(key);
-		if (p != null) {
-			p.setValueFromString(val);
-			return true;
-		}
-		return false;
-	}
-}
--- a/servlet/src/digilib/servlet/Raster.java	Wed Jul 14 16:36:42 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,328 +0,0 @@
-/*
- * 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.DocuDirectory;
-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.2b1";
-	/** 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);
-	}
-
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
-	 */
-	protected long getLastModified(HttpServletRequest request) {
-		logger.debug("GetLastModified from " + request.getRemoteAddr()
-				+ " for " + request.getQueryString());
-		long mtime = -1;
-		// create new request with defaults
-		DigilibRequest dlReq = new DigilibRequest();
-		// set with request parameters
-		dlReq.setWithRequest(request);
-		// find the requested file
-		
-		// get PathInfo
-		String loadPathName = dlReq.getFilePath();
-		// find the file(set)
-		SVGFile fileToLoad =
-			(SVGFile) dirCache.getFile(
-				loadPathName,
-				dlReq.getAsInt("pn"),
-				FileOps.CLASS_SVG);
-		if (fileToLoad != null) {
-			DocuDirectory dd = (DocuDirectory) fileToLoad.getParent();
-			mtime = dd.getDirMTime() / 1000 * 1000;
-		}
-		return mtime;
-	}
-
-	
-	
-	
-	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();
-			logger.debug("IMG: "+imgWidth+"x"+imgHeight);
-			
-			/*
-			 * 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	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/Scaler.java	Tue Dec 21 09:52:16 2010 +0100
@@ -1,32 +1,10 @@
-/*
- * Scaler -- Scaler servlet main class
- * 
- * Digital Image Library servlet components
- * 
- * Copyright (C) 200-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
- *  
- */
-
 package digilib.servlet;
 
-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 java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
 
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
@@ -39,714 +17,303 @@
 
 import digilib.auth.AuthOpException;
 import digilib.auth.AuthOps;
-import digilib.image.ImageOpException;
-import digilib.image.ImageOps;
-import digilib.image.ImageSize;
+import digilib.image.DocuImage;
+import digilib.image.ImageJobDescription;
+import digilib.image.ImageWorker;
 import digilib.io.DocuDirCache;
 import digilib.io.DocuDirectory;
 import digilib.io.DocuDirent;
-import digilib.io.FileOpException;
-import digilib.io.FileOps;
+import digilib.io.FileOps.FileClass;
 import digilib.io.ImageFile;
-import digilib.io.ImageFileset;
+import digilib.util.DigilibJobCenter;
 
-/**
- * @author casties
- */
-// public class Scaler extends HttpServlet implements SingleThreadModel {
+@SuppressWarnings("serial")
 public class Scaler extends HttpServlet {
 
-	private static final long serialVersionUID = -325080527268912852L;
-
-	/** digilib servlet version (for all components) */
-	public static final String dlVersion = "1.7.1b";
-
-	/** 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;
+    /** digilib servlet version (for all components) */
+    public static final String version = "1.9.0a";
 
-	/** 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;
+    /** servlet error codes */
+    public static enum Error {UNKNOWN, AUTH, FILE, IMAGE};
+    
+    /** type of error message */
+    public static enum ErrMsg {IMAGE, TEXT, CODE};
+    
+    /** logger for accounting requests */
+    protected static Logger accountlog = Logger.getLogger("account.request");
 
-	/** DocuDirCache instance */
-	DocuDirCache dirCache;
-
-	/** authentication error image file */
-	File denyImgFile;
-
-	/** image error image file */
-	File errorImgFile;
-
-	/** not found error image file */
-	File notfoundImgFile;
+    /** gengeral logger for this class */
+    protected static Logger logger = Logger.getLogger("digilib.servlet");
 
-	/** subsampling before scaling */
-	float minSubsample = 2f;
-
-	/** send files as is? */
-	boolean sendFileAllowed = true;
-
-	/** default scaling quality */
-	int defaultQuality = 1;
+    /** logger for authentication related */
+    protected static Logger authlog = Logger.getLogger("digilib.auth");
 
-	/** DigilibConfiguration instance */
-	DigilibConfiguration dlConfig;
-
-	/** use authorization database */
-	boolean useAuthorization = true;
-
-	/** AuthOps instance */
-	AuthOps authOp;
+    /** DocuDirCache instance */
+    DocuDirCache dirCache;
 
-	// EXPRIMENTAL
-	/** try to enlarge cropping area for "oblique" angles */
-	boolean wholeRotArea = false;
+    /** Image executor */
+    DigilibJobCenter<DocuImage> imageJobCenter;
 
-	/**
-	 * Initialisation on first run.
-	 * 
-	 * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
-	 */
-	public void init(ServletConfig config) throws ServletException {
-		super.init(config);
+    /** authentication error image file */
+    File denyImgFile;
 
-		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 + ") *****");
+    /** image error image file */
+    File errorImgFile;
 
-		// 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");
+    /** not found error image file */
+    File notfoundImgFile;
+
+    /** send files as is? */
+    boolean sendFileAllowed = true;
 
-		// DocuDirCache instance
-		dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
-		denyImgFile = ServletOps.getFile((File) dlConfig.getValue("denied-image"), config);
-		errorImgFile = ServletOps.getFile((File) dlConfig.getValue("error-image"), config);
-		notfoundImgFile = ServletOps.getFile((File) dlConfig.getValue("notfound-image"), config);
-		sendFileAllowed = dlConfig.getAsBoolean("sendfile-allowed");
-		minSubsample = dlConfig.getAsFloat("subsample-minimum");
-		defaultQuality = dlConfig.getAsInt("default-quality");
-	}
+    /** DigilibConfiguration instance */
+    DigilibConfiguration dlConfig;
 
-	/** 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);
-	}
+    /** use authorization database */
+    boolean useAuthorization = true;
+
+    /** AuthOps instance */
+    AuthOps authOp;
 
-	/** 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);
-	}
+    /**
+     * Initialisation on first run.
+     * 
+     * @throws ServletException
+     * 
+     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
 
-	/*
-	 * (non-Javadoc)
-	 * 
-	 * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
-	 */
-	protected long getLastModified(HttpServletRequest request) {
-		accountlog.debug("GetLastModified from " + request.getRemoteAddr()
-				+ " for " + request.getQueryString());
-		long mtime = -1;
-		// create new request with defaults
-		DigilibRequest dlReq = new DigilibRequest();
-		// set with request parameters
-		dlReq.setWithRequest(request);
-		// find the requested file
-		DocuDirent f = findFile(dlReq);
-		if (f != null) {
-			DocuDirectory dd = (DocuDirectory) f.getParent();
-			mtime = dd.getDirMTime() / 1000 * 1000;
-		}
-		return mtime;
-	}
-
-	/** 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";
+        System.out
+                .println("***** Digital Image Library Image Scaler Servlet (version "
+                        + version + ") *****");
+        // say hello in the log file
+        logger.info("***** Digital Image Library Image Scaler Servlet (version "
+                + version + ") *****");
 
-		/* preset request parameters */
-
-		// 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;
-		// use low resolution images only
-		boolean loresOnly = false;
-		// use hires images only
-		boolean hiresOnly = false;
-		// send the image always as a specific type (e.g. JPEG or PNG)
-		int forceType = ImageOps.TYPE_AUTO;
-		// interpolation to use for scaling
-		int scaleQual = defaultQuality;
-		// send html error message (or image file)
-		boolean errorMsgHtml = false;
-		// original (hires) image resolution
-		float origResX = 0;
-		float origResY = 0;
-
-		/* request parameters */
-
-		DigilibRequest dlRequest = (DigilibRequest) request
-				.getAttribute("digilib.servlet.request");
+        // 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");
 
-		// 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;
-		}
-		// absolute scale factor for mo=ascale (and mo=osize)
-		float paramSCALE = dlRequest.getAsFloat("scale");
+        // DocuDirCache instance
+        dirCache = (DocuDirCache) dlConfig.getValue("servlet.dir.cache");
+
+        // Executor
+        imageJobCenter = (DigilibJobCenter<DocuImage>) dlConfig
+                .getValue("servlet.worker.imageexecutor");
+
+        denyImgFile = ServletOps.getFile(
+                (File) dlConfig.getValue("denied-image"), config);
+        errorImgFile = ServletOps.getFile(
+                (File) dlConfig.getValue("error-image"), config);
+        notfoundImgFile = ServletOps.getFile(
+                (File) dlConfig.getValue("notfound-image"), config);
+        sendFileAllowed = dlConfig.getAsBoolean("sendfile-allowed");
+    }
 
-		/*
-		 * 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;
-			hiresOnly = true;
-		} else if (dlRequest.hasOption("mo", "fit")) {
-			scaleToFit = true;
-			absoluteScale = false;
-			hiresOnly = false;
-		} else if (dlRequest.hasOption("mo", "osize")) {
-			scaleToFit = false;
-			absoluteScale = true;
-			hiresOnly = true;
-		} else if (dlRequest.hasOption("mo", "ascale")) {
-			scaleToFit = false;
-			absoluteScale = true;
-			hiresOnly = false;
-		}
+    /** Returns modification time relevant to the request for caching.
+     * 
+     * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
+     */
+    protected long getLastModified(HttpServletRequest request) {
+        accountlog.debug("GetLastModified from " + request.getRemoteAddr()
+                + " for " + request.getQueryString());
+        long mtime = -1;
+        // create new request
+        DigilibRequest dlReq = new DigilibRequest(request);
+		// find the file(set)
+		DocuDirent f = dirCache.getFile(dlReq.getFilePath(),
+		        dlReq.getAsInt("pn"), FileClass.IMAGE);
+        // find the requested file
+        if (f != null) {
+            DocuDirectory dd = (DocuDirectory) f.getParent();
+            mtime = dd.getDirMTime() / 1000 * 1000;
+        }
+        return mtime;
+    }
 
-		// 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;
-		}
-		// operation mode: "jpg": always use JPEG
-		if (dlRequest.hasOption("mo", "jpg")) {
-			forceType = ImageOps.TYPE_JPEG;
-		}
-		// operation mode: "png": always use PNG
-		if (dlRequest.hasOption("mo", "png")) {
-			forceType = ImageOps.TYPE_PNG;
-		}
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
+        accountlog.info("GET from " + request.getRemoteAddr());
+        this.processRequest(request, response);
+    }
+
 
-		// 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;
-		}
-
-		// "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();
+    /* (non-Javadoc)
+     * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+     */
+    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
+        accountlog.info("POST from " + request.getRemoteAddr());
+        this.processRequest(request, response);
+    }
+    
 
-			/* 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();
-					}
-				}
-			}
+    /** Service this request using the response.
+     * @param request
+     * @param response
+     * @throws ServletException 
+     */
+    public void processRequest(HttpServletRequest request,
+            HttpServletResponse response) throws ServletException {
 
-			// find the file
-			fileset = (ImageFileset) findFile(dlRequest);
-			if (fileset == null) {
-				throw new FileOpException("File " + loadPathName + "("
-						+ dlRequest.getAsInt("pn") + ") not found.");
-			}
+        if (dlConfig == null) {
+            logger.error("ERROR: No Configuration!");
+            throw new ServletException("NO VALID digilib CONFIGURATION!");
+        }
 
-			/* for absolute scale and original size we need the hires size */
-			ImageSize hiresSize = null;
-			if (absoluteScale) {
-				ImageFile hiresFile = fileset.getBiggest();
-				if (!hiresFile.isChecked()) {
-					ImageOps.checkFile(hiresFile);
-				}
-				hiresSize = hiresFile.getSize();
-				
-				/* prepare resolution and scale factor for original size */
-				if (dlRequest.hasOption("mo", "osize")) {
-					// 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!");
-					}
+        accountlog.debug("request: " + request.getQueryString());
+        logger.debug("request: " + request.getQueryString());
+        long startTime = System.currentTimeMillis();
 
-					if ((paramDDPIX == 0) || (paramDDPIY == 0)) {
-						throw new ImageOpException(
-								"Missing display DPI information!");
-					}
-					// calculate absolute scale factor
-					float sx = paramDDPIX / origResX;
-					float sy = paramDDPIY / origResY;
-					// currently only same scale :-(
-					paramSCALE = (sx + sy)/2f;
-				}
-				
-			}
-			
+        // parse request
+        DigilibRequest dlRequest = new DigilibRequest(request);
+        // extract the job information
+        ImageJobDescription jobTicket = ImageJobDescription.getInstance(dlRequest, dlConfig);
 
-			/* calculate expected source image size */
-			ImageSize expectedSourceSize = new ImageSize();
-			if (scaleToFit) {
-				// scale to fit -- calculate minimum source size
-				float scale = (1 / Math.min(paramWW, paramWH)) * paramWS;
-				expectedSourceSize.setSize((int) (paramDW * scale),
-						(int) (paramDH * scale));
-			} else if (absoluteScale && dlRequest.hasOption("mo", "ascale")) {
-				// absolute scale -- apply scale to hires size
-				expectedSourceSize = hiresSize.getScaled(paramSCALE);
-			} else {
-				// clip to fit -- source = destination size
-				expectedSourceSize.setSize((int) (paramDW * paramWS),
-						(int) (paramDH * paramWS));
-			}
-
-			ImageFile fileToLoad;
-			/* 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("Planning to load: " + fileToLoad.getFile());
+        // type of error reporting
+        ErrMsg errMsgType = ErrMsg.IMAGE;
+        if (dlRequest.hasOption("errtxt")) {
+        	errMsgType = ErrMsg.TEXT;
+        } else if (dlRequest.hasOption("errcode")) {
+        	errMsgType = ErrMsg.CODE;
+        }
+        
+        try {
+        	/*
+        	 *  check if we can fast-track without scaling
+        	 */
+            ImageFile fileToLoad = jobTicket.getFileToLoad();
 
-			/*
-			 * 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);
-					logger.info("Done in "
-							+ (System.currentTimeMillis() - startTime) + "ms");
-					return;
-				}
-			}
-
-			// 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.");
+            // check permissions
+            if (useAuthorization) {
+                // get a list of required roles (empty if no restrictions)
+                List<String> rolesRequired = authOp.rolesForPath(
+                        jobTicket.getFilePath(), 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();
+                    }
+                }
+            }
 
-				ServletOps.sendFile(fileToLoad.getFile(), null, response);
-
-				logger.info("Done in "
-						+ (System.currentTimeMillis() - startTime) + "ms");
-				return;
-			}
+            // if requested, send image as a file
+            if (sendFileAllowed && jobTicket.getSendAsFile()) {
+                String mt = null;
+                if (jobTicket.hasOption("rawfile")) {
+                    mt = "application/octet-stream";
+                }
+                logger.debug("Sending RAW File as is.");
+                ServletOps.sendFile(fileToLoad.getFile(), mt, null, response);
+                logger.info("Done in " + (System.currentTimeMillis() - startTime) + "ms");
+                return;
+            }
 
-			
-			/*
-			 * stop here if we're overloaded...
-			 * 
-			 * 503 Service Unavailable 
-			 * The server is currently unable to
-			 * handle the request due to a temporary overloading or maintenance
-			 * of the server. The implication is that this is a temporary
-			 * condition which will be alleviated after some delay. If known,
-			 * the length of the delay MAY be indicated in a Retry-After header.
-			 * If no Retry-After is given, the client SHOULD handle the response
-			 * as it would for a 500 response. Note: The existence of the 503
-			 * status code does not imply that a server must use it when
-			 * becoming overloaded. Some servers may wish to simply refuse the
-			 * connection.
-			 * (RFC2616 HTTP1.1)
-			 */
-			if (! DigilibWorker.canRun()) {
-				logger.error("Servlet overloaded!");
-				response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
-				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 areaWidth;
-			float areaHeight;
-			float scaleX;
-			float scaleY;
-			float scaleXY;
+            // if possible, send the image without actually having to transform it
+            if (! jobTicket.isTransformRequired()) {
+                logger.debug("Sending File as is.");
+                ServletOps.sendFile(fileToLoad.getFile(), null, null, response);
+                logger.info("Done in " + (System.currentTimeMillis() - startTime) + "ms");
+                return;
+            }
 
-			// 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();
+            // check load of workers
+            if (imageJobCenter.isBusy()) {
+                logger.error("Servlet overloaded!");
+                response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+                return;
+            }
+            // create job
+            ImageWorker job = new ImageWorker(dlConfig, jobTicket);
+            // submit job
+            Future<DocuImage> jobResult = imageJobCenter.submit(job);
+            // wait for result
+            DocuImage img = jobResult.get();
+            // send image
+            ServletOps.sendImage(img, null, response);
+            logger.debug("Job Processing Time: "
+                    + (System.currentTimeMillis() - startTime) + "ms");
 
-			// 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) {
-				scaleXY = paramSCALE;
-				// we need to correct the factor if we use a pre-scaled image
-				if (imgSize.getWidth() != hiresSize.getWidth()) {
-					scaleXY *= (float)hiresSize.getWidth() / (float)imgSize.getWidth();
-				}
-				scaleX = scaleXY;
-				scaleY = scaleXY;
-				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;
-			}
+        } catch (IOException e) {
+            logger.error(e.getClass() + ": " + e.getMessage());
+            digilibError(errMsgType, Error.FILE, null, response);
+        } catch (AuthOpException e) {
+            logger.error(e.getClass() + ": " + e.getMessage());
+            digilibError(errMsgType, Error.AUTH, null, response);
+        } catch (InterruptedException e) {
+            logger.error(e.getClass() + ": " + e.getMessage());
+        } catch (ExecutionException e) {
+            logger.error(e.getClass() + ": " + e.getMessage());
+            String causeMsg = e.getCause().getMessage();
+            logger.error("caused by: " + causeMsg);
+            digilibError(errMsgType, Error.IMAGE, causeMsg, response);
+        }
 
-			// 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, forceType);
-
-			job.run();
-			if (job.hasError()) {
-				throw new ImageOpException(job.getError().toString());
-			}
-
-			logger.debug("servlet done in "
-					+ (System.currentTimeMillis() - startTime));
-
-			/* error handling */
-
-		} // 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 type
+     * @param error
+     * @param msg
+     * @param response
+     */
+    public void digilibError(ErrMsg type, Error error, String msg,
+            HttpServletResponse response) {
+        try {
+            File img = null;
+            int status = 0;
+            if (error == Error.AUTH) {
+                if (msg == null) {
+                    msg = "ERROR: Unauthorized access!";
+                }
+                img = denyImgFile;
+                status = HttpServletResponse.SC_FORBIDDEN;
+            } else if (error == Error.FILE) {
+                if (msg == null) {
+                    msg = "ERROR: Image file not found!";
+                }
+                img = notfoundImgFile;
+                status = HttpServletResponse.SC_NOT_FOUND;
+            } else {
+                if (msg == null) {
+                    msg = "ERROR: Other image error!";
+                }
+                img = this.errorImgFile;
+                status = HttpServletResponse.SC_BAD_REQUEST;
+            }
+            if (type == ErrMsg.TEXT) {
+                ServletOps.htmlMessage(msg, response);
+            } else if (type == ErrMsg.CODE) {
+                response.sendError(status, msg);
+            } else if (img != null) {
+                // default: image
+                ServletOps.sendFile(img, null, null, response);
+            }
+        } catch (IOException e) {
+            logger.error("Error sending error!", e);
+        }
 
-	/**
-	 * Returns the DocuDirent corresponding to the DigilibRequest.
-	 * 
-	 * @param dlRequest
-	 * @return
-	 */
-	public DocuDirent findFile(DigilibRequest dlRequest) {
-		// find the file(set)
-		DocuDirent f = dirCache.getFile(dlRequest.getFilePath(), dlRequest
-				.getAsInt("pn"), FileOps.CLASS_IMAGE);
-		return f;
-	}
+    }
 
-	/**
-	 * 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 (type == ERROR_FILE) {
-				if (msg == null) {
-					msg = "ERROR: Image file not found!";
-				}
-				img = notfoundImgFile;
-			} 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);
-			}
-		} catch (IOException e) {
-			logger.error("Error sending error!", e);
-		}
+    public static String getVersion() {
+        return version;
+    }
 
-	}
-
-	/**
-	 * @return the dlVersion
-	 */
-	public static String getVersion() {
-		return dlVersion;
-	}
-
-} // Scaler class
+}
--- a/servlet/src/digilib/servlet/ServletOps.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/ServletOps.java	Tue Dec 21 09:52:16 2010 +0100
@@ -33,193 +33,226 @@
 
 import org.apache.log4j.Logger;
 
+import digilib.image.DocuImage;
 import digilib.io.FileOpException;
 import digilib.io.FileOps;
 
 public class ServletOps {
 
-	private static Logger logger = Logger.getLogger("servlet.op");
+    private static Logger logger = Logger.getLogger("servlet.op");
 
-	/**
-	 * 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;
-	}
+    /**
+     * 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;
+    }
+
+    /**
+     * get a real File for a web app File.
+     * 
+     * If the File is not absolute the path is appended to the base directory of
+     * the web-app.
+     * 
+     * @param file
+     * @param sc
+     * @return
+     */
+    public static File getFile(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(f.getPath());
+            f = new File(fn);
+        }
+        return f;
+    }
 
-	/**
-	 * get a real File for a web app File.
-	 * 
-	 * If the File is not absolute the path is appended to the base directory
-	 * of the web-app.
-	 * 
-	 * @param file
-	 * @param sc
-	 * @return
-	 */
-	public static File getFile(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(f.getPath());
-			f = new File(fn);
-		}
-		return f;
-	}
+    /**
+     * get a real file name for a web app file pathname.
+     * 
+     * If filename starts with "/" its treated as absolute else the path is
+     * appended to the base directory of the web-app.
+     * 
+     * @param filename
+     * @param sc
+     * @return
+     */
+    public static String getFile(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(filename);
+        }
+        return filename;
+    }
 
-	/**
-	 * get a real file name for a web app file pathname.
-	 * 
-	 * If filename starts with "/" its treated as absolute else the path is
-	 * appended to the base directory of the web-app.
-	 * 
-	 * @param filename
-	 * @param sc
-	 * @return
-	 */
-	public static String getFile(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(filename);
-		}
-		return filename;
-	}
+    /**
+     * 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) {
+        String fn = f.getPath();
+        // is the filename absolute?
+        if (f.isAbsolute()) {
+            // does it exist?
+            if (f.canRead()) {
+                // fine
+                return f;
+            } else {
+                // try just the filename as relative
+                fn = f.getName();
+            }
+        }
+        // relative path -> use getRealPath to resolve in WEB-INF
+        String newfn = sc.getServletContext().getRealPath("WEB-INF/" + fn);
+        f = new File(newfn);
+        return f;
+    }
 
-	/**
-	 * 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) {
-	    String fn = f.getPath();
-		// is the filename absolute?
-		if (f.isAbsolute()) {
-		    // does it exist?
-		    if (f.canRead()) {
-		        // fine
-		        return f;
-		    } else {
-		        // try just the filename as relative
-		        fn = f.getName();
-		    }
-		}
-		// relative path -> use getRealPath to resolve in WEB-INF
-		String newfn = sc.getServletContext().getRealPath(
-		        "WEB-INF/" + fn);
-		f = new File(newfn);
-		return f;
-	}
+    /**
+     * 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;
+    }
 
-	/**
-	 * 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 msg, HttpServletResponse response)
+            throws IOException {
+        htmlMessage("Scaler", msg, response);
+    }
 
-	/**
-	 * 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>");
+    }
 
-	/**
-	 * 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>");
-	}
+    /**
+     * 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 f
+     *            Image file to be sent.
+     * @param mt
+     *            mime-type of the file.
+     * @param name 
+     *            name of the download file (for application/x)
+     * @param res
+     *            ServletResponse where the image file will be sent.
+     * 
+     * @throws FileOpException
+     *             Exception is thrown for a IOException.
+     * @throws IOException 
+     */
+    public static void sendFile(File f, String mt, String name, HttpServletResponse response)
+            throws FileOpException, IOException {
+        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
+        if (mt.startsWith("application")) {
+            if (name == null) {
+                // no download name -- use filename
+                name = f.getName();
+            }
+            response.addHeader("Content-Disposition", "attachment; filename=\""+name+"\"");
+        }
+        FileInputStream inFile = new FileInputStream(f);
+        OutputStream outStream = response.getOutputStream();
+        response.setContentLength( (int) f.length());
+        byte dataBuffer[] = new byte[4096];
+        int len;
+        while ((len = inFile.read(dataBuffer)) != -1) {
+            // copy out file
+            outStream.write(dataBuffer, 0, len);
+            outStream.flush();
+        }
+        response.flushBuffer();
+        inFile.close();
+    }
 
-	/**
-	 * 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 sendFile(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.");
-		}
-	}
+    /**
+     * Write image img to ServletResponse response.
+     * 
+     * @param img
+     * @param mimeType
+     * @param response
+     * @throws FileOpException
+     * @throws IOException
+     */
+    public static void sendImage(DocuImage img, String mimeType,
+            HttpServletResponse response) throws FileOpException, IOException {
+        OutputStream outstream = response.getOutputStream();
+        // setup output -- if mime type is set use that otherwise
+        // if source is JPG then dest will be JPG else it's PNG
+        if (mimeType == null) {
+            mimeType = img.getMimetype();
+        }
+        if ((mimeType.equals("image/jpeg") || mimeType.equals("image/jp2") || mimeType
+                .equals("image/fpx"))) {
+            mimeType = "image/jpeg";
+        } else {
+            mimeType = "image/png";
+        }
+
+        // write the image
+        img.writeImage(mimeType, outstream);
+        outstream.flush();
+        img.dispose();
+    }
 
 }
\ No newline at end of file
--- a/servlet/src/digilib/servlet/Texter.java	Wed Jul 14 16:36:42 2010 +0200
+++ b/servlet/src/digilib/servlet/Texter.java	Tue Dec 21 09:52:16 2010 +0100
@@ -31,6 +31,7 @@
 import digilib.io.DocuDirCache;
 import digilib.io.FileOpException;
 import digilib.io.FileOps;
+import digilib.io.FileOps.FileClass;
 import digilib.io.TextFile;
 
 /**
@@ -107,12 +108,6 @@
 	 */
 	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);
 	}
@@ -125,12 +120,6 @@
 	 */
 	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);
 	}
@@ -141,7 +130,8 @@
 		/*
 		 * request parameters
 		 */
-		DigilibRequest dlRequest = (DigilibRequest) request.getAttribute("digilib.servlet.request");
+        // create new request with defaults
+        DigilibRequest dlRequest = new DigilibRequest(request);
 		try {
 			
 			/*
@@ -149,11 +139,11 @@
 			 */
 			TextFile f = getTextFile(dlRequest, "/txt");
 			if (f != null) {
-				ServletOps.sendFile(f.getFile(), null, response);
+				ServletOps.sendFile(f.getFile(), null, null, response);
 			} else {
 				f = getTextFile(dlRequest, "");
 				if (f != null) {
-					ServletOps.sendFile(f.getFile(),	null, response);
+					ServletOps.sendFile(f.getFile(),	null, null, response);
 				} else {
 					response.sendError(HttpServletResponse.SC_NOT_FOUND, "Text-File not found!");
 					//ServletOps.htmlMessage("No Text-File!", response);
@@ -185,6 +175,6 @@
 		String loadPathName = dlRequest.getFilePath() + subDirectory;
 		// find the file(set)
 		return (TextFile) dirCache.getFile(loadPathName, dlRequest
-				.getAsInt("pn"), FileOps.CLASS_TEXT);
+				.getAsInt("pn"), FileClass.TEXT);
 	}
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/DigilibJobCenter.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,114 @@
+/** Wrapper around ExecutionService.
+ * 
+ */
+package digilib.util;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.apache.log4j.Logger;
+
+import digilib.image.DocuImage;
+
+/** Wrapper around ExecutionService.
+ * 
+ * @author casties
+ *
+ */
+public class DigilibJobCenter<V> {
+    /** general logger for this class */
+    private static Logger logger = Logger.getLogger("digilib.jobcenter");
+    /** ExecutorService */
+    private ExecutorService executor;
+    /** max number of running threads */
+    private int maxThreads = 1;
+    /** max number of waiting threads */
+    private int maxQueueLen = 50;
+    /** label for this job center */
+    private String label = "";
+    
+    /**
+     * @param maxThreads
+     * @param label TODO
+     * @param maxQueueLength
+     */
+    public DigilibJobCenter(int maxThreads, int maxQueueLen, boolean prestart, String label) {
+        super();
+        this.label = (label != null) ? label : "";
+        this.maxThreads = maxThreads;
+        this.maxQueueLen = maxQueueLen;
+        executor = Executors.newFixedThreadPool(maxThreads);
+        if (prestart) {
+            // prestart threads so Tomcat's leak protection doesn't complain
+            int st = ((ThreadPoolExecutor)executor).prestartAllCoreThreads();
+            logger.debug(label+" prestarting threads: "+st);
+        }
+    }
+    
+    /** Submit job to execute
+     * 
+     * @param job
+     * @return Future to control the job
+     */
+    public Future<V> submit(Callable<V> job) {
+        return executor.submit(job);
+    }
+
+    /** Returns if the service is not overloaded.
+     *  
+     * @return
+     */
+    public boolean canRun() {
+        int jql = getWaitingJobs();
+        int jrl = getRunningJobs();
+        logger.debug(label+" canRun: waiting jobs="+jql+" running jobs="+jrl);
+        return (jql <= maxQueueLen);
+    }
+    
+    /** Returns if the service is overloaded.
+     *  
+     * @return
+     */
+    public boolean isBusy() {
+        int jql = getWaitingJobs();
+        int jrl = getRunningJobs();
+        logger.debug(label+" isBusy: waiting jobs="+jql+" running jobs="+jrl);
+        return (jql > maxQueueLen);
+    }
+    
+    public int getRunningJobs() {
+        return ((ThreadPoolExecutor)executor).getActiveCount();
+    }
+    
+    public int getWaitingJobs() {
+        BlockingQueue<Runnable> jq = ((ThreadPoolExecutor)executor).getQueue();
+        int jql = jq.size();
+        return jql;
+    }
+
+    public void setMaxThreads(int maxThreads) {
+        this.maxThreads = maxThreads;
+    }
+
+    public int getMaxThreads() {
+        return maxThreads;
+    }
+
+    public void setMaxQueueLen(int maxQueueLen) {
+        this.maxQueueLen = maxQueueLen;
+    }
+
+    public int getMaxQueueLen() {
+        return maxQueueLen;
+    }
+
+    public List<Runnable> shutdownNow() {
+        return executor.shutdownNow();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/NumRange.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,175 @@
+/**
+ * 
+ */
+package digilib.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author casties
+ * 
+ */
+public class NumRange implements Iterable<Integer> {
+
+    private Integer start = 1;
+    private Integer end = Integer.MAX_VALUE;
+    private List<Integer> list = null;
+    private Integer maxnum = null;
+
+    /**
+     * @param start
+     * @param end
+     */
+    public NumRange(Integer start, Integer end) {
+        this.start = start;
+        this.end = end;
+    }
+
+    /**
+     * @param range
+     */
+    public NumRange(String range) {
+        parseString(range);
+    }
+
+    /**
+     * @param range
+     */
+    public NumRange(String range, Integer max) {
+        this.maxnum = max;
+        parseString(range);
+    }
+
+
+    public void parseString(String pages) {
+
+        ArrayList<Integer> pgs = new ArrayList<Integer>();
+
+        String intervals[] = pages.split(",");
+
+        // convert the page-interval-strings into a list containing every single
+        // page
+        for (String interval : intervals) {
+            if (interval.contains("-")) {
+                String nums[] = interval.split("-");
+                int start = Integer.valueOf(nums[0]);
+                if (nums.length > 1) {
+                    // second number is end of range
+                    int end = Integer.valueOf(nums[1]);
+                    if (intervals.length == 1) {
+                        // optimized case of just one interval
+                        this.start = start;
+                        this.end = end;
+                        this.list = null;
+                        return;
+                    }
+                    for (int i = start; i <= end; i++) {
+                        // add all numbers to list
+                        pgs.add(i);
+                    }
+                } else {
+                    // second number missing: range to infinity
+                    pgs.add(start);
+                    pgs.add(Integer.MAX_VALUE);
+                }
+            } else {
+                // single number
+                pgs.add(Integer.valueOf(interval));
+            }
+        }
+        if (intervals.length > 1) {
+            Collections.sort(pgs);
+        }
+        list = pgs;
+    }
+
+    public int getStart() {
+        if (list == null) {
+            return start;
+        } else {
+            return list.get(0);
+        }
+    }
+
+    public int getEnd() {
+        Integer last;
+        if (list == null) {
+            last = end;
+        } else {
+            last = list.get(list.size() - 1);
+        }
+        if (maxnum == null) {
+            return last;
+        } else {
+            return Math.min(last, maxnum);
+        }
+    }
+
+    public Iterator<Integer> iterator() {
+        if (list == null) {
+            // return count-based iterator
+            return new Iterator<Integer>() {
+                // anonymous inner Iterator class
+                private int num = getStart();
+                private int end = getEnd();
+
+                public boolean hasNext() {
+                    return (num <= end);
+                }
+
+                public Integer next() {
+                    return num++;
+                }
+
+                public void remove() {
+                    // don't do this
+                }
+            };
+        } else {
+            // return list-based iterator
+            return new Iterator<Integer>() {
+                // anonymous inner Iterator class
+                private int listidx = 0;
+                private int listend = list.size();
+                private int num = getStart();
+                private int end = getEnd();
+
+                public boolean hasNext() {
+                    return (num <= end);
+                }
+
+                public Integer next() {
+                    if (listidx < listend - 1) {
+                        num = list.get(listidx++);
+                        return num;
+                    } else if (listidx == listend - 1) {
+                        // last element in list
+                        int n = list.get(listidx++);
+                        if (n == Integer.MAX_VALUE) {
+                            // open end -- continue
+                            num++;
+                            return num++;
+                        } else {
+                            num = n;
+                            return num++;
+                        }
+                    } else {
+                        return num++;
+                    }
+                }
+
+                public void remove() {
+                    // don't do this
+                }
+            };
+        }
+    }
+
+    public void setMaxnum(Integer maxnum) {
+        this.maxnum = maxnum;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/OptionsSet.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,67 @@
+/**
+ * 
+ */
+package digilib.util;
+
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+/**
+ * @author casties
+ *
+ */
+@SuppressWarnings("serial")
+public class OptionsSet extends HashSet<String> {
+
+	protected String optionSep = ",";
+	
+	public OptionsSet() {
+		super();
+	}
+
+	/** Constructor with String of options.
+	 * @param s
+	 */
+	public OptionsSet(String s) {
+		super();
+		parseString(s);
+	}
+
+	/** Adds all options from String to Set.
+	 * @param s
+	 */
+	public void parseString(String s) {
+		if (s != null) {
+			StringTokenizer i = new StringTokenizer(s, optionSep);
+			while (i.hasMoreTokens()) {
+				String opt = i.nextToken();
+				this.add(opt);
+			}
+		}
+	}
+	
+	public boolean hasOption(String opt) {
+		return this.contains(opt);
+	}
+
+	public String toString() {
+		StringBuffer b = new StringBuffer();
+		for (String s: this) {
+			if (b.length() > 0) {
+				b.append(optionSep);
+			}
+			b.append(s);			
+		}
+		return b.toString();
+	}
+	
+	
+	public String getOptionSep() {
+		return optionSep;
+	}
+
+	public void setOptionSep(String optionSep) {
+		this.optionSep = optionSep;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/Parameter.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,284 @@
+/* Parameter -- General digilib parameter 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 02.09.2003 by casties
+ * 
+ */
+package digilib.util;
+
+import java.io.File;
+import java.io.IOException;
+
+
+/**
+ * General digilib parameter class.
+ * 
+ * @author casties
+ *  
+ */
+public class Parameter {
+	/** real value */
+	protected Object value = null;
+
+	/** default value */
+	protected Object defval = null;
+
+	/** parameter name (e.g. in config file) */
+	protected String name = null;
+
+	/** parameter type */
+	protected int type = 0;
+
+	/**
+	 * Default constructor.
+	 *  
+	 */
+	public Parameter() {
+		super();
+	}
+
+	/**
+	 * Constructor with name, default, and value.
+	 * 
+	 * @param value
+	 * @param defval
+	 */
+	public Parameter(String name, Object defval, Object value) {
+		this.name = name;
+		this.value = value;
+		this.defval = defval;
+	}
+
+	/**
+	 * Constructor with name, default, value, and type.
+	 * 
+	 * @param value
+	 * @param defval
+	 */
+	public Parameter(String name, Object defval, Object value, int type) {
+		this.name = name;
+		this.value = value;
+		this.defval = defval;
+		this.type = type;
+	}
+
+	/**
+	 * Is the value valid.
+	 * 
+	 * @return
+	 */
+	public boolean hasValue() {
+		return (value != null);
+	}
+
+	/**
+	 * Try to set the value from a String.
+	 * 
+	 * Tries to convert the String to the same type as the default value. Sets
+	 * the value anyway if the default is null. Returns if the value could be
+	 * set.
+	 * 
+	 * @param val
+	 * @return
+	 */
+	public boolean setValueFromString(String val) {
+		if (val == null) {
+			val = "";
+		}
+		// no default matches all
+		if (defval == null) {
+			this.value = val;
+			return true;
+		}
+		Class<? extends Object> c = defval.getClass();
+		// take String as is
+		if (c == String.class) {
+			this.value = val;
+			return true;
+		}
+		// set File
+		if (c == File.class) {
+			this.value = new File(val);
+			return true;
+		}
+		// set Options
+		if (c == OptionsSet.class) {
+			this.value = new OptionsSet(val);
+			return true;
+		}
+		// set Boolean if string == "true"
+		if (c == Boolean.class) {
+			this.value = new Boolean(val.compareToIgnoreCase("true") == 0);
+			return true;
+		}
+		try {
+			// set Integer
+			if (c == Integer.class) {
+				this.value = new Integer(Integer.parseInt(val));
+				return true;
+			}
+			// set Float
+			if (c == Float.class) {
+				this.value = new Float(Float.parseFloat(val));
+				return true;
+			}
+		} catch (NumberFormatException e) {
+		}
+		// then it's unknown
+		return false;
+	}
+
+	/**
+	 * Get the default as Object.
+	 * 
+	 * @return
+	 */
+	public Object getDefault() {
+		return defval;
+	}
+
+	/**
+	 * Set the default.
+	 * 
+	 * @param defval
+	 */
+	public void setDefault(Object defval) {
+		this.defval = defval;
+	}
+
+	/**
+	 * Get the value as Object.
+	 * 
+	 * Returns the default if the value is not set.
+	 * 
+	 * @return
+	 */
+	public Object getValue() {
+		return (value != null) ? value : defval;
+	}
+
+	public int getAsInt() {
+		Integer i = (Integer) getValue();
+		return (i != null) ? i.intValue() : 0;
+	}
+
+	public float getAsFloat() {
+		Float f = (Float) getValue();
+		return (f != null) ? f.floatValue() : 0f;
+	}
+
+	public String getAsString() {
+		Object s = getValue();
+		if (s == null) {
+			return "";
+		}
+		if (s.getClass() == File.class) {
+			try {
+				return ((File) s).getCanonicalPath();
+			} catch (IOException e) {
+				return "ERR: " + s.toString();
+			}
+		}
+		return s.toString();
+	}
+
+	public boolean getAsBoolean() {
+		Boolean b = (Boolean) getValue();
+		return (b != null) ? b.booleanValue() : false;
+	}
+
+	public String[] parseAsArray(String separator) {
+		String s = getAsString();
+		String[] sa = s.split(separator);
+		return sa;
+	}
+
+	public float[] parseAsFloatArray(String separator) {
+		String s = getAsString();
+		String[] sa = s.split(separator);
+		float[] fa = null;
+		try {
+			int n = sa.length;
+			fa = new float[n];
+			for (int i = 0; i < n; i++) {
+				float f = Float.parseFloat(sa[i]);
+				fa[i] = f;
+			}
+		} catch (Exception e) {
+		}
+
+		return fa;
+	}
+
+	/**
+	 * Set the value.
+	 * 
+	 * @param value
+	 */
+	public void setValue(Object value) {
+		this.value = value;
+	}
+
+	/**
+	 * Set the value.
+	 * 
+	 * @param value
+	 */
+	public void setValue(int value) {
+		this.value = new Integer(value);
+	}
+
+	/**
+	 * Set the value.
+	 * 
+	 * @param value
+	 */
+	public void setValue(float value) {
+		this.value = new Float(value);
+	}
+
+	/**
+	 * @return
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * @param name
+	 */
+	public void setName(String name) {
+		this.name = name;
+	}
+
+	/**
+	 * @return
+	 */
+	public int getType() {
+		return type;
+	}
+
+	/**
+	 * @param type
+	 */
+	public void setType(int type) {
+		this.type = type;
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/ParameterMap.java	Tue Dec 21 09:52:16 2010 +0100
@@ -0,0 +1,322 @@
+/* ParameterMap.java -- HashMap of Parameters.
+
+  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 02.09.2003 by casties
+ *
+ */
+package digilib.util;
+
+import java.util.HashMap;
+
+
+/** HashMap of digilib.servlet.Parameter's.
+ * 
+ * Keys are Strings. Values are Parameters.
+ * 
+ * @author casties
+ *
+ */
+public class ParameterMap {
+
+	protected HashMap<String, Parameter> params;
+	
+	protected OptionsSet options;
+	
+	/** Default constructor.
+	 * 
+	 */
+	public ParameterMap() {
+		params = new HashMap<String, Parameter>();
+		options = new OptionsSet();
+		initParams();
+	}
+
+	/** Constructor with initial size.
+	 * @param size
+	 */
+	public ParameterMap(int size) {
+		params = new HashMap<String, Parameter>(size);
+		options = new OptionsSet();
+		initParams();
+	}
+
+	/** Shallow copy constructor.
+	 * Be warned that the maps are only cloned i.e. keys and values are shared!
+	 * @param pm
+	 */
+	@SuppressWarnings("unchecked")
+	public static ParameterMap cloneInstance(ParameterMap pm) {
+		ParameterMap newPm = new ParameterMap();
+		// clone params to this map
+		newPm.params = (HashMap<String, Parameter>) pm.params.clone();
+		newPm.options = (OptionsSet) pm.options.clone();
+		return newPm;
+	}
+
+	
+	/** Creates new ParameterMap by merging Parameters from another ParameterMap.
+	 * @param pm
+	 * @return
+	 */
+	public static ParameterMap getInstance(ParameterMap pm) {
+		ParameterMap newPm = new ParameterMap();
+		// add all params to this map
+		newPm.params.putAll(pm.params);
+		newPm.initOptions();
+		return newPm;
+	}
+	
+	/** set up parameters
+	 * 
+	 */
+	protected void initParams() {
+		// no default parameters
+	}
+	
+	/** set up options
+	 * 
+	 */
+	protected void initOptions() {
+		// no default options
+	}
+	
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public Parameter get(String key) {
+		return params.get(key);
+	}
+
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public Object getValue(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.getValue() : null;
+	}
+	
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public String getAsString(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.getAsString() : "";
+	}
+
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public int getAsInt(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.getAsInt() : 0;
+	}
+
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public float getAsFloat(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.getAsFloat() : 0f;
+	}
+
+	/** Get the Parameter with the corresponding key.
+	 * 
+	 * Returns null if no element is associated with key.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public boolean getAsBoolean(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.getAsBoolean() : false;
+	}
+
+	/** Returns if the Parameter's value has been set.
+	 * 
+	 * @param key
+	 * @return
+	 */
+	public boolean hasValue(String key) {
+		Parameter p = params.get(key);
+		return (p != null) ? p.hasValue() : false;
+	}
+	
+	/** Add the Parameter to the map with a certain key.
+	 * 
+	 * Returns the value that was previously associated with key. 
+	 * 
+	 * @param key
+	 * @param val
+	 * @return
+	 */
+	public Parameter put(String key, Parameter val) {
+		return params.put(key, val);
+	}
+
+	/** Add the Parameter val to the map, using val's name.
+	 * 
+	 * Returns the value that was previously associated with val's name. 
+	 * 
+	 * @param val
+	 * @return
+	 */
+	public Parameter put(Parameter val) {
+		return params.put(val.getName(), val);
+	}
+	
+	/** Add a new Parameter with name, default and value.
+	 * 
+	 * Returns the key that was previously associated with name. 
+	 * 
+	 * @param name
+	 * @param def
+	 * @param val
+	 * @return
+	 */
+	public Parameter newParameter(String name, Object def, Object val) {
+		Parameter p = new Parameter(name, def, val);
+		return params.put(name, p);
+	}
+
+	/** Add a new Parameter with name, default, value and type.
+	 * 
+	 * Returns the key that was previously associated with name. 
+	 * 
+	 * @param name
+	 * @param def
+	 * @param val
+	 * @param type
+	 * @return
+	 */
+	public Parameter newParameter(String name, Object def, Object val, int type) {
+		Parameter p = new Parameter(name, def, val, type);
+		return params.put(name, p);
+	}
+
+	/** Set the value of an existing parameter.
+	 * 
+	 * Sets the value and returns true if the parameter exists.
+	 * 
+	 * @param key
+	 * @param val
+	 * @return
+	 */
+	public boolean setValue(String key, Object val) {
+		Parameter p = params.get(key);
+		if (p != null) {
+			p.setValue(val);
+			return true;
+		}
+		return false;
+	}
+
+	/** Set the value of an existing parameter.
+	 * 
+	 * Sets the value and returns true if the parameter exists.
+	 * 
+	 * @param key
+	 * @param val
+	 * @return
+	 */
+	public boolean setValue(String key, int val) {
+		Parameter p = params.get(key);
+		if (p != null) {
+			p.setValue(val);
+			return true;
+		}
+		return false;
+	}
+
+	/** Set the value of an existing parameter.
+	 * 
+	 * Sets the value and returns true if the parameter exists.
+	 * 
+	 * @param key
+	 * @param val
+	 * @return
+	 */
+	public boolean setValue(String key, float val) {
+		Parameter p = params.get(key);
+		if (p != null) {
+			p.setValue(val);
+			return true;
+		}
+		return false;
+	}
+
+	/** Set the value of an existing parameter.
+	 * 
+	 * Sets the value and returns true if the parameter exists.
+	 * 
+	 * @param key
+	 * @param val
+	 * @return
+	 */
+	public boolean setValueFromString(String key, String val) {
+		Parameter p = params.get(key);
+		if (p != null) {
+			p.setValueFromString(val);
+			return true;
+		}
+		return false;
+	}
+	
+	/** Returns of the option has been set.
+	 * @param opt
+	 * @return
+	 */
+	public boolean hasOption(String opt) {
+		return options.hasOption(opt);
+	}
+
+	public HashMap<String, Parameter> getParams() {
+		return params;
+	}
+
+	public void setParams(HashMap<String, Parameter> params) {
+		this.params = params;
+	}
+
+	public OptionsSet getOptions() {
+		return options;
+	}
+
+	public void setOptions(OptionsSet options) {
+		this.options = options;
+	}
+}