changeset 557:0885f5ca5b24 digilibPDF

more refactoring and rearranging pdf and image generation works now
author robcast
date Thu, 16 Dec 2010 21:19:11 +0100
parents 5cc180bb0a5c
children 2e971b9f3022
files client/digitallibrary/dlInfo-xml.jsp 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 client/digitallibrary/greyskin/diginew.jsp client/digitallibrary/pdf/error.jsp client/digitallibrary/pdf/wip.jsp client/digitallibrary/server/dlConfig.jsp servlet/src/digilib/image/ImageJobDescription.java servlet/src/digilib/image/ImageWorker.java servlet/src/digilib/io/DigilibInfoReader.java servlet/src/digilib/pdf/PDFFileWorker.java servlet/src/digilib/pdf/PDFStreamWorker.java servlet/src/digilib/pdf/PDFTitlePage.java servlet/src/digilib/servlet/DigilibConfiguration.java servlet/src/digilib/servlet/DigilibInfoReader.java servlet/src/digilib/servlet/DigilibJobCenter.java servlet/src/digilib/servlet/DigilibPDFWorker.java servlet/src/digilib/servlet/DigilibRequest.java servlet/src/digilib/servlet/DigilibWorker1.java servlet/src/digilib/servlet/ImageJobDescription.java servlet/src/digilib/servlet/ImageWorker.java servlet/src/digilib/servlet/Initialiser.java servlet/src/digilib/servlet/NumRange.java servlet/src/digilib/servlet/OptionsSet.java servlet/src/digilib/servlet/PDFCache.java servlet/src/digilib/servlet/PDFFileWorker.java servlet/src/digilib/servlet/PDFJobDescription.java servlet/src/digilib/servlet/PDFMaker.java servlet/src/digilib/servlet/PDFRequest.java servlet/src/digilib/servlet/PDFStreamWorker.java servlet/src/digilib/servlet/PDFTitlePage.java servlet/src/digilib/servlet/Parameter.java servlet/src/digilib/servlet/ParameterMap.java servlet/src/digilib/servlet/RequestHandler.java servlet/src/digilib/servlet/Scaler.java servlet/src/digilib/servlet/ServletOps.java servlet/src/digilib/servlet/Texter.java servlet/src/digilib/util/DigilibJobCenter.java servlet/src/digilib/util/NumRange.java servlet/src/digilib/util/OptionsSet.java servlet/src/digilib/util/Parameter.java servlet/src/digilib/util/ParameterMap.java
diffstat 44 files changed, 2412 insertions(+), 3852 deletions(-) [+]
line wrap: on
line diff
--- a/client/digitallibrary/dlInfo-xml.jsp	Thu Dec 16 17:02:34 2010 +0100
+++ b/client/digitallibrary/dlInfo-xml.jsp	Thu Dec 16 21:19:11 2010 +0100
@@ -23,7 +23,7 @@
 %><!-- Automatically generated XML snippet with document parameters -->
 <document-parameters>
 <%
-    Object[] keys = dlRequest.getParams.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	Thu Dec 16 17:02:34 2010 +0100
+++ /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	Thu Dec 16 17:02:34 2010 +0100
+++ /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	Thu Dec 16 17:02:34 2010 +0100
+++ /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	Thu Dec 16 17:02:34 2010 +0100
+++ /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	Thu Dec 16 17:02:34 2010 +0100
+++ b/client/digitallibrary/greyskin/diginew.jsp	Thu Dec 16 21:19:11 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>
--- a/client/digitallibrary/pdf/error.jsp	Thu Dec 16 17:02:34 2010 +0100
+++ b/client/digitallibrary/pdf/error.jsp	Thu Dec 16 21:19:11 2010 +0100
@@ -1,13 +1,13 @@
-<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+<%@ 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=ISO-8859-1">
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 <title>Error</title>
 </head>
 <body>
 <h1>Error</h1>
-W&auml;hrend der Verarbeitung ihrer Anfrage trat ein Fehler auf. M&ouml;glicherweise sind die &uuml;bergebenen Parameter fehlerhaft.
+There was an error while processing your request. Please check the parameters and try again.
 </body>
 </html>
