From 51901ba647b2760e3e3f00c33ff95a28a1949d72 Mon Sep 17 00:00:00 2001
From: Andreas Romeyke <andreas.romeyke@slub-dresden.de>
Date: Wed, 18 Mar 2015 12:38:23 +0100
Subject: [PATCH] - Prerelease

---
 Makefile                    |  37 ++++
 README.1st                  |   6 +
 doc/Makefile                |  35 ++++
 doc/exit_strategie.asciidoc | 384 ++++++++++++++++++++++++++++++++++++
 perl/Makefile               |  50 +++++
 perl/exit_strategy.pl       | 365 ++++++++++++++++++++++++++++++++++
 6 files changed, 877 insertions(+)
 create mode 100644 Makefile
 create mode 100644 README.1st
 create mode 100644 doc/Makefile
 create mode 100644 doc/exit_strategie.asciidoc
 create mode 100644 perl/Makefile
 create mode 100644 perl/exit_strategy.pl

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..2a54cb5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+###############################################################################
+###                                                                         ###
+### all:                  compiles all stuff                                ###
+### clean:                cleans all stuff                                  ###
+### check_prerequisites   checks if all prerequisites to compile are fine   ###
+### distclean:            makes Mr Proper clean                             ###
+### build:                generates a builded environment,                  ###
+###                       defaults in ./build/                              ###
+### buildclean:           removes builded environment                       ###
+### force_build:          force build also if perl tests failed             ###
+### install:              installs builded environment on system            ###
+### uninstall:            removes strong related stuff from system if part  ###
+###                       of previously builded environment                 ###
+###############################################################################
+
+# If you unsure, the following steps will be a good idea in general:
+# $> make check_prerequisites
+# $> make clean; build; install
+
+
+
+
+
+PROGRAM=rosettaExitStrategy
+PREFIX?=/usr/local/
+PATH_PERL_MOD=perl/
+PATH_SHARE=share/${PROGRAM}/
+PATH_DOC=${PATH_SHARE}/doc/
+PATH_XSL=${PATH_SHARE}/xsl/
+PATH_XSD=${PATH_SHARE}/schema/
+PATH_BIN=bin/
+BUILD?=./build/
+SHELL=/bin/bash
+
+include ../subprojects.mk
+
+.PHONY: all clean check_prerequisites distclean doc test uninstall 
diff --git a/README.1st b/README.1st
new file mode 100644
index 0000000..f48b1e1
--- /dev/null
+++ b/README.1st
@@ -0,0 +1,6 @@
+- the textfile 'exit_strategie.asciidoc' is a asciidoc document, it can
+be used to generate various output files using asciidoc. As an example
+to generate html-output, call: "asciidoc exit_strategie.asciidoc". To get
+correctly rendered ER-graphs, the asciidoc filter for 'ditaa' must be
+installed. To colorize the code-examples, the 'source-highlight'
+programm must be installed as well.
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..28d0f08
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,35 @@
+SOURCES=$(wildcard *.asciidoc)
+TARGETS=$(SOURCES:.asciidoc=.html)
+SHELL=/bin/bash
+RED=\033[0;31m
+RESET=\033[0m
+GREEN=\033[0;32m
+
+all: doc
+
+doc: ${TARGETS}
+
+%.html: %.asciidoc
+	asciidoc $<
+
+clean:
+	rm -f *.png *.html
+
+distclean: clean
+	rm -f *~
+
+check_prerequisites:
+	@echo -n "### Checking asciidoc: ...."
+	@path=$$(which asciidoc); if [ -e "$${path}" ]; then echo -e " ($${path}) $(GREEN)fine :)$(RESET)"; else echo -e " $(RED)not found! :($(RESET)"; fi
+	@echo -n "### Checking source-highlight: ...."
+	@path=$$(which source-highlight); if [ -e "$${path}" ]; then echo -e " ($${path}) $(GREEN)fine :)$(RESET)"; else echo -e " $(RED)not found! :($(RESET)"; fi
+	@echo -n "### Checking pygmentize: ...."
+	@path=$$(which pygmentize); if [ -e "$${path}" ]; then echo -e " ($${path}) $(GREEN)fine :)$(RESET)"; else echo -e " $(RED)not found! :($(RESET)"; fi
+	@echo -n "### Checking ditaa: ...."
+	@path=$$(which ditaa); if [ -e "$${path}" ]; then echo -e " ($${path}) $(GREEN)fine :)$(RESET)"; else echo -e " $(RED)not found! :($(RESET)"; fi
+	@echo -n "### Checking asciidoc filter 'ditaa-filter': ...."
+	@if [[ -e "/etc/asciidoc/filters/ditaa/ditaa-filter.conf" || -e ~/.asciidoc/filters/ditaa/ditaa-filter.conf ]]; then echo -e " ($${path}) $(GREEN)fine :)$(RESET)"; else echo -e " $(RED)not found! :($(RESET)"; fi
+
+
+
+.PHONY: all clean check_prerequisites distclean doc
diff --git a/doc/exit_strategie.asciidoc b/doc/exit_strategie.asciidoc
new file mode 100644
index 0000000..e1aa4a7
--- /dev/null
+++ b/doc/exit_strategie.asciidoc
@@ -0,0 +1,384 @@
+Exit Strategie Rosetta
+======================
+:lang: de
+:encoding: utf-8
+:date: 2013-05-09
+:author: Andreas Romeyke
+:toc:
+
+.Erzeugen HTML-Version dieses Dokumentes
+[TIP]
+===============================================================================
+Nutzen Sie folgenden Aufruf um eine HTML-Version dieses Dokumentes zu
+erzeugen:
+
+[source,bash]
+$> asciidoc exit_strategie.txt
+
+Dies erzeugt die Datei 'exit_strategie.html'
+===============================================================================
+
+
+== Ziel
+
+Um die Verfügbarkeit der langzeitarchivierten Daten auch bei einem wie auch
+immer verursachten Wegfall des Rosetta-Systems sicherzustellen, ist es
+notwendig, rechtzeitig Vorsorge zu treffen.
+
+Im Team wurde dazu folgende Vorgehensweise vereinbart:
+
+. Perl-Script, welches über das '/permanent_storage' Verzeichnis wandert
+. dabei die 'IE.xml' Dateien parst
+. und ein SQL-Script produziert
+. welches Standard-SQL Befehle für den Aufbau einer Datenbank generiert
+
+Der Hintergrund der Entscheidung nicht direkt aus dem Perl-Script heraus die
+Datenbank zu befeuern (zB. mittels dbi-Treiber) ist, daß man
+
+. keine Treiber-Module im Perl-Script nachpflegen muß
+. falls man eine DB nicht hinbekommt, wenigstens schon eine menschen- und 
+  maschinenlesbare Textdatei hat, die notfalls durchsuchbar ist 
+. man nicht erst Firewall-Regeln umbiegen muß um dem Script eine 
+  Datenbankverbindung zu ermöglichen
+
+== Datenbankschema
+
+Die zu erzeugende Datenbank soll dabei die DublinCore-Elemente der DMD-Section
+der AIP-Pakete (aus 'IE.xml' Dateien), den Namen, den tatsächlichen
+Speicherpfad der einzelnen Dateien und die Kopie enthalten.
+
+Aus Performancegründen wird die Lage der 'ie.xml' Dateien in von den sonstigen
+Dateien getrennten Tabellen verwaltet.
+
+Auf die Speicherung der Prüfsummen der Dateien wird verzichtet, da im System 3
+bzw. 4 Kopien der Dateien (inklusive 'ie.xml') vorliegen und so im Falle einer
+Datenkorruption beim Ingest ein Mehrheitsentscheid zur Sicherstellung der
+Korrektheit ausreichend ist.
+
+.Entity Relationship Modell
+[ditaa]
+----------------------------------------------------------------------------
+                          1 +---------------+ 1
+                      +---->| AIP           |<----+
+                      |  +->+---------------+     |
+                      |  | 1| *ID* (ID)     |     |
+                      |  |  | IE_ID (string)|     |
+                      |  |  +---------------+     |   +------------------+
+                      |  |                        |   | DC               |
+                      |  |  +---------------+ 1   |   +------------------+
++---------------+     |  |  | SOURCEDATAFILE|<-+  | n | *ID* (ID)        |
+| METADATAFILE  |     |  |  +---------------+  |  +---| AIP_ID (ID)      |
++---------------+     |  | n| *ID* (ID)     |  |      | ELEMENT (string) |
+| *ID* (ID)     |n    |  +--| AIP_ID (ID)   |  |      | VALUE (string)   |
+| AIP_ID (ID)   |-----+     | NAME (string) |  |      +------------------+
+| LOCATION      |           +---------------+  |
+|       (string)|                              |
+| SOURCETYPE    |                              |
+|       (string)|                              |
++---------------+                              |
+                            +---------------+  |
+                            |SOURCEDATALOCAT|  |
+                            +---------------+  |
+                            | *ID* (ID)     |n |
+                            | FILE_ID (ID)  |--+
+                            | LOCATION      |
+                            |       (string)|
+                            | SOURCETYPE    |
+                            |       (string)|
+                            +---------------+
+
+----------------------------------------------------------------------------
+
+Es gibt pro AIP-Eintrag in der 'AIP' Tabelle eins oder mehrere 'METADATAFILE', 
+welches die Lage der ExLibris-Rosetta-METS/MODS Datei beschreiben. Wenn mehrere
+Kopien abgelegt sind, gibt es mehrere Einträge.
+
+Auch die 'SOURCEDATAFILE'-Tabelle beschreibt mehrere Roh-Dateien (zB. die
+gescannten TIFFS der einzelnen Buchseiten), deren Kopienspeicherorte aber in 
+der Tabelle 'SOURCEDATALOCAT' hinterlegt sind.
+
+Die wichtigsten bibliographischen Metadaten zur Suche sind in der
+'DC'-Tabelle hinterlegt.
+
+[WARNING]
+============================================================================
+Da der SQL-Standard keine Angaben zum Erzeugen einer Datenbank macht,
+muß das Anlegen einer Datenbank (zB. durch Anweisung 'CREATE
+DATABASE…') und die Zuweisung der Benutzerrechte vor dem Einlesen des
+Scriptes erfolgen.
+============================================================================
+
+
+
+Das Script erzeugt dann SQL-Anweisungen, die pro AIP-Eintrag
+als Transaktion geklammert werden.  Ein Auszug des erzeugten SQL-Scriptes:
+
+.SQL-Script
+[source,sql]
+----------------------------------------------------------------------------
+
+BEGIN;
+/* create SEQUENCE generator */
+CREATE SEQUENCE serial START 1;
+/* create AIP table */
+CREATE TABLE aip (
+	id INT PRIMARY KEY DEFAULT nextval('serial'),
+	ie_id VARCHAR(30) NOT NULL UNIQUE
+);
+/* create IEFILE table */
+CREATE TABLE metadatafile (
+	id INT PRIMARY KEY DEFAULT nextval('serial'),
+	aip_id INT NOT NULL REFERENCES aip (id),
+	location VARCHAR(1024) NOT NULL,
+	sourcetype VARCHAR(30) NOT NULL
+);
+/* create DC table */
+CREATE TABLE dc (
+	id INT PRIMARY KEY DEFAULT nextval('serial'),
+	aip_id INT NOT NULL REFERENCES aip (id),
+	element VARCHAR(30) NOT NULL,
+	value VARCHAR(1024) NOT NULL
+);
+/* create FILE table */
+CREATE TABLE sourcedatafile (
+	id INT PRIMARY KEY DEFAULT nextval('serial'), 
+	aip_id INT NOT NULL REFERENCES aip (id),
+	name VARCHAR(1024) NOT NULL
+);
+/* create LOCAT table */
+CREATE TABLE sourcedatalocat (
+	id INT PRIMARY KEY DEFAULT nextval('serial'),
+	file_id INT NOT NULL REFERENCES sourcedatafile (id),
+	location VARCHAR(1024) NOT NULL,
+	sourcetype VARCHAR(30) NOT NULL
+);
+COMMIT;
+BEGIN;
+PREPARE aip_plan (varchar) AS
+  INSERT INTO aip (ie_id) VALUES ($1);
+PREPARE ie_plan (varchar, varchar, varchar) AS
+  INSERT INTO metadatafile (aip_id, location, sourcetype) VALUES (
+    (SELECT id FROM aip WHERE aip.ie_id=$1), $2, $3
+  );
+PREPARE file_plan (varchar, varchar) AS
+  INSERT INTO sourcedatafile (aip_id, name) VALUES (
+    (SELECT id FROM aip WHERE aip.ie_id=$1), $2
+  );
+PREPARE locat_plan (varchar, varchar, varchar, varchar) AS
+  INSERT INTO sourcedatalocat (file_id, location, sourcetype) VALUES (
+    (SELECT sourcedatafile.id FROM sourcedatafile,aip WHERE
+    sourcedatafile.aip_id=aip.id AND aip.ie_id=$1 AND
+    sourcedatafile.name=$2), $3, $4
+  );
+PREPARE dc_plan (varchar, varchar, varchar) AS
+  INSERT INTO dc (aip_id, element, value) VALUES (
+    (SELECT id FROM aip WHERE aip.ie_id=$1), $2, $3
+  );
+COMMIT;
+BEGIN;
+EXECUTE aip_plan ('V1-IE30441');
+EXECUTE ie_plan ('V1-IE30441', 'V1-IE30441.xml', 'hdd');
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30444.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30444.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30444.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30443.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30443.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30443.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30446.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30446.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30446.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30445.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30445.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30445.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30448.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30448.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30448.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30447.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30447.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30447.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30455.xml');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30455.xml', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30455.xml', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30449.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30449.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30449.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30454.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30454.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30454.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30452.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30452.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30452.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30453.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30453.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30453.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30450.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30450.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30450.tif', 'hdd' );
+EXECUTE file_plan ('V1-IE30441', 'V1-FL30451.tif');
+EXECUTE locat_plan ('V1-IE30441', 'V1-FL30451.tif', '/permanent_storage/file/storage1/2013/03/26/file_1/V1-FL30451.tif', 'hdd' );
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:coverage', 'DE-14');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:coverage', '7.A.1869,angeb.32');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:relation', 'Drucke des 18. Jahrhunderts');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:relation', 'Projekt: Verzeichnis der im deutschen Sprachraum erschienenen Drucke des 18. Jahrhunderts (VD18)');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', 'oai:de:slub-dresden:db:id-340981210');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:format', '[4] Bl.');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', '340981210');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', 'http://digital.slub-dresden.de/id340981210');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', 'urn:nbn:de:bsz:14-db-id3409812108');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', '088741990');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:identifier', 'VD18 11664185');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:title', 'Facultatis Iuridicae, Decanus Ernestus Tenzell, J. U. D. Iudicii Provincialis Erfurtensis Assessor, Civitatis Consul Ac Syndicus Primarius ...');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:language', 'la');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:publisher', 'Groschius');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:date', '[1716]');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:subject', 'facuiudee');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:contributor', 'Tentzel, Ernst (Tentzel, Ernst)');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:contributor', 'Talheim, Johann Philipp (Talheim, Johann Philipp)');
+EXECUTE dc_plan ( 'V1-IE30441', 'dc:contributor', '(Deutsche Forschungsgemeinschaft)');
+COMMIT;
+
+/* INSERT… */
+
+-- BEGIN;
+-- CREATE UNIQUE INDEX aip_index on aip (ie_id);
+-- COMMIT;
+----------------------------------------------------------------------------
+
+
+== Installation
+
+Das Script ist in Perl 5.14 geschrieben (älterer Perlversionen haben ua. 
+Probleme mit UTF-8). Es verwendet die Perl-Module 'File::Basename', 
+'File::Find', 'XML::XPath' und für Debugging 'Data::Dumper'.
+
+Dem Script ist das Repository-Verzeichnis mitzugeben. Der Aufruf sieht so aus:
+
+.Beispiel
+[source,bash]
+----------------------------------------------------------------------------
+$> perl exit_strategy /permanent_storage/ >create_exit_database.sql
+----------------------------------------------------------------------------
+
+== Beispiel Durchführung Einspielung SQL-Script für Postgres-SQL
+
+Um unter Postgres-SQL 9.1 die Exitstrategie durchzuführen, sind unter Debian 
+Wheezy folgende Schritte notwendig footnote::[Benutzer 'exituser' soll
+Datenbank 'exit_strategy' gehören]:
+
+.Beispiel
+[source,bash]
+----------------------------------------------------------------------------
+$user> sudo aptitude install postgresql
+$user> su -c "su -s /bin/sh postgres"
+$> createuser -dlr exituser
+Soll die neue Rolle ein Superuser sein? (j/n) j
+$> createdb exit_strategy -O exituser -E UTF8
+$> exit
+----------------------------------------------------------------------------
+
+Das Script wird dann so eingespielt:
+
+.Beispiel
+[source,bash]
+----------------------------------------------------------------------------
+$user> su -c "su -s /bin/sh postgres"
+$> psql -U exituser -d exit_strategy -f exit_strategy.sql \
+             -L rosetta_exit.log 2> rosetta_exit.err 
+----------------------------------------------------------------------------
+
+Für weitere Informationen zu Postgres 9.1 siehe 
+http://www.postgresql.org/docs/9.1/static/
+
+== Abschätzungen
+
+Nach ersten Tests verarbeitet das Perl-Script 277 AIPs in 112s, macht ca. 0,4s
+pro AIP. Es wurden dabei ca. 5200 SQL-Anweisungen erzeugt, also ca. 19 pro AIP.
+Das erzeugte SQL-File ist 387kB groß, pro AIP fallen also ca. 1,4kB an.
+
+Bei anvisierten 20 Goobi Vorgängen pro Tag und Exit nach 5 Jahren würden sich
+ff. Werte ergeben: 35600 AIPs, Dauer ca. 4h, ca. 68000 SQL Anweisungen, 49 MB
+SQL-Datei.
+
+PostgreSQL benötigte dann 5s um die 277 AIPs aus dem SQL-Script einzulesen,
+hochgerechnet wäre die Datenbank dann in 11 min aufgebaut..
+
+NOTE: Eine Exit-DB wäre demnach innerhalb von 10h prinzipiell wieder verfügbar.
+
+== Probleme
+
+=== UTF-8 Bereiche in Dublincore
+
+Es ist elementar, daß die Metadaten aus ExLibris-Rosetta sauber sind und alle
+Zeichen der verwendeten Dublincore-Felder als UTF-8 aus den Bereichen Basic 
+Latin (U+0000 => U+007F), Latin-1 Supplement (U+0080 => U+00FF) und 
+Latin-Extended-A (U+0100 => U+017F) und nicht aus anderen Bereichen stammen.
+
+Beispielsweise wird das Zeichen '�' (U+FFFD) von Postgres 9.1 abgewiesen.
+
+In dem Fall muß vor dem Exit eine Metadatenvalidierung innerhalb von 
+Exlibris-Rosetta durchgeführt werden. In der Regel ist das Vorkommen von
+Zeichen außerhalb der oben genannten Bereiche, wie '�' (U+FFFD), ein Hinweis
+darauf, daß im Vorfeld ein Problem mit der Konvertierung zwischen UTF-8 und 
+anderen Zeichenkodierungen vorgelegen hat. 
+
+[NOTE]
+====
+Relevant ist ff. Seite:
+http://docs.oracle.com/javase/7/docs/api/java/lang/Character.UnicodeBlock.html#forName%28java.lang.String%29[Unicode
+Block in Java RegEx]
+bzw. 
+http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html#sum[Unicode
+Pattern in Java RegEx]
+
+Genauer muß geprüft werden, ob ff. Unicode-Block verwendet wird: LATIN_1_SUPPLEMENT
+
+In RegEx-Notation sieht das Bspw. so aus:
+
+[source, java]
+^[\u0x0000-\u0x00ff]+$
+
+====
+
+Wichtig ist, daß das PSQL-Kommando auf einer Shell mit aktivierter UTF-8
+Unterstützung genutzt wird, dies kann über die Abfrage 'echo $LANG' geprüft
+werden, als Rückgabe sollte 'de_DE.UTF-8' zurückgeliefert werden.
+
+=== Fehlende Unterstützung mehrfacher Kopien
+
+Zur Zeit wird nur eine Kopie einer Datei durch das Perl-Script unterstützt.
+Sobald klar ist, wie diese Informationen in den AIP-Paketen hinterlegt sind,
+sollte das Perl-script daran angepasst werden.
+
+== Anmerkungen seitens ExLibris Rosetta
+
+Leider ist es zur Zeit so, daß seitens ExLibris noch keine offizielle 
+Dokumentation zur Rosetta eigenen Datenablage der AIPs im '/permanent_storage'
+vorhanden ist.
+
+Allerdings hat ExLibris auf einen Support Incident wie folgt geantwortet:
+
+.Auszug aus Incident #16384-420304 SI Name: Overview / Explanation of AIP relevant files - information is requested
+[NOTE]
+====
+As you know from SI 16384-418600: "All AIPs (a.k.a. Intellectual Entities) 
+metadata including audit (provenance) information is stored on the disk in
+Rosetta METS format." The actual place of the IE Rosetta METS XML files in the
+file system is configured in the storage rules and definitions.
+
+Home > Advanced Configuration > Repository > Storage Rules and Definitions
+> Storage Group List > IE Group     
+
+Storage media that contains the IE METS files
+ 
+For example, on your staging server the NFS path to the storage 1 is 
+'/permanent_storage/ie/storage1'.
+ 
+The configured storages have the same structure:
+
+'<root path><storage group><storage>/<year>/<month>/<day>/<1-999 numbered
+subdirs with prefix>/'
+ 
+Example:
+'/permanent_storage/ie/storage1/2013/03/26/file_1/'
+ 
+Here you find all versions of the IE Rosetta METS XML files, e.g.
+'/permanent_storage/ie/storage1/2013/03/26/file_1/V1-IE31220.xml'
+ 
+The prefix 'V1-' indicates that this is version #1 of the IE Rosetta METS XML
+file. The link to the actual file streams is in the XML in the streamref
+section. You have to make sure that you are using the highest (i.e. latest)
+version of the METS.
+  
+With the information above you can develop an exit strategy which parses all
+IE storage directories to find the IEs and their related file streams.
+====
+
+
diff --git a/perl/Makefile b/perl/Makefile
new file mode 100644
index 0000000..658624f
--- /dev/null
+++ b/perl/Makefile
@@ -0,0 +1,50 @@
+REQUIRED_MODULES=$(shell find ./ \( -name "*.p[ml]" -o -name "*.t" \) -exec grep "^use \([A-Za-z][A-Za-z0-9:]\+\)" \{\} \; | sed -e "s/^use \([A-Za-z][A-Za-z0-9:]\+\).*/\1/g" | sort | uniq)
+REQUIRED_SYSMODULES=$(shell find ./ \( -name "*.p[ml]" -o -name "*.t" \) -exec grep "^use \([A-Za-z][A-Za-z0-9:]\+\)" \{\} \; | grep -v "SLUB::LZA" | grep "^use [A-Z]" | sed -e "s/^use \([A-Za-z][A-Za-z0-9:]\+\).*/\1/g" | sort | uniq)
+PMs=$(shell find ./ -name "*.pm")
+PODs=$(PMs:.pm=.pod)
+
+all: perl_tests_ok
+
+clean:
+	rm -f perl_tests_ok
+
+distclean: clean
+	find ./ -name "*~" | xargs rm -f
+	find ./ -name "*.pod" | xargs rm -f
+	rm -Rf testdir/
+	rm -Rf cover_db/
+
+find_subs:
+	find ./ -name "*.pm" -exec grep -E "^\s*sub\s+(\w+)\b" \{\} \;
+
+find_vars:
+	find ./ -name '*.pm' -exec grep 'my \$$[A-Za-z0-9_]' \{\} \; | egrep -o '\$$[A-Za-z0-9_]+' | sort | uniq
+
+check_prerequisites:
+	@for i in $(REQUIRED_MODULES); do\
+	  echo -n "### Checking if Perl-Module '$$i' exists ..." ;\
+	  /usr/bin/perl -I./ -e "use Term::ANSIColor; if (eval {require $$i; 1;} ne 1) { print color 'bold red'; print \" not found! :(\n\";} else {print color 'green'; print \"fine! :)\n\";}; print color 'reset';";\
+	  done
+
+perl_tests_ok:
+	touch perl_tests_ok
+	@true
+
+list_required_perl_modules: 
+	@for i in $(REQUIRED_SYSMODULES) ; do\
+	  echo $$i ;\
+	  done
+
+# autpod is part of Pod::Autopod
+%.pod:%.pm
+	autopod -r $< --pod -w $@ 
+
+doc: $(PODs)
+
+#cover: clean
+#	PERL5OPT=-MDevel::Cover $(MAKE) perl_tests_ok
+#	cover -ignore_re '.*\.t' -ignore_re '.*prove'
+#	@echo report found in cover_db/coverage.html
+
+.PHONY: all clean check_prerequisites doc distclean find_subs list_required_perl_modules perl_tests_ok
+
diff --git a/perl/exit_strategy.pl b/perl/exit_strategy.pl
new file mode 100644
index 0000000..367e038
--- /dev/null
+++ b/perl/exit_strategy.pl
@@ -0,0 +1,365 @@
+#!/usr/bin/perl -w
+###############################################################################
+# Author: Andreas Romeyke
+# SLUB Dresden, Department Longterm Preservation
+#
+# scans a given repository and creates an SQL script to create a database. 
+# This is part of the exit-strategy for details, see asciidoc file
+# exit_strategie.asciidoc (also contains ER-diagram for database)
+#
+# file tested with postgres-database
+#
+# using:  
+#         psql -U romeyke -d exit_strategy \
+#              -f rosetta_exit_strategy/tmp.sql -L rosetta_exit.log
+#
+###############################################################################
+
+use 5.14.0;
+use strict;
+use warnings;
+use Carp;
+use File::Basename;
+use File::Find;
+use XML::XPath;
+use XML::XPath::XMLParser;
+
+# guarantee, that output will be UTF8
+binmode(STDOUT, ":encoding(UTF-8)");
+my $db_name="exit_strategy";
+my $schema_name="exit_strategy";
+my $sourcetype="hdd"; #default value
+
+###############################################################################
+# write database creation
+# write tables creation
+# scan repository
+#   if IE.xml file found, read its metadata, create SQL add entry
+#   write SQL add entry
+###############################################################################
+sub write_database_creation {
+     # non standard conform SQL keywords
+     #say "CREATE DATABASE $db_name;";
+     #say "CREATE SCHEMA $schema_name;";
+     #say "USE ";
+}
+
+# write tables creation;:
+sub write_tables_creation {
+  # Transactions for tables creation
+  say "BEGIN;";
+
+  # SEQUENCE
+  say "/* create SEQUENCE generator */";
+  say "CREATE SEQUENCE serial START 1;";
+
+  # AIP
+  say "/* create AIP table */";
+  say "CREATE TABLE aip (";
+  say "\tid INT PRIMARY KEY DEFAULT nextval('serial'),";
+  say "\tie_id VARCHAR(30) NOT NULL UNIQUE";
+  say ");";
+  # IEFILE
+  say "/* create IEFILE table */";
+  say "CREATE TABLE metadatafile (";
+  say "\tid INT PRIMARY KEY DEFAULT nextval('serial'),";
+  say "\taip_id INT NOT NULL REFERENCES aip (id),";
+  say "\tlocation VARCHAR(1024) NOT NULL,";
+  say "\tsourcetype VARCHAR(30) NOT NULL";
+  say ");";
+  # DC
+  say "/* create DC table */";
+  say "CREATE TABLE dc (";
+  say "\tid INT PRIMARY KEY DEFAULT nextval('serial'),";
+  say "\taip_id INT NOT NULL REFERENCES aip (id),";
+  say "\telement VARCHAR(30) NOT NULL,";
+  say "\tvalue VARCHAR(1024) NOT NULL";
+  say ");";
+  # FILE
+  say "/* create FILE table */";      
+  say "CREATE TABLE sourcedatafile (";
+  say "\tid INT PRIMARY KEY DEFAULT nextval('serial'), ";
+  say "\taip_id INT NOT NULL REFERENCES aip (id),";
+  say "\tname VARCHAR(1024) NOT NULL";
+  say ");";
+  # LOCAT
+  say "/* create LOCAT table */";            
+  say "CREATE TABLE sourcedatalocat (";
+  say "\tid INT PRIMARY KEY DEFAULT nextval('serial'),";
+  say "\tfile_id INT NOT NULL REFERENCES sourcedatafile (id),";
+  say "\tlocation VARCHAR(1024) NOT NULL,";
+  say "\tsourcetype VARCHAR(30) NOT NULL";
+  say ");";
+  #end transaction
+  say "COMMIT;";
+  return;
+}
+
+###############################################################################
+# Prepare SQL INSERT Statements for AIPs
+###############################################################################
+sub write_prepare_insert {
+  say "BEGIN;";
+  say "PREPARE aip_plan (varchar) AS";
+  say "  INSERT INTO aip (ie_id) VALUES (\$1);";
+  say "PREPARE ie_plan (varchar, varchar, varchar) AS";
+  say "  INSERT INTO metadatafile (aip_id, location, sourcetype) VALUES (";
+  say "    (SELECT id FROM aip WHERE aip.ie_id=\$1), \$2, \$3";
+  say "  );";
+  say "PREPARE file_plan (varchar, varchar) AS";
+  say "  INSERT INTO sourcedatafile (aip_id, name) VALUES (";
+  say "    (SELECT id FROM aip WHERE aip.ie_id=\$1), \$2";
+  say "  );";
+  say "PREPARE locat_plan (varchar, varchar, varchar, varchar) AS";
+  say "  INSERT INTO sourcedatalocat (file_id, location, sourcetype) VALUES (";
+  say "    (SELECT sourcedatafile.id FROM sourcedatafile,aip WHERE";
+  say "    sourcedatafile.aip_id=aip.id AND aip.ie_id=\$1 AND";
+  say "    sourcedatafile.name=\$2), \$3, \$4";
+  say "  );";
+  say "PREPARE dc_plan (varchar, varchar, varchar) AS";
+  say "  INSERT INTO dc (aip_id, element, value) VALUES (";
+  say "    (SELECT id FROM aip WHERE aip.ie_id=\$1), \$2, \$3";
+  say "  );";
+  say "COMMIT;";
+  return;
+}
+
+
+###############################################################################
+# write add SQL entry, expects a hashref which contains ff. params 
+# (foreach file location/copy):
+# INSERT INTO aip (ie_id) VALUES ($ieid);
+# INSERT INTO iefile (aip_id, location, sourcetype) VALUES (
+#       (SELECT id FROM aip where aip.ieid = $ieid), $location, $sourcetype);
+# INSERT INTO file (aip_id, name) VALUES (
+#       (SELECT id FROM aip where aip.ieid = $ieid), $name);
+# INSERT INTO locat (file_id, location, sourcetype) VALUES (
+#       (SELECT file.aip_id FROM file where file.aip_id = aip.id 
+#        AND aip.ie_id=$ieid), $location, $sourcetype)
+# INSERT INTO dc (aip_id, element, value) VALUES (
+#       (SELECT id FROM aip where aip.ieid = $ieid), $element, $value);
+# TODO: needs additional work
+# expects a reference of an hash:
+#    $ret{"filename" } = $filename;
+#     $ret{"title"} = $title;
+#     $ret{"repid"} = $repid;
+#     $ret{"files"} = \@files;
+#     $ret{"dcrecords"} = \@dcrecords;
+###############################################################################
+sub write_addsql {
+  my $refhash = $_[0];
+  my $ieid = basename($refhash->{"filename"},qw/.xml/);
+  say "BEGIN;";
+  say "EXECUTE aip_plan ('$ieid');";
+  # FIXME if multiple locations exists
+  my $iefile = basename($refhash->{"filename"});
+  say "EXECUTE ie_plan ('$ieid', '$iefile', '$sourcetype');";
+  foreach my $location (@{$refhash->{"files"}}) {
+    my $file = basename($location); # FIXME if multiple locations 
+    my $dir = dirname($location);
+    say "EXECUTE file_plan ('$ieid', '$file');";
+    say "EXECUTE locat_plan ('$ieid', '$file', '$location', '$sourcetype' );";
+  }
+  foreach my $dcpair   (@{$refhash->{"dcrecords"}}) {
+    my ($dckey,$dcvalue) = @{$dcpair};
+    # quote ' in dcvalue
+    $dcvalue=~tr/'/"/;
+    say "EXECUTE dc_plan ( '$ieid', '$dckey', '$dcvalue');";
+  }
+  say "COMMIT;";
+  say "\n"; 
+  return;
+}
+
+
+
+###############################################################################
+# add INDEX and other TRICKs to increase performance
+###############################################################################
+sub write_index_creation() {
+  say "-- BEGIN;";
+  say "-- CREATE UNIQUE INDEX aip_index on aip (ie_id);";
+  say "-- COMMIT;";
+  return;
+}
+
+###############################################################################
+# checks if a given string from from a given file contains only utf-8 chars
+# which are compatible to common used databases
+###############################################################################
+sub check_if_db_conform ($$) {
+  my $string = "$_[0]";
+  my $filename = $_[1];
+  if ($string ne '') {
+    if ( not utf8::is_utf8($string)) { 
+      croak "no utf8: '$string' in file '$filename'\n"; 
+    }
+  }#
+  return;
+}
+
+
+###############################################################################
+#
+# /mets:mets/mets:dmdSec[1]/mets:mdWrap[1]/mets:xmlData[1]/dc:record[1]/dc:title[1]
+# /mets:mets/mets:amdSec[1]/mets:techMD[1]/mets:mdWrap[1]/mets:xmlData[1]/dnx[1]/section[1]/record[1]/key[2] 
+# mit ID=Label und Wert = LOCAL
+# dort die ID von techMD (Referenz für Files)
+#
+# Files via /mets:mets/mets:fileSec[1]/mets:fileGrp[1]/mets:file[1]/mets:FLocat[1]
+#
+###############################################################################
+sub parse_iexml {
+  my $filename = $_[0];
+    # create object
+  my $xp = XML::XPath->new (filename => $filename);
+  ############################################
+  # get title
+  my $title = $xp->findvalue('/mets:mets/mets:dmdSec/mets:mdWrap[1]/mets:xmlData[1]/dc:record/dc:title[1]');
+  check_if_db_conform($title, $filename);
+  ############################################
+  # get dc-records
+  my @dcrecords;
+  my $dcnodes = $xp->find('/mets:mets/mets:dmdSec/mets:mdWrap/mets:xmlData/dc:record/*');
+  foreach my $dcnode ($dcnodes->get_nodelist) {
+    my $key = $dcnode->getName(".");
+    my $value = $dcnode->findvalue(".");
+    if (defined $value) {
+      $value=~s/\n/ /g;
+      $value=~s/'/\\'/g;
+    }
+    check_if_db_conform ($value, $filename);
+    my @pair;
+    push @pair, $key;
+    push @pair, $value;
+    push @dcrecords, \@pair;
+  }
+  ############################################
+  # get right representation ID (has a dnx-section with <key id=label>LOCAL</key>)
+  my $repids = $xp->find('/mets:mets/mets:amdSec');
+  my $repid;
+  # FIXME: if only one represenation exists (Qucosa), select this. If there
+  # are more than one, use them with label LOCAL
+  my @repnodes = $repids->get_nodelist;
+
+  $repid = $repnodes[0]->findvalue('@ID' );
+  foreach my $node (@repnodes) {
+    my $id = $node->findvalue('@ID' );
+    check_if_db_conform($id, $filename);
+    #/mets:mets/mets:amdSec[1]/mets:techMD[1]/mets:mdWrap[1]/mets:xmlData[1]/dnx[1]/section[1]/record[1]/key[1]
+    #
+    if ($node->findvalue('mets:techMD/mets:mdWrap/mets:xmlData/dnx/section/record/key[@id=\'label\']') eq 'LOCAL') {                   
+      $repid=$id;
+    }
+    #print XML::XPath::XMLParser::as_string($node), "\n\n"; 
+  }
+  ############################################
+  # get all files of LOCAL representation
+  my @files;
+  my $filegrpnodes = $xp->find('/mets:mets/mets:fileSec/mets:fileGrp');
+  foreach my $filegrpnode ($filegrpnodes->get_nodelist) {
+    #die XML::XPath::XMLParser::as_string($filegrpnode), "\n\n";
+    #die Dumper($filegrpnode);
+    if ($filegrpnode->findvalue('@ADMID') eq $repid) {
+      #die Dumper($filegrpnode);
+      my $filesnodes = $filegrpnode ->find("mets:file/mets:FLocat");
+      foreach my $filesnode ($filesnodes->get_nodelist) {
+        my $value = $filesnode->findvalue('@xlin:href');
+        check_if_db_conform($value, $filename);
+        push @files,  sprintf("%s", $value);
+      }
+    }
+  }
+  my %ret;
+  $ret{"filename" } = $filename;
+  $ret{"title"} = $title;
+  $ret{"repid"} = $repid;
+  $ret{"files"} = \@files;
+  $ret{"dcrecords"} = \@dcrecords;
+  return \%ret;
+}
+
+###############################################################################
+# because ExLibris Rosetta produces filenames of following format:
+# V\d+-IE\d+\.xml
+# e.G.: 
+# V1-IE23891.xml
+# V1-IE94621.xml
+# V2-IE23891.xml
+# …
+# we must find the relevant file with highest V-value, in example the file
+# "V2-IE23891.xml"
+#
+# this function gets an array reference with all possible files of given regEx
+# and returns an array reference with reduced files using only highest V-value
+################################################################################
+sub find_newest_iefile_version ($) {
+  my $files = $_[0];
+  #say "$files=";
+  #say Dumper($files);
+  my %fileshash;
+  foreach my $file (@{ $files } ) {
+    $file=~m/^(.+?V)(\d+)(-IE\d+\.xml)$/;
+    my ($prefix, $version, $suffix) = ($1, $2, $3);
+    if (defined $fileshash{$suffix}) {
+      my ($stored_version, $stored_prefix) = @{ $fileshash{$suffix} };
+      if ($version > $stored_version) {
+        carp "replaced $stored_version with $version of $suffix";
+        my @tmp = ($version, $prefix);
+        $fileshash{$suffix} = \@tmp;
+      }
+    } else {
+        my @tmp = ($version, $prefix);
+        $fileshash{$suffix} = \@tmp;
+    }
+  }
+  # build new array
+  my @newfiles = sort { $a eq $b } map {
+        my $suffix=$_;
+        my ($version, $prefix) = @{ $fileshash{ $suffix } };
+        join ("", $prefix, $version, $suffix);
+  } (keys %fileshash);
+  #say "filtered $files=";
+  #say Dumper(\@newfiles);
+  return \@newfiles;
+}
+
+# begin closure
+{
+  my @files;
+###############################################################################
+# call back function to File::Find
+#
+###############################################################################
+  sub process_sip () {
+    my $file=$File::Find::name;
+    if ($file =~ m/V\d+-IE\d+\.xml$/) {
+      push @files, $file;
+    }
+    return;
+  }
+###############################################################################
+###############################################################################
+############# main ############################################################
+###############################################################################
+###############################################################################
+  my $dir = shift @ARGV;
+  if (defined $dir && -d "$dir") {
+    write_database_creation();
+    write_tables_creation();
+    write_prepare_insert();
+    find(\&process_sip, $dir);
+    # find newest version of files
+    my @sorted_files = sort {$a eq $b} @files;
+    my $files = find_newest_iefile_version ( \@sorted_files );
+    foreach my $file (@{ $files }) {
+      my $ret = parse_iexml($file);
+      write_addsql($ret);
+    }
+    write_index_creation();
+  } else {
+    die "no directory given on commandline"
+  }
+} #end closure
+1;
+
-- 
GitLab