← Back to team overview

slub.team team mailing list archive

lp:~zeutschel/goobi-production/web-service-interface-to-create-processes into lp:goobi-production

 

Matthias Ronge has proposed merging lp:~zeutschel/goobi-production/web-service-interface-to-create-processes into lp:goobi-production.

Requested reviews:
  Saxon State Library Team (slub.team)

For more details, see:
https://code.launchpad.net/~zeutschel/goobi-production/web-service-interface-to-create-processes/+merge/112564

Provides Active MQ web service interface with two service processors (one to create new processes from process templates, and one to close steps) and provides a JSTL function tag library with JSP files to print out some related configuration information.

Note: It’s necessary to add the new directory “/ws” to the “deployment assembly” in order to use the new JSPs.
-- 
https://code.launchpad.net/~zeutschel/goobi-production/web-service-interface-to-create-processes/+merge/112564
Your team Saxon State Library Team is requested to review the proposed merge of lp:~zeutschel/goobi-production/web-service-interface-to-create-processes into lp:goobi-production.
=== modified file 'WEB-INF/web.xml'
--- WEB-INF/web.xml	2012-04-20 06:57:46 +0000
+++ WEB-INF/web.xml	2012-06-28 13:28:37 +0000
@@ -215,6 +215,11 @@
 		<listener-class>org.goobi.production.ImageIOInitializer</listener-class>
     </listener>
 
+	<!-- Listener to run ActiveMQ services -->
+	<listener>
+		<listener-class>org.goobi.webservice.ActiveMQDirector</listener-class>
+	</listener>
+
 	<!--
 		xml-Rpc-Server starten <listener> <listener-class>
 		de.sub.goobi.XmlRpc.Listener </listener-class> </listener>

=== added file 'WEB-INF/webserviceTaglib.tld'
--- WEB-INF/webserviceTaglib.tld	1970-01-01 00:00:00 +0000
+++ WEB-INF/webserviceTaglib.tld	2012-06-28 13:28:37 +0000
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  -->
+
+<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee";
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd";>
+	<tlib-version>1.0</tlib-version>
+	<short-name>ws</short-name>
+	<uri>http://taglib.production.goobi.org/webService</uri>
+	<function>
+		<name>getAdditionalFieldsToBeCompletedManually</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getAdditionalFieldsToBeCompletedManually()</function-signature>
+	</function>
+	<function>
+		<name>getAllFields</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getAllFields()</function-signature>
+	</function>
+	<function>
+		<name>getCatalogues</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getCatalogues()</function-signature>
+	</function>
+	<function>
+		<name>getCollections</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getCollections()</function-signature>
+	</function>
+	<function>
+		<name>getAllDoctypes</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getAllDoctypes()</function-signature>
+	</function>
+	<function>
+		<name>getProjects</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getProjects()</function-signature>
+	</function>
+	<function>
+		<name>getProjectsAndTemplates</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getProjectsAndTemplates()</function-signature>
+	</function>
+	<function>
+		<name>getSearchFields</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getSearchFields()</function-signature>
+	</function>
+	<function>
+		<name>getTemplates</name>
+		<function-class>org.goobi.webservice.taglib.WebServiceTaglib</function-class>
+		<function-signature>java.util.String getTemplates()</function-signature>
+	</function>
+</taglib>

=== modified file 'config/GoobiConfig.properties'
--- config/GoobiConfig.properties	2012-05-02 12:02:23 +0000
+++ config/GoobiConfig.properties	2012-06-28 13:28:37 +0000
@@ -179,6 +179,41 @@
 # Password encryption SHA or MD5
 ldap_encryption=SHA
 
+# -----------------------------------
+# ActiveMQ web services
+# -----------------------------------
+
+# If you want to use Goobi's ActiveMQ web servic interface, set the host here 
+#activeMQ.hostURL=tcp://localhost:61616
+
+# You can provide a topic that Goobi reports results and status messages to
+#activeMQ.results.topic=GoobiProduction.ResultMessages.Topic
+
+# By default, Goobi instructs the server to keep status messages for a
+# equivalent of 7 days. You can change this value in (milliseconds to) meet
+# your needs. 0 will disable the deletion of messages completely. (However,
+# the messages will only available on the Active MQ server if your
+# TopicSubscriber is online with the Active MQ server before the message is
+# sent. You migth therefore consider to configure the timeToLive for offline
+# usage within the Active MQ server’s activemq.xml file by adding a
+#
+#   <policyEntry topic="GoobiProduction.ResultMessages.Topic">
+#       <subscriptionRecoveryPolicy>
+#           <timedSubscriptionRecoveryPolicy recoverDuration="604800000" />
+#       </subscriptionRecoveryPolicy>
+#   </policyEntry>
+#	
+# block inside the <policyEntries>-Element. “recoverDuration” has to be given
+# in milliseconds here, too.)
+#activeMQ.results.timeToLive=604800000
+
+# You can provide a queue from which messages are read to create new processes
+#activeMQ.createNewProcess.queue=GoobiProduction.CreateNewProcesses.Queue
+
+# You can provide a queue from which messages are read to finalise steps
+#activeMQ.finaliseStep.queue=GoobiProduction.FinaliseStep.Queue
+
+
 ##################################
 # DO NOT CHANGE THE OPTIONS BELOW!
 ##################################