--- a/client/digitallibrary/pdf/wip.jsp	Thu Dec 16 17:02:34 2010 +0100
+++ b/client/digitallibrary/pdf/wip.jsp	Thu Dec 16 21:19:11 2010 +0100
@@ -1,14 +1,14 @@
-<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
+<%@ 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=ISO-8859-1">
+<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>Bitte warten ...</h1>
-ihre Anfrage wird bearbeitet.  Der Download beginnt automatisch, sobald das Dokument fertig generiert ist. 
+<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	Thu Dec 16 17:02:34 2010 +0100
+++ b/client/digitallibrary/server/dlConfig.jsp	Thu Dec 16 21:19:11 2010 +0100
@@ -1,4 +1,4 @@
-<%@page import="digilib.servlet.DigilibJobCenter"%>
+<%@page import="digilib.util.DigilibJobCenter"%>
 <%@ page language="java" %>
 
 <%!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageJobDescription.java	Thu Dec 16 21:19:11 2010 +0100
@@ -0,0 +1,503 @@
+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.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() {
+		if (options == null) {
+			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()){
+				ImageOps.checkFile(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"), FileOps.CLASS_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()) {
+				ImageOps.checkFile(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 get_scaleQual(){
+		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 int getForceType(){
+		if(hasOption("jpg"))
+			return ImageOps.TYPE_JPEG;
+		if(hasOption("png"))
+			return ImageOps.TYPE_PNG;
+		
+		return ImageOps.TYPE_AUTO;
+	}
+	
+	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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/image/ImageWorker.java	Thu Dec 16 21:19:11 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 = dlConfig.getDocuImageInstance();
+        if (docuImage == null) {
+            throw new ImageOpException("Unable to load DocuImage class!");
+        }
+
+        // set interpolation quality
+        docuImage.setQuality(jobinfo.get_scaleQual());
+
+        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.get_scaleQual() > 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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/io/DigilibInfoReader.java	Thu Dec 16 21:19:11 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;
+		}
+	}
+	
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/pdf/PDFFileWorker.java	Thu Dec 16 21:19:11 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	Thu Dec 16 21:19:11 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	Thu Dec 16 21:19:11 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	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/DigilibConfiguration.java	Thu Dec 16 21:19:11 2010 +0100
@@ -35,6 +35,8 @@
 import digilib.image.DocuImageImpl;
 import digilib.io.FileOps;
 import digilib.io.XMLListLoader;
+import digilib.util.Parameter;
+import digilib.util.ParameterMap;
 
 /**
  * Class to hold the digilib servlet configuration parameters. The parameters
--- a/servlet/src/digilib/servlet/DigilibInfoReader.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-package digilib.servlet;
-
-/** 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/servlet/DigilibJobCenter.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-/** Wrapper around ExecutionService.
- * 
- */
-package digilib.servlet;
-
-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;
-    
-    /**
-     * @param maxThreads
-     * @param maxQueueLength
-     */
-    public DigilibJobCenter(int maxThreads, int maxQueueLen, boolean prestart) {
-        super();
-        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("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("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("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();
-    }
-
-}
--- a/servlet/src/digilib/servlet/DigilibPDFWorker.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,214 +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.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-import com.itextpdf.text.BadElementException;
-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.ImageOpException;
-import digilib.io.FileOpException;
-
-/**
- * Worker for pdf generation.
- * 
- * @author cmielack
- * 
- */
-public class DigilibPDFWorker extends DigilibWorker1 {
-
-	private DigilibConfiguration dlConfig = null;
-
-	private Document doc = null;
-
-	private File outputfile = null;
-	
-	private PDFJobDescription job_info = null;
-	
-	public DigilibPDFWorker(DigilibConfiguration dlConfig, PDFJobDescription pdfji, File outputfile) {
-		super();
-		// TODO dlConfig 
-		this.dlConfig = dlConfig;
-		this.job_info = pdfji;
-		this.outputfile = outputfile;
-	}
-
-	public void run() {
-		// create document object
-		doc = new Document(PageSize.A4, 0,0,0,0);
-		PdfWriter docwriter = null;
-		FileOutputStream fos;
-		
-		try {
-			fos = new FileOutputStream(outputfile);
-		} catch (FileNotFoundException e1) {
-			// TODO Auto-generated catch block
-			logger.error(e1.getMessage());
-			e1.printStackTrace();
-			return;
-		}
-		
-		long start_time = System.currentTimeMillis();
-		
-		try {
-			docwriter = PdfWriter.getInstance(doc, fos);
-			
-			setPDFProperties();
-
-			doc.open();
-
-			addTitlePage();
-			
-			logger.debug("- "+outputfile+" doc.open()ed ("+(System.currentTimeMillis()-start_time) + "ms)");
-			start_time = System.currentTimeMillis();
-
-			//Integer[] pgs = job_info.getPageNrs();//get_pgs();
-			NumRange pgs = job_info.getPages();
-
-			for(Integer p: pgs){
-				logger.debug(" - adding Image "+p+" to " + outputfile);
-				addImage(p);
-				logger.debug(" - done adding Image "+p+" to " + outputfile);
-			}
-			
-			logger.debug(" - done adding all Images to " + outputfile);
-			
-		} catch(Exception e) {
-			logger.error(e.getMessage());
-			error = e;
-			return;
-		} finally {
-			if (doc!=null){
-				doc.close();
-				logger.debug("- "+outputfile+" doc.close() ("+(System.currentTimeMillis()-start_time) + "ms)");
-			}
-			if (docwriter!=null){
-				docwriter.close();
-			}
-		}
-
-		try {
-			fos.flush();
-		} catch (IOException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-			error = e;
-		} finally{
-			if(fos!=null){
-				try {
-					fos.close();
-				} catch (IOException e) {
-					logger.error(e.getMessage());
-					e.printStackTrace();
-				}
-			}
-		}
-	}
-
-	/**
-	 * Set PDF-Meta-Attributes.
-	 */
-	public void setPDFProperties(){
-		// 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());
-	}
-	
-	/**
-	 * Create a title page and append it to the document (should, of course, be called first)
-	 * @throws DocumentException
-	 */
-	public void addTitlePage() throws DocumentException{
-		PDFTitlePage titlepage = new PDFTitlePage(job_info);
-		doc.add(titlepage.getPageContents());
-		doc.newPage();
-	}
-		
-	/**
-	 * add the image with page number 'pn' to the document.
-	 * 
-	 * @param pn
-	 */
-	public void addImage(int pn) {
-		// create ImageJobInformation
-		ImageJobDescription iji = job_info.getImageJobInformation();
-		iji.setValue("pn", pn);
-		// create image worker
-		/* FIXME: DigilibImageWorker1 image_worker = new DigilibImageWorker1(dlConfig, null, iji);
-		try {
-			DocuImage img = image_worker.render();
-
-			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();
-			
-			pdfimg.scaleToFit(docW,docH);
-			
-			doc.add(pdfimg);
-			
-		} catch (FileOpException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-		} catch (IOException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-		} catch (ImageOpException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-		} catch (BadElementException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-		} catch (DocumentException e) {
-			logger.error(e.getMessage());
-			e.printStackTrace();
-		}
-		*/
-	}
-
-
-	
-	
-	
-	@Override
-	public DocuImage render() throws Exception {
-		return null;
-	}
-
-	@Override
-	public void write(DocuImage img) throws Exception {
-		
-	}
-	
-}
\ No newline at end of file
--- a/servlet/src/digilib/servlet/DigilibRequest.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/DigilibRequest.java	Thu Dec 16 21:19:11 2010 +0100
@@ -36,6 +36,9 @@
 
 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
@@ -69,6 +72,18 @@
 		super(30);
 	}
 
+    /**
+     * 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();
+    }
+
 	/** set up parameters.
 	 * 
 	 */
@@ -157,16 +172,13 @@
 		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) {
-		super(30);
-		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.
--- a/servlet/src/digilib/servlet/DigilibWorker1.java	Thu Dec 16 17:02:34 2010 +0100
+++ /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 DigilibWorker1 {
-
-	protected static Logger logger = Logger.getLogger(DigilibWorker1.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 DigilibWorker1() {
-		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 ((DigilibWorker1.maxWaitingThreads == 0) || (DigilibWorker1.getNumWaiting() <= DigilibWorker1.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) {
-		DigilibWorker1.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) {
-		DigilibWorker1.maxWaitingThreads = maxWaitingThreads;
-	}
-
-	public static int getMaxRunningThreads() {
-		return maxRunningThreads;
-	}
-
-	public static void setMaxRunningThreads(int maxRunningThreads) {
-		DigilibWorker1.maxRunningThreads = maxRunningThreads;
-	}
-}
--- a/servlet/src/digilib/servlet/ImageJobDescription.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,502 +0,0 @@
-package digilib.servlet;
-
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
-
-import org.apache.log4j.Logger;
-
-import digilib.image.ImageOpException;
-import digilib.image.ImageOps;
-import digilib.image.ImageSize;
-import digilib.io.DocuDirCache;
-import digilib.io.DocuDirectory;
-import digilib.io.FileOpException;
-import digilib.io.FileOps;
-import digilib.io.ImageFile;
-import digilib.io.ImageFileset;
-
-
-/** 
- * 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.servlet.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() {
-		if (options == null) {
-			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 setFrom(ParameterMap pm, DigilibConfiguration dlcfg) {
-		ImageJobDescription newMap = new ImageJobDescription(dlcfg);
-		// add all params to this map
-		newMap.params.putAll(pm.params);
-		newMap.initOptions();
-		return newMap;
-	}
-
-	
-	public String getMimeType() throws IOException {
-		if (mimeType == null) {
-			fileToLoad = getFileToLoad();
-			if(! fileToLoad.isChecked()){
-				ImageOps.checkFile(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"), FileOps.CLASS_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()) {
-				ImageOps.checkFile(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 get_scaleQual(){
-		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 int getForceType(){
-		if(hasOption("jpg"))
-			return ImageOps.TYPE_JPEG;
-		if(hasOption("png"))
-			return ImageOps.TYPE_PNG;
-		
-		return ImageOps.TYPE_AUTO;
-	}
-	
-	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/servlet/ImageWorker.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-/** Worker (Callable) that renders an image.
- * 
- */
-package digilib.servlet;
-
-import java.awt.Rectangle;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-
-import org.apache.log4j.Logger;
-
-import digilib.image.DocuImage;
-import digilib.image.ImageOpException;
-import digilib.io.FileOpException;
-
-/** 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 = dlConfig.getDocuImageInstance();
-        if (docuImage == null) {
-            throw new ImageOpException("Unable to load DocuImage class!");
-        }
-
-        // set interpolation quality
-        docuImage.setQuality(jobinfo.get_scaleQual());
-
-        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.get_scaleQual() > 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/servlet/Initialiser.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/Initialiser.java	Thu Dec 16 21:19:11 2010 +0100
@@ -39,6 +39,7 @@
 import digilib.io.AliasingDocuDirCache;
 import digilib.io.DocuDirCache;
 import digilib.io.FileOps;
+import digilib.util.DigilibJobCenter;
 
 /**
  * Singleton initalisation servlet for setup tasks and resources.
--- a/servlet/src/digilib/servlet/NumRange.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,175 +0,0 @@
-/**
- * 
- */
-package digilib.servlet;
-
-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;
-    }
-
-}
--- a/servlet/src/digilib/servlet/OptionsSet.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * 
- */
-package digilib.servlet;
-
-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;
-	}
-
-}
--- a/servlet/src/digilib/servlet/PDFCache.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/PDFCache.java	Thu Dec 16 21:19:11 2010 +0100
@@ -4,9 +4,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.OutputStream;
 import java.util.concurrent.Future;
 
 import javax.servlet.RequestDispatcher;
@@ -14,10 +12,15 @@
 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.  
@@ -30,7 +33,18 @@
  */
 
 @SuppressWarnings("serial")
-public class PDFCache extends RequestHandler {
+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;
 	
@@ -56,7 +70,6 @@
 	
 	public static Integer STATUS_ERROR = 3;     // an error occurred while processing the request
 	
-	public static String version = "0.3a";
 	
 	// TODO ? functionality for the pre-generation of complete books/chapters using default values
 	
@@ -82,12 +95,13 @@
 		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);
 		}
-        // rid the temporary directory of possible incomplete document files
-        emptyDirectory(temp_directory);
         
 		String cache_fn = dlConfig.getAsString("pdf-cache-dir");
        	cache_directory = new File(cache_fn);
@@ -107,12 +121,28 @@
 		
 	}
 	