=== added file 'lib/activemq-core-5.5.1.jar'
Binary files lib/activemq-core-5.5.1.jar	1970-01-01 00:00:00 +0000 and lib/activemq-core-5.5.1.jar	2012-06-28 13:28:37 +0000 differ
=== added file 'lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar'
Binary files lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar	1970-01-01 00:00:00 +0000 and lib/geronimo-j2ee-management_1.1_spec-1.0.1.jar	2012-06-28 13:28:37 +0000 differ
=== added file 'lib/geronimo-jms_1.1_spec-1.1.1.jar'
Binary files lib/geronimo-jms_1.1_spec-1.1.1.jar	1970-01-01 00:00:00 +0000 and lib/geronimo-jms_1.1_spec-1.1.1.jar	2012-06-28 13:28:37 +0000 differ
=== added file 'lib/json-simple-1.1.1.jar'
Binary files lib/json-simple-1.1.1.jar	1970-01-01 00:00:00 +0000 and lib/json-simple-1.1.1.jar	2012-06-28 13:28:37 +0000 differ
=== modified file 'src-dubious/dubious/sub/goobi/helper/Page.java'
--- src-dubious/dubious/sub/goobi/helper/Page.java	2012-03-10 13:34:49 +0000
+++ src-dubious/dubious/sub/goobi/helper/Page.java	2012-06-28 13:28:37 +0000
@@ -48,7 +48,7 @@
 	public Page(Criteria criteria, int page) {
 		this.page = page;
 		LoginForm login = (LoginForm) Helper.getManagedBeanValue("#{LoginForm}");
-		if (login.getMyBenutzer() == null)
+		if (login == null || login.getMyBenutzer() == null)
 			this.pageSize = 10;
 		else
 			this.pageSize = login.getMyBenutzer().getTabellengroesse().intValue();

=== modified file 'src/de/sub/goobi/config/ConfigMain.java'
--- src/de/sub/goobi/config/ConfigMain.java	2012-05-30 08:24:19 +0000
+++ src/de/sub/goobi/config/ConfigMain.java	2012-06-28 13:28:37 +0000
@@ -119,9 +119,9 @@
 	
 
 	/**
-	 * Ermitteln eines bestimmten Paramters der Konfiguration
+	 * Ermitteln eines bestimmten Parameters der Konfiguration
 	 * 
-	 * @return Paramter als String
+	 * @return Parameter als String
 	 */
 	public static String getParameter(String inParameter) {
 		try {
@@ -135,10 +135,10 @@
 	
 	//TODO: Remove this methods, they are provided by Commons Configuration
 	/**
-	 * Ermitteln eines bestimmten Paramters der Konfiguration mit Angabe eines
+	 * Ermitteln eines bestimmten Parameters der Konfiguration mit Angabe eines
 	 * Default-Wertes
 	 * 
-	 * @return Paramter als String
+	 * @return Parameter als String
 	 */
 	public static String getParameter(String inParameter, String inDefaultIfNull) {
 		try {
@@ -152,18 +152,18 @@
 	
 
 	/**
-	 * Ermitteln eines boolean-Paramters der Konfiguration, default if missing: false
+	 * Ermitteln eines boolean-Parameters der Konfiguration, default if missing: false
 	 * 
-	 * @return Paramter als String
+	 * @return Parameter als String
 	 */
 	public static boolean getBooleanParameter(String inParameter) {
 		return getBooleanParameter(inParameter, false);
 	}
 
 	/**
-	 * Ermitteln eines boolean-Paramters der Konfiguration
+	 * Ermitteln eines boolean-Parameters der Konfiguration
 	 * 
-	 * @return Paramter als String
+	 * @return Parameter als String
 	 */
 	public static boolean getBooleanParameter(String inParameter, boolean inDefault) {
 		return config.getBoolean(inParameter, inDefault);
@@ -172,17 +172,12 @@
 	
 
 	/**
-	 * Ermitteln eines long-Paramters der Konfiguration
+	 * Ermitteln eines long-Parameters der Konfiguration
 	 * 
-	 * @return Paramter als Long
+	 * @return Parameter als Long
 	 */
-	public static long getLongParameter(String inParameter, int inDefault) {
-		try {
-			return config.getLong(inParameter, inDefault);
-		} catch (RuntimeException e) {
-			myLogger.error(e);
-			return 0;
-		}
+	public static long getLongParameter(String inParameter, long inDefault) {
+		return config.getLong(inParameter, inDefault);
 	}
 
 	
@@ -190,7 +185,7 @@
 	/**
 	 * Request int-parameter from Configuration
 	 * 
-	 * @return Paramter as Int
+	 * @return Parameter as Int
 	 */
 	public static int getIntParameter(String inParameter) {
 		return getIntParameter(inParameter, 0);
@@ -199,7 +194,7 @@
 	/**
 	 * Request int-parameter from Configuration with default-value
 	 * 
-	 * @return Paramter as Int
+	 * @return Parameter as Int
 	 */
 	public static int getIntParameter(String inParameter, int inDefault) {
 		try {

=== added file 'src/de/sub/goobi/config/DigitalCollections.java'
--- src/de/sub/goobi/config/DigitalCollections.java	1970-01-01 00:00:00 +0000
+++ src/de/sub/goobi/config/DigitalCollections.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,55 @@
+package de.sub.goobi.config;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.JDOMException;
+import org.jdom.input.SAXBuilder;
+
+import de.sub.goobi.beans.Prozess;
+
+public class DigitalCollections {
+
+	@SuppressWarnings("unchecked")
+	public static List<String> possibleDigitalCollectionsForProcess(
+			Prozess process) throws JDOMException, IOException {
+		
+		List<String> result = new ArrayList<String>();
+		String filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "digitalCollections.xml";
+		if (!(new File(filename).exists())) {
+			throw new FileNotFoundException("File not found: " + filename);
+		}
+		
+		/* Datei einlesen und Root ermitteln */
+		SAXBuilder builder = new SAXBuilder();
+		Document doc = builder.build(new File(filename));
+		Element root = doc.getRootElement();
+		/* alle Projekte durchlaufen */
+		List<Element> projekte = root.getChildren();
+		for (Iterator<Element> iter = projekte.iterator(); iter.hasNext();) {
+			Element projekt = (Element) iter.next();
+			List<Element> projektnamen = projekt.getChildren("name");
+			for (Iterator<Element> iterator = projektnamen.iterator(); iterator.hasNext();) {
+				Element projektname = (Element) iterator.next();
+
+				/*
+				 * wenn der Projektname aufgeführt wird, dann alle Digitalen Collectionen in die Liste
+				 */
+				if (projektname.getText().equalsIgnoreCase(process.getProjekt().getTitel())) {
+					List<Element> myCols = projekt.getChildren("DigitalCollection");
+					for (Iterator<Element> it2 = myCols.iterator(); it2.hasNext();) {
+						Element col = (Element) it2.next();
+						result.add(col.getText());
+					}
+				}
+			}
+		}
+		return result;
+	}
+}

=== modified file 'src/de/sub/goobi/forms/AktuelleSchritteForm.java'
--- src/de/sub/goobi/forms/AktuelleSchritteForm.java	2012-04-24 13:22:01 +0000
+++ src/de/sub/goobi/forms/AktuelleSchritteForm.java	2012-06-28 13:28:37 +0000
@@ -109,7 +109,7 @@
 		 * --------------------- Vorgangsdatum generell anzeigen? -------------------
 		 */
 		LoginForm login = (LoginForm) Helper.getManagedBeanValue("#{LoginForm}");
-		if (login.getMyBenutzer() != null)
+		if (login != null && login.getMyBenutzer() != null)
 			anzeigeAnpassen.put("processDate", login.getMyBenutzer().isConfVorgangsdatumAnzeigen());
 		else
 			anzeigeAnpassen.put("processDate", false);

=== modified file 'src/de/sub/goobi/forms/ProzesskopieForm.java'
--- src/de/sub/goobi/forms/ProzesskopieForm.java	2012-05-09 15:19:19 +0000
+++ src/de/sub/goobi/forms/ProzesskopieForm.java	2012-06-28 13:28:37 +0000
@@ -23,6 +23,7 @@
 package de.sub.goobi.forms;
 
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.sql.SQLException;
 import java.util.ArrayList;
@@ -84,6 +85,7 @@
 import de.sub.goobi.config.ConfigOpac;
 import de.sub.goobi.config.ConfigOpacDoctype;
 import de.sub.goobi.config.ConfigProjects;
+import de.sub.goobi.config.DigitalCollections;
 import de.sub.goobi.helper.BeanHelper;
 import de.sub.goobi.helper.Helper;
 import de.sub.goobi.helper.Messages;
@@ -932,46 +934,22 @@
 		return possibleDigitalCollection;
 	}
 
-	@SuppressWarnings("unchecked")
 	private void initializePossibleDigitalCollections() {
 		possibleDigitalCollection = new ArrayList<String>();
-		String filename = help.getGoobiConfigDirectory() + "digitalCollections.xml";
-		if (!(new File(filename).exists())) {
-			Helper.setFehlerMeldung("File not found: ", filename);
-			return;
-		}
-
-		try {
-			/* Datei einlesen und Root ermitteln */
-			SAXBuilder builder = new SAXBuilder();
-			Document doc = builder.build(new File(filename));
-			Element root = doc.getRootElement();
-			/* alle Projekte durchlaufen */
-			List<Element> projekte = root.getChildren();
-			for (Iterator<Element> iter = projekte.iterator(); iter.hasNext();) {
-				Element projekt = (Element) iter.next();
-				List<Element> projektnamen = projekt.getChildren("name");
-				for (Iterator<Element> iterator = projektnamen.iterator(); iterator.hasNext();) {
-					Element projektname = (Element) iterator.next();
-
-					/*
-					 * wenn der Projektname aufgeführt wird, dann alle Digitalen Collectionen in die Liste
-					 */
-					if (projektname.getText().equalsIgnoreCase(prozessKopie.getProjekt().getTitel())) {
-						List<Element> myCols = projekt.getChildren("DigitalCollection");
-						for (Iterator<Element> it2 = myCols.iterator(); it2.hasNext();) {
-							Element col = (Element) it2.next();
-							possibleDigitalCollection.add(col.getText());
-						}
-					}
-				}
-			}
+		try{
+			possibleDigitalCollection = DigitalCollections.possibleDigitalCollectionsForProcess(prozessKopie);
+		} catch (FileNotFoundException e1) {
+			myLogger.error("File not found: ", e1);
+			Helper.setFehlerMeldung("File not found: ", e1);
 		} catch (JDOMException e1) {
 			myLogger.error("error while parsing digital collections", e1);
 			Helper.setFehlerMeldung("Error while parsing digital collections", e1);
 		} catch (IOException e1) {
 			myLogger.error("error while parsing digital collections", e1);
 			Helper.setFehlerMeldung("Error while parsing digital collections", e1);
+		} finally {
+			if(possibleDigitalCollection == null)
+				possibleDigitalCollection = new ArrayList<String>();
 		}
 
 		// if only one collection is possible take it directly

=== modified file 'src/de/sub/goobi/helper/Helper.java'
--- src/de/sub/goobi/helper/Helper.java	2012-05-30 08:24:19 +0000
+++ src/de/sub/goobi/helper/Helper.java	2012-06-28 13:28:37 +0000
@@ -42,13 +42,16 @@
 import javax.faces.context.FacesContext;
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.log4j.Level;
 import org.apache.log4j.Logger;
+import org.goobi.webservice.WebServiceResult;
 import org.hibernate.Session;
 import org.jdom.Element;
 
 import de.sub.goobi.beans.Benutzer;
 import de.sub.goobi.config.ConfigMain;
 import de.sub.goobi.forms.LoginForm;
+import de.sub.goobi.helper.enums.ReportLevel;
 import de.sub.goobi.persistence.HibernateUtilOld;
 
 //TODO: Check if more method can be made static
@@ -59,6 +62,7 @@
 
 	private String myMetadatenVerzeichnis;
 	private String myConfigVerzeichnis;
+	public static Map<String, String> activeMQReporting = null;
 
 	/**
 	 * Ermitteln eines bestimmten Paramters des Requests
@@ -146,43 +150,59 @@
 	}
 
 	/**
-	 * Dem aktuellen Formular eine Fehlermeldung für ein bestimmtes Control übergeben
+	 * The method setMeldung() adds an error message for a given control to the
+	 * current form.
+	 * 
+	 * @param control
+	 *            Name of control that caused the error or “null” if the error
+	 *            was not caused by a control
+	 * @param messageKey
+	 *            The key of the error message. The method will try to resolve
+	 *            the key against its messages file.
+	 * @param descriptionKey
+	 *            The description key of the error. The method will try to
+	 *            resolve the key against its messages file.
+	 * @param infoOnly
+	 *            Set to false for error messages. Set to true for info
+	 *            messages.
 	 */
-	private static void setMeldung(String control, String meldung, String beschreibung, boolean nurInfo) {
+	private static void setMeldung(String control, String messageKey,
+			String descriptionKey, boolean infoOnly) {
 		FacesContext context = FacesContext.getCurrentInstance();
 
-		// Never forget: Strings are immutable
-		meldung = meldung.replaceAll("<", "&lt;");
-		meldung = meldung.replaceAll(">", "&gt;");
-		beschreibung = beschreibung.replaceAll("<", "&lt;");
-		beschreibung = beschreibung.replaceAll(">", "&gt;");
-		/* wenn kein Kontext da ist, dann die Meldungen in Log */
-		if (context == null) {
-			if (nurInfo) {
-				myLogger.info(meldung + " " + beschreibung);
-			} else {
-				myLogger.error(meldung + " " + beschreibung);
-			}
-			return;
-		}
-
-		String msg = "";
-		String beschr = "";
-		try {
-			msg = Messages.getString(meldung);
-		} catch (RuntimeException e) {
-			msg = meldung;
-		}
-		try {
-			beschr = Messages.getString(beschreibung);
-		} catch (RuntimeException e) {
-			beschr = beschreibung;
-		}
-
-		if (nurInfo) {
-			context.addMessage(control, new FacesMessage(FacesMessage.SEVERITY_INFO, msg, beschr));
-		} else {
-			context.addMessage(control, new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, beschr));
+		String message;
+		String description;
+		try {
+			message = Messages.getString(messageKey);
+		} catch (RuntimeException e) {
+			message = messageKey;
+		}
+		try {
+			description = Messages.getString(descriptionKey);
+		} catch (RuntimeException e) {
+			description = descriptionKey;
+		}
+
+		message = message.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+		description = description.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
+
+		String compoundMessage = message.replaceFirst(":\\s*$", "") + ": "
+				+ description;
+
+		/* If the Active MQ service is at work, report errors there, too. */
+		if (activeMQReporting != null) {
+			new WebServiceResult(activeMQReporting.get("queueName"),
+					activeMQReporting.get("id"), infoOnly ? ReportLevel.INFO
+							: ReportLevel.ERROR, compoundMessage).send();
+		}
+
+		if (context != null) {
+			context.addMessage(
+					control,
+					new FacesMessage(infoOnly ? FacesMessage.SEVERITY_INFO
+							: FacesMessage.SEVERITY_ERROR, message, description));
+		} else { // wenn kein Kontext da ist, dann die Meldungen in Log
+			myLogger.log(infoOnly ? Level.INFO : Level.ERROR, compoundMessage);
 		}
 	}
 

=== modified file 'src/de/sub/goobi/helper/UghHelper.java'
--- src/de/sub/goobi/helper/UghHelper.java	2012-02-22 07:43:02 +0000
+++ src/de/sub/goobi/helper/UghHelper.java	2012-06-28 13:28:37 +0000
@@ -42,6 +42,7 @@
 import ugh.exceptions.DocStructHasNoTypeException;
 import ugh.exceptions.MetadataTypeNotAllowedException;
 import de.sub.goobi.beans.Prozess;
+import de.sub.goobi.config.ConfigMain;
 import de.sub.goobi.helper.exceptions.UghHelperException;
 
 //TODO: Try to move this methods to UGH (ugh.util.UGHUtils would be a better place)
@@ -206,8 +207,13 @@
 	public String convertLanguage(String inLanguage) {
 		/* Pfad zur Datei ermitteln */
 		FacesContext context = FacesContext.getCurrentInstance();
-		HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
-		String filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opaclanguages.txt";
+		String filename;
+		if (context != null) {
+			HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
+			filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opaclanguages.txt";
+		} else {
+			filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "opaclanguages.txt";
+		}
 		/* Datei zeilenweise durchlaufen und die Sprache vergleichen */
 		try {
 			FileInputStream fis = new FileInputStream(filename);
@@ -233,8 +239,13 @@
 		String temp = inString;
 		/* Pfad zur Datei ermitteln */
 		FacesContext context = FacesContext.getCurrentInstance();
-		HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
-		String filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opacumlaut.txt";
+		String filename;
+		if (context != null) {
+			HttpSession session = (HttpSession) context.getExternalContext().getSession(false);
+			filename = session.getServletContext().getRealPath("/WEB-INF") + File.separator + "classes" + File.separator + "opacumlaut.txt";
+		} else {
+			filename = ConfigMain.getParameter("KonfigurationVerzeichnis") + "opacumlaut.txt";
+		}
 
 		/* Datei zeilenweise durchlaufen und die Sprache vergleichen */
 		try {

=== modified file 'src/de/sub/goobi/helper/WebDav.java'
--- src/de/sub/goobi/helper/WebDav.java	2012-04-24 12:00:57 +0000
+++ src/de/sub/goobi/helper/WebDav.java	2012-06-28 13:28:37 +0000
@@ -108,7 +108,8 @@
 
 	public void UploadFromHome(Prozess myProzess) {
 		Benutzer aktuellerBenutzer = (Benutzer) Helper.getManagedBeanValue("#{LoginForm.myBenutzer}");
-		UploadFromHome(aktuellerBenutzer, myProzess);
+		if (aktuellerBenutzer != null)
+			UploadFromHome(aktuellerBenutzer, myProzess);
 	}
 
 	public void UploadFromHome(Benutzer inBenutzer, Prozess myProzess) {

=== added file 'src/de/sub/goobi/helper/enums/ReportLevel.java'
--- src/de/sub/goobi/helper/enums/ReportLevel.java	1970-01-01 00:00:00 +0000
+++ src/de/sub/goobi/helper/enums/ReportLevel.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,36 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package de.sub.goobi.helper.enums;
+
+/**
+ * These are the possible states for output to “activeMQ.results.topic”.
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public enum ReportLevel {
+	FATAL, ERROR, WARN, INFO, SUCCESS, DEBUG, VERBOSE, LUDICROUS;
+
+	public String toLowerCase() {
+		return name().toLowerCase();
+	}
+}

=== modified file 'src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java'
--- src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java	2012-02-22 07:43:02 +0000
+++ src/org/goobi/production/flow/statistics/hibernate/FilterHelper.java	2012-06-28 13:28:37 +0000
@@ -94,7 +94,7 @@
 		/* identify current user */
 		LoginForm login = (LoginForm) Helper
 				.getManagedBeanValue("#{LoginForm}");
-		if (login.getMyBenutzer() == null)
+		if (login == null || login.getMyBenutzer() == null)
 			return;
 		/* init id-list, preset with item 0 */
 		List<Integer> idList = new ArrayList<Integer>();

=== added directory 'src/org/goobi/webservice'
=== added file 'src/org/goobi/webservice/ActiveMQDirector.java'
--- src/org/goobi/webservice/ActiveMQDirector.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/ActiveMQDirector.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,231 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice;
+
+import javax.jms.Connection;
+import javax.jms.DeliveryMode;
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.activemq.ActiveMQConnectionFactory;
+import org.apache.log4j.Logger;
+import org.goobi.webservice.processors.CreateNewProcessProcessor;
+import org.goobi.webservice.processors.FinaliseStepProcessor;
+
+import de.sub.goobi.config.ConfigMain;
+
+/**
+ * The class ActiveMQDirector is the head of all Active MQ processors. It
+ * implements the ServletContextListener interface and is − if configured in
+ * web.xml − called automatically upon server starup. Its job is to connect to
+ * the Active MQ server and register the listeners configured.
+ * 
+ * The ActiveMQDirector should ALWAYS be declared in web.xml. The Active MQ
+ * services are intended to be run in case that “activeMQ.hostURL” is configured
+ * in the GoobiConfig.properties file. To disable the service, the entry there
+ * should be emptied or commented out. Otherwise, a valid configuration would
+ * not start working and an administrator will hardly have a chance to find out
+ * why without inspecting the source code.
+ * 
+ * The class ActiveMQDirector also provides a basic ExceptionListener
+ * implementation as required for the connection.
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public class ActiveMQDirector implements ServletContextListener,
+		ExceptionListener {
+	private static final Logger logger = Logger
+			.getLogger(ActiveMQDirector.class);
+
+	// *** CONFIGURATION ***
+	// When implementing new Services, add them to this list:
+
+	protected static ActiveMQProcessor[] services;
+	static{
+		services = new ActiveMQProcessor[] {
+			new CreateNewProcessProcessor(),
+			new FinaliseStepProcessor()
+		};
+	}
+
+	protected static Connection connection = null;
+	protected static Session session = null;
+	protected static MessageProducer resultsTopic;
+
+	/**
+	 * The method contextInitialized() is called by the web container on startup
+	 * and is used to start up the active MQ connection. All processors from
+	 * services[] are registered.
+	 * 
+	 * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet
+	 *      .ServletContextEvent)
+	 */
+	@Override
+	public void contextInitialized(ServletContextEvent initialisation) {
+		String activeMQHost = ConfigMain.getParameter("activeMQ.hostURL", null);
+		if (activeMQHost != null) {
+			session = connectToServer(activeMQHost);
+			if (session != null) {
+				registerListeners(services);
+				if (ConfigMain.getParameter("activeMQ.results.topic", null) != null) {
+					resultsTopic = setUpReportChannel(ConfigMain.getParameter("activeMQ.results.topic"));
+	}	}	}	}
+
+	/**
+	 * Sets up a connection to an active MQ server. The connection object is
+	 * global because it is needed later to shut down the connection.
+	 * 
+	 * @param server
+	 *            should be “tcp://{host}:{port}” or “vm://localhost” in case
+	 *            that the server is run inside the same virtual machine
+	 * @return the session object or “null” upon error
+	 */
+	protected Session connectToServer(String server) {
+		try {
+			connection = new ActiveMQConnectionFactory(server).createConnection();
+			connection.start();
+			connection.setExceptionListener(this); // → ActiveMQDirector.onException()
+			return connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+		} catch (Exception e) {
+			logger.fatal("Error connecting to ActiveMQ server, giving up.", e);
+		}
+		return null;
+	}
+
+	/**
+	 * This method registers the listeners with the active MQ server.
+	 * 
+	 * If a queue name was configured for a service, a MessageConsumer is set up
+	 * to listen on that queue and, in case of incoming messages, make the
+	 * service process the message. The message checker is saved inside the
+	 * service to be able to shut it down later.
+	 */
+	protected void registerListeners(ActiveMQProcessor[] processors) {
+		for (ActiveMQProcessor processor : processors) {
+			if (processor.getQueueName() != null) {
+				MessageConsumer messageChecker = null;
+				try {
+					Destination queue = session.createQueue(processor.getQueueName());
+					messageChecker = session.createConsumer(queue);
+					messageChecker.setMessageListener(processor);
+					processor.saveChecker(messageChecker);
+				} catch (Exception e) {
+					logger.fatal("Error setting up monitoring for \"" + processor.getQueueName() + "\": Giving up.", e);
+	}	}	}	}
+
+	/**
+	 * This sets up a connection to the topic the results shall be written to.
+	 * The delivery mode is set so “persistent” to allow consumers not online
+	 * with the server in the moment of message submission to read the messages
+	 * later. The log messages are set to be kept on the server for 7 days. This
+	 * value can be overridden from the GoobiConfig.properties parameter
+	 * “activeMQ.results.timeToLive”. The time to live must be specified in
+	 * milliseconds; 0 disables the oblivion. (See also:
+	 * http://docs.oracle.com/javaee/6/api/javax/jms/MessageProducer.html#setTimeToLive%28long%29 )
+	 * 
+	 * @param topic
+	 *            name of the active MQ topic
+	 * @return a MessageProducer object ready for writing or “null” on error
+	 */
+	protected MessageProducer setUpReportChannel(String topic) {
+		MessageProducer result;
+		try {
+			Destination channel = session.createTopic(topic);
+			result = session.createProducer(channel);
+			result.setDeliveryMode(DeliveryMode.PERSISTENT);
+			result.setTimeToLive(ConfigMain.getLongParameter("activeMQ.results.timeToLive", 604800000));
+			return result;
+		} catch (Exception e) {
+			logger.fatal("Error setting up report channel \"" + topic + "\": Giving up.", e);
+		}
+		return null;
+	}
+
+	/**
+	 * This method is referenced from this.connectToServer() − see there.
+	 * 
+	 * @see javax.jms.ExceptionListener#onException(javax.jms.JMSException)
+	 */
+	@Override
+	public void onException(JMSException exce) {
+		logger.error(exce);
+	}
+
+	/**
+	 * Any class that wants to create new Active MQ Messages needs read access
+	 * to the session, since Active MQ messages don’t have a constructor.
+	 * 
+	 * @return the session object
+	 */
+	public static Session getSession() {
+		return session;
+	}
+
+	/**
+	 * Instances of WebServiceResult can be sent by calling their send() method.
+	 * Therefore, they need read access on their topic.
+	 * 
+	 * @return the resultsTopic object
+	 */
+	public static MessageProducer getResultsTopic() {
+		return resultsTopic;
+	}
+
+	/**
+	 * The method contextDestroyed is called by the web container on shutdown.
+	 * It shuts down all listeners, the session and last, the connection.
+	 * 
+	 * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.
+	 *      ServletContextEvent)
+	 */
+	@Override
+	public void contextDestroyed(ServletContextEvent destruction) {
+		// Shut down all watchers on any queues
+		for (ActiveMQProcessor service : services) {
+			MessageConsumer watcher = service.getChecker();
+			if (watcher != null) {
+				try {
+					watcher.close();
+				} catch (JMSException e) {
+					logger.error(e);
+		}	}	}
+
+		// quit session
+		try {
+			session.close();
+		} catch (JMSException e) {
+			logger.error(e);
+		}
+
+		// shut down connection
+		try {
+			connection.close();
+		} catch (JMSException e) {
+			logger.error(e);
+}	}	}

=== added file 'src/org/goobi/webservice/ActiveMQProcessor.java'
--- src/org/goobi/webservice/ActiveMQProcessor.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/ActiveMQProcessor.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,161 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+
+import de.sub.goobi.helper.Helper;
+import de.sub.goobi.helper.enums.ReportLevel;
+
+/**
+ * The class ActiveMQProcessor offers general services, such as making the
+ * incoming messages available as MapMessages and publishing the results. When I
+ * came clear that this code would be necessary for every processor, I thought
+ * an abstract class would be the right place for it. ActiveMQProcessor also
+ * provides a place to save the checker for the ActiveMQDirector, to be able to
+ * shut it down later.
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public abstract class ActiveMQProcessor implements MessageListener {
+
+	protected String queueName; // the queue name will be available here
+	private MessageConsumer checker;
+
+	/**
+	 * Implement the method process() to let your service actually do what you
+	 * want him to do.
+	 * 
+	 * @param ticket
+	 *            A MapMessage which can be processor-specific except that it
+	 *            requires to have a field “id”.
+	 */
+	protected abstract void process(MapMessageObjectReader ticket) throws Exception;
+	
+	/**
+	 * Instantiating the class ActiveMQProcessor always requires to pass the
+	 * name of the queue it should be attached to. That means, your constructor
+	 * should look like this:
+	 * 
+	 * <pre>
+	 * 	  public MyServiceProcessor(){
+	 * 		  super(ConfigMain.getParameter("activeMQ.myService.queue", null));
+	 * 	  }
+	 * </pre>
+	 * 
+	 * If the parameter is not set in GoobiConfig.properties, it will return
+	 * “null” and so prevents it from being set up in ActiveMQDirector.
+	 * 
+	 * @param queueName
+	 *            the queue name, if configured, or “null” to prevent the
+	 *            processor from being connected.
+	 */
+	public ActiveMQProcessor(String queueName) {
+		this.queueName = queueName;
+	}
+
+	/**
+	 * The method onMessage() provides a corset which checks the message being a
+	 * MapMessage, performs a type safe type cast, and extracts the job’s ID to
+	 * be able to send useful results to the results topic, which it does after
+	 * the abstract method process() has finished.
+	 * 
+	 * Since this will be the same for all processors which use MapMessages, I
+	 * extracted the portion into the abstract class.
+	 * 
+	 * @see javax.jms.MessageListener#onMessage(javax.jms.Message)
+	 */
+	@Override
+	public void onMessage(Message arg) {
+		MapMessageObjectReader ticket = null;
+		String ticketID = null;
+
+		try {
+			// Basic check ticket
+			if (arg instanceof MapMessage)
+				ticket = new MapMessageObjectReader((MapMessage) arg);
+			else
+				throw new IllegalArgumentException("Incompatible types.");
+			ticketID = ticket.getMandatoryString("id");
+
+			// turn on logging
+			Map<String,String> loggingConfig = new HashMap<String,String>();
+			loggingConfig.put("queueName", queueName);
+			loggingConfig.put("id", ticketID);
+			Helper.activeMQReporting = loggingConfig;
+			
+			// process ticket
+			process(ticket);
+			
+			// turn off logging again
+			Helper.activeMQReporting = null;
+
+			// if everything ‘s fine, report success
+			new WebServiceResult(queueName, ticketID, ReportLevel.SUCCESS)
+					.send();
+
+		} catch (Exception exce) {
+			// report any errors
+			new WebServiceResult(queueName, ticketID, ReportLevel.FATAL,
+					exce.getMessage()).send();
+		}
+	}
+
+	/**
+	 * This method is used to get the queue name upon initialisation.
+	 * 
+	 * @return the queue name
+	 */
+	public String getQueueName() {
+		return queueName;
+	}
+
+	/**
+	 * The parent object which is there to check for new messages and to trigger
+	 * the method onMessage() is saved inside the class, to have it lately for
+	 * shutting down the service again.
+	 * 
+	 * @param checker
+	 *            the MessageConsumer object responsible for checking messages
+	 */
+
+	public void saveChecker(MessageConsumer checker) {
+		this.checker = checker;
+	}
+
+	/**
+	 * This method is used to get back the message checking object upon
+	 * shutdown.
+	 * 
+	 * @return the MessageConsumer object responsible for checking messages
+	 */
+	public MessageConsumer getChecker() {
+		return checker;
+	}
+}

=== added file 'src/org/goobi/webservice/MapMessageObjectReader.java'
--- src/org/goobi/webservice/MapMessageObjectReader.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/MapMessageObjectReader.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,242 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+
+public class MapMessageObjectReader {
+
+	private MapMessage ticket;
+
+	/**
+	 * This instantiates a new MapMessageObjectReader which is attached to a
+	 * given MapMessage.
+	 * 
+	 * @param message
+	 */
+	public MapMessageObjectReader(MapMessage message) {
+		if (message == null)
+			throw new IllegalArgumentException(
+					"MapMessageObjectReader: null argument in constructor.");
+		this.ticket = message;
+	}
+
+	/**
+	 * The function getMandatorySetOfString() fetches a Set<String> from a MapMessage.
+	 * This is a strict implementation that requires the collection not to be
+	 * empty and not to contain the null element.
+	 * 
+	 * Please note that the Set is allowed to contain the empty String (“”).
+	 * 
+	 * @param key
+	 *            the name of the set to return
+	 * @return the set requested
+	 * @throws IllegalArgumentException
+	 *             in case that getObject returns null, the returned object is
+	 *             not of type Collection, any of the content elements are not
+	 *             of type String or the collection is empty at all.
+	 * @throws JMSException
+	 *             can be thrown by MapMessage.getObject(String)
+	 */
+	public Set<String> getMandatorySetOfString(String key)
+			throws IllegalArgumentException, JMSException {
+		Set<String> result = new HashSet<String>();
+		Boolean emptiness = Boolean.TRUE;
+
+		Object collectionObject = ticket.getObject(key);
+		if (collectionObject == null)
+			throw new IllegalArgumentException("Missing mandatory argument: \""
+					+ key + "\"");
+		if (!(collectionObject instanceof Collection<?>))
+			throw new IllegalArgumentException("Incompatible types: \"" + key
+					+ "\" was not found to be of type Collection<?>.");
+		for (Object contentObject : (Collection<?>) collectionObject) {
+			if (contentObject == null || !(contentObject instanceof String))
+				throw new IllegalArgumentException(
+						"Incompatible types: An element of \"" + key
+								+ "\" was not found to be of type String.");
+			result.add((String) contentObject);
+			emptiness = false;
+		}
+		if (emptiness)
+			throw new IllegalArgumentException("Missing mandatory argument: \""
+					+ key + "\" must not be empty.");
+		return result;
+	}
+
+	/**
+	 * The function getMandatoryString() fetches a String from a MapMessage. This is
+	 * a strict implementation that requires the string not to be null and not
+	 * to be empty.
+	 * 
+	 * @param key
+	 *            the name of the string to return
+	 * @return the string requested
+	 * @throws IllegalArgumentException
+	 *             in case that getObject returns null or the returned string is
+	 *             of length “0”.
+	 * @throws JMSException
+	 *             can be thrown by MapMessage.getString(String)
+	 */
+	public String getMandatoryString(String key) throws IllegalArgumentException,
+			JMSException {
+		String result = ticket.getString(key);
+		if (result == null || result.length() == 0)
+			throw new IllegalArgumentException("Missing mandatory argument: \""
+					+ key + "\"");
+		return result;
+	}
+
+	/**
+	 * The function getString() fetches a String from a MapMessage. This is an
+	 * access forward to the native function of the MapMessage. You may
+	 * consider to use getMandatoryString() instead.
+	 * 
+	 * @param key
+	 *            the name of the string to return
+	 * @return the string requested (may be null or empty)
+	 * @throws JMSException
+	 *             can be thrown by MapMessage.getString(String)
+	 */
+
+	public String getString(String key) throws JMSException {
+		return ticket.getString(key);
+	}
+
+	/**
+	 * The function getMandatoryInteger() fetches an Integer object from a MapMessage. This is
+	 * a strict implementation that requires the Integer not to be null.
+	 * 
+	 * @param key
+	 *            the name of the string to return
+	 * @return the string requested
+	 * @throws IllegalArgumentException
+	 *             in case that getObject returns null
+	 * @throws JMSException
+	 *             can be thrown by MapMessage.getString(String)
+	 */
+	public Integer getMandatoryInteger(String key) throws IllegalArgumentException,
+			JMSException {
+		Integer result = ticket.getInt(key);
+		if (result == null)
+			throw new IllegalArgumentException("Missing mandatory argument: \""
+					+ key + "\"");
+		return result;
+	}
+
+	/**
+	 * The function getMapOfStringToString() fetches a Map<String,String> from a
+	 * MapMessage. This is a partly strict implementation that allows no null
+	 * element neither as key, nor as value. However, if no object was found for
+	 * the given key, or the key returned a null object, the function returns
+	 * null. Also, the Map is allowed to contain the empty String (“”) as key
+	 * and as values.
+	 * 
+	 * @param key
+	 *            the name of the map to return
+	 * @return the map requested
+	 * @throws IllegalArgumentException
+	 *             in case that the object returned by getObject returned object
+	 *             is not of type Map or any of the content elements are not of
+	 *             type String.
+	 */
+	public Map<String, String> getMapOfStringToString(String key) {
+		Map<String, String> result = new HashMap<String, String>();
+
+		Object mapObject = null;
+		try {
+			mapObject = ticket.getObject(key);
+		} catch (Exception irrelevant) {
+		}
+		if (mapObject == null)
+			return null;
+
+		if (!(mapObject instanceof Map<?, ?>))
+			throw new IllegalArgumentException("Incompatible types: \"" + key
+					+ "\" was not found to be of type Map<?, ?>.");
+		for (Object keyObject : ((Map<?, ?>) mapObject).keySet()) {
+			Object valueObject = ((Map<?, ?>) mapObject).get(keyObject);
+			if (keyObject == null || !(keyObject instanceof String))
+				throw new IllegalArgumentException(
+						"Incompatible types: A key element of \"" + key
+								+ "\" was not found to be of type String.");
+			if (valueObject == null || !(valueObject instanceof String))
+				throw new IllegalArgumentException(
+						"Incompatible types: A value element of \"" + key
+								+ "\" was not found to be of type String.");	
+			result.put((String) keyObject, (String) valueObject);
+		}
+		
+		return result;
+	}
+
+	/**
+	 * The function hasField() tests whether a field can be obtained from a
+	 * MapMessage.
+	 * 
+	 * @param string
+	 *            name of the field
+	 * @return true or false
+	 * @throws IllegalArgumentException
+	 *             can be thrown by MapMessage
+	 * @throws JMSException
+	 *             can be thrown by MapMessage
+	 */
+	public boolean hasField(String string) throws IllegalArgumentException,
+			JMSException {
+		String result = ticket.getString(string);
+		return (result != null && result.length() > 0);
+	}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

=== added file 'src/org/goobi/webservice/WebServiceResult.java'
--- src/org/goobi/webservice/WebServiceResult.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/WebServiceResult.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,88 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice;
+
+import javax.jms.MapMessage;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+
+import de.sub.goobi.helper.enums.ReportLevel;
+
+public class WebServiceResult {
+	private static final Logger logger = Logger.getLogger(ActiveMQDirector.class);
+	
+	private String queueName;
+	private String id;
+	private ReportLevel level;
+	private String message = null;
+	
+	public WebServiceResult(String queueName, String id, ReportLevel level,
+			String message){
+		this.queueName = queueName;
+		this.id = id;
+		this.level = level;
+		this.message = message;
+	}
+	
+	public WebServiceResult(String queueName, String id, ReportLevel level){
+		this.queueName = queueName;
+		this.id = id;
+		this.level = level;
+	}
+	
+	public void send() {
+		if (ActiveMQDirector.getResultsTopic() == null) {
+
+			// If reporting to ActiveMQ is disabled, write log message
+			logger.log(level == ReportLevel.SUCCESS ? Level.INFO : Level.WARN,
+					"Processing message \"" + id + '@' + queueName
+							+ "\" reports " + level.toLowerCase() + "."
+							+ (message != null ? " (" + message + ")" : ""));
+		} else {
+			try {
+				MapMessage report = ActiveMQDirector.getSession().createMapMessage();
+
+				DateTime now = new DateTime();
+				DateTimeFormatter iso8601formatter = ISODateTimeFormat.dateTime();
+				report.setString("timestamp", iso8601formatter.print(now));
+				report.setString("queue", queueName);
+				report.setString("id", id);
+				report.setString("level", level.toLowerCase());
+				if (message != null)
+					report.setString("message", message);
+
+				ActiveMQDirector.getResultsTopic().send(report);
+
+			} catch (Exception exce) {
+				logger.fatal("Error sending report  for \"" + id + '@'
+						+ queueName + "\" (" + level.toLowerCase()
+						+ (message != null ? ": " + message : "")
+						+ "): Giving up.", exce);
+			}
+		}
+	}
+}

=== added directory 'src/org/goobi/webservice/doc'
=== added file 'src/org/goobi/webservice/doc/implementing_active_mq_web_services_for_goobi.txt'
--- src/org/goobi/webservice/doc/implementing_active_mq_web_services_for_goobi.txt	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/doc/implementing_active_mq_web_services_for_goobi.txt	2012-06-28 13:28:37 +0000
@@ -0,0 +1,80 @@
+                 Implementing Active MQ web services for Goobi
+
+Active Message Queue is an open source Java Messaging (JMS) implementation
+provided by the Apache Software Foundation. It is intended to be used to
+to connect software components in a flexible way. The core is the Active MQ
+server which can be pictured like a post office. The mail boxes are named
+“queue” or “topic”. Queues work as expected: A producer sends a message where
+a consumer can pick it up. Topics can be pictured as black boards: The main
+difference is: A message read from a queue is removed from the queue. A message
+read from a topic is still available to others. Consumer clients can actively
+check the server or may register listeners with the server to be notified of
+new messages.
+
+This behaviour has already been implemented to Goobi: The org.goobi.webservice.
+ActiveMQDirector is a ServletContextListener which is registered in web.xml.
+On application startup, it registers all consumers from its “services” variable
+to the server configured in “activeMQ.hostURL”. 
+
+The elements of this variable are classes extending the abstract class
+ActiveMQProcessor. This class implements the MessageListener and provides
+facilities to handle exceptions and to store the consumer which is required on
+shutdown to disconnect.
+
+To implement another web service processor, you have to implement a class which
+extends ActiveMQProcessor and implements its abstract void process(MapMessage).
+Here is the right place to do whatever your processor is intended to do. There
+is a class MapMessageObjectReader which shall be used to type safe retrieve
+complex objects from MapMessages. You must add your new class to the “services”
+variable of ActiveMQDirector then.
+
+The Goobi server administrator shall be in control which processors are being
+started, and which queue names they listen on. Implementation of this
+configurability is designed this way: The implementing class must pass its
+queue name to the constructor of the parent class. This is done by implementing
+the constructor like in the following skeleton. If the queue name is not
+configured, it will return null which will prevent the ActiveMQDirector from
+registering it to the server. Inside the class, the queue name is available in
+the global variable “queueName” which is set by the parent class.  The
+implementation may use arbitrary “activeMQ.myService.*” entries in
+GoobiConfig.properties for configuration.
+
+---------------------[ Service processor skeleton sample ]---------------------
+package org.goobi.webservice.processores;
+
+import org.goobi.webservice.*;
+import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.helper.enums.ReportLevel;
+
+public class MyServiceProcessor extends ActiveMQProcessor {
+
+	public MyServiceProcessor() {
+		super(ConfigMain.getParameter("activeMQ.myService.queue", null));
+	}
+
+	@Override
+	protected void process(MapMessageObjectReader args) throws Exception {
+		// TODO Auto-generated method stub
+	}
+}
+-------------------------------------------------------------------------------
+
+Responses from processors are designed to be handled as WebServiceResult
+objects. Those objects are MapMessages which send themselves to a topic
+configured in “activeMQ.results.topic”. They consist of the Strings “queue”
+(the name of the queue the job ticket was sent to), “id” (a String “id” in
+the MapMessage which is mandatory), “level” and an optional “message”. When
+designing the MapMessage layout to parameterise your web service processor,
+please keep in mind that a String element “id” is mandatory. 
+
+If process() terminates without error, it is meant to have done its job
+successfully and a WebServiceResult with level “success” will be sent. If
+process() returns an exception, a WebServiceResult with level “fatal” will be
+sent. The exception will be returned as the “message” String. You may also use
+the WebServiceResult class to send messages with the levels “error”, “warn”,
+“info”, “debug”, “verbose” and “ludicrous” which are meant to be informative
+only:
+        new WebServiceResult(queueName, args.getMandatoryString("id"),
+                ReportLevel.INFO, "Remote host is down, trying again later.")
+                .send();
+

=== added file 'src/org/goobi/webservice/doc/web_service_to_create_new_processes.txt'
--- src/org/goobi/webservice/doc/web_service_to_create_new_processes.txt	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/doc/web_service_to_create_new_processes.txt	2012-06-28 13:28:37 +0000
@@ -0,0 +1,104 @@
+                      Web service to create new processes
+
+Goobi.Production is equiped with a web service interface to automatically
+create new processes based on a given template. This allows the digitization
+process to be initiated from outside the application, for example by assigning
+a new digital ID to a record in a library catalogue (or—at choice of the
+library—by duplicating a record and assigning a new digital ID to the
+duplicate) and then running a script.
+
+The web service infrastructure is providet by an Active MQ server (see
+http://activemq.apache.org/ for details) which needs to be downloaded and
+started. Without further configuration, it provides everything necessary on
+port 61616 of the machine in question.
+
+The “activeMQ.hostURL” must be set in GoobiConfig.properties to point to this
+server. The “activeMQ.createNewProcess.queue” must be set to point to a queue
+of your choice where Goobi.Production shall pick up orders to create new
+processes.
+
+Orders must be javax.jms.MapMessage objects with the following key-value-pairs
+provided:
+
+	String template
+		name of the process template to use
+	String opac
+		Cataloge to use for lookup
+	String field
+		Field to look into, usually 12 (PPN)
+	String value
+		Value to look for, id of physical medium
+	String id
+		Ticket ID (used in log responses)
+	List<String> collections
+		Collections to be selected
+	Map<String, String> userFields (optional)
+		May be used to populates AdditionalField entries
+
+Here is a sample java client to do the job. It expects to be passed from the
+command line the Active MQ host (e.g. tcp://localhost:61616), the queue name
+and the parameters as listed above.
+
+To run this application, the following JARs from the ActiveMQ server’s /lib
+folder are required on the classpath:
+	* activemq-core
+	* geronimo-j2ee-management_1.1_spec
+	* genonimo-jms_1.1_spec
+	* log4j
+	* slf4j-api
+	* slf4j-log4j12
+
+--------------------------------[ Main.java ]----------------------------------
+import java.util.*;
+import javax.jms.*;
+import org.apache.activemq.ActiveMQConnectionFactory;
+
+public class Main {
+	public static int main(String[] args) { try {
+
+		// Check arguments
+		if (args.length < 8 || (args.length % 2) != 0) {
+			System.out.println("Parameters: Active MQ host, queue name, "
+					+ "template name, opac name,");
+			System.out.println("            no. of search field, search "
+					+ "string, digital id, collection name,");
+			System.out.println("            [additional details field, "
+					+ "value, [add. details field, value, [...");
+			return 1;
+		}
+
+		// Connect to server
+		Connection connection = new ActiveMQConnectionFactory(args[0])
+				.createConnection();
+		connection.start();
+		Session session = connection.createSession(false,
+				Session.AUTO_ACKNOWLEDGE);
+		Destination destination = session.createQueue(args[1]);
+		MessageProducer producer = session.createProducer(destination);
+		producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
+
+		// Create job ticket
+		MapMessage message = session.createMapMessage();
+		message.setString("template", args[2]);
+		message.setString("opac", args[3]);
+		message.setString("field", args[4]);
+		message.setString("value", args[5]);
+		message.setString("id", args[6]);
+		List<String> collections = new ArrayList<String>();
+		collections.add(args[7]);
+		message.setObject("collections", collections);
+		Map<String, String> userFields = new HashMap<String, String>();
+		for (int i = 8; i < args.length; i += 2)
+			userFields.put(args[i], args[i + 1]);
+		if (userFields.size() != 0)
+			message.setObject("userFields", userFields);
+
+		// Send job ticket
+		producer.send(message);
+
+		// Shutdown
+		session.close();
+		connection.close();
+	} catch (Exception e) {	e.printStackTrace(); return 2; }
+	return 0;
+}	}

=== added directory 'src/org/goobi/webservice/processors'
=== added file 'src/org/goobi/webservice/processors/CreateNewProcessProcessor.java'
--- src/org/goobi/webservice/processors/CreateNewProcessProcessor.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/processors/CreateNewProcessProcessor.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,387 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice.processors;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.log4j.Logger;
+import org.goobi.webservice.ActiveMQProcessor;
+import org.goobi.webservice.MapMessageObjectReader;
+import org.hibernate.Criteria;
+import org.hibernate.Session;
+
+import de.sub.goobi.beans.Prozess;
+import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.config.ConfigOpacDoctype;
+import de.sub.goobi.forms.AdditionalField;
+import de.sub.goobi.forms.ProzesskopieForm;
+import de.sub.goobi.helper.Helper;
+
+/**
+ * CreateNewProcessProcessor is an Apache Active MQ consumer which registers to
+ * a queue configured by "activeMQ.createNewProcess.queue" on application
+ * startup. It was designed to create new processes from outside Goobi. There
+ * are two ways providing to create new processes. If the MapMessage on that
+ * queue contains of all the fields listed, the bibliographic data is retrieved
+ * using a catalogue configured within Goobi. If “opac” is missing, it will try
+ * to create a process just upon the data passed in the “userFields” − “field”
+ * and “value” will be ignored in that case, and the “docType” can be set
+ * manually.
+ * 
+ * Field summary:
+ * 
+ * <dl>
+ * 		<dt>String template</dt>
+ * 			<dd>name of the process template to use. A list of all available
+ *              templates can be obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listTemplates.jsp</kbd>.</dd>
+ * 		<dt>String opac</dt>
+ * 			<dd>Cataloge to use for lookup. A list of all available catalogues
+ *              can be obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listCatalogues.jsp</kbd>.</dd>
+ * 		<dt>String field</dt>
+ * 			<dd>Field to look into, usually 12 (PPN). A list of all available
+ *              search fields can be obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listSearchFields.jsp</kbd>.</dd>
+ * 		<dt>String value</dt>
+ * 			<dd>Value to look for, id of physical medium</dd>
+ * 		<dt>String docType</dt>
+ * 			<dd>DocType value to use if no catalogue request is performed. A
+ *              list of possible values can be obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listAllDoctypes.jsp</kbd></dd>
+ * 		<dt>Set&lt;String&gt; collections</dt>
+ * 			<dd>Collections to be selected. A list of all available collections
+ *              can be obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listCollections.jsp</kbd>.</dd>
+ * 		<dt>Map&lt;String, String&gt; userFields collections</dt>
+ * 			<dd>Fields to be populated manually. A list of fields can be
+ *              obtained in JSON format calling
+ *              <kbd><em>{ContextRoot}</em>/ws/listFieldConfig.jsp</kbd>
+ *          </dd>
+ * </dl>
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public class CreateNewProcessProcessor extends ActiveMQProcessor {
+	private static final Logger logger = Logger.getLogger(CreateNewProcessProcessor.class);
+
+	public CreateNewProcessProcessor() {
+		super(ConfigMain.getParameter("activeMQ.createNewProcess.queue", null));
+	}
+
+	@Override
+	protected void process(MapMessageObjectReader args) throws Exception {
+
+		Set<String> collections = args.getMandatorySetOfString("collections");
+		String id = args.getMandatoryString("id");
+		String template = args.getMandatoryString("template");
+		Map<String, String> userFields = args.getMapOfStringToString("userFields");
+		if (args.hasField("opac")) {
+			String opac = args.getMandatoryString("opac");
+			String field = args.getMandatoryString("field");
+			String value = args.getMandatoryString("value");
+			createNewProcessMain(template, opac, field, value, id, null, collections, userFields);
+		} else {
+			String docType = args.getString("docType");
+			createNewProcessMain(template, null, null, null, id, docType, collections, userFields);
+		}
+
+	}
+
+	/**
+	 * This is the main routine used to create new processes.
+	 * 
+	 * @param template
+	 *            titel of the process template the new process shall be derived
+	 *            from
+	 * @param opac
+	 *            name of the connection to a library catalogue to load the
+	 *            bibliographic data from (may be null)
+	 * @param field
+	 *            number of the catalogue search field (ignored if “opac” is
+	 *            null)
+	 * @param value
+	 *            search string (ignored if “opac” is null)
+	 * @param id
+	 *            identifier to be used for the digitisation
+	 * @param docType
+	 *            docType to set (may be null)
+	 * @param collections
+	 *            collections to add the digitisation to
+	 * @param userFields
+	 *            Values for additional fields can be set here (may be null)
+	 * @throws Exception
+	 *             in various cases, such as bad parameters or errors in the
+	 *             underlying layers
+	 */
+	protected void createNewProcessMain(String template, String opac, String field, String value, String id, String docType,
+			Set<String> collections, Map<String, String> userFields) throws Exception {
+
+		try {
+			ProzesskopieForm newProcess = newProcessFromTemplate(template);
+			newProcess.setDigitalCollections(validCollectionsForProcess(collections, newProcess));
+			if (opac != null)
+				getBibliorgaphicData(newProcess, opac, field, value);
+			if (docType != null && docTypeIsPossible(newProcess, docType))
+				newProcess.setDocType(docType);
+			if (userFields != null)
+				setUserFields(newProcess, userFields);
+			newProcess.CalcProzesstitel();
+			String state = newProcess.NeuenProzessAnlegen();
+			if (!state.equals("ProzessverwaltungKopie3"))
+				throw new RuntimeException();
+			logger.info("Created new process: " + id);
+		} catch (Exception exited) {
+			logger.error("Failed to create new process: " + id, exited);
+			throw exited;
+		}
+	}
+
+	/**
+	 * The function newProcessFromTemplate() derives a ProzesskopieForm object
+	 * from a given template.
+	 * 
+	 * @param templateTitle
+	 *            titel value of the template to look for
+	 * @return a ProzesskopieForm object, prepared from a given template
+	 * @throws IllegalArgumentException
+	 *             if no suitable template is found
+	 */
+	protected ProzesskopieForm newProcessFromTemplate(String templateTitle) throws IllegalArgumentException {
+		ProzesskopieForm result = new ProzesskopieForm();
+
+		List<Prozess> allTemplates = allTemplatesFromDatabase();
+		Prozess selectedTemplate = selectTemplateByTitle(allTemplates, templateTitle);
+		result.setProzessVorlage(selectedTemplate);
+		result.Prepare();
+		return result;
+	}
+
+	/**
+	 * This method reads all Prozess objects from the hibernate.
+	 * 
+	 * @return a List<Prozess> holding all templates
+	 */
+	protected List<Prozess> allTemplatesFromDatabase() {
+		Session hibernateSession = Helper.getHibernateSession();
+		Criteria request = hibernateSession.createCriteria(Prozess.class);
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> result = (List<Prozess>) request.list();
+
+		return result;
+	}
+
+	/**
+	 * The function selectTemplateByTitle() iterates over a List of Prozess and
+	 * returns the first element whose titel equals the given templateTitle.
+	 * 
+	 * @param allTemplates
+	 *            a List<Prozess> which shall be examined
+	 * @param templateTitle
+	 *            the title of the template to be picked up
+	 * @return the template, if found
+	 * @throws IllegalArgumentException
+	 *             is thrown, if there is no template matching the given
+	 *             templateTitle
+	 */
+	protected Prozess selectTemplateByTitle(List<Prozess> allTemplates, String templateTitle) throws IllegalArgumentException {
+
+		Prozess result = null;
+		for (Prozess aTemplate : allTemplates) {
+			if (aTemplate.getTitel().equals(templateTitle)) {
+				result = aTemplate;
+				break;
+			}
+		}
+		if (result == null)
+			throw new IllegalArgumentException("Bad argument: No template \"" + templateTitle + "\" available.");
+		return result;
+	}
+
+	/**
+	 * The function validCollectionsForProcess() tests whether a given set of
+	 * collections can be assigned to new process. If so, the set of collections
+	 * is returned as a list ready for assignment.
+	 * 
+	 * @param collections
+	 *            a set of collection names to be tested
+	 * @param process
+	 *            a ProzesskopieForm object whose prozessVorlage has been set
+	 * @return an ArrayList which can be used to set the digitalCollections of a
+	 *         ProzesskopieForm
+	 * @throws IllegalArgumentException
+	 *             in case that the given collection isn’t a valid subset of the
+	 *             digitalCollections possible here
+	 */
+	protected List<String> validCollectionsForProcess(Set<String> collections, ProzesskopieForm process) throws IllegalArgumentException {
+
+		HashSet<String> possibleCollections = new HashSet<String>(process.getPossibleDigitalCollections());
+		if (!possibleCollections.containsAll(collections))
+			throw new IllegalArgumentException("Bad argument: One or more elements of \"collections\" is not available for template \""
+					+ process.getProzessVorlage().getTitel() + "\".");
+		return new ArrayList<String>(collections);
+	}
+
+	/**
+	 * The function docTypeIsPossible() tests whether a given docType String can
+	 * be applied to a given process template. If so, it will return “true”,
+	 * otherwise, it will throw an informative IllegalArgumentException.
+	 * 
+	 * @param dialog
+	 *            the ProzesskopieForm object to test against
+	 * @param docType
+	 *            the desired docType ID string
+	 * @return true on success
+	 * @throws IllegalArgumentException
+	 *             if a docType is not applicable to the template or the docType
+	 *             isn’t valid
+	 */
+	protected boolean docTypeIsPossible(ProzesskopieForm dialog, String docType) throws IllegalArgumentException {
+		Boolean fieldIsUsed = dialog.getStandardFields().get("doctype");
+		if (fieldIsUsed == null || fieldIsUsed.equals(Boolean.FALSE))
+			throw new IllegalArgumentException("Bad argument “docType”: Selected template doesn’t provide the standard field “doctype”.");
+
+		boolean valueIsValid = false;
+		Iterator<ConfigOpacDoctype> configOpacDoctypeIterator = dialog.getAllDoctypes().iterator();
+		do {
+			ConfigOpacDoctype option = configOpacDoctypeIterator.next();
+			valueIsValid = docType.equals(option.getTitle());
+		} while (!valueIsValid && configOpacDoctypeIterator.hasNext());
+		if (valueIsValid)
+			return true;
+		throw new IllegalArgumentException("Bad argument “docType”: Selected template doesn’t provide a docType “{0}”.".replace("{0}",
+				docType));
+	}
+
+	/**
+	 * The method setUserFields() allows to set any AdditionalField to a user
+	 * specific value.
+	 * 
+	 * @param form
+	 *            a ProzesskopieForm object whose AdditionalField objects are
+	 *            subject to the change
+	 * @param userFields
+	 *            the data to pass to the form
+	 * @throws RuntimeException
+	 *             in case that no field with a matching title was found in the
+	 *             ProzesskopieForm object
+	 */
+	protected void setUserFields(ProzesskopieForm form, Map<String, String> userFields) throws RuntimeException {
+
+		for (String key : userFields.keySet()) {
+			setAdditionalField(form, key, userFields.get(key));
+		}
+
+	}
+
+	/**
+	 * Sets the bibliographic data for a new process from a library catalogue.
+	 * This is equal to manually choosing a catalogue and a search field,
+	 * entering the search string and clicking “Apply”.
+	 * 
+	 * Since the underlying OpacAuswerten() method doesn’t raise exceptions, we
+	 * count the populated “additional details” fields before and after running
+	 * the request and assume the method to have failed if not even one more
+	 * field was populated by the method call.
+	 * 
+	 * @param inputForm
+	 *            the ProzesskopieForm to be set
+	 * @param id
+	 *            the ticket’s id
+	 * @param opac
+	 *            the value for “Search in Opac”
+	 * @param field
+	 *            the number of the search field, e.g. “12” for PPN.
+	 * @param value
+	 *            the search string
+	 * @throws RuntimeException
+	 *             is thrown if the search didn’t bring any results
+	 */
+	protected void getBibliorgaphicData(ProzesskopieForm inputForm, String opac, String field, String value) throws RuntimeException {
+
+		inputForm.setOpacKatalog(opac);
+		inputForm.setOpacSuchfeld(field);
+		inputForm.setOpacSuchbegriff(value);
+
+		int before = countPopulatedAdditionalFields(inputForm);
+		inputForm.OpacAuswerten();
+		int afterwards = countPopulatedAdditionalFields(inputForm);
+
+		if (!(afterwards > before))
+			throw new RuntimeException("Searching the OPAC didn’t yield any results.");
+	}
+
+	/**
+	 * The function countPopulatedAdditionalFields() returns the number of
+	 * AdditionalFields in the given ProzesskopieForm that have meaningful
+	 * content.
+	 * 
+	 * @param form
+	 *            a ProzesskopieForm object to examine
+	 * @return the number of AdditionalFields populated
+	 */
+	protected int countPopulatedAdditionalFields(ProzesskopieForm form) {
+		int result = 0;
+
+		for (AdditionalField field : form.getAdditionalFields()) {
+			String value = field.getWert();
+			if (value != null && value.length() > 0)
+				result++;
+		}
+
+		return result;
+	}
+
+	/**
+	 * The method setAdditionalField() sets the value of an AdditionalField held
+	 * by a ProzesskopieForm object.
+	 * 
+	 * @param inputForm
+	 *            a ProzesskopieForm object
+	 * @param key
+	 *            the title of the AdditionalField whose value shall be modified
+	 * @param value
+	 *            the new value for the AdditionalField
+	 * @throws RuntimeException
+	 *             in case that no field with a matching title was found in the
+	 *             ProzesskopieForm object
+	 */
+	protected void setAdditionalField(ProzesskopieForm inputForm, String key, String value) throws RuntimeException {
+
+		for (AdditionalField field : inputForm.getAdditionalFields()) {
+			if (key.equals(field.getTitel())) {
+				field.setWert(value);
+				return;
+			}
+		}
+
+		throw new RuntimeException("Couldn’t set “" + key + "” to “" + value + "”: No such field in record.");
+	}
+
+}

=== added file 'src/org/goobi/webservice/processors/FinaliseStepProcessor.java'
--- src/org/goobi/webservice/processors/FinaliseStepProcessor.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/processors/FinaliseStepProcessor.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,97 @@
+/*
+ * This file is part of the Goobi Application - a Workflow tool for the support of
+ * mass digitization.
+ *
+ * Visit the websites for more information.
+ *     - http://gdz.sub.uni-goettingen.de
+ *     - http://www.goobi.org
+ *     - http://launchpad.net/goobi-production
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE. See the GNU General Public License for more details. You
+ * should have received a copy of the GNU General Public License along with this
+ * program; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ * Suite 330, Boston, MA 02111-1307 USA
+ */
+
+package org.goobi.webservice.processors;
+
+import org.apache.commons.lang.StringEscapeUtils;
+import org.goobi.webservice.ActiveMQProcessor;
+import org.goobi.webservice.MapMessageObjectReader;
+
+import de.sub.goobi.config.ConfigMain;
+import de.sub.goobi.forms.AktuelleSchritteForm;
+import de.sub.goobi.persistence.SchrittDAO;
+
+/**
+ * This is a web service interface to close steps. You have to provide the step
+ * id as “id”; you can add a field “message” which will be added to the wiki
+ * field.
+ * 
+ * @author Matthias Ronge <matthias.ronge@xxxxxxxxxxxx>
+ */
+public class FinaliseStepProcessor extends ActiveMQProcessor {
+
+	/**
+	 * The default constructor looks up the queue name to use in
+	 * GoobiConfig.properties. If that is not configured and “null” is passed to
+	 * the super constructor, this will prevent
+	 * ActiveMQDirector.registerListeners() from starting this service.
+	 */
+	public FinaliseStepProcessor() {
+		super(ConfigMain.getParameter("activeMQ.finaliseStep.queue", null));
+	}
+
+	/**
+	 * This is the main routine processing incoming tickets. It gets an
+	 * AktuelleSchritteForm object, sets it to the appropriate step which is
+	 * retrieved from the database, appends the message − if any − to the wiki
+	 * field, and executes the form’s the step close function.
+	 * 
+	 * @param ticket
+	 *            the incoming message
+	 * 
+	 * @see org.goobi.webservice.ActiveMQProcessor#process(org.goobi.webservice.MapMessageObjectReader)
+	 */
+	protected void process(MapMessageObjectReader ticket) throws Exception {
+		AktuelleSchritteForm dialog = new AktuelleSchritteForm();
+		Integer stepID = ticket.getMandatoryInteger("id");
+		dialog.setMySchritt(new SchrittDAO().get(stepID));
+		if (ticket.hasField("message"))
+			addMessageToWikiField(dialog, ticket.getString("message"));
+		dialog.SchrittDurchBenutzerAbschliessen();
+	}
+
+	/**
+	 * The addMessageToWikiField() method is a helper method which composes the
+	 * new wiki field using a StringBuilder. The message is encoded using HTML
+	 * entities to prevent certain characters from playing merry havoc when the
+	 * message box shall be rendered in a browser later.
+	 * 
+	 * @param form
+	 *            the AktuelleSchritteForm which is the owner of the wiki field
+	 * @param message
+	 *            the message to append
+	 */
+	protected void addMessageToWikiField(AktuelleSchritteForm form, String message) {
+		StringBuilder composer = new StringBuilder();
+		String wikiField = form.getWikiField();
+		if (wikiField != null && wikiField.length() > 0) {
+			composer.append(wikiField);
+			composer.append("\r\n");
+		}
+		composer.append("<p>");
+		composer.append(StringEscapeUtils.escapeHtml(message));
+		composer.append("</p>");
+		form.setWikiField(composer.toString());
+		return;
+	}
+
+}

=== added directory 'src/org/goobi/webservice/taglib'
=== added file 'src/org/goobi/webservice/taglib/WebServiceTaglib.java'
--- src/org/goobi/webservice/taglib/WebServiceTaglib.java	1970-01-01 00:00:00 +0000
+++ src/org/goobi/webservice/taglib/WebServiceTaglib.java	2012-06-28 13:28:37 +0000
@@ -0,0 +1,214 @@
+package org.goobi.webservice.taglib;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.json.simple.JSONValue;
+
+import de.sub.goobi.beans.Prozess;
+import de.sub.goobi.config.ConfigOpac;
+import de.sub.goobi.config.ConfigOpacDoctype;
+import de.sub.goobi.config.ConfigProjects;
+import de.sub.goobi.config.DigitalCollections;
+import de.sub.goobi.helper.Helper;
+
+public class WebServiceTaglib {
+
+	/**
+	 * The function getAllFields() returns a map that contains all fields for
+	 * each process template with their respective configurations.
+	 * 
+	 * @return A map in JSON format
+	 */
+
+	public static String getAllFields() throws Exception {
+		Map<String, Map<String, Object>> result = new HashMap<String, Map<String, Object>>();
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> processes = (List<Prozess>) Helper.getHibernateSession().createCriteria(Prozess.class).list();
+
+		for (Prozess p : processes) {
+			if (p.isIstTemplate()) {
+				Map<String, Object> fields = new HashMap<String, Object>();
+				ConfigProjects projectConfig = new ConfigProjects(p.getProjekt());
+				Integer numFields = projectConfig.getParamList("createNewProcess.itemlist.item").size();
+				for (Integer field = 0; field < numFields; field++) {
+					String fieldRef = "createNewProcess.itemlist.item(" + field + ")";
+					String fieldName = projectConfig.getParamString(fieldRef);
+					Map<String, Object> fieldConfig = new HashMap<String, Object>();
+					fieldConfig.put("from", projectConfig.getParamString(fieldRef + "[@from]"));
+					if (projectConfig.getParamBoolean(fieldRef + "[@ughbinding]")) {
+						fieldConfig.put("ughbinding", Boolean.TRUE);
+						fieldConfig.put("docstruct", projectConfig.getParamString(fieldRef + "[@docstruct]"));
+						fieldConfig.put("metadata", projectConfig.getParamString(fieldRef + "[@metadata]"));
+					} else {
+						fieldConfig.put("ughbinding", Boolean.FALSE);
+					}
+					Integer selectEntries = projectConfig.getParamList(fieldRef + ".select").size();
+					if (selectEntries > 0) {
+						Map<String, String> selectConfig = new HashMap<String, String>();
+						for (Integer selectEntry = 0; selectEntry < selectEntries; selectEntry++) {
+							String key = projectConfig.getParamString(fieldRef + ".select(" + selectEntry + ")");
+							String value = projectConfig.getParamString(fieldRef + ".select(" + selectEntry + ")[@label]");
+							selectConfig.put(key, value);
+						}
+						fieldConfig.put("select", selectConfig);
+					}
+					fieldConfig.put("required", projectConfig.getParamBoolean(fieldRef + "[@required]"));
+					fields.put(fieldName, fieldConfig);
+				}
+				result.put(p.getTitel(), fields);
+			}
+		}
+		return JSONValue.toJSONString(result);
+	}
+
+	/**
+	 * The function getCatalogues() returns a list that contains all catalogues
+	 * configured to read in bibliographic data from
+	 * 
+	 * @return A list in JSON format
+	 */
+	public static String getCatalogues() throws Exception {
+		List<String> catalogues = new ConfigOpac().getAllCatalogueTitles();
+		return JSONValue.toJSONString(catalogues);
+	}
+
+	/**
+	 * The function getCollections returns a map of all possible digital
+	 * collections for all available templates
+	 * 
+	 * @return A map in JSON format
+	 */
+	public static String getCollections() throws Exception {
+		HashMap<String, List<String>> result = new HashMap<String, List<String>>();
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> processes = (List<Prozess>) Helper.getHibernateSession().createCriteria(Prozess.class).list();
+		for (Prozess process : processes) {
+			if (process.isIstTemplate()) {
+				result.put(process.getTitel(), DigitalCollections.possibleDigitalCollectionsForProcess(process));
+			}
+		}
+		return JSONValue.toJSONString(result);
+	}
+
+	/**
+	 * The function getAllDoctypes() returns a list of all doctypes configured
+	 * in Goobi.
+	 * 
+	 * @return A list in JSON format
+	 */
+	public static String getAllDoctypes() throws Exception {
+		HashMap<String, Map<String, Object>> result = new HashMap<String, Map<String, Object>>();
+		ConfigOpac co = new ConfigOpac();
+		List<ConfigOpacDoctype> mediaTypesList = co.getAllDoctypes();
+		for (ConfigOpacDoctype mediaTypeEntry : mediaTypesList) {
+			Map<String, Object> resultEntry = new HashMap<String, Object>();
+			resultEntry.put("containedWork", Boolean.valueOf(mediaTypeEntry.isContainedWork()));
+			resultEntry.put("labels", mediaTypeEntry.getLabels());
+			resultEntry.put("mappings", mediaTypeEntry.getMappings());
+			resultEntry.put("multiVolume", Boolean.valueOf(mediaTypeEntry.isMultiVolume()));
+			resultEntry.put("periodical", Boolean.valueOf(mediaTypeEntry.isPeriodical()));
+			resultEntry.put("rulesetType", mediaTypeEntry.getRulesetType());
+			resultEntry.put("tifHeaderType", mediaTypeEntry.getTifHeaderType());
+			result.put(mediaTypeEntry.getTitle(), resultEntry);
+		}
+		return JSONValue.toJSONString(result);
+	}
+
+	/**
+	 * The function getProjects() returns a list of all projects configured.
+	 * 
+	 * @return A list in JSON format
+	 */
+	public static String getProjects() {
+		Set<String> projects = new HashSet<String>();
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> processes = (List<Prozess>) Helper.getHibernateSession().createCriteria(Prozess.class).list();
+		for (Prozess process : processes) {
+			if (process.isIstTemplate()) {
+				projects.add(process.getProjekt().getTitel());
+			}
+		}
+		return JSONValue.toJSONString(new ArrayList<String>(projects));
+	}
+
+	/**
+	 * The function getProjectsAndTemplates() returns a map with all projects
+	 * and their associated templates.
+	 * 
+	 * @return a map in JSON format.
+	 */
+	public static String getProjectsAndTemplates() {
+		Map<String, Set<String>> data = new HashMap<String, Set<String>>();
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> processes = (List<Prozess>) Helper.getHibernateSession().createCriteria(Prozess.class).list();
+		for (Prozess process : processes) {
+			if (process.isIstTemplate()) {
+				String projectName = process.getProjekt().getTitel();
+				Set<String> templateList = data.containsKey(projectName) ? data.get(projectName) : new HashSet<String>();
+				templateList.add(process.getTitel());
+				data.put(projectName, templateList);
+			}
+		}
+
+		Map<String, List<String>> result = new HashMap<String, List<String>>();
+		for (String projectName : data.keySet())
+			result.put(projectName, new ArrayList<String>(data.get(projectName)));
+
+		return JSONValue.toJSONString(result);
+	}
+
+	/**
+	 * The function getSearchFields returns a map of all search fields available
+	 * for any of the catalogs configured with their respective labels.
+	 * 
+	 * This is coded statically in /newpages/NewProcess/inc_process.jsp and so
+	 * it is also hardcoded here by now.
+	 * 
+	 * @return A map in JSON format.
+	 */
+	public static String getSearchFields() throws Exception {
+		Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>();
+
+		// Not yet configurable within Goobi.Production
+		Map<String, String> staticSearchFields = new LinkedHashMap<String, String>();
+		staticSearchFields.put("12", "PPN");
+		staticSearchFields.put("8535", "Barcode");
+		staticSearchFields.put("8200", "Barcode 8200");
+		staticSearchFields.put("7", "ISBN");
+		staticSearchFields.put("8", "ISSN");
+
+		for (String catalogue : new ConfigOpac().getAllCatalogueTitles()) {
+			result.put(catalogue, staticSearchFields);
+		}
+		return JSONValue.toJSONString(result);
+	}
+
+	/**
+	 * The function getTemplates() returns a list of all available process
+	 * templates.
+	 * 
+	 * @return a list in JSON format.
+	 */
+	public static String getTemplates() {
+		ArrayList<String> result = new ArrayList<String>();
+
+		@SuppressWarnings("unchecked")
+		List<Prozess> processes = (List<Prozess>) Helper.getHibernateSession().createCriteria(Prozess.class).list();
+		for (Prozess process : processes) {
+			if (process.isIstTemplate()) {
+				result.add(process.getTitel());
+			}
+		}
+		return JSONValue.toJSONString(result);
+	}
+}

=== added directory 'ws'
=== added file 'ws/listAllDoctypes.jsp'
--- ws/listAllDoctypes.jsp	1970-01-01 00:00:00 +0000
+++ ws/listAllDoctypes.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,5 @@
+<%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getAllDoctypes()}

=== added file 'ws/listCatalogues.jsp'
--- ws/listCatalogues.jsp	1970-01-01 00:00:00 +0000
+++ ws/listCatalogues.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getCatalogues()}

=== added file 'ws/listCollections.jsp'
--- ws/listCollections.jsp	1970-01-01 00:00:00 +0000
+++ ws/listCollections.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getCollections()}

=== added file 'ws/listFieldConfig.jsp'
--- ws/listFieldConfig.jsp	1970-01-01 00:00:00 +0000
+++ ws/listFieldConfig.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getAllFields()}
\ No newline at end of file

=== added file 'ws/listProjects.jsp'
--- ws/listProjects.jsp	1970-01-01 00:00:00 +0000
+++ ws/listProjects.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getProjects()}

=== added file 'ws/listProjectsTemplates.jsp'
--- ws/listProjectsTemplates.jsp	1970-01-01 00:00:00 +0000
+++ ws/listProjectsTemplates.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getProjectsAndTemplates()}

=== added file 'ws/listSearchFields.jsp'
--- ws/listSearchFields.jsp	1970-01-01 00:00:00 +0000
+++ ws/listSearchFields.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getSearchFields()}
\ No newline at end of file

=== added file 'ws/listTemplates.jsp'
--- ws/listTemplates.jsp	1970-01-01 00:00:00 +0000
+++ ws/listTemplates.jsp	2012-06-28 13:28:37 +0000
@@ -0,0 +1,27 @@
+<%--
+  ~ This file is part of the Goobi Application - a Workflow tool for the support of
+  ~ mass digitization.
+  ~
+  ~ Visit the websites for more information.
+  ~     - http://gdz.sub.uni-goettingen.de
+  ~     - http://www.goobi.org
+  ~     - http://launchpad.net/goobi-production
+  ~
+  ~ 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.
+  ~
+  ~ This program is distributed in the hope that it will be useful, but WITHOUT ANY
+  ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+  ~ PARTICULAR PURPOSE. See the GNU General Public License for more details. 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
+  --%>
+  
+  <%@ page language="java"
+	contentType="application/json; charset=utf-8" pageEncoding="utf-8"
+	session="false" trimDirectiveWhitespaces="true"%>
+<%@ taglib uri="http://taglib.production.goobi.org/webService"; prefix="ws" %>
+${ws:getTemplates()}