+    /* (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 temp_dir){
-		File[] temp_files = temp_dir.listFiles();
-		
+	public void emptyDirectory(File dir){
+		File[] temp_files = dir.listFiles();
 		for (File f: temp_files){
 			f.delete();
 		}
@@ -120,12 +150,17 @@
 	
 	
 	public void processRequest(HttpServletRequest request,
-			HttpServletResponse response) {
+			HttpServletResponse response) throws ServletException {
 
-	    String docid = "";
+        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)
-		PDFJobDescription pdfji = new PDFJobDescription(request, dlConfig); 
+		PDFRequest pdfji = new PDFRequest(request, dlConfig); 
 		
 		docid = pdfji.getDocumentId();
 		
@@ -150,7 +185,8 @@
         } else if (status == STATUS_DONE) {
         	// pdf created -- send it
             try {
-                sendFile(docid, getDownloadFilename(pdfji), response);
+                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());
@@ -233,7 +269,7 @@
 	 * @return 
 	 * @throws FileNotFoundException 
 	 */
-	public Future<File> createNewPdfDocument(PDFJobDescription pdfji, String filename) 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);
@@ -250,7 +286,7 @@
 	 * @param pdfji
 	 * @return
 	 */
-	public String getDownloadFilename(PDFJobDescription pdfji){
+	public String getDownloadFilename(PDFRequest pdfji){
 		// filename example: digilib_example_pgs1-3.pdf
 		String filename;
 		filename =  "digilib_";
--- a/servlet/src/digilib/servlet/PDFFileWorker.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-/**
- * 
- */
-package digilib.servlet;
-
-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;
-
-/**
- * @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,
-			PDFJobDescription job_info,
-			DigilibJobCenter<DocuImage> imageJobCenter) throws FileNotFoundException {
-    	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;
-    }
-    
-    
-}
--- a/servlet/src/digilib/servlet/PDFJobDescription.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-package digilib.servlet;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.log4j.Logger;
-
-import digilib.io.DocuDirectory;
-import digilib.io.FileOpException;
-import digilib.io.FileOps;
-
-
-/** 
- * A container class for storing a set of instruction parameters 
- * used for content generator classes like MakePDF.  
- * 
- * 
- * @author cmielack, casties
- *
- */
-public class PDFJobDescription 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 PDFJobDescription(DigilibConfiguration dlcfg) {
-		super(30);
-		dlConfig = dlcfg;
-	}
-
-	/**
-	 * Initialize the PDFJobInformation with a request.
-	 * 
-	 * @param dlcfg		The DigilibConfiguration. 		
-	 * @param request
-	 * @throws FileOpException 
-	 */
-	public PDFJobDescription(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');
-	}
-	
-	/**
-	 * 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.setFrom(this, dlConfig);
-        DocuDirectory dir = ij.getFileDirectory();
-        int dirsize = dir.size(FileOps.CLASS_IMAGE);
-        pages.setMaxnum(dirsize);
-	}
-	
-	
-	/**
-	 * Generate a filename for the pdf to be created.
-	 * 
-	 * @return
-	 */
-	public String getDocumentId(){
-		String id;
-
-		// TODO use complete request information for id generation
-		
-		String fn = getAsString("fn");
-		String dh = getAsString("dh");
-		String dw = getAsString("dw");
-		String pgs = getAsString("pgs");
-			
-		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.setFrom(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/PDFMaker.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-package digilib.servlet;
-
-import java.io.File;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServlet;
-
-import org.apache.log4j.Logger;
-
-
-
-
-/**
- * A Runnable that creates the PDFWorker and moves completed files from a temporary location
- *  (defined in PDFCache) to the cache directory.
- * 
- * @author cmielack
- *
- */
-@SuppressWarnings("serial")
-public class PDFMaker extends HttpServlet implements Runnable {
-
-	public static String version = "0.1";
-	
-	private PDFJobDescription job_info = null;
-	private String filename = null;
-	private DigilibConfiguration dlConfig = null;
-	private ServletContext context = null;
-
-	/** gengeral logger for this class */
-	protected static Logger logger = Logger.getLogger("digilib.PDFMaker");
-
-	
-	
-	public PDFMaker(ServletContext context, PDFJobDescription pdfji, String filename){
-		this.job_info = pdfji;
-		this.filename = filename;
-		this.dlConfig = pdfji.getDlConfig();
-		this.context = context;
-	}
-		
-	public void run() {
-
-		if (! DigilibWorker1.canRun()) {
-			// TODO include the logger
-			logger.error("Servlet overloaded!");			
-			return;
-		}
-
-		PDFCache pdfcache = (PDFCache) context.getAttribute(PDFCache.instanceKey);
-		
-		File tempfile = pdfcache.getTempFile(filename);
-		// create PDFWorker
-		DigilibPDFWorker pdf_worker = new DigilibPDFWorker(dlConfig, job_info, tempfile);
-		
-		// run PDFWorker
-		pdf_worker.run();
-
-		if(pdf_worker.hasError()){
-			// raise error, write to logger
-			logger.error(pdf_worker.getError().getMessage());
-			tempfile.delete();
-			return;
-		}
-		else{ // move the completed file to the cache directory
-			boolean success = tempfile.renameTo(pdfcache.getCacheFile(filename));
-			if(!success){
-				// TODO raise error
-			}
-		}
-		
-	}
-	
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/servlet/PDFRequest.java	Thu Dec 16 21:19:11 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;
+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(FileOps.CLASS_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/PDFStreamWorker.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-package digilib.servlet;
-
-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;
-
-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 PDFJobDescription job_info = null;
-
-	protected DigilibJobCenter<DocuImage> imageJobCenter = null;
-
-	/**
-	 * @param dlConfig
-	 * @param outputfile
-	 * @param job_info
-	 */
-	public PDFStreamWorker(DigilibConfiguration dlConfig, OutputStream outputfile,
-			PDFJobDescription 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.setFrom(job_info, job_info.dlConfig);
-			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;
-	}
-
-}
--- a/servlet/src/digilib/servlet/PDFTitlePage.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-package digilib.servlet;
-
-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.DocuDirCache;
-
-/** A class for the generation of title pages for the generated pdf documents.
- * 
- * 
- */
-public class PDFTitlePage {
-	
-	private PDFJobDescription 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(PDFJobDescription 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."+PDFMaker.version;
-	}
-	
-}
--- a/servlet/src/digilib/servlet/Parameter.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +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) {
-		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;
-	}
-
-}
--- a/servlet/src/digilib/servlet/ParameterMap.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,323 +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;
-import java.util.Map.Entry;
-
-/** 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 cloneFrom(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 setFrom(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;
-	}
-}
--- a/servlet/src/digilib/servlet/RequestHandler.java	Thu Dec 16 17:02:34 2010 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-package digilib.servlet;
-
-import javax.servlet.ServletConfig;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.apache.log4j.Logger;
-
-import digilib.image.ImageOpException;
-
-public abstract class RequestHandler extends HttpServlet {
-
-	/** logger for accounting requests */
-	protected static Logger accountlog = Logger.getLogger("account.request");
-
-	/** gengeral logger for this class */
-	protected static Logger logger = Logger.getLogger("digilib.servlet");
-
-	/** logger for authentication related */
-	protected static Logger authlog = Logger.getLogger("digilib.auth");
-
-	
-	public void init(ServletConfig config) throws ServletException{
-		try {
-			super.init(config);
-		} catch (ServletException e) {
-			e.printStackTrace();
-			logger.error(e.getMessage());
-		}
-
-		
-		
-	}
-	
-	
-	public void doGet(HttpServletRequest request, HttpServletResponse response){
-		accountlog.info("GET from " + request.getRemoteAddr());
-		
-		try {
-			this.processRequest(request, response);
-		} catch (ServletException e) {
-			e.printStackTrace();
-			logger.error(e.getMessage());
-		} catch (ImageOpException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
-
-
-	public void doPost(HttpServletRequest request, HttpServletResponse response){
-		accountlog.info("POST from " + request.getRemoteAddr());
-
-		try {
-			this.processRequest(request, response);
-		} catch (ServletException e) {
-			e.printStackTrace();
-			logger.error(e.getMessage());
-		} catch (ImageOpException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
-	}
-	
-	/**
-	 * processRequest 
-	 * 
-	 * evaluate request (,generate content), send content to user
-	 * @throws ServletException 
-	 * @throws ImageOpException 
-	 * 
-	 * */
-	abstract public void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, ImageOpException;
-	
-	
-	/** send the requested content to the response */
-	// abstract public void sendFile(File f, HttpServletResponse response, String filename);
-	
-	
-}
--- a/servlet/src/digilib/servlet/Scaler.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/Scaler.java	Thu Dec 16 21:19:11 2010 +0100
@@ -9,23 +9,26 @@
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.log4j.Logger;
+
 import digilib.auth.AuthOpException;
 import digilib.auth.AuthOps;
 import digilib.image.DocuImage;
-import digilib.image.ImageOpException;
+import digilib.image.ImageJobDescription;
+import digilib.image.ImageWorker;
 import digilib.io.DocuDirCache;
 import digilib.io.DocuDirectory;
 import digilib.io.DocuDirent;
 import digilib.io.FileOps;
 import digilib.io.ImageFile;
-
-// TODO digilibError is not used anymore and may need to get reintegrated
+import digilib.util.DigilibJobCenter;
 
 @SuppressWarnings("serial")
-public class Scaler extends RequestHandler {
+public class Scaler extends HttpServlet {
 
     /** digilib servlet version (for all components) */
     public static final String dlVersion = "1.9.0a";
@@ -42,6 +45,15 @@
     /** error code for image operation error */
     public static final int ERROR_IMAGE = 3;
 
+    /** logger for accounting requests */
+    protected static Logger accountlog = Logger.getLogger("account.request");
+
+    /** gengeral logger for this class */
+    protected static Logger logger = Logger.getLogger("digilib.servlet");
+
+    /** logger for authentication related */
+    protected static Logger authlog = Logger.getLogger("digilib.auth");
+
     /** DocuDirCache instance */
     DocuDirCache dirCache;
 
@@ -115,7 +127,7 @@
         sendFileAllowed = dlConfig.getAsBoolean("sendfile-allowed");
     }
 
-    /** Returns modification time relevant to the request.
+    /** Returns modification time relevant to the request for caching.
      * 
      * @see javax.servlet.http.HttpServlet#getLastModified(javax.servlet.http.HttpServletRequest)
      */
@@ -136,13 +148,35 @@
         return mtime;
     }
 
+    /* (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);
+    }
+    
+
+    /** Service this request using the response.
+     * @param request
+     * @param response
+     * @throws ServletException 
+     */
     public void processRequest(HttpServletRequest request,
-            HttpServletResponse response) throws ServletException,
-            ImageOpException {
+            HttpServletResponse response) throws ServletException {
 
         if (dlConfig == null) {
-            throw new ServletException("ERROR: No Configuration!");
+            logger.error("ERROR: No Configuration!");
+            throw new ServletException("NO VALID digilib CONFIGURATION!");
         }
 
         accountlog.debug("request: " + request.getQueryString());
@@ -152,9 +186,8 @@
         // parse request
         DigilibRequest dlRequest = new DigilibRequest(request);
         // extract the job information
-        ImageJobDescription jobTicket = ImageJobDescription.setFrom(dlRequest, dlConfig);
+        ImageJobDescription jobTicket = ImageJobDescription.getInstance(dlRequest, dlConfig);
 
-        ImageWorker job = null;
         try {
         	/*
         	 *  check if we can fast-track without scaling
@@ -184,7 +217,7 @@
                     mt = "application/octet-stream";
                 }
                 logger.debug("Sending RAW File as is.");
-                ServletOps.sendFile(fileToLoad.getFile(), mt, response);
+                ServletOps.sendFile(fileToLoad.getFile(), mt, null, response);
                 logger.info("Done in " + (System.currentTimeMillis() - startTime) + "ms");
                 return;
             }
@@ -192,7 +225,7 @@
             // 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, response);
+                ServletOps.sendFile(fileToLoad.getFile(), null, null, response);
                 logger.info("Done in " + (System.currentTimeMillis() - startTime) + "ms");
                 return;
             }
@@ -204,7 +237,7 @@
                 return;
             }
             // create job
-            job = new ImageWorker(dlConfig, jobTicket);
+            ImageWorker job = new ImageWorker(dlConfig, jobTicket);
             // submit job
             Future<DocuImage> jobResult = imageJobCenter.submit(job);
             // wait for result
@@ -216,15 +249,17 @@
 
         } catch (IOException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
-            // response.sendError(1);
+            digilibError(dlRequest.hasOption("errtxt"), dlRequest.hasOption("errimg"), dlRequest.hasOption("errcode"), ERROR_FILE, null, response);
         } catch (AuthOpException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
-            // response.sendError(1);
+            digilibError(dlRequest.hasOption("errtxt"), dlRequest.hasOption("errimg"), dlRequest.hasOption("errcode"), ERROR_AUTH, null, response);
         } catch (InterruptedException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
         } catch (ExecutionException e) {
             logger.error(e.getClass() + ": " + e.getMessage());
-            logger.error("caused by: " + e.getCause().getMessage());
+            String causeMsg = e.getCause().getMessage();
+            logger.error("caused by: " + causeMsg);
+            digilibError(dlRequest.hasOption("errtxt"), dlRequest.hasOption("errimg"), dlRequest.hasOption("errcode"), ERROR_IMAGE, causeMsg, response);
         }
 
     }
@@ -232,35 +267,42 @@
     /**
      * Sends an error to the client as text or image.
      * 
-     * @param asHTML
+     * @param asText
      * @param type
      * @param msg
      * @param response
      */
-    public void digilibError(boolean asHTML, int type, String msg,
+    public void digilibError(boolean asText, boolean asImage, boolean asCode, int type, String msg,
             HttpServletResponse response) {
         try {
             File img = null;
+            int status = 0;
             if (type == ERROR_AUTH) {
                 if (msg == null) {
                     msg = "ERROR: Unauthorized access!";
                 }
                 img = denyImgFile;
+                status = HttpServletResponse.SC_FORBIDDEN;
             } else if (type == 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 (asHTML && (img != null)) {
+            if (asText) {
                 ServletOps.htmlMessage(msg, response);
-            } else {
-                ServletOps.sendFile(img, null, response);
+            } else if (asCode) {
+                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);
--- a/servlet/src/digilib/servlet/ServletOps.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/ServletOps.java	Thu Dec 16 21:19:11 2010 +0100
@@ -179,18 +179,20 @@
      * 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 f
-     *            Image file to be sent.
+     * @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, HttpServletResponse response)
+    public static void sendFile(File f, String mt, String name, HttpServletResponse response)
             throws FileOpException, IOException {
         logger.debug("sendRawFile(" + mt + ", " + f + ")");
         if (mt == null) {
@@ -202,12 +204,16 @@
         }
         response.setContentType(mt);
         // open file
-        if (mt.equals("application/octet-stream")) {
-            response.addHeader("Content-Disposition", "attachment; filename=\""
-                    + f.getName() + "\"");
+        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) {
@@ -215,8 +221,8 @@
             outStream.write(dataBuffer, 0, len);
             outStream.flush();
         }
+        response.flushBuffer();
         inFile.close();
-        response.flushBuffer();
     }
 
     /**
--- a/servlet/src/digilib/servlet/Texter.java	Thu Dec 16 17:02:34 2010 +0100
+++ b/servlet/src/digilib/servlet/Texter.java	Thu Dec 16 21:19:11 2010 +0100
@@ -107,10 +107,6 @@
 	 */
 	protected void doGet(HttpServletRequest request,
 			HttpServletResponse response) throws ServletException, IOException {
-		// create new request with defaults
-		DigilibRequest dlReq = new DigilibRequest(request);
-		// add DigilibRequest to ServletRequest
-		request.setAttribute("digilib.servlet.request", dlReq);
 		// do the processing
 		processRequest(request, response);
 	}
@@ -123,10 +119,6 @@
 	 */
 	protected void doPost(HttpServletRequest request,
 			HttpServletResponse response) throws ServletException, IOException {
-		// create new request with defaults
-		DigilibRequest dlReq = new DigilibRequest(request);
-		// add DigilibRequest to ServletRequest
-		request.setAttribute("digilib.servlet.request", dlReq);
 		// do the processing
 		processRequest(request, response);
 	}
@@ -137,7 +129,8 @@
 		/*
 		 * request parameters
 		 */
-		DigilibRequest dlRequest = (DigilibRequest) request.getAttribute("digilib.servlet.request");
+        // create new request with defaults
+        DigilibRequest dlRequest = new DigilibRequest(request);
 		try {
 			
 			/*
@@ -145,11 +138,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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/servlet/src/digilib/util/DigilibJobCenter.java	Thu Dec 16 21:19:11 2010 +0100
@@ -0,0 +1,110 @@
+/** 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;
+    
+    /**
+     * @param maxThreads
+     * @param maxQueueLength
+     */
+    public DigilibJobCenter(int maxThreads, int maxQueueLen, boolean prestart) {
+        super();
+        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("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("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("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	Thu Dec 16 21:19:11 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	Thu Dec 16 21:19:11 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	Thu Dec 16 21:19:11 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	Thu Dec 16 21:19:11 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;
+	}
+}