GSB 7.0 Standardlösung

Migrationsanleitung GSB7.2 -> GSB 10.0/10.1

1 Einleitung

Dieses Dokument stellt am Beispiel der Standardlösung Ansätze und Vorgehen zur Migration eines CoreMedia basierten GSB-Mandanten (Version 7.x) vor.

Zielgruppe sind im GSB/CM erfahrene Entwickler und Site-Admins.

Ausgangspunkt für die Migration ist die Implementierung des Mandanten Standardlösung (standardlsg_basis) sowie der redaktionelle Content des Mandanten in einem GSB/CM Content-Repository.

Der Migrationsleitfaden skizziert die Migration typischer GSB-Konfigurationen bzw. -Umsetzungen. Sollten innerhalb eines Mandanten Speziallösungen umgesetzt sein, die bspw. direkt auf CoreMedia Funktionalitäten zugreifen, müssen diese händisch migriert werden. Entsprechende Migrationsschritte sind nicht in diesem Leitfaden enthalten.

2 Struktur eines Mandanten

Die Bestandteile eines GSB-Mandanten können thematisch in 4 Bereiche aufgeteilt werden:

Mandantenprojekt

Das Mandantenprojekt umfasst die einzelnen Artefakte wie Java-Klassen und Konfigurationsdateien, eine definierte Ablagestruktur sowie die build-Prozesse zur Erstellung eines deployfähigen Mandanten-Releases.

Content

Im Themenbereich Content werden die im Repository verwalteten redaktionellen Inhalte betrachtet und die für die redaktionelle Arbeit benötigten User, Rollen und Rechte, Editorkonfigurationen und die Workflows.

GSB-Funktionalitäten

Unter GSB-Funktionalitäten werden Funktionalitäten zusammengefasst, die ihre Daten in der GSB-Datenbank ablegen und über eine Administrationsoberfläche verwalten wie z.B. Gästebücher, Kommentare und Bewertungen sowie Umfragen.

Dokumentation

Die Dokumentationsdateien des Mandanten werden in Dokumentation behandelt.

Wie unterscheidet sich nun ein GSB/OS-Mandant im wesentlichen von einem GSB/CM-Mandanten?

  • Im Mandantenprojekt haben sich das build-Tool, die Ablagestruktur und eine Vielzahl von Artefakten geändert.
  • Der Content wird im GSB/OS nicht mehr in einem CoreMedia-Repository sondern mittels einer OpenSource-Komponente verwaltet.
  • Bei den GSB-Funktionalitäten ändert sich die Administration (Liferay-Portal) sowie die Ablagestruktur in der Datenbank.
  • Dokumentation gibt eine Struktur vor für die Dokumentation des Mandantenreleases wie auch für rein interne Dokumentationsdateien, die nicht mit dem Release ausgeliefert werden. Diese Struktur ist neu im GSB/OS und hat kein Gegenstück im GSB/CM.

Im Folgenden betrachten wir entlang dieser Themenstruktur die Details der Änderungen und die daraus resultierenden Migrationsschritte.

3 Migration des Mandantenprojekts

Der GSB/OS unterscheidet sich sowohl aus fachlicher als auch struktureller Sicht vom GSB/CM. Diese Unterschiede sind auch für die GSB-Mandanten relevant, so dass diese an dieser Stelle kurz vorgestellt werden.

  • Der GSB/OS nutzt Gradle anstelle von Ant für das GSB-Build, so dass die Ablagestruktur innerhalb eines Mandanten geändert wird.
  • Die Java Package-Pfade haben sich geändert

    • Alle GSB-Klassen liegen nicht mehr unterhalb des Pfades de.materna.cms sondern liegen im GSB/OS unterhalb von de.bund.gsb
    • Das Delivery wird über die Site-Webapplikation durchgeführt, so dass der Pfad .cae. auf .site geändert worden ist.
    • Weiterhin ist der Pfad .acegi. auf .security umgestellt worden.

Das Dokumentenmodell ist angepasst worden. Die im GSB 6 als deprecated markierten Dokumenttypen stehen im GSB/OS nicht mehr zur Verfügung.

  • Entfernte Dokumenttypen: BFE4Feedback, BFE4LangVersion, BFE4PDFVersion, BFE4Printversion, ConfigQuery, ConfigUserPrefs, DCHierachical, DynDocQueryEnt, DynDocSearchEnt, FEOrganigram, GenericCatalog, LODAlternate, _LODispatcher, LOGridDiv, LOGridTable, MCHierachical, MCKeyword, MIHierachical, MetaCategory, MetaEnt, MetaIndicator, NavStyleDropDown, NavStyleExplView, PSDiv, PSISB, PSPlain, PSTable, PageStyleClass, PageStyleText, Query, Settings
  • Angepasste Dokumenttypen:

    • Bei LanguageEnt wurde die Property 'metaEnt' entfernt.
    • Bei PageStyleBundle wurde die Property 'styleCompnts' entfernt.
    • Bei PSTemplate wurden die Properties 'screenResolution' und 'pageAlignment' entfernt.
    • Bei MILanguage wurden die Properties 'metaTag', 'tagValue', 'description' und 'descriptiveLangEnt' entfernt.
    • Bei ServiceContact und Employee wurde die Property 'name' in 'sureName' umbenannt, um Kollisionen mit dem Dokumentnamen zu beheben.
    • Bei ConfigNewsletter sind die Properties 'inclCfgQueries' und 'exclCfgQueries' entfernt worden.
    • Die Dokumenttypen HFSCheckboxGrp, HFSRadiobuttonGrp und HFSSelectBox müssen auf die Dokumenttypen HFDCheckboxGrp, HFDRadiobuttonGrp und HFDSelectBox umgeschrieben werden. Dabei kann statt der Property 'checkboxes', 'radiobuttons' bzw. 'options' entweder die Property 'firstChoices' oder 'lastChoices' verwendet werden.

Die GSB/OS Mandanten basieren auf einem Gradle-Projekt. Dieses (Haupt-)Projekt besteht aus weiteren Unterprojekten. Diese sind:

  • content
  • eventdispatcher
  • httpd
  • indexer
  • infrastructure
  • site
  • solr

Entwicklungsumgebungen die Gradle unterstützen, wie z.B. Intellij IDEA oder Eclipse, können das Mandantenprojekt direkt importieren. Der Aufbau eines importierten Mandanten-Projektes anhand der Standardlösung kann dem folgenden Screenshot entnommen werden.

Mandantenprojekt GSBOS IDE Abbildung 1: Mandantenprojekt im GSB/OS

Das Mandanten-Release kann über Gradle-Tasks erstellt werden. Der Task des Hauptprojekts führt die Ergebnisse der übrigen Tasks zusammen und kopiert sie in ein temporäres Release-Verzeichnis. Aus diesem wird ein Zip-Archiv erzeugt, welches das vollständige Release des Mandanten enthält. Das Zip-Archiv befindet sich im Verzeichnis „build/distributions“ im Hauptverzeichnis des Mandanten. Den Inhalt einer solchen Zip-Datei ist in der folgenden Abbildung dargestellt.

Struktur Mandantenrelease GSBOS Abbildung 2. Struktur des Mandantenrelease im GSB/OS

Die Beschreibung der einzelnen Migrationsschritte erfolgt zunächst für übergreifende Aspekte wie z.B. Java-Sourcen und geht dann auf die Unterprojekte ein.

Migrationsschritte

Die Beschreibung der einzelnen Migrationsschritte erfolgt zunächst für übergreifende Aspekte wie z.B. Java-Sourcen und geht dann auf die Unterprojekte ein.

Migrationsschritte

Für die Migration des Mandantenprojekts wird ein Script zur Verfügung gestellt, das als Grundlage für eine halbautomatisierte Migration der Basis eines GSB-Mandanten genutzt werden kann. Es führt die notwendigen Verschiebungen von Dateien sowie Anpassungen der Package-Pfade durch.

Das Skript ist ggf. an die jeweilige Entwicklungs- bzw. Migrationsumgebung anzupassen.

Das vollständige Script findet sich in Anhang A: Script zur halbautomatischen Migration des Mandantenprojekts.

3.1 Erstellung der Struktur des Mandantenprojekts

Als erster Schritt muss die Ablagestruktur des GSB/CM-Mandanten an die neue Ablagestruktur und die build-Scripte des GSB/OS angepasst werden.

Migrationsschritte

Die Erstellung der Ablagestruktur erfolgt über das Migrationsskript "migrate-customers.sh". Die Ausführung des Skripts sieht wie folgt aus:

migrate-customer.sh -c=<customer> -s=<sourceDir> -t=<targetDir>

Hierbei werden folgende Parameter vorrausgesetzt:

  • c [customer]: Der Name des Mandanten
  • s [sourceDir]: Das Ursprungsverzeichnis, z.B. "standardlsg_basis"
  • t [targetDir]: Das Zielverzeichnis, z.B. "standardlsg"

...# Die Mandantenzielverzeichnisse
TARGET_CONFIG_DIR="${TARGET_DIR}/config"
TARGET_JAVA_SITE_DIR="${TARGET_DIR}/src/main/java/de/bund/gsb/customers/${CUSTOMER_LC}/site"
TARGET_RES_DIR="${TARGET_DIR}/src/main/resources"
TARGET_SPRING_DIR="${TARGET_RES_DIR}/spring/customers"
TARGET_SQL_DIR="${TARGET_DIR}/src/main/sql"
TARGET_WEB_DIR="${TARGET_DIR}/src/main/webapp"

# Erstellen der Mandantenzielverzeichnisse
mkdir -p ${TARGET_CONFIG_DIR} ${TARGET_JAVA_SITE_DIR} ${TARGET_RES_DIR} ${TARGET_SQL_DIR} ${TARGET_WEB_DIR} ${TARGET_SPRING_DIR}

# Kopieren der Sourcen
cp -r ${CUSTOMER_JAVA_CAE_DIR}/* ${TARGET_JAVA_SITE_DIR}
cp -r ${CUSTOMER_SQL_DIR}/* ${TARGET_SQL_DIR}
cp -r ${CUSTOMER_CAE_DIR}/* ${TARGET_WEB_DIR}
cp -r ${CUSTOMER_CONFIG_DIR}/* ${TARGET_CONFIG_DIR}

# Verschieben der Springkonfigurationen
mv ${TARGET_WEB_DIR}/WEB-INF/spring/* ${TARGET_SPRING_DIR}
rm -rf ${TARGET_WEB_DIR}/WEB-INF/spring

# Verschieben anderer Konfigurationen
mv ${TARGET_WEB_DIR}/WEB-INF/classes/* ${TARGET_RES_DIR}/rm -rf ${TARGET_WEB_DIR}/WEB-INF/classes
...

Das Migrationsskript orientiert sich an der bisherigen Struktur des Mandantenprojekts. D.h. dass alle migrierten Klassen und Resourcen werden wie bisher alle in einem Verzeichnis abgelegt.

Diese müssen daher manuell in die einzelnen Unterprojekte (site, indexer, etc.) verteilt werden.

Die weiteren Konfigurationen unterhalb des config Ordnerns werden ohne Änderungen in das neue Projekt übernommen. Die darin enthaltenen Konfigurationen für den Apache, EventDispatcher, Indexer, etc. müssen manuell migriert werden.

In den folgenden Abschnitten wird, ausgehend von der bisherigen Struktur eines Mandantenprojekts, auf die automatisch und manuell durchzuführenden Schritte zur Migration eingegangen.

3.2 Automatische Schritte

Durch die Anwendung des Migrationsskriptes werden die Klassen unterhalb von src/java sowie die Konfigurationen und Templates unterhalb von cae in die neuen Strukturen überführt und dabei Ersetzungen vorgenommen.

3.2.1 Java-Klassen

  • Die Ablagestruktur verändert sich auf Grund des Einsatzes des Build-Tools Gradle anstatt Ant

    • Alle Java-Dateien werden von src/java nach src/main/java verschoben
    • Alle Resource-Dateien (*.hbm.xml, *.xml, *.xsl, etc.) werden aus src/java nach src/main/resources verschoben
  • In src/main/java_ und src/main/resources müssen dann die Package-Pfade geändert werden

    • de.materna.cmsde.bund.gsb
    • .cae..site.
    • .acegi..security.
  • Ggf. alte Packagepfade umschreiben (GSB3-GSB5)

    • de.bundonline.basis.web.de.bund.gsb.site.
    • .struts..forms.
  • <Mandant>.gradle und settings.gradle im Mandantenverzeichnis anlegen

Migrationsschritte

Die Verschiebung der Dateien und die Anpassung der Packagepfade erfolgt durch den Aufruf des Scripts migrate-customer.sh. Ein erneuter Aufruf des Scripts nach der ersten Ausführung ist nicht erforderlich.

...
# Entferne CVS Metadaten/Kommentare aus den Java-Files
find ${TARGET_DIR} -name "*.java" -exec perl -pi -e 'BEGIN{undef $/;} s/^\/\*.*?\*\/\n+//sm' {} \;
find ${TARGET_DIR} -name "*.java" -exec sed -i -E \
`# Entferne alle trennenden Plus-Kommentare` \
-e '/^.*\/\*[ +]+\*\/.*$/d' \
`# Entferne vom CVS verwaltete Header` \
-e '/^.*\* (Version|Filespec|Modified by|Date):.*$/d' \
`# Entferne die SCCS ID` \
-e '/^.*SCCS_ID.*$/d' \
`# Entferne das ignorieren von nicht benutzten Methoden` \
-e '/^.*@SuppressWarnings\("unused"\).*$/d' \
`# Entferne die Kommentare hinter geschweiften Klammern` \
-e 's/\} \/\/.*$/}/g' \
`# Ersetze den Methodenzugriff auf getId_() / getName_() durch getId() / getName()` \
-e 's/\.get(Name|Id)_\(\)/.get\1()/g' \
`# Ersetze Coremedia / Materna CapBlobRef durch den neuen Blob-Typen` \
-e 's/com\.coremedia\.cap\.common\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \
-e 's/de\.materna\.cms\.cae\.contentbeans\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \
`# Ersetze de.materna.cms.cae. durch de.bund.gsb.site.` \
-e 's/de\.materna\.cms\.cae\./de.bund.gsb.site./g' \
`# Ersetze de.materna.cms. durch de.bund.gsb.` \
-e 's/de\.materna\.cms\./de.bund.gsb./g' \
`# Ersetze .acegi. durch .security.` \
-e 's/\.acegi\./.security./g' \
`# Ersetze .customers.<Mandant>.cae durch .customers.<Mandant>.site` \
-e 's/\.customers\.([^.]*)\.cae/.customers.\1.site/g' \
`# Ersetze commons-lang escapeJavaScript durch commons-lang3 escapeEcmaScript` \
-e 's/org\.apache\.commons\.lang\.StringEscapeUtils\.escapeJavaScript/org.apache.commons.lang3.StringEscapeUtils.escapeEcmaScript/g' \
`# Ersetze HTML-Entities durch UTF-8 Zeichen` \
-e 's/&auml;/ä/g' \
-e 's/&ouml;/ö/g' \
-e 's/&uuml;/ü/g' \
-e 's/&Auml;/Ä/g' \
-e 's/&Ouml;/Ö/g' \
-e 's/&Uuml;/Ü/g' \
-e 's/&szlig;/ß/g' \

-e 's/INDEXER_COREMEDIA_COREPROPERTY_CUSTOMER/INDEXER_COREPROPERTY_CUSTOMER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_DOCUMENT_TYPE/INDEXER_COREPROPERTY_DOCUMENT_TYPE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_FOLDER_ID/INDEXER_COREPROPERTY_FOLDER_ID/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_FOLDER_PATH/INDEXER_COREPROPERTY_FOLDER_PATH/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LANGUAGE/INDEXER_COREPROPERTY_LANGUAGE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_MODIFICATION_DATE/INDEXER_COREPROPERTY_MODIFICATION_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_NAME/INDEXER_COREPROPERTY_NAME/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_PATH/INDEXER_COREPROPERTY_PATH/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_INDEX_DATE/INDEXER_COREPROPERTY_INDEX_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_SOURCE/INDEXER_COREPROPERTY_SOURCE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_VERSION/INDEXER_COREPROPERTY_VERSION/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CREATOR/INDEXER_COREPROPERTY_CREATOR/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CREATION_DATE/INDEXER_COREPROPERTY_CREATION_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_MODIFIER/INDEXER_COREPROPERTY_MODIFIER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LAST_PUBLISHER/INDEXER_COREPROPERTY_LAST_PUBLISHER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_PUBLISHED_VERSION/INDEXER_COREPROPERTY_PUBLISHED_VERSION/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LAST_APPROVER/INDEXER_COREPROPERTY_LAST_APPROVER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_/INDEXER_COREPROPERTY_/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CONTENT_ID/INDEXER_COREPROPERTY_CONTENT_ID/g' \
-e 's/AbstractCoreMediaAction/AbstractAction/g' \
-e 's/de.bund.gsb.site.database.ratings.BkcmsRating/de.bund.gsb.site.rest.rating.RatingConstants/g' \
-e 's/BkcmsRating.PARAM_COMMENT/RatingConstants.PARAM_COMMENT/g' \
-e 's/BkcmsRating.PARAM_RATING/RatingConstants.PARAM_RATING/g' \
-e 's/BkcmsRating.PARAM_RATING_EMAIL/RatingConstants.PARAM_RATING_EMAIL/g' \
-e 's/BkcmsRating.PARAM_RATING_NAME/RatingConstants.PARAM_RATING_NAME/g' \
-e 's/BkcmsRating.PARAM_DOCID/RatingConstants.PARAM_DOCID/g' \
-e 's/BkcmsRating.PARAM_PARENTID/RatingConstants.PARAM_PARENTID/g' \
{} \;

...

3.2.2 JSP-Templates

  • Die Ablagestruktur verändert sich auf Grund des Einsatzes des Build-Tools Gradle anstatt Ant

    • Alle Webapp-Inhalte werden von cae nach src/main/webapp verschoben
    • Alle Inhalte von cae/WEB-INF/classes werden nach src/main/resources verschoben
  • In allen Resourcen müssen die Package-Pfade angepaßt werden

    • de.materna.cmsde.bund.gsb
    • .cae..site.
    • .acegi..security.
  • Ggf. alte Packagepfade umschreiben (GSB3-GSB5)

    • de.bundonline.basis.web.de.bund.gsb.site.
    • .struts..forms.

Migrationsschritte

Die Verschiebung der Dateien und die Anpassung der Packagepfade erfolgt durch den Aufruf des Scripts migrate-customer.sh. Ein erneuter Aufruf des Scripts nach der ersten Ausführung ist nicht erforderlich.

...
# Entferne CVS Metadaten/Kommentare aus den JSP-Files
find ${TARGET_WEB_DIR} -name "*.jsp" -exec perl -pi -e 'BEGIN{undef $/;} s/^(<%--)+\n.*?\n--%>//smg' {} \;
find ${TARGET_WEB_DIR} -name "*.jsp" -exec sed -i -E \
`# Ersetze Coremedia / Materna CapBlobRef durch den neuen Blob-Typen` \
-e 's/de\.materna\.cms\.cae\.contentbeans\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \
-e 's/com\.coremedia\.cap\.common\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \
`# Ersetze de.materna.cms.cae.struts. durch de.bund.gsb.site.forms.` \
-e 's/de\.materna\.cms\.cae\.struts\./de.bund.gsb.site.forms./g' \
`# Ersetze de.materna.cms.cae. durch de.bund.gsb.site.` \
-e 's/de\.materna\.cms\.cae\./de.bund.gsb.site./g' \
`# Ersetze de.materna.cms. durch de.bund.gsb.` \
-e 's/de\.materna\.cms\./de.bund.gsb./g' \
`# Ersetze .acegi. durch .security.` \
-e 's/\.acegi\./.security./g' \
`# Ersetze commons-lang escapeJavaScript durch commons-lang3 escapeEcmaScript` \
-e 's/org\.apache\.commons\.lang\.StringEscapeUtils\.escapeJavaScript/org.apache.commons.lang3.StringEscapeUtils.escapeEcmaScript/g' \
`# Ersetze .docType.name bzw. type.name durch .docTypeName` \
-e 's/\.(doctype|type\.name)/.docTypeName/g' \
`# Ersetze .theDirections durch .theDirection` \
-e 's/\.theDirections/.theDirection/g' \
`# Ersetze .FAQsData durch .FAQData` \
-e 's/\.FAQsData/.FAQData/g' \
`# Ersetze .intId durch .id` \
-e 's/\.intId/.id/g' \
`# Entferne .content` \
-e 's/(self|entry|docRef|doc|beanLabel)\.content\./\1./g' \
`# Ersetze .parent.path durch .folder.path` \
-e 's/\.parent\.path/.folder.path/g' \
`# Entferne die Unterstriche für den Zugriff auf Content Properties` \
-e 's/\.(id|name)_/.\1/g' \
-e 's/\.(approvalDate|creationDate|modificationDate|publicationDate)_/.\1/g' \
-e 's/\.(approver|creator|modifier|publisher)_/.\1/g' \
`# Entferne den Zugriff auf den Namen, im GSB/OS sind die Felder einfache Strings` \
-e 's/(approver|creator|modifier|publisher)\.name/\1/g' \
`# Ersetze cms:getContentBean durch cms:getContentBeanFromPath` \
-e 's/cms\:getContentBean\(/cms\:getContentBeanFromPath(/g' \
`# Ersetze <logic:... > und <html:...> Tags durch <cms:... > Tags` \
-e 's/<(logic|html)\:/<cms\:/g' \ -e 's/<\/(logic|html)\:/<\/cms\:/g' \
`# Ersetze .customers.<Mandant>.cae durch .customers.<Mandant>.site` \
-e 's/\.customers\.([^.]*)\.cae/.customers.\1.site/g' \
`# Ersetze HTML-Entities durch UTF-8 Zeichen oder XML-Entities` \
-e 's/&auml;/ä/g' \
-e 's/&ouml;/ö/g' \
-e 's/&uuml;/ü/g' \
-e 's/&Auml;/Ä/g' \
-e 's/&Ouml;/Ö/g' \
-e 's/&Uuml;/Ü/g' \
-e 's/&szlig;/ß/g' \
-e 's/\&nbsp;/\&#160;/g' \
-e 's/\&hellip;/\&#8230;/g' \
-e 's/\&euro;/\&#8364;/g' {} \;
...

3.3 Manuelle Tätigkeiten

Nachdem das Migrationsskript ausgrführt wurde liegen die migrierten Dateien im neuem Project im Ordner src. Die nicht migrierten Dateien, wie die Inhalte des Ordners config wurden in das neue Projekt kopiert.

Da im GSB 10 jede Komponente in einem eigenen Unterprojekt gepflegt wird, müssen die migrierten Dateien noch in die richtigen Unterprojekte verschoben werden.

Migrationsschritte

Die Verschiebung der Dateien wird in den folgenden Abschnitten, ausgehend von den jeweiligen Ausgangsverzeichnissen, beschrieben.

3.3.1 cae

Die Inhalte des cae Ordners müssen in das site Unterprojekt überführt werden.

Im Site-Unterprojekt sind die Templates des Mandanten sowie die mandantenspezifischen Erweiterungen der site Webapplikation abgelegt.

Die site.gradle sieht vor dass neben den Spring-Konfigurationen die nach „WEB-INF/classes“ kopiert werden und den kompilierten Klassen die nach „WEB-INF/lib“ kopiert werden, auch die separat abgelegten Templates berücksichtigt werden.

Die Konfigurationen die im Verzeichnis cae/WEB-INF/classes und cae/WEB-INF/spring lagen müssen für den GSB 10 angepasst werden. Viele Spring-Konfigurationen wurden hier als Snippets abgelegt, welche durch den build-Prozess zusammengeführt wurden. Dieser Mechanismuss wird im GSB 10 nicht länger genutzt. Die Inhalte der mandantenspezifischen Spring-Konfigurationen müssen daher in eigene Dateien überführt werden. Details zu den benötigten Konfigurationsdateien erhalten sie in der Entwicklerdokumentation.

Eine Erweiterung der Logging-Konfiguration durch einen Mandanten ist nicht länger vorgesehen. Das logback-Snippet des Mandanten kann daher ersatzlos gelöscht werden.

Das Hibernate-Snippet ist ebenfalls überflüssig, da die Konfiguration nun per Annotations erfolgt.

3.3.2 config

Der config Ordner im GSB Mandanten enthält u.a. Konfigurationen für den Indexer und den EventDispatcher. Diese müssen in die jeweiligen Unterprojekte indexer und eventdispatcher überführt werden.

Weitere Informationen hierzu sind sind in den Kapiteln Unterprojekt eventdispatcher und Unterprojekt indexer zu finden.

Migrationsschritte

Beim Indexer und Eventdispatcher müssen die vorhandenen Spring-Konfigurationen mit Hilfe der Vorlagen aus der standardlsg an das neue Format angepasst werden und dann in dem jeweiligen Unterprojekt in das src/main/resources Verzeichnis verschoben werden.

Eventuell vorhandene Properties-Dateien können in die Runtime-Konfiguration für die jeweilige Komponente übernommen werden. Die Runtime-Konfigurationen liegen im Unterprojekt infrastructure.

Die weiteren Inhalte des infrastructure Unterprojekts sind hier näher beschrieben: Unterprojekt infrastructure.

3.3.3 src

Die aus dem src Verzeichnis migrierten Java-Klassen sind für verschiedene Komonenten vorgesehen.

Migrationsschritte

Die Java-Klassen für die jeweiligen Komponenten lassen sich anhand des Package-Pfads identifizieren. Die Klassen der site lassen sich z.B. anhand des Packages site identifizieren.

Nachdem die Java-Klassen in die einzelnen Unterprojekte verschoben wurden, muss deren kompilierbarkeit gewährleistet werden. Hierbei müssen gegebenenfalls Abhängigkeiten korrigiert werden.

Hibernate-Klassen müssen auf Annotationen umgestellt werden. Als Vorlage kann z.B. die Klasse BkcmsCustomer aus der Basis dienen.

3.4 Tomcat 8 Anpassungen

Der Tomcat 8 verwendet Java-Server-Pages in der Version 2.3, Tomcat 7 dagegen die JSP-Version 2.2. Die JSP Version 2.3 nutzt die Unified-Expression-Language 3.0 welche die Referenzierung von statischen Feldern und Methoden unterstützt. Hierfür wurde die ScopedAttributeELResolver Implementierung angepasst, damit dieser überprüft ob sich hinter einem Identifier der Name einer importierten Klasse oder eines Feldes verbirgt. Bei Identifiern deren Scope (page, request, session oder application) nicht mit angegeben ist und die in keinem Context definiert wurden, kann dies zu erheblich längeren Ladezeiten führen (s.a. https://tomcat.apache.org/migration-8.html#JavaServer_Pages_2.3) Daher sollte in den JSP-Templates bei Identifieren der passende Scope mit angegeben werden. Dies gilt insbesondere dann, wenn nicht gesichert ist, dass diese in einem bestimmten Kontext definiert wurden.

Migrationsschritte

Die Umstellung der JSP-Templates muss manuell durchgeführt werden und kann nicht pauschal und automatisiert durchgeführt werden, so dass an dieser Stelle nur allgemeine Hinweise zur Analyse und Migration gegeben werden können.

Das folgende Beispiel skizziert die notwendige Umstellung exemplarisch an einem Identifier (cssClassSize) mit Page-Scope:

Identifier ohne Scope:

<c:set var="cssClassSize" value="${requestScope['cssAttribute']}" scope="page"/>
<c:if test="${fn:length(cssClassSize) <=0}">
<c:set var="cssClassSize" value="l" scope="page"/>
</c:if>
<div class="form-item ${cssClassSize}">

Identifier mit Scope:

<c:set var="cssClassSize" value="${requestScope['cssAttribute']}" scope="page"/>
<c:if test="${fn:length(pageScope.cssClassSize) <=0}">
<c:set var="cssClassSize" value="l" scope="page"/>
</c:if>
<div class="form-item ${pageScope.cssClassSize}">

Hinweis Die Funktionalität des Mandanten und der JSP-Templates ist auch ohne Umstellung gegeben, so dass die skizzierte Umstellung nicht zwingend im Mandanten durchgeführt werden muss. Allerdings empfiehlt es sich die Umstellung im Mandanten aufgrund der zu erwartenden Performanceeinbrüche bei fehlenden Scopes durchzuführen.

3.4.1 Konfiguration

Bei der Konfiguration des Mandanten ergeben sich Änderungen in folgenden Properties:

  • site_specific_templates

Subsite-spezifische Templates. Dieses Feature muss in den customer.properties der betroffenen Mandanten explizit mit dem Eintrag site_specific_templates=true aktiviert werden. Die Subsite-spezifischen Templates werden dann vorrangig unter customer/MANDANT/sites/SITE gesucht.

  • customer_specific_cae_login_domain

Die LDAP-Domäne des Benutzers kann jetzt im Loginformular ausgewählt werden. Dazu ist ein Eingabefeld oder eine Selectbox mit dem idName = j_domain erforderlich. Die erlaubten Domains müssen in den customer.properties unter customer_specific_cae_login_domain in Form einer Semikolon-separierten Liste eingetragen sein. Wenn dort nur ein einziger Wert steht, ist eine Angabe der Domain im Loginformular überflüssig.

  • cae_case_insensitive_db_users

Der Benutzername von Datenbank-Usern wird jetzt case-insensitiv gelesen, wenn in den customer.properties des Mandanten der Eintrag cae_case_insensitive_db_users=true steht.

  • customer_specific_cae_template_base_customer

Mandanten können als Fallback die Templates eines anderen Mandanten verwenden. Dieser muss in den customer.properties unter customer_specific_cae_template_base_customer eingetragen sein. Templates von Komponenten-Mandanten, die in den customer.properties mit component_customer=true gekennzeichnet sind, werden zwischen den Templates des eigentlichen Mandanten und den Default-Templates gesucht.

  • customer_specific_cae_overload_error_url

Für jeden Mandanten läßt sich in den customer.properties eine Zielseite konfigurieren, welche angezeigt wird, wenn die Anzahl an gleichzeitig erlaubten Requests überschritten ist: customer_specific_cae_overload_error_url=/static/Mandant/Ueberlastet.html

Migrationsschritte

Der jeweilige Eintrag ist ggf. manuell in der customer.properties zu setzen.

3.5 Unterprojekt eventdispatcher

Der EventDispatcher Konfiguration eines Mandanten wird in dem Unterprojekt eventdispatcher definiert. In dem Unterprojekt befinden sich sowohl die Konfiguration (Spring-Beans) als auch die optionalen mandantenspezifischen Implementierungen.

Der Aufbau und Inhalt sind im GSB 7 und GSB 10 ähnlich so dass eine Migration der betreffenden Konfigurationen und der optional vorhandenen Implementierung einfach durchgeführt werden kann.

3.5.1 GSB 7

Die EventDispatcher Konfiguration im GSB 7 und die optionalen mandantenspezifischen Implementierungen liegen in unterschiedlichen Verzeichnissen (Pfade für den Mandanten standardlsg):

  • standardlsg_basis/config/EventDispatcher/config/standardlsg-Performer.xml
  • standardlsg_basis/src/java/de/materna/cms/customers/standardlsg/eventdispatcher

Die optionalen Java-Klassen und die Spring-Konfigurationen werden vom GSB-Build-Prozess aufgesammelt und in die EventDispatcher Applikation hineinkopiert.

3.5.2 GSB 10

Im EventDispatcher-Unterprojekt werden mandantenspezifische Erweiterungen des Eventdispatchers implementiert. Außerdem werden hier die Spring-Konfigurationen zum EventDispatcher des Mandanten abgelegt. Für den Mandanten standardlsg sind hierbei folgende Pfade relevant:

  • standardlsg/src/main/resources/spring/customers/standardlsg-performer.xml
  • standardlsg/src/main/java/de/bund/gsb/customers/standardlsg/eventdispatcher

In der standardlsg ist eine Beispiel-Implementierung für einen mandantenspezifischen Performer vorhanden:

StandardlsgPerformer.java

/*
* Copyright (c) 2018 Materna GmbH, Germany
*/
package de.bund.gsb.customers.standardlsg.eventdispatcher;

import de.bund.gsb.content.events.ContentEvent;
import de.bund.gsb.eventdispatcher.performer.Performer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StandardlsgPerformer extends Performer {
private Logger log = LoggerFactory.getLogger(getClass());

@Override
public void perform(ContentEvent event) {
if (shouldEventBePerformed(event)) {
log.info("Fuehre die Action fuer Mandant '{}' im StandardlsgPerformer aus.", event.getCustomer());
}
else {
log.debug("Fuehre die Action fuer Mandant '{}' im StandardlsgPerformer nicht aus.", event.getCustomer());
}
}
}

3.6 Unterprojekt httpd

Die Webserver Konfiguration eines GSB Mandanten wird in dem Gradle Subproject httpd definiert. Der Aufbau der mandantenspezifischen Webserver Konfiguration unterscheidet sich zwischen dem GSB 7 und GSB 10 deutalich, da im GSB 10 komplette VirtualHost Definitionen für die Websites eines Mandanten definiert werden.

3.6.1 GSB 7

Die Webserver Konfiguration im GSB 7 wird über das GSB Deployment und entsprechenden Build-Properties erstellt. Die Definitionen finden sich im Mandanten im Verzeichnis config/Apache. Unterhalb dieses Verzeichnisses befinden sich Unterordner

  • static enthält statische Inhalte, die direkt vom Webserver (und nicht über die Delivery-Server) ausgeliefert werden.
  • VirtualHostsLive enthält die Vhost-Konfigurationen für die Liveauslieferung des Mandanten
  • VirtualHostsPreview enthält die Vhost-Konfigurationen für die Auslieferung im Redaktionssystem (Preview und Editor)
  • VirtualHostsStagingLive Konfigurationen für verschiedene Umgebungen können neben der Produktivumgebung (VirtualHostsLive) definiert werden. Die Definition einer Staging-Umgebung als Beispiel ist optional.
  • VirtualHostsStagingPreview Konfigurationen für verschiedene Umgebungen können neben der Produktivumgebung (VirtualHostsPreview) definiert werden. Die Definition einer Staging-Umgebung als Beispiel ist optional.

In den einzelnen VirtualHosts-Ordnern liegen Konfigurationsdateien standardlsg.properties mit Build-Properties die wie folgt aufgebaut sind:

Konfigurationsdatei standardlsg.properties der Liveumgebung (VirtualHostsLive)

mandant=standardlsg
isSslEnabled=false
secure_session_cookies=false
server_name=www.standardlsg.domain.example
virtual_host_name=www.standardlsg.domain.example
virtual_host_port=80
virtual_host_cae=true
#additional_server_names=standardlsg.domain.example
#custom_config_file=standardlsg_custom_config.txt

Bei eine Migration auf den GSB 10 müssen die statischen Inhalte sowie die skizzierten Konfigurationen migriert werden.

3.6.2 GSB 10

Das HTTPD-Unterprojekt beinhaltet die Konfiguration für den Apache HTTPD. Das sind die virtuellen Hosts des Mandanten für die Preview- und die Live-Umgebung.

Das Basisverzeichnis für die mandantenspezifischen Webserver-Konfigurationen ist httpd/src/main/dist/conf. Unterhalb dieses Verzeichnisses spannt sich ein Verzeichnisbaum auf, der wie folgt aufgebaut ist:

  • variables enthält Variablendefinitionen, die für die Konfiguration der mandantenspezifischen Virtual-Hosts benötigt werden.
  • vhosts ist das Basisverzeichnis in dem die mandantenspezifischen Virtual-Hosts abgelegt werden.

    • live enthält alle Virtual-Hosts der Liveauslieferung.
    • preview enthält alle Virtual-Hosts der Redaktionsumgebung (Preview, Editor).

Die Virtual-Hosts eines Mandanten sind alle ähnlich aufgebaut und wird anhand der Liveauslieferung der Standardlösung vorgestellt.

VirtualHost-Definition standardlsg_lvie_vhost.conf der Liveauslieferung

<VirtualHost *:${HttpPort}>
ServerName ${standardlsg-live-servername}
ServerAlias ${standardlsg-live-serveralias}
use activateStdLogging standardlsg
use disableRequestMethodsPreview
use enableDeflate
use enableStaticContent standardlsg
use httpsSupport
#use subSite subsiteName
use gsbRewriteLive standardlsg
</VirtualHost>

Wie dem obigen Beispiel entnommen werden kann, wird jede Website als vollwertiger Virtual-Host definiert. Die Regeln des Virtual-Hosts (Rewrite, etc.) werden nicht direkt in die Virtual-Host-Definitionen aufgenommen, sondern mit Hilfe von Apache Macros definiert. Der GSB stellt hierfür einen Satz von Macros zur Verfügung, die in dem Virtual-Hosts der Mandanten genutzt werden können.

Darüber hinaus werden Server- und Alias-Namen über Variablen definiert, die über eine mandantenspezifische Konfigurationsdatei mit Default-Werten belegt werden. Die folgende Konfigurationsdatei skizziert den Aufbau anhand des Mandanten standardlsg

Konfigurationsdatei standardlsg_variables.conf

##############################################################################
# Preview-Zone
# Preview

Define standardlsg-preview-servername preview.standardlsg.example.comDefine standardlsg-preview-serveralias# EditorDefine standardlsg-editor-servername editor.standardlsg.example.comDefine standardlsg-editor-serveralias# Subsite "subsite"Define subsite-standardlsg-preview-servername preview-subsite.standardlsg.example.com


##############################################################################
# Live-Zone
Define standardlsg-live-servername live.standardlsg.example.com
Define standardlsg-live-serveralias

Wie dem Beispiel entnommen werden kann, korrespondieren die Variablennamen in den Virtual-Hosts (bspw. standardlsg-live-servername und standardlsg-live-serveralias) mit den Definitionen der Datei standardlsg_variables.conf. Die Live-Auslieferung der Standardlösung ist auf den Servernamen live.standardlsg.example.com konfiguriert und die Liste der Aliasnamen ist leer.

3.6.3 httpd-Migration

Wie in den beiden vorherigen Kapiteln skizziert, müssen die GSB 7 Konfigurationen auf den GSB 10 abgebildet werden. Hierzu bietet es sich an, den Mandanten standardlsg als Grundlage für einen zu migrierenden Mandanten zu nehmen, um direkt schon die benötigte Verzeichnis- und Dateistruktur anzulegen. Beim Anlegen der Strukturen muss darauf geachtet werden, dass der Name des Mandanten von standardlsg auf den Namen des Zielmandanten geändert wird. Dies gilt sowohl für die Datei- und Verzeichnisnamen als auch die Dateiinhalte.

Nachdem die Verzeichnisstruktur angelegt ist, können die GSB 7 Definitionen einfach wie folgt transformiert werden.

  • static-Content: Die statischen Inhalte werden im GSB 10 nicht über das GSB Deployment im static-Bereich des Webservers abgelegt. Diese Inhalte ändern sich nur sehr selten, so dass diese manuell durch den Applikationsbetrieb der Hostingplattform im Webserver abgelegt werden müssen.
  • Build-Property server_name: Der Wert der Property ist in die Konfigurationsdatei <CUSTOMER>_variables.conf zu übernehmen. Die entsprechende Variable aus VirtualHostsLive wird unter <CUSTOMER>-live-servername und die Variable aus VirtualHostsPreview wird unter <CUSTOMER>-preview-servername definiert.
  • Build-Property additional_server_name: Der Wert der Property ist in die Konfigurationsdatei <CUSTOMER>_variables.conf zu übernehmen. Die entsprechende Variable aus VirtualHostsLive wird unter <CUSTOMER>-live-serveralias und die Variable aus VirtualHostsPreview wird unter <CUSTOMER>-preview-serveralias definiert.
  • Build-Property secureSessionCookie: Wenn diese Variable true ist dann muss im Mandangen das Macro secureSessionCookieAttribute zusätzlich definiert werden.
  • Inhalte von custom_config_file: Es empfiehlt sich die Inhalte der referenzierten Datei als mandantenspezifische Macros im Mandanten abzulegen. Die Macros werden im Mandanten im Verzeichnis httpd/src/main/dist/conf/macro abgelegt. Die Dateinamen müssen über alle Mandanten eindeutig sein, so dass folgendes Namensschema verwendet werden sollte <CUSTOMER>_macro.conf.
Hinweis:
Eine Unterstützung für verschiedene Umgebungen (Produktiv, Staging, etc.) innerhalb des GSB-Mandanten steht nicht mehr zur Verfügung. Wenn ein Mandant in verschiedenen Umgebungen betrieben werden soll, dann können umgebungsspezifische Anpassungen (bspw. Server-Namen) in der Runtime-Konfiguration des Webservers erfolgen. Diese wird durch den jeweiligen Applikationsbetrieb erstellt und gepflegt.

3.7 Unterprojekt indexer

Die Indexer Konfiguration eines GSB Mandanten wird in dem Gradle Subproject indexer definiert. In den Subproject befindet sich sowohl die Indexer Konfiguration (Spring-Beans) als auch eine optional vorhandene mandantenspezifische Indexer Implementierung (bspw. Contentmapper oder Indexer).

Der Aufbau und Inhalt sind im GSB 7 und GSB 10 ähnlich so dass eine Migration der betreffenden Konfigurationen und der optional vorhandenen Implementierung einfach durchgeführt werden kann.

3.7.1 GSB 7

Die Indexer Konfiguration und die ggf. vorhandene mandantenspezifische Implementierung sind getrennt abgelegt und finden sich unterhabl der folgenden Verzeichnisse (am Beispiel des Mandanten standardlsg):

  • config/Indexer/config/ enthält die Indexer Definitionen des Mandanten (als Spring-Beans). In der Standardlösung ist dies die Datei standardlsg-solr-indexer.xml
  • src/java/de/materna/cms/customers/standardlsg/indexer/solr enthält ggf. vorhandene mandantenspezifische Indexer oder Contentmapper Implementierungen
Hinweis:
Der Mandant standardlsg enthält keine mandantenspezifischen Implementierungen. Diese sind nur erforderlich, wenn Sonderlogiken für die Indizierung oder Contentextraktion und -aufbereitung erforderlich sind. In der Regel können die vom GSB Kern bereitgestellten Implementierungen genutzt werden, so dass in diesem Falll der Mandant nur die Indexer Konfiguration enthält.

3.7.2 GSB 10

Das Subproject indexer enthält sowohl die Konfiguration als auch die ggf. vorhandenen mandantenspezifischen Implementierungen. Diese liegen in den folgenden Verzeichnissen:

  • indexer/src/main/java/de/bund/gsb/customers/standardlsg enthält die mandantenspezifische Implementierung
  • indexer/src/main/resources/spring/customers enthält die Indexer Konfigurationen des Mandanten
Hinweis:
Der GSB 10 nutzt Solr sowohl für die Suche auf den GSB Websites als auch für die Suche im Redaktionssystem über den GSBEditor, so dass für die beiden Suchen jeweils eigenständige Indexer Konfigurationen im Mandanten zu definieren sind. Die Standardlösung enthält mit standardlsg-solr-indexer.xml die Konfiguration der Website-Suche un mit standardlsg_editor-solr-indexer.xml die Konfiguration der Suche im GSBEditor.

3.7.3 indexer-Migration

Wie in den beiden vorherigen Kapiteln skizziert, müssen die GSB 7 Konfigurationen und Implementierungen des Mandanten auf den GSB 10 abgebildet werden. Hierzu bietet es sich an, den Mandanten standardlsg als Grundlage für einen zu migrierenden Mandanten zu nehmen, um direkt schon die benötigte Verzeichnis- und Dateistruktur anzulegen. Beim Anlegen der Strukturen muss darauf geachtet werden, dass der Name des Mandanten von standardlsg auf den Namen des Zielmandanten geändert wird. Dies gilt sowohl für die Datei- und Verzeichnisnamen als auch die Dateiinhalte. Wie in dem folgeden Ausschnitt ersichtlich müssen insbeondere die für die Indexierung relevaten Pfade (includedDirectories und excludedDirectories) und die Adresse des Solr-Server inkl. des Solr-Kerns des Mandanten unter serverAddress angepasst werden.

Ausschnitt aus der standardlsg-solr-indexer.xml

<bean id="standardlsgIndexer"
class="de.bund.gsb.indexer.writer.SolrIndexer"
parent="baseIndexer"
scope="singleton"
init-method="init">
<property name="serverAddress" value="http://${indexer.solr.tomcat.host}:${indexer.solr.tomcat.port}/solr/standardlsg" />
<property name="contentMapper" ref="standardlsgContentMapper" />
<property name="source" value="repository" />
<property name="includedDirectories" value="/standardlsg/.*" />
<property name="excludedDirectories" value="/standardlsg/_config/Editor/.*|/standardlsg/__EditorConfig/.*" />
<property name="indexingRights" value="false" />
<property name="indexingCugRights" value="true"/>
<property name="reindexQuery" value="standardlsg//element(*,gsb:AbsDocument)" />
<property name="queryType" value="xpath" />
</bean>

Nachdem die Verzeichnisstruktur angelegt ist, können die GSB 7 Definitionen einfach wie folgt transformiert werden.

  • Konfiguration: Die Konfiguration des Indexers kann für die einzelnen Properties übernommen werden, da der Aufbau und der Inhalt der konfigurativen Properties identisch ist.
  • Implementierung: Die Implementierung muss aus der zentralen Ablage der GSB 7 Sourcen in das indexer-Subproject übernommen werden. Hier muss die in den einleitenden Kapiteln definierte Herangehensweise berücksichtigt werden und insbesondere der Package-Pfad GSB 10 konform angepasst werden.

3.8 Unterprojekt infrastructure

Das infrastructure-Subproject im GSB 10 steht so im GSB 7 nicht zur Verfügung, so dass entsprechende Definitionen nicht vom GSB 7 in den GSB 10 migriert werden müssen. Das Subproject dient im Wesentlichen als Sammelbecken für verschiedene infrastrukturelle Definitionen die in folgenden Unterordnern abgelegt sind (Basisverzeichnis infrastructure/src/main/dist/):

  • etc kann eine Datei hosts enthalten die als "Schnipsel" für eine manuelle Übernahme in die Hosts Datei eines GSB Servers genutzt werden kann. Die Datei ist optional und hat nur Dokumentationscharakter. In dieser Datei können bspw. alle externen Dienstnamen des Mandanten aufgenommen werden.
  • ldap enthält LDIF-Dateien für die Definition von Benutzern und -Gruppen des Redaktionssystems/Editor und des Adminportal. Die entsprechenden GSB 7 Benutzer und Gruppen müssen manuell in ein LDIF-Format transformiert werden, um dieses anschließend per LDIF-Import in den zugehörigen LDAP-Server laden zu können.
  • runtime bietet die Möglichkeit mandantenspezifische Runtime-Konfigurationen für GSB-Webapplikationen zu definieren. Die entsrpechenden Definitionen der Properties können dann durch den Applikationsbetrieb genutzt werden, um die Runtime-Konfiguration der Hostingplattform entsprechend zu erweitern. Die Übernahme der mandantenspezifischen Definitionen in die Runtime-Konfiguration einer Hostingplattform ist ein manueller Prozess.
  • security enthält Rechtedefinitionen für das Redaktionssystem. Die Definitionen sind durch den Applikationsbetrieb bei der erstmaligen Einspielung eines Mandanten auf einer Hostingplattform zu importieren (s.a. Installationsanleitung)

3.8.1 infrastructure-Migration

Wie im vorherigen Kapitel skizziert, müssen die GSB 7 Konfigurationen des Mandanten auf den GSB 10 abgebildet werden. Hierzu bietet es sich an, den Mandanten standardlsg als Grundlage für einen zu migrierenden Mandanten zu nehmen, um direkt schon die benötigte Verzeichnis- und Dateistruktur anzulegen. Beim Anlegen der Strukturen muss darauf geachtet werden, dass der Name des Mandanten von standardlsg auf den Namen des Zielmandanten geändert wird. Dies gilt sowohl für die Datei- und Verzeichnisnamen als auch die Dateiinhalte.

Die folgenden Inhalte sind zu migrieren:

  • LDIF-Dateien: Die entsprechenden Dateien für die Redakteure müssen manuell bei einer Migration Wie im vorherigen Kapitel skizziert müssen bei einer Migration migriert werden. Die entsprechenden Definitionen für das Adminportal sind mandantenspezifisch neu zu erstellen. Als Vorlage und Grundlage für die Migration der Definitionen für redaktionelle Benutzer und Gruppen kann die Datei standardlsg.ldif des Mandanten standardlsg dienen.
  • Editor Rechtedefinitionen: Die Editor-Rechtedefintionen des GSB 7 können mit den vorhandenen Tools des zugrundeliegenden CMS als Datei exportiert werden. Anschließend kann diese Datei mit der XSL-Transformation convertRights.xsl in ein GSB 10 Format transformiert werden.

XSL-Transformation convertRights.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:cm="http://www.coremedia.com/2005/contentserver/userrepository" exclude-result-prefixes="cm">
<!--
***************************************************************************
* Dieses Stylesheet-Document generiert aus dem GSB7-Userrepository- *
* XML ein vom GSB in Jackrabbit importierbares Format. *
***************************************************************************
-->
<xsl:output method="xml" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/">
<securitydefinitions>
<xsl:call-template name="acls" />
<xsl:call-template name="groups" />
<xsl:call-template name="relations" />
<xsl:call-template name="users" />
</securitydefinitions>
</xsl:template>
<xsl:template name="users">
<userdefinitions>
<xsl:for-each select="//cm:user">
<userdefinition>
<xsl:apply-templates select="@home" />
<xsl:apply-templates select="@name" />
</userdefinition>
</xsl:for-each>
</userdefinitions>
</xsl:template>
<xsl:template name="groups">
<groupdefinitions>
<xsl:for-each select="//cm:group">
<groupdefinition>
<xsl:apply-templates select="@administrative" />
<xsl:apply-templates select="@contentgroup" />
<xsl:apply-templates select="@livegroup" />
<xsl:apply-templates select="@name" />
</groupdefinition>
</xsl:for-each>
</groupdefinitions>
</xsl:template>
<xsl:template name="relations">
<relationdefinitions>
<xsl:for-each select="//cm:group">
<xsl:if test="ancestor::cm:group">
<relationdefinition>
<xsl:attribute name="child">
<xsl:value-of select="@name" />
</xsl:attribute>
<xsl:attribute name="parent">
<xsl:value-of select="ancestor::cm:group[1]/@name" />
</xsl:attribute>
</relationdefinition>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="//cm:groupref">
<xsl:if test="ancestor::cm:group">
<relationdefinition>
<xsl:variable name="currentId" select="@id" />
<xsl:attribute name="child">
<xsl:value-of select="//cm:group[@id=$currentId]/@name" />
</xsl:attribute>
<xsl:attribute name="parent">
<xsl:value-of select="ancestor::cm:group[1]/@name" />
</xsl:attribute>
</relationdefinition>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="//cm:user">
<xsl:if test="ancestor::cm:group">
<relationdefinition>
<xsl:attribute name="child">
<xsl:value-of select="@name" />
</xsl:attribute>
<xsl:attribute name="parent">
<xsl:value-of select="ancestor::cm:group[1]/@name" />
</xsl:attribute>
</relationdefinition>
</xsl:if>
</xsl:for-each>
<xsl:for-each select="//cm:userref">
<xsl:if test="ancestor::cm:group">
<relationdefinition>
<xsl:variable name="currentId" select="@id" />
<xsl:attribute name="child">
<xsl:value-of select="//cm:user[@id=$currentId]/@name" />
</xsl:attribute>
<xsl:attribute name="parent">
<xsl:value-of select="ancestor::cm:group[1]/@name" />
</xsl:attribute>
</relationdefinition>
</xsl:if>
</xsl:for-each>
</relationdefinitions>
</xsl:template>
<xsl:template name="acls">
<acldefinitions>
<xsl:for-each select="//cm:rule">
<acldefinition>
<xsl:apply-templates select="@content" />
<xsl:attribute name="group"><xsl:value-of select="ancestor::cm:group[1]/@name" /></xsl:attribute>
<xsl:apply-templates select="@rights" />
<xsl:apply-templates select="@type" />
</acldefinition>
</xsl:for-each>
</acldefinitions>
</xsl:template>
<xsl:template match="@type">
<xsl:choose>
<xsl:when test=".='Document_'">
<xsl:attribute name="type">gsb:AbsDocument</xsl:attribute>
</xsl:when>
<xsl:when test=".='Folder_'">
<xsl:attribute name="type">gsb:Folder</xsl:attribute>
</xsl:when>
<xsl:when test=".='ConfigNLMailman' or .='ConfigNLMajordomo'">
<xsl:attribute name="type">gsb:ConfigNewsletter</xsl:attribute>
</xsl:when>
<xsl:when test=".='HFSCheckboxGrp'">
<xsl:attribute name="type">gsb:HFDCheckboxGrp</xsl:attribute>
</xsl:when>
<xsl:when test=".='HFSRadiobuttonGrp'">
<xsl:attribute name="type">gsb:HFDRadiobuttonGrp</xsl:attribute>
</xsl:when> <xsl:when test=".='HFSSelectBox'">
<xsl:attribute name="type">gsb:HFDSelectBox</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="type">gsb:<xsl:value-of select="." /></xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="@*">
<xsl:copy />
</xsl:template> <xsl:template match="@id" />
</xsl:stylesheet>

3.9 Unterprojekt solr

Der GSB 7 nutzt für die Suche die Solr Version 4.x, der GSB 10 nutzt Solr in der Version 10.x. Die Solr Konfigurationen zwischen den Versionen 4.x und 7.x haben sich grundlegend geändert, so dass an dieser Stelle keine einfache Transformation angeboten werden kann. Darüber hinaus benötigt der GSB 10 und die GSB 10 Infrastrukturkomponenten eine Reihe von Solr Suchindices, die im GSB 7 nicht erforderlich waren und somit dort auch nicht definiert sind.

Eine Migration der Solr Konfiguration kann am einfachsten durchgeführt werden, indem die GSB 10 Standardlösung als Grundlage genommen wird. Beim Anlegen der Strukturen muss darauf geachtet werden, dass der Name des Mandanten von standardlsg auf den Namen des Zielmandanten geändert wird. Dies gilt sowohl für die Datei- und Verzeichnisnamen als auch die Dateiinhalte.

Nachdem das solr-Subproject des Mandanten auf Basis und Grundlage des gleichnamigen Subprojects des Mandanten standardlsg angelegt worden ist, können dann individuelle Anpassungen in den erzeugten Dateien vorgenommen werden.

4 Content

4.1 Migration des Contents

Für eine direkte Migration der redaktionellen Inhalte eines Mandanten können die Tools JcrPublisher für den Export und der ImportAgent für den Import verwendet werden. Der JcrPublisher verbindet sich zu einem CoreMedia Repository und kann Inhalte direkt aus dem Repository lesen und in ein JCR Importformat konvertieren.

Migrationsschritte

Über den Deploymentprozess des GSB/OS wird der JcrPublisher durch die Gradle Tasks 'exportPublisherConfiguration' und 'installPublisher' installiert. Dabei wird im zugehörigen Konfigurations-Verzeichnis config/jcrpublisher die Properties-Datei jcrexporter.properties angelegt:

###############################################################################
#
# JcrPublisher configuration | Export Mode
#
###############################################################################
#
# Coremedia Repository Connection
repository.url=http://<host>:<port>/contentmanagementserver/ior
repository.user=<coremedia-user>
repository.domain=
repository.password=<coremedia-password>
#
# Export path | Commit URI
exporter.exportPath=<path-for-export>
#
###############################################################################
#
# Optional Coremedia Configuration
#
###############################################################################
#
# U-API defaults
#repository.heapCacheSize=20000000
#repository.blobCacheSize=32000000
#repository.blobStreamingSizeThreshold=131072
#repository.blobStreamingThreads=2
#repository.maxCachedBlobSize=32000000
#repository.blobCachePath=

In dieser Datei müssen die Properties für die Verbindung zum Coremedia Repository angepasst werden und ein Pfad für die exportierten Dateien angegeben werden. Der Exportpfad sollte am besten der gleiche Pfad sein, der später im ImportAgent für den Import genutzt wird, ansonsten muss man die exportierten Dateien noch mal händisch dahin umkopieren. Aufgerufen wird der Exporter dann über:

#!/bin/sh#
Kompletter Mandant
./<installation-path>/jcrpublisher/scripts/jcrexporter <Mandant>
# oder für einen Teilbaum eines Mandanten
./<installation-path>/jcrpublisher/scripts/jcrexporter <repository-path>

Nach diesem Aufruf liegt der exportierte Content dann im Verzeichnis '/'. Um diesen zu importieren muss dieses Verzeichnis in das Importverzeichnis kopiert werden, falls Import- und Exportverzeichnis nicht gleich sind. Danach kann dann z.B. mit dem Kommandozeilentool curl der Content in das Jackrabbit-Repository importiert werden:

#!/bin/sh#
Import über einen HTTP-POST Request mit curl
curl --request POST http://<host>:<port>/ia/transaction/<uuid>

Danach sollte der Content im GSB/OS verfügbar sein.

4.2 Editorkonfiguration

Die Konfiguration des GSB/OS Webeditors entspricht dem des GSB/CM Webeditors. D.h. eine explizite Migration der Webeditorkonfiguration vom GSB/CM in den GSB/OS ist nicht erforderlich. Mandanten die im GSB/CM keinen Webeditor nutzen müssen zunächst eine Migration der Javaeeditor Konfiguration in den Webeditor im GSB/CM durchführen und können diese Konfiguration dann in den GSB/OS übernehmen. Die Beschreibung der Webeditor Konfiguration für den GSB/CM kann der GSB/CM Dokumentation entnommen werden.

4.3 Redaktionsdefinition

Das Format der Redaktionsdefinition im GSB/OS ist angepasst worden. Die Struktur der Redaktionsdefinition ist deutlich klarer und flexibler. Die Konfiguration der Funktionalitäten der GSB/OS Worklfows wird in der Redaktionsdefinition festgelegt. Hierzu zählen einerseits neu hinzugekommene Funktionalitäten und andererseits auch entfallenen Möglichkeiten. Die notwendigen Schritte zur Migration einer Redaktionsdefinition wird anhand typischer Konfigurationen der 2-, 4- und 6-Augen-Workflows gezeigt.

4.3.1 2-Augen-Workflow

Eine Konfiguration für die 2-Augen Workflows sieht im GSB/CM typischerweise wie folgt aus:

<Redaktion name="gsbsl_red_ChiefAdministration" default="ja">
<Rollen>
<Rolle typ="mandant-composer-role" startgruppe="ja">gsbsl_red_ChiefAdministration_RD</Rolle>
<Rolle typ="mandant-supervisor-role">gsbsl_red_ChiefAdministration_RD</Rolle>
</Rollen>
<Ressourcen>
<Sammlung name="gsbsl_lrs_Alles" />
</Ressourcen>
<Workflows>
<Workflow name="TwoEyesPublicationSubWorkflow" />
<Workflow name="TwoEyesDepublicationSubWorkflow" />
<Workflow name="TwoEyesDelayedPublicationSubWorkflow" />
<Workflow name="TwoEyesDelayedDepublicationSubWorkflow" />
</Workflows>
</Redaktion>

Zur Umstellung dieser Redaktionsdefinition auf den GSB/OS sind folgende Schritte notwendig:

  1. Der Name der Redaktion wird bei der Definition eingetragen.
  2. Die LRS, die bisher unter Sammlung eingetragen war, wird unter lrs eingetragen
  3. Die Rolle, die bisher unter mandant-supervisor-role eintragen wurde, wird als Startgruppe unter startgroups eingetragen

Hinweis: Werden in einer Redaktionsdefinition sowohl 2- als auch 4- oder 6-Augen-Workflows eingesetzt, so muss in der Redaktionsdefiniton alle notwendigen Konfigurationen für die enthaltenen Workflows enthalten. Die entsprechenden Definitionen können den Beschreibungen der anderen Workflows entnommen werden.

Die oben skizzierte Redaktionsdefinition stellt sich im GSB/OS wie folgt dar:

<Definition name="gsbsl_red_ChiefAdministration">
<lrs>gsbsl_lrs_Alles</lrs>
<Workflow name="twoeyesworkflow">
<Startgroups>
<startgroup>gsbsl_red_ChiefAdministration_RD</startgroup>
</Startgroups>
<Tasks />
</Workflow>
</Definition>

4.3.2 4-Augen-Workflow

Eine Konfiguration für die 4-Augen Workflows sieht im GSB/CM typischerweise wie folgt aus:

<Redaktion name="gsbsl_red_Content" default="ja">
<Mailconfig server="localhost" betreff="Workflow-Benachrichtigung: Gestartet von ${workflowOwnerShort}$, ${resourceCount}$ Ressourcen publizieren" absender="vorname.nachname@domain.example" />
<Rollen>
<Rolle typ="mandant-composer-role" startgruppe="ja">gsbsl_red_Content_RD</Rolle>
<Rolle typ="mandant-approver-role">gsbsl_red_Content_QB</Rolle>
</Rollen>
<Ressourcen>
<Sammlung name="gsbsl_lrs_Content" />
</Ressourcen>
<Workflows>
<Workflow name="FourEyesSubWorkflow" modifizierendeTasks="Approve,Compose">
<Mail task="Approve" vorlage="4AugenPub" standardEmpfaenger="vorname.nachname@domain.example" nurStandardEmpfaenger="ja" />
</Workflow>
<Workflow name="FourEyesDepublicationSubWorkflow" modifizierendeTasks="Approve,Compose">
<Mail task="Approve" vorlage="4AugenPub" />
</Workflow>
<Workflow name="FourEyesDelayedSubWorkflow" modifizierendeTasks="Approve,Compose">
<Mail task="Approve" vorlage="4AugenPub" />
</Workflow>
<Workflow name="FourEyesDelayedDepublicationSubWorkflow" modifizierendeTasks="Approve,Compose">
<Mail task="Approve" vorlage="4AugenPub" />
</Workflow>
</Workflows>
</Redaktion>

Zur Umstellung dieser Redaktionsdefinition auf den GSB/OS sind folgende Schritte notwendig:

  1. Der Name der Redaktion wird bei der Definition eingetragen.
  2. Die LRS, die bisher unter Sammlung eingetragen war, wird unter lrs eingetragen
  3. Die Rolle, die bisher unter mandant_composer_role eintragen wurde, wird als Startgruppe unter startgroups eingetragen
  4. Die Rolle, die bisher unter mandant-approver_role eingetragen war, wird im Approve-Task unter groups eingetragen.
  5. Der Absender der Benachrichtigungsemails muss in das Dokument /mandantenname/_config/workflows/mailSender eingetragen werden.
  6. Das Mail-Template (hier 4-AugenPub) wird in den MailToApprover task unter mailtemplate eingetragen.
  7. Die Formate der Mailtemplates haben sich geändert und lassen nun mehr Möglichkeiten zu als bisher. Welche das sind ist in der Workflow Dokumentation unter: Mailtemplates beschrieben
  8. Es lassen sich mehr Emails als bisher innerhalb des Workflows versenden. Zum Beispiel bei erfolgreicher oder fehlerhafter Publikation. Welche das sind ist in der Workflowdokumentation beschrieben.

Hinweis: Werden in einer Redaktionsdefinition sowohl 2- als auch 4- oder 6-Augen-Workflows eingesetzt, so muss in der Redaktionsdefiniton alle notwendigen Konfigurationen für die enthaltenen Workflows enthalten. Die entsprechenden Definitionen können den Beschreibungen der anderen Workflows entnommen werden.

Die oben skizzierte Redaktionsdefinition stellt sich im GSB/OS wie folgt dar:

<Definition name="content-redaktion">
<lrs>gsbsl_lrs_Alles</lrs>
<Workflow name="foureyesworkflow">
<startgroups>
<Startgroup>gsbsl_red_Content_RD</Startgroup>
</startgroups>
<Tasks>
<Approve>
<excludedUsersFromTask>Compose</excludedUsersFromTask>
<groups>gsbsl_red_Content_QB</groups>
</Approve>
<MailToApprover>
<mailToAssigneeFromTask />
<mailToCanidatesFromTask />
<mailtemplate>4AugenPub</mailtemplate>
<standardEmailReceiver>vorname.nachname@domain.example</standardEmailReceiver>
</MailToApprover>
</Tasks>
</Workflow>
</Definition>

4.3.3 6-Augen-Workflow

Eine Konfiguration für die 6-Augen Workflows sieht im GSB/CM typischerweise wie folgt aus:

<Redaktion name="red_sechs_augen" default="ja">
<Mailconfig server="localhost" betreff="Workflow-Benachrichtigung: Gestartet von ${workflowOwnerShort}$, ${resourceCount}$ Ressourcen publizieren" absender="vorname.nachname@domain.example" />
<Rollen>
<Rolle typ="mandant-composer-role" startgruppe="ja">gsbsl_red_Content_RD</Rolle>
<Rolle typ="mandant-approver-role-6eyes">gsbsl_red_Content_QB</Rolle>
<Rolle typ="mandant-publisher-role">gsbsl_red_Chief_Content_QB</Rolle>
</Rollen>
<Ressourcen>
<Sammlung name="gsbsl_lrs_Content" />
</Ressourcen>
<Workflows>
<Workflow name="SixEyesPublicationSubWorkflow">
<Mail task="Approve" vorlage="6-Augen-Approve" />
<Mail task="Publish" vorlage="6-Augen-Publish" />
</Workflow>
<Workflow name="SixEyesDepublicationSubWorkflow">
<Mail task="Approve" vorlage="6-Augen-Approve" />
<Mail task="Depublish" vorlage="6-Augen-Depublish" />
</Workflow>
</Workflows>
</Redaktion>

Zur Umstellung dieser Redaktionsdefinition auf den GSB/OS sind folgende Schritte notwendig:

  1. Der Name der Redaktion wird bei der Definition eingetragen.
  2. Die LRS, die bisher unter Sammlung eingetragen war, wird unter lrs eingetragen
  3. Die Rolle, die bisher unter mandant-composer-role eintragen wurde, wird als Startgruppe unter startgroups eingetragen
  4. Die Rolle, die bisher unter mandant-approver_role-6eyes eingetragen war, wird im Approve-Task unter groups eingetragen.
  5. Die Rolle, die bisher unter mandant-publisher-role eingetragen war, wird im Publish-Task unter groups eingetragen.
  6. Der Absender der Benachrichtigungsemails muss in das Dokument /mandantenname/_config/workflows/mailSender eingetragen werden.
  7. Das Mailtemplate für Approve (6-Augen-Approve) wird nun in der in der Konfiguration des MailToApprover-tasks eingetragen.
  8. Das Mail-Template, für die Benachrichtigung des Approvers (hier 6-Augen-Approve), wird in den MailToApprover task unter mailtemplate eingetragen.
  9. Beim Mailtemplate fürs Publish/Depublish stehen wir nun vor dem Problem, das im GSB/OS derzeit leider nicht verschiedene Mailtemplates für Publikation und Depublikation vorgesehen sind. Insofern müssen wir ein Mailtemplate für die Publiaktion/Depublikation bestimmen und dieses in den Benachrichtigung zum Publizieren eintragen. In diesem Fall ist das das Template 6-Augen-Publish was wir in den Wert mailtemplate des Tasks MailToPublisher eintragen.
  10. Die Formate der Mailtemplates haben sich geändert und lassen nun mehr Möglichkeiten zu als bisher. Welche das sind ist in der Workflow Dokumentation unter: Mailtemplates beschrieben
  11. Es lassen sich mehr Emails als bisher innerhalb des 6-Augen.Workflows versenden. Zum Beispiel bei erfolgreicher oder fehlerhafter Publikation. Welche dies genau sind und wie diese konfiguriert werden,das ist in der Workflowdokumentation beschrieben.

Hinweis: Werden in einer Redaktionsdefinition sowohl 2- als auch 4- oder 6-Augen-Workflows eingesetzt, so muss in der Redaktionsdefiniton alle notwendigen Konfigurationen für die enthaltenen Workflows enthalten. Die entsprechenden Definitionen können den Beschreibungen der anderen Workflows entnommen werden.

Die oben skizzierte Redaktionsdefinition stellt sich im GSB/OS wie folgt dar:

<Definition name="content-redaktion">
<lrs>standardlsg_lrs_Content@standardlsg</lrs>
<Workflow name="sixeyesworkflow">
<Startgroups>
<startgroup>gsbsl_red_Content_RD</startgroup>
</Startgroups>
<Tasks>
<Approve>
<excludedUsersFromTask>Compose</excludedUsersFromTask>
<groups>gsbsl_red_Content_QB</groups>
</Approve>
<Publish>
<excludedUsersFromTask>Approve</excludedUsersFromTask>
<groups>gsbsl_red_Chief_Content_QB</groups>
</Publish>
<MailToApprover>
<mailToAssigneeFromTask />
<mailToCanidatesFromTask>Approve</mailToCanidatesFromTask>
<mailtemplate>6-Augen-Approve</mailtemplate>
<standardEmailReceiver></standardEmailReceiver>
</MailToApprover>
<MailToPublisher>
<mailToAssigneeFromTask />
<mailToCanidatesFromTask>Publish</mailToCanidatesFromTask>
<mailtemplate>6-Augen-Publish</mailtemplate>
<standardEmailReceiver></standardEmailReceiver>
</MailToPublisher>
</Tasks>
</Workflow>
</Definition>

4.4 Übernahme der CMS-Benutzer (Redakteure)

Es wird von einer Ablage der CMS-Benutzer in einem LDAP-Verzeichnis ausgegangen. Die notwendigen Migrationsschritte sind beim Mandantenprojekt im Abschnitt Unterprojekt infrastructure beschrieben.

4.5 Rollen und Rechte

Die Migrationsschritte zur Übernahme der Rollen und Rechte sind ebenfalls beim Mandantenprojekt im Abschnitt Unterprojekt infrastructure beschrieben.

4.6 Manuelle Tätigkeiten

Durch die Migration auf Solr 7 muss die Konfiguration der Suche im Content angepasst werden. In der Suchkonfiguration muss der Parameter solr.sow=true ergänzt werden. Das kann z.B. in der StandardEdismax Konfiguration gemacht werden.

In dem Dokumenttyp HFTextSeparator wird das Feld formatting nicht mehr ausgewertet. Der Inhalt des Felds seperator wird stattdessen immer mittels Velocity-Unterstützung ausgegeben. Bei HFTextSeparator-Dokumenten die im GSB 7 noch mit Richtext-Auszeichnungen versehen wurden, müssen diese durch einfache HTML-Auszeichnungen ersetzt werden.

5 GSB-Funktionalitäten

Unter dem Themenbereich GSB-Funktionalitäten werden Funktionen zusammengefasst, die ihre Daten in der GSB-Datenbank ablegen und über eine Administrationsoberfläche verwalten werden. Es handelt sich dabei um

  • Gästebücher
  • Kommentare und Bewertungen
  • Datentabellen sowie
  • Umfragen

Im GSB/OS ändert sich bei den GSB-Funktionalitäten die Administration (Liferay-Portal) sowie die Ablagestruktur in der Datenbank.

Migrationsschritte

Für die Übernahme der Daten der GSB-Funktionalitäten in den GSB/OS müssen jeweils händisch Einstellungen im Content vorgenommen werden und anschliessend der im Folgenden beschriebene GSB Data Migrator aufgerufen werden.

5.1 GSB Data Migrator

Der GSB Data Migrator ist ein externes Java-Programm für die Migration der GSB-Funktionen. Mit Hilfe dieses Tools können die folgenden Daten aus der AppDB eines GSB/CM in das Serviceportal des GSB/OS übernommen werden:

  • Gästebucheinträge (--guestbook-entries)
  • Bewertungen (--ratings)
  • Umfragen (--surveys)
  • Umfrageergebnisse (--survey-instances)

5.1.1 Direkte und indirekte Migration

Der Data Migrator unterstützt sowohl eine direkte als auch eine indirekte Migration.

Die direkte Migration kann genutzt werden, wenn sich das Tool gleichzeitig zur AppDB und zum Serviceportal verbinden kann.

Direkte Migration Abbildung 3. Direkte Migration

Bei der indirekten Migration, werden die Daten von der AppDB in eine Datei exportiert, welche dann in einem zweiten Schritt ins Serviceportal importiert werden kann.

Indirekte Migration Abbildung 4. Indirekte Migration

5.1.2 Properties

5.1.2.1 Liste der Properties

Die folgenden Properties können gesetzt werden um das Verhalten des Tools zu steuern.

customer: Name des Mandanten, dessen Daten migriert werden sollen.

spring.datasource.url: JDBC-Verbindungs URL für die GSB/CM Datenbank

spring.datasource.username: Benutzername für die Verbindung zur GSB/CM Datenbank

spring.datasource.password: Passwort düe die Verbindung zur GSB/CM Datenbank

spring.jpa.show-sql: Auf true setzen, um sämtliche Datenbank-Aktionen zu loggen.

Weitere Informationen zu den Datenbank-Spezifischen Properties sind im Kapitel 29.1.2 der Spring-Boot 2 Referenzdokumentation zu finden.

to-file: Name der Ziel-Datei für den ersten Schritt der indirekten Migration

from-file: Name der Quell-Datei für den zweiten Schritt der indirekten Migration

de.bund.gsb.liferay.url: URL für die Verbindung zum Serviceportal

5.1.2.2 Setzen der Properties

Die og. Properties können sowohl über eine Konfigurationsdatei als auch als Kommandozeilenargumente gesetzt werden.

Um Properties über eine Datei zu setzen muss eine Datei mit dem Namen application.properties erstellt werden, welche die entsprechenden Einträge enthält.

Um Properties über die Kommandozeile zu setzen, muss der Name der Property mit zwei Bindestrichen genutzt werden; z.B. --customer=standardlsg

Weitere Informationen zum Setzen von Properties sind in den Kapiteln "24. Externalized Configuration" und "24.3 Application Property Files" der Spring-Boot 2 Referenzdokumentation zu finden.

Migrationsschritte

Zunächst sind einige vorbereitende Schritte erforderlich. Als erstes ist zu überprüfen, ob von der Maschine, auf der der Data Migrator läuft, die AppDB des GSB/CM erreichbar ist. Ggf. sollte dies mit Hilfe eines JDBC-Clients getestet werden. Ist die Erreichbarkeit nicht gegeben muss stattdessen ein Export aus der AppDB bereitgestellt werden.

Die Erreichbarkeit des Serviceportals sollte über einen Browseraufruf oder ggf. auch wget getestet werden.

Danach empfiehlt es sich, die oben aufgeführten Properties in der Datei application.properties zu setzen.

Für jede GSB-Funktionalität sind dann im Anschluss jeweils händisch im Content Dokumente zu überprüfen und ggf. anzulegen und danach der GSB Data Migrator aufzurufen.

5.2 Gästebücher

Migrationsschritte

Bei der Migration von Gästebüchern ist im GSB/OS zu jedem bestehenden Gästebuch ein namensgleiches Gästebuch im Content des Mandanten anzulegen.

Anschließend ist das Migrationswerkzeug mit den Parametern

--customer=<Mandant> und --guestbook-entries

aufzurufen.

5.3 Kommentare und Bewertungen

Migrationsschritte

Vor der Migration der Bewertungen und Kommentare ist sicherzustellen, dass alle zuvor bewertbaren und kommentierbaren Dokumente in den Content des Mandanten migriert wurden und dort auf „kommentierbar“ bzw. „bewertbar“ eingestellt sind.

Anschließend ist das Migrationswerkzeug mit folgenden Kommandozeilen-Parametern aufzurufen:

--customer=<Mandant> und --ratings

5.4 Datentabellen und Umfragen

Migrationsschritte

Die Migration von Datentabellen und Umfragen läuft identisch ab. Zunächst ist sicherzustellen, dass für jede zu migrierende Umfrage bzw. Datentabelle ein namensgleiches Dokument im Content des Mandanten angelegt wurde.

Anschließend ist das Migrationswerkzeug mit den Parametern

--customer=<Mandant> und --surveys

aufzurufen.

6 Mailman Migration

Die Anleitung beschreibt die Migration der Newsletter-Versand von der auf Datenbank gestützten Mailman basierenden Version auf den MailDistributor.

6.1 CSV-Export

Bevor ein CSV-Export für eine Mailing-Liste durchgeführt werden kann ist zu prüfen, ob E-Mail-Adressen doppelt in der Mailman-DB vorhanden sind. Dies ist in der MailDistributor-DB per Constraint nicht mehr zulässig und beim Import würde es zum Datenverlust aufgrund der SQL-Exception kommen. Folgendes SQL-Statement liefert alle Nutzer mit gleichen Adressen:

SELECT email, COUNT(*)
FROM mm_users
#WHERE list_id = <listID>
GROUP BY email
HAVING COUNT(*) > 1;

Die gefundenen Benutzer sind entweder in der Datenbank zu entfernen oder in der exportierten CSV-Datei. Falls sie nur in der Datei entfernt wurden, könnte es bei der Migrationskontrolle (Vergleich der berechneten Empfänger) zur Abweichung kommen. Die vorhandene Liste mit ihren Usern und den abonnierten Attribut-Werten werden über den GSB-Admin in ein CSV-Format exportiert. Dazu:

  • Anmelden
  • „Mailinglisten verwalten“
  • Mandanten wählen
  • „User bearbeiten“
  • „Benutzer suchen“ mit leerer Filtermaske
  • Bei den Suchergebnissen: „Alle gefundenen User im CSV-Format exportieren“

Das Skript zum Konvertieren der CSV-Datei in SQL erwartet folgende Felder, wobei die Spaltenüberschriften in der ersten Zeile zur Identifikation der Attribute und Attribut-Werte vorhanden sein müssen:

IDNameEmailActivationStatusDeliveryStatusAttribute[]

Die „Attribute[]“ haben die Form: „Attribut:Attributwert“ – „Format:NewsletterFormat-Text“ oder „Taetigkeitsfelder:Taetigkeitsfeld-Energieversorgung“

6.2 CSV2SQLConverter

Das Perl-Skript liest die exportierte CSV Datei ein und erstellt drei SQL-Dateien im Oracle- oder MySQL-Format. Neben dem Skript selbst werden im gleichen Verzeichnis ein lib-Ordner (mit den Modulen String, Text und Tie) und die CSV-Datei erwartet. Vor dem Ausführen des Skriptes sind Anpassungen im oberen Teil der Skriptdatei notwendig.

ParameterBeschreibung
$listIdID, die die Liste in der neuen Datenbank erhalten soll. Da im Oracle-Format Sequences neu erstellt werden und diese IDs nur hoch zählen, ist die höchste „freie“ ID zu wählen
$mandantName des Mandanten der Liste (z.B. „standardlsg“)
$listNameName der Liste (z.B. "TestListe")
$listAddressAdresse der Liste (z.B “testlist@domain.example.de)
$fromAddressLocal-Part des Absenders der Newsletter-Mails (z.B. “cmadmin“)
$attributeIdID, die das erste Attribut in der neuen Datenbank erhält. Analog zur Listen-ID ist die höchste „freie“ ID zu wählen
$attributeValueIdID, die der erste Attribut-Wert in der neuen Datenbank erhält. ID Wahl analog zu ListId, AttributId
$userIdID, die der erste User in der neuen Datenbank erhält. ID Wahl analog zu ListId, AttributId, ..
$migrationTimestampZeitpunkt der Migration. Der Zeitstempel wird für alle User als Zeitpunkt der Registrierung, Bestätigung und für die letzten Änderungszeiten anderer Werte genutzt
$importCSVFilenameDateiname der zu lesenden exportierten CSV Datei im gleichen Verzeichnis
$outputFormatFestlegung des Ausgabeformates: Wahl zwischen „oracle“ und „mysql“

Beim Import-Vorgang müssen die drei erstellten Dateien in der nachfolgend gezeigten Reihenfolge ausgeführt werden.

  1. <listenname>_import_list_attributes_attributesvalues_<format>.sql enthält das Anlegen der Liste mit dessen Attributen und Attribut-Werten und der Listenkonfiguration
  2. <listenname>_import_user_<format>.sql enthält den Import der User
  3. <listenname>_import_user_attribute_reference_<format>.sql enthält die Referenzen der User zu den von ihnen abonnierten Attributen

Falls die Daten für einen Testzweck importiert werden sollen, kann im Skript die Zeile 124 einkommentiert werden. Diese hängt jeder E-Mail-Adress beim Import die Domain notExistent.de an um auszuschließen, dass Test-Mails an die validen Adressen versandt werden.

csv2sqlConverter.pl

#!/usr/bin/perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/";
use lib "$FindBin::Bin/lib";
use Tie::IxHash;use Text::CSV;
use String::Util qw(trim);
# ---------------------------------------------
# change these for each list to import
# ---------------------------------------------
my $listId = 30;
my $mandant = 'standardlsg';
my $listName = 'bsi';
my $listAddress = $listName.'@newsletter.bund.de';
my $toAddress = $listName.'@noreply.bund.de';
my $fromAddress = 'cmadmin';
my $attributeId = 30;
my $attributeValueId = 30;
my $userId = 30;
my $migrationTimestamp = '26.06.15 12:00:00';
my $importCSVFilename = "userexport.csv";
my $outputFormat = "oracle";
#my $outputFormat = "mysql";
# ---------------------------------------------
# ---------------------------------------------
# SQL string parts for oracle and mysql database
# --------------------------------------------------
# ORACLE:
my $listStringOracle = 'INTO "NL_LIST" ("ID", "MANDANT", "NAME", "ADDRESS", "BOUNCE_COUNT") VALUES ';
my $listConfigStringOracle = 'INTO "NL_LIST_CONFIG" ("ID", "BULK_SEND_RANGE", "ADMIN_ADDRESS", "FROM_ADDRESS", "BOUNCE_IGNORE_PERIOD", "BOUNCE_SCORE_THRESHOLD", "LIST_ID", "TO_ADDRESS") VALUES ';
my $attributeStringOracle = 'INTO "NL_ATTRIBUTE" ("ID", "NAME", "DISPLAY_NAME", "POSITION", "LIST_ID") VALUES ';
my $attributeValueStringOracle = 'INTO "NL_ATTRIBUTE_VALUE" ("ID", "VALUE", "DISPLAY_VALUE", "SUBSCRIBEABLE", "POSITION", "ATTRIBUTE_ID") VALUES ';
my $userStringOracle = 'INTO "NL_USER" ("ID", "NAME", "ADDRESS", "MANUALLY_ADDED", "ACTIVATION_STATUS", "ACTIVATION_STATUS_TIMESTAMP", "DELIVERY_STATUS", "DELIVERY_STATUS_TIMESTAMP", "BOUNCE_SCORE", "BOUNCE_SCORE_TIMESTAMP", "REGISTRATION_TIMESTAMP", "CONFIRMATION_TIMESTAMP", "LIST_ID") VALUES ';
my $attributeReferenceStringOracle = 'INTO "NL_USER_ATTRIBUTE_VALUE" ("USER_ID", "ATTRIBUTE_VALUE_ID") VALUES ';
my $insertStartStringOracle = "INSERT ALL\n";
my $insertEndStringOracle = "SELECT * FROM DUAL;\nCOMMIT;\n";

# MYSQL:
my $listStringMySQL = 'INSERT INTO `nl_list` (`ID`, `MANDANT`, `NAME`, `ADDRESS`, `BOUNCE_COUNT`) VALUES ';
my $listConfigStringMySQL = 'INSERT INTO `nl_list_config` (`ID`, `BULK_SEND_RANGE`, `ADMIN_ADDRESS`, `FROM_ADDRESS`, `BOUNCE_IGNORE_PERIOD`, `BOUNCE_SCORE_THRESHOLD`, `LIST_ID`, `TO_ADDRESS`) VALUES ';
my $attributeStringMySQL = 'INSERT INTO `nl_attribute` (`ID`, `NAME`, `DISPLAY_NAME`, `POSITION`, `LIST_ID`) VALUES' . "\n";
my $attributeValueStringMySQL = 'INSERT INTO `nl_attribute_value` (`ID`, `VALUE`, `DISPLAY_VALUE`, `SUBSCRIBEABLE`, `POSITION`, `ATTRIBUTE_ID`) VALUES' . "\n";
my $userStringMySQL = 'INSERT INTO `nl_user` (`ID`, `NAME`, `ADDRESS`, `MANUALLY_ADDED`, `ACTIVATION_STATUS`, `ACTIVATION_STATUS_TIMESTAMP`, `DELIVERY_STATUS`, `DELIVERY_STATUS_TIMESTAMP`, `BOUNCE_SCORE`, `BOUNCE_SCORE_TIMESTAMP`, `REGISTRATION_TIMESTAMP`, `CONFIRMATION_TIMESTAMP`, `LIST_ID`) VALUES' . "\n";
my $attributeReferenceStringMySQL = 'INSERT INTO `nl_user_attribute_value` (`USER_ID`, `ATTRIBUTE_VALUE_ID`) VALUES' . "\n";
my $insertEndStringMySQL = "COMMIT;\n";

# open files
open(my $csvFile, '<:encoding(UTF-8)', $importCSVFilename) or die "Could not open $importCSVFilename $!\n";
open(my $fh_list, '>', "$listName" . "_import_list_attributes_attributevalues_" . "$outputFormat" . ".sql");
open(my $fh_user, '>', "$listName" . "_import_user_" . "$outputFormat" . ".sql");
open(my $fh_ref, '>' , "$listName" . "_import_user_attribute_reference_" . "$outputFormat" . ".sql");

# write timestamp format value to files for oracle
if($outputFormat eq 'oracle'){
print $fh_user "ALTER SESSION SET nls_timestamp_format='DD.MM.YY HH24:MI:SS'\n";
}
# convert timestamp format value for mysql - YYYY-MM-DD HH:MM:SS
my ($migDate, $migHour) = split(/ /, $migrationTimestamp);
my ($day, $month, $year) = split(/\./, $migDate);
my $migrationTimestampMySQL = "20".$year."-".$month."-".$day." ".$migHour;

# set separation character to ';'
my $csv = Text::CSV->new({ sep_char => ';' });

# define counter values
my $entryCounter = 0;
my $insertUserStmntCounter = 0;
my $insertUserStmntCounterMax = 50;
my $attValStartId = $attributeValueId;
my $lastOutputAttRef = "";
my $lastOutputUser = "";

# parse file line by line
while (my $line = <$csvFile>) {
# remove trailing newline chars
chomp $line;
# check for blank line
if ($line =~ /^\s*$/){
print "skipping empty line..\n";
next;
}
# parse line
if ($csv->parse($line)) {
my @fieldArray = $csv->fields();
# remove leading and trailing white spaces for each cell
foreach my $field (@fieldArray){
$field = trim($field);
}
# parse first line --> build attributes and attribute values
if($entryCounter == 0){
if(lc($fieldArray[0]) eq "id"){
print "parsing first line with attribute and attribute value information..\n";
parseAttributeTreeAndConvert(@fieldArray);
} else {
print "Error: first row is missing: expecting 'id; name; email; activationStatus; deliveryStatus; attributes[]..\n";
exit;
}
$entryCounter++;
print "\nparsing user and user -> attribute value references..\n";
next;
}
if($insertUserStmntCounter == 0 ){
if($outputFormat eq 'oracle'){
print $fh_user $insertStartStringOracle;
print $fh_ref $insertStartStringOracle;
} else {
print $fh_user $userStringMySQL;
print $fh_ref $attributeReferenceStringMySQL;
}
}

# parse user
# INTO "NL_USER" ("ID", "NAME", "ADDRESS", "MANUALLY_ADDED", "ACTIVATION_STATUS", "ACTIVATION_STATUS_TIMESTAMP", "DELIVERY_STATUS", "DELIVERY_STATUS_TIMESTAMP", "BOUNCE_SCORE", "BOUNCE_SCORE_TIMESTAMP", "REGISTRATION_TIMESTAMP", "CONFIRMATION_TIMESTAMP", "LIST_ID") VALUES'
# (1, 'name', 'address@domain.de', 0, 0, '21.06.15 12:00:00', 0, '21.06.15 12:00:00', 0, '21.06.15 12:00:00', '21.06.15 12:00:00', '21.06.15 12:00:00', 1)
# testing purpose: suffix to disable every mail address
my $replacedAddress = $fieldArray[2];
#$replacedAddress = $fieldArray[2] . ".notExistentDomain";
# take local part from email as name for user
(my $replacedName, my $domain) = split(/\@/, $fieldArray[2], 2);
if($outputFormat eq "oracle"){
print $fh_user $userStringOracle . "(" . $userId . ", '" . $replacedName . "', '" . $replacedAddress . "', 0, " . $fieldArray[3] . ", '" . $migrationTimestamp . "', " . $fieldArray[4] . ", '" . $migrationTimestamp . "', 0, '" . $migrationTimestamp ."', '" . $migrationTimestamp ."', '" . $migrationTimestamp ."', " . $listId . ")\n";
} else {
$lastOutputUser = $lastOutputUser . "(" . $userId . ", '" . $replacedName . "', '" . $replacedAddress . "', 0, " . $fieldArray[3] . ", '" . $migrationTimestampMySQL . "', " . $fieldArray[4] . ", '" . $migrationTimestampMySQL . "', 0, '" . $migrationTimestampMySQL ."', '" . $migrationTimestampMySQL ."', '" . $migrationTimestampMySQL ."', " . $listId . "),\n";
}

# parse user attribute references:
# 'INTO "NL_USER_ATTRIBUTE_VALUE" ("USER_ID", "ATTRIBUTE_VALUE_ID") VALUES'
for(my $i = 0; $i < @fieldArray-5; $i++){
if(lc($fieldArray[$i+5]) eq "true"){
if($outputFormat eq "oracle"){
print $fh_ref $attributeReferenceStringOracle . "(" . $userId . ", " . ($attValStartId+$i) . ")\n";
} else {
$lastOutputAttRef = $lastOutputAttRef . "(" . $userId . ", " . ($attValStartId+$i) . "),\n";
}
}
}
$entryCounter++;
$insertUserStmntCounter++;
$userId++;
# commit after 10 user entries
if($insertUserStmntCounter == $insertUserStmntCounterMax){
if($outputFormat eq "oracle"){
print $fh_user $insertEndStringOracle;
print $fh_ref $insertEndStringOracle;
} else {
# replace ',' with ';'
$lastOutputAttRef = substr($lastOutputAttRef, 0, -2);
$lastOutputUser = substr($lastOutputUser, 0, -2);
print $fh_ref $lastOutputAttRef . ";\n";
print $fh_user $lastOutputUser . ";\n";
$lastOutputAttRef = "";
$lastOutputUser = "";
print $fh_ref $insertEndStringMySQL;
print $fh_user $insertEndStringMySQL;
}
$insertUserStmntCounter = 0;
}
# debug: end after 51 entries
#if($entryCounter == 5001){
# last;
#}
} else {
# error on parsing line
warn "Line could not be parsed: $line\n";
}
}
if($outputFormat eq "oracle"){
print $fh_user $insertEndStringOracle;
print $fh_ref $insertEndStringOracle;
# create new user_id sequence for oracle db
print $fh_user 'DROP SEQUENCE "SEQ_NL_USER_ID";' . "\n" . 'CREATE SEQUENCE "SEQ_NL_USER_ID" START WITH ' . $userId . ";\n";}
else {
# replace ',' with ';'
$lastOutputAttRef = substr($lastOutputAttRef, 0, -2);
$lastOutputUser = substr($lastOutputUser, 0, -2);
print $fh_ref $lastOutputAttRef . ";\n";
print $fh_user $lastOutputUser . ";\n";
$lastOutputAttRef = "";
$lastOutputUser = "";
print $fh_ref $insertEndStringMySQL;
print $fh_user $insertEndStringMySQL;}
# close all files
close($csvFile);
close($fh_list);
close($fh_user);
close($fh_ref);
# finished

print "finished - 3 files created.\n";

sub parseAttributeTreeAndConvert {
my @fieldArray = @_;
my %attributeMap;
# preserve order of inserting into hash
tie %attributeMap, 'Tie::IxHash';
my $output = '';
# build list and listConfig
if($outputFormat eq "oracle"){
# build list sql output oracle
$output = $output . $insertStartStringOracle;
$output = $output . $listStringOracle;
# 'INTO "NL_LIST" ("ID", "MANDANT", "NAME", "ADDRESS", "BOUNCE_COUNT") VALUES';
$output = $output . "(" . $listId . ", '" . $mandant . "', '" . $listName . "', '" . $listAddress . "', 0)\n" ;
$output = $output . $insertEndStringOracle;
# build list config sql output oracle
$output = $output . $insertStartStringOracle;
$output = $output . $listConfigStringOracle;
# 'INTO "NL_LIST_CONFIG" ("ID", "BULK_SEND_RANGE", "ADMIN_ADDRESS", "FROM_ADDRESS", "BOUNCE_IGNORE_PERIOD", "BOUNCE_SCORE_THRESHOLD", "LIST_ID", "TO_ADDRESS")';
$output = $output . "(" . $listId . ", 50, 'admin', '" . $fromAddress . "', 24, 6, " . $listId . ",'" . $toAddress . "')\n";
$output = $output . $insertEndStringOracle;
} elsif($outputFormat eq "mysql") {
# build list sql output mysql
$output = $output . $listStringMySQL;
# 'INSERT INTO `nl_list` (`ID`, `MANDANT`, `NAME`, `ADDRESS`, `BOUNCE_COUNT`) VALUES ';
$output = $output . "(" . $listId . ", '" . $mandant . "', '" . $listName . "', '" . $listAddress . "', 0);\n" ;
$output = $output . $insertEndStringMySQL;
# build list config sql output mysql
$output = $output . $listConfigStringMySQL;
# 'INSERT INTO `nl_list_config` (`ID`, `BULK_SEND_RANGE`, `ADMIN_ADDRESS`, `FROM_ADDRESS`, `BOUNCE_IGNORE_PERIOD`, `BOUNCE_SCORE_THRESHOLD`, `LIST_ID`, `TO_ADDRESS`) VALUES ';
$output = $output . "(" . $listId . ", 50, 'admin', '" . $fromAddress . "', 24, 6, " . $listId . ",'" . $toAddress . "');\n";
$output = $output . $insertEndStringMySQL;
} else {
print "Output format neither 'oracle' nor 'mysql'";
exit;
}
# parse - create map of attributes with attribute values
# loop all fields (first row of csv file)
foreach my $field (@fieldArray){
# if field contains 'attribute:attributeValue'
if($field =~ m/[^:]+:+[^:]+/){
# split on ':'
(my $attribute, my $attributeValue) = split(/\s*:\s*/, $field, 2);
# trim each part
$attribute = trim($attribute);
$attributeValue = trim($attributeValue);
# push into array of hash with key '$attribute'
push (@{ $attributeMap{$attribute} }, $attributeValue);
}
}
my $attOutput;
if($outputFormat eq "oracle"){
# build attribute and attributeValue sql output oracle
$output = $output . $insertStartStringOracle;
$attOutput = $insertStartStringOracle;
} else {
$output = $output . $attributeStringMySQL;
$attOutput = $attributeValueStringMySQL;
}
# convert map with attributes and attributeValues
my $attPosition = 0;
my $attValPosition = 0;
# attributes
for my $att (keys %attributeMap) {
if($outputFormat eq "oracle"){
$output = $output . $attributeStringOracle . "(" . $attributeId . ", '" . $att . "', '" . $att . "', " . $attPosition . ", " . $listId . ")\n";
} else {
$output = $output . "(" . $attributeId . ", '" . $att . "', '" . $att . "', " . $attPosition . ", " . $listId . "),\n";
}
$attPosition = $attPosition + 10;
$attValPosition = 0;
# attribute values
for my $attVal (@{$attributeMap{$att}}) {
if($outputFormat eq "oracle"){
$attOutput = $attOutput . $attributeValueStringOracle . "(" . $attributeValueId . ", '" . $attVal . "', '" . $attVal . "', 1, " . $attValPosition . ", " . $attributeId . ")\n";
} else {
$attOutput = $attOutput . "(" . $attributeValueId . ", '" . $attVal . "', '" . $attVal . "', 1, " . $attValPosition . ", " . $attributeId . "),\n";
}
$attValPosition = $attValPosition + 10;
$attributeValueId ++;
}
$attributeId++; }
if($outputFormat eq "oracle"){
$output = $output . $insertEndStringOracle;
$output = $output . $attOutput;
$output = $output . $insertEndStringOracle;
# delete and create new sequences
$output = $output . 'DROP SEQUENCE "SEQ_NL_LIST_ID";' . "\n" . 'CREATE SEQUENCE "SEQ_NL_LIST_ID" START WITH ' . ($listId + 1) . ";\n";
$output = $output . 'DROP SEQUENCE "SEQ_NL_LIST_CONF_ID";' . "\n" . 'CREATE SEQUENCE "SEQ_NL_LIST_CONF_ID" START WITH ' . ($listId + 1) . ";\n";
$output = $output . 'DROP SEQUENCE "SEQ_NL_ATTRIBUTE_ID";' . "\n" . 'CREATE SEQUENCE "SEQ_NL_ATTRIBUTE_ID" START WITH ' . $attributeId . ";\n";
$output = $output . 'DROP SEQUENCE "SEQ_NL_ATTRIBUTE_VAL_ID";' . "\n" . 'CREATE SEQUENCE "SEQ_NL_ATTRIBUTE_VAL_ID" START WITH ' . $attributeValueId . ";\n";
$output = $output . "COMMIT;";
} else {
$output = substr($output, 0, -2);
$output = $output . ";\n";
$output = $output . $insertEndStringMySQL;
$attOutput = substr($attOutput, 0, -2);
$attOutput = $attOutput . ";\n";
$output = $output . $attOutput;
$output = $output . $insertEndStringMySQL;
}
print $output;
print $fh_list $output;
}

6.3 Migrationskontrolle

Der CSV Export einer Mailing-Liste der neuen MailDistributor-Datenbank ist über die Webapp des AdminService realisiert und erfolgt über Aufruf der Adresse:

http://<hostname>:<port>/AdminService/rest/mailinglists/{listId}/exportcsv

Das Perl Skript csvComparator.pl vergleicht die beiden CSV-Dateien miteinander und prüft, ob die Abonnentendaten (Email, ActivationStatus, DeliveryStatus) und ihre Attributwert-Konfigurationen gleich sind (die Reihenfolge der User oder Attribut-Werte müssen bei den Dateien nicht übereinstimmen). Neben dem Skript selbst werden im gleichen Verzeichnis der lib-Ordner (mit den Modulen String, Text) und die beiden CSV-Dateien erwartet. Der Aufruf erfolgt unter Angabe der CSV Dateien z.B.:

perl csvComparator.pl userexport.csv test_userexport_2015-06-26-105306.csv

Eine Übereinstimmung bzw. entsprechende Fehler werden ausgegeben.

csvComparator.pl

#!/usr/bin/perl



use strict;

use warnings;

use FindBin;

use lib "$FindBin::Bin/";

use lib "$FindBin::Bin/lib";




use Text::CSV;

use String::Util qw(trim);

 

# get 2 filenames by command line

my $num_args = $#ARGV + 1;

if ($num_args != 2) {

    print "Usage: csvComparator.pl <filename first userexport.csv> <filename second userexport.csv>\n";

    exit;

}

my $oldExportCSVFilename = $ARGV[0];

my $newExportCSVFilename = $ARGV[1];

 

# open files

open(my $fh_oldExportCSV, '<:encoding(UTF-8)', $oldExportCSVFilename) or die "Could not open $oldExportCSVFilename $!\n";

open(my $fh_newExportCSV, '<:encoding(UTF-8)', $newExportCSVFilename) or die "Could not open $newExportCSVFilename $!\n";

 

# set separation character to ';'

my $csv = Text::CSV->new({ sep_char => ';' });

 

print "parsing first file and creating hash..\n";

my %oldUserMap = parseCSVFileAndReturnHash($fh_oldExportCSV);

print "\nparsing second file and creating hash..\n";

my %newUserMap = parseCSVFileAndReturnHash($fh_newExportCSV);

 

print "comparing hashes..\n";

# check if both contain same amount

if(keys(%oldUserMap) != keys(%newUserMap)){

        print "content not matching: both files do not contain the same amout of users!\n";

        exit;

}

print "both hashes contain the same amout of users..\n";

 

# loop old values and compare

while (my ($key, $userMapValue) = each(%oldUserMap) ) {

        # check if key is also in the second map

        if (!exists $newUserMap{$key}) {

                print "content not matching: the user with address $key is not existent in the second file\n";

                exit;

        }

        if (ref($userMapValue) eq "HASH") {

                #print "$key =>\n";

                my $newUserValues = $newUserMap{$key};

                while (my ($userValueKey, $userValue) = each(%$userMapValue) ) {

                        # check if key is also in second user values map

                        if (!exists $newUserValues->{$userValueKey}) {

                                print "content not matching: the user with address $key does not have the same attribute configuration in both files:\n";

                                print "\t'$userValueKey' is missing in the second file..\n";

                                exit;

                        }

                        # skip id check

                        if(lc($userValueKey) eq "id" or lc($userValueKey) eq "name"){

                                delete $newUserValues->{$userValueKey};

                                next;

                        }

                        # compare values for key

                        if(!lc($userValue) eq  lc($newUserValues->{$userValueKey})){

                                print "content not matching: the user with address $key does not have the same value in both files:\n";

                                print "\t'$userValue' in column '$userValueKey' is not equal..\n";

                        }

                        # delete already compared 'key => value' from second hash

                        delete $newUserValues->{$userValueKey};

                }

 

                # if there are values left in the new map under the address key, contents are not matching --> there is more in the new file than in the old

                if(keys(%$newUserValues) > 0){

                        print "content not matching: the user with address $key does not have the same attribute configuration in both files:\n";

                        print "attribute values which are subscribed in the new file but not in the old file:\n";

                        for my $leftoverKeys (keys %$newUserValues){

                                print "\t'$leftoverKeys'";

                        }

                        print "\n";

                        exit;

                }

        }

}



print "\ncomparism complete: user values (except 'id' and 'name') and subscription configuration are matching!\n";

exit;



sub parseCSVFileAndReturnHash {

        my $fh = $_[0];

        my $entryCounter = 0;

        my @keyArray;

        my %userMap;

 

        while (my $line = <$fh>) {

                # remove trailing newline chars

                chomp $line;

                # check for blank line

                if ($line =~ /^\s*$/){

                        print "skipping empty line..\n";

                        next;

                }

 

                # parse line

                if ($csv->parse($line)) {

                        my @fieldArray = $csv->fields();

                        # remove leading and trailing white spaces for each cell

                        foreach my $field (@fieldArray){

                                $field = trim($field);

                        }

 

                        # parse first line and fill @oKeyArray

                        if($entryCounter == 0){

                                if(lc($fieldArray[0]) eq "id"){

                                        foreach my $field (@fieldArray){

                                                push (@keyArray, lc($field));

                                        }

                                } else {

                                        print "Error: first row is missing: expecting 'id; name; email; activationStatus; deliveryStatus; attributes[]..";

                                        exit;

                                }

                                print "found following 'attribut:attributeValues': ";

                                foreach my $keys (@keyArray){

                                        if($keys =~ m/[^:]+:+[^:]+/){

                                                print "'$keys', ";

                                        }

                                }

                                print "\n";

                                $entryCounter++;

                                next;

                        }

 

                        # parse user lines, add values to userValues as key => value

                        my $userValues;

                        for (my $i = 0; $i < @fieldArray; $i++) {

                                if($keyArray[$i] =~ m/[^:]+:+[^:]+/){

                                        # attribute:attributeValue

                                        if(lc($fieldArray[$i]) eq "true"){

                                                # only add attribute:attributeValue as key if true

                                                $userValues->{ $keyArray[$i] } = "true";

                                        }

                                } else {

                                        # normale user values

                                        $userValues->{ $keyArray[$i] } = $fieldArray[$i];

                                }

                        }

 

                        # add userValues to userMap with address as key

                        my $userAddress = lc($fieldArray[2]);

                        $userMap{ $userAddress } = $userValues;



                } else {

                        # error on parsing line

                        warn "Line could not be parsed: $line\n";

                }

        }

 

        return %userMap;

}

 



7 Anhang A: Script zur halbautomatischen Migration des Mandantenprojekts

Das Script zur halbautomatischen Migration eines Mandantenprojekts vom GSB/CM zum GSB/OS führt folgende Aufgaben aus:

  • Anpassung an die geänderte Verzeichnisstruktur
  • Anpassung von Packagenamen in Java-Klassen und JSP-Templates
  • Aufräumarbeiten wie Umkodierung von Sonderzeichen und Entfernen von CVS-Headern

Der Aufruf erfolgt durch

migrate-customer.sh -c=<customer> -s=<sourceDir> -t=<targetDir>

Dabei sind die Parameter folgendermassen zu setzen:

<customer>

Der Mandant in Kleinschreibung, in unserem Fall standardlsg

<sourceDir>

Der Pfad zu dem Verzeichnis in dem das GSB/CM-Mandantenprojekt abgelegt ist

<targetDir>

Der Pfad zu dem Verzeichnis in dem das GSB/OS-Mandantenprojekt abgelegt wird

#!/bin/sh

 

if [ $# -eq 0 ]; then

      echo "Usage migrate-customer.sh -c=<customer> -s=<sourceDir> -t=<targetDir>"

      exit 2

fi

 

for i in "$@"

  do

  case $i in

      -c=*|--customer=*)

      # Der zu migrierende Mandant

      CUSTOMER="${i#*=}"

      shift

      ;;

      # Das Mandantenverzeichnis

      -s=*|--sourceDir=*)

      CUSTOMER_DIR="${i#*=}"

      shift

      ;;

      # Das Mandantenzielverzeichnis

      -t=*|--targetDir=*)

      TARGET_DIR="${i#*=}"

      shift

      ;;

      *)

      ;;

  esac

done

 

if [ ! "$CUSTOMER" ]; then

  echo "Customer not set (-c=<customer> | --customer=<customer>)"

  exit 2

fi

 

if [ ! "$TARGET_DIR" ]; then

  echo "Target dir not set (-t=<targetDir> | --targetDir=<targetDir>)"

  exit 2

fi

 

if [ ! "$CUSTOMER_DIR" ]; then

  echo "Source dir not set (-s=<sourceDir> | --sourceDir=<sourceDirr>)"

  exit 2

fi

 

echo "Migrating customer '$CUSTOMER' from source dir '$CUSTOMER_DIR' to target dir '$TARGET_DIR'."

 

# Der zu migrierende Mandant lowercase

CUSTOMER_LC=${CUSTOMER,,}

 

# Die Mandantenverzeichnisse

CUSTOMER_CAE_DIR="${CUSTOMER_DIR}/cae"

CUSTOMER_CONFIG_DIR="${CUSTOMER_DIR}/config"

CUSTOMER_JAVA_BASE_DIR="${CUSTOMER_DIR}/src/java/de/materna/cms/customers/${CUSTOMER_LC}"

CUSTOMER_JAVA_CAE_DIR="${CUSTOMER_JAVA_BASE_DIR}/cae"

CUSTOMER_SQL_DIR="${CUSTOMER_DIR}/src/sql"

 

# Die Mandantenzielverzeichnisse

TARGET_CONFIG_DIR="${TARGET_DIR}/config"

TARGET_JAVA_SITE_DIR="${TARGET_DIR}/src/main/java/de/bund/gsb/customers/${CUSTOMER_LC}/site"

TARGET_RES_DIR="${TARGET_DIR}/src/main/resources"

TARGET_SPRING_DIR="${TARGET_RES_DIR}/spring/customers"

TARGET_SQL_DIR="${TARGET_DIR}/src/main/sql"

TARGET_WEB_DIR="${TARGET_DIR}/src/main/webapp"

 

# Erstellen der Mandantenzielverzeichnisse

mkdir -p ${TARGET_CONFIG_DIR} ${TARGET_JAVA_SITE_DIR} ${TARGET_RES_DIR} ${TARGET_SQL_DIR} ${TARGET_WEB_DIR} ${TARGET_SPRING_DIR}

 

# Kopieren der Sourcen

cp -r ${CUSTOMER_JAVA_CAE_DIR}/* ${TARGET_JAVA_SITE_DIR}

cp -r ${CUSTOMER_SQL_DIR}/* ${TARGET_SQL_DIR}

cp -r ${CUSTOMER_CAE_DIR}/* ${TARGET_WEB_DIR}

cp -r ${CUSTOMER_CONFIG_DIR}/* ${TARGET_CONFIG_DIR}

 

# Verschieben der Springkonfigurationen

mv ${TARGET_WEB_DIR}/WEB-INF/spring/* ${TARGET_SPRING_DIR}

rm -rf ${TARGET_WEB_DIR}/WEB-INF/spring

 

# Verschieben anderer Konfigurationen

mv ${TARGET_WEB_DIR}/WEB-INF/classes/* ${TARGET_RES_DIR}/

rm -rf ${TARGET_WEB_DIR}/WEB-INF/classes

 

# Entferne CVS Einträge aus dem Filesystem

find ${TARGET_DIR} -name "CVS" -type d | xargs rm -rf

 

# Entferne CVS Metadaten/Kommentare aus den Java-Files

find ${TARGET_DIR} -name "*.java" -exec perl -pi -e 'BEGIN{undef $/;} s/^\/\*.*?\*\/\n+//sm' {} \;

find ${TARGET_DIR} -name "*.java" -exec sed -i -E \

        `# Entferne alle trennenden Plus-Kommentare` \

        -e '/^.*\/\*[ +]+\*\/.*$/d' \

        `# Entferne vom CVS verwaltete Header` \

        -e '/^.*\* (Version|Filespec|Modified by|Date):.*$/d' \

        `# Entferne die SCCS ID` \

        -e '/^.*SCCS_ID.*$/d' \

        `# Entferne das ignorieren von nicht benutzten Methoden` \

        -e '/^.*@SuppressWarnings\("unused"\).*$/d' \

        `# Entferne die Kommentare hinter geschweiften Klammern` \

        -e 's/\} \/\/.*$/}/g' \

        `# Ersetze den Methodenzugriff auf getId_() / getName_() durch getId() / getName()` \

        -e 's/\.get(Name|Id)_\(\)/.get\1()/g' \

        `# Ersetze Coremedia / Materna CapBlobRef durch den neuen Blob-Typen` \

        -e 's/com\.coremedia\.cap\.common\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \

        -e 's/de\.materna\.cms\.cae\.contentbeans\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \

        `# Ersetze de.materna.cms.cae. durch de.bund.gsb.site.` \

        -e 's/de\.materna\.cms\.cae\./de.bund.gsb.site./g' \

        `# Ersetze de.materna.cms. durch de.bund.gsb.` \

        -e 's/de\.materna\.cms\./de.bund.gsb./g' \

        `# Ersetze .acegi. durch .security.` \

        -e 's/\.acegi\./.security./g' \

        `# Ersetze .customers.<Mandant>.cae durch .customers.<Mandant>.site` \

        -e 's/\.customers\.([^.]*)\.cae/.customers.\1.site/g' \

        `# Ersetze commons-lang escapeJavaScript durch commons-lang3 escapeEcmaScript` \

        -e 's/org\.apache\.commons\.lang\.StringEscapeUtils\.escapeJavaScript/org.apache.commons.lang3.StringEscapeUtils.escapeEcmaScript/g' \

        `# Ersetze HTML-Entities durch UTF-8 Zeichen` \

        -e 's/&auml;/ä/g' \

        -e 's/&ouml;/ö/g' \

        -e 's/&uuml;/ü/g' \

        -e 's/&Auml;/Ä/g' \

        -e 's/&Ouml;/Ö/g' \

        -e 's/&Uuml;/Ü/g' \

        -e 's/&szlig;/ß/g'

-e 's/INDEXER_COREMEDIA_COREPROPERTY_CUSTOMER/INDEXER_COREPROPERTY_CUSTOMER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_DOCUMENT_TYPE/INDEXER_COREPROPERTY_DOCUMENT_TYPE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_FOLDER_ID/INDEXER_COREPROPERTY_FOLDER_ID/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_FOLDER_PATH/INDEXER_COREPROPERTY_FOLDER_PATH/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LANGUAGE/INDEXER_COREPROPERTY_LANGUAGE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_MODIFICATION_DATE/INDEXER_COREPROPERTY_MODIFICATION_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_NAME/INDEXER_COREPROPERTY_NAME/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_PATH/INDEXER_COREPROPERTY_PATH/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_INDEX_DATE/INDEXER_COREPROPERTY_INDEX_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_SOURCE/INDEXER_COREPROPERTY_SOURCE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_VERSION/INDEXER_COREPROPERTY_VERSION/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CREATOR/INDEXER_COREPROPERTY_CREATOR/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CREATION_DATE/INDEXER_COREPROPERTY_CREATION_DATE/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_MODIFIER/INDEXER_COREPROPERTY_MODIFIER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LAST_PUBLISHER/INDEXER_COREPROPERTY_LAST_PUBLISHER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_PUBLISHED_VERSION/INDEXER_COREPROPERTY_PUBLISHED_VERSION/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_LAST_APPROVER/INDEXER_COREPROPERTY_LAST_APPROVER/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_/INDEXER_COREPROPERTY_/g' \
-e 's/INDEXER_COREMEDIA_COREPROPERTY_CONTENT_ID/INDEXER_COREPROPERTY_CONTENT_ID/g' \
-e 's/AbstractCoreMediaAction/AbstractAction/g' \
-e 's/de.bund.gsb.site.database.ratings.BkcmsRating/de.bund.gsb.site.rest.rating.RatingConstants/g' \
-e 's/BkcmsRating.PARAM_COMMENT/RatingConstants.PARAM_COMMENT/g' \
-e 's/BkcmsRating.PARAM_RATING/RatingConstants.PARAM_RATING/g' \
-e 's/BkcmsRating.PARAM_RATING_EMAIL/RatingConstants.PARAM_RATING_EMAIL/g' \
-e 's/BkcmsRating.PARAM_RATING_NAME/RatingConstants.PARAM_RATING_NAME/g' \
-e 's/BkcmsRating.PARAM_DOCID/RatingConstants.PARAM_DOCID/g' \
-e 's/BkcmsRating.PARAM_PARENTID/RatingConstants.PARAM_PARENTID/g' \


{} \;

 

# Entferne CVS Metadaten/Kommentare aus den JSP-Files

find ${TARGET_WEB_DIR} -name "*.jsp" -exec perl -pi -e 'BEGIN{undef $/;} s/^(<%--)+\n.*?\n--%>//smg' {} \;

find ${TARGET_WEB_DIR} -name "*.jsp" -exec sed -i -E \

        `# Ersetze Coremedia / Materna CapBlobRef durch den neuen Blob-Typen` \

        -e 's/de\.materna\.cms\.cae\.contentbeans\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \

        -e 's/com\.coremedia\.cap\.common\.CapBlobRef/de.bund.gsb.site.content.Blob/g' \

        `# Ersetze de.materna.cms.cae.struts. durch de.bund.gsb.site.forms.` \

        -e 's/de\.materna\.cms\.cae\.struts\./de.bund.gsb.site.forms./g' \

        `# Ersetze de.materna.cms.cae. durch de.bund.gsb.site.` \

        -e 's/de\.materna\.cms\.cae\./de.bund.gsb.site./g' \

        `# Ersetze de.materna.cms. durch de.bund.gsb.` \

        -e 's/de\.materna\.cms\./de.bund.gsb./g' \

        `# Ersetze .acegi. durch .security.` \

        -e 's/\.acegi\./.security./g' \

        `# Ersetze commons-lang escapeJavaScript durch commons-lang3 escapeEcmaScript` \

        -e 's/org\.apache\.commons\.lang\.StringEscapeUtils\.escapeJavaScript/org.apache.commons.lang3.StringEscapeUtils.escapeEcmaScript/g' \

        `# Ersetze .docType.name bzw. type.name durch .docTypeName` \

        -e 's/\.(doctype|type\.name)/.docTypeName/g' \

        `# Ersetze .theDirections  durch .theDirection` \

        -e 's/\.theDirections/.theDirection/g' \

        `# Ersetze .FAQsData  durch .FAQData` \

        -e 's/\.FAQsData/.FAQData/g' \

        `# Ersetze .intId  durch .id` \

        -e 's/\.intId/.id/g' \

        `# Entferne .content` \

        -e 's/(self|entry|docRef|doc|beanLabel)\.content\./\1./g' \

        `# Ersetze .parent.path durch .folder.path` \

        -e 's/\.parent\.path/.folder.path/g' \

        `# Entferne die Unterstriche für den Zugriff auf Content Properties` \

        -e 's/\.(id|name)_/.\1/g' \

        -e 's/\.(approvalDate|creationDate|modificationDate|publicationDate)_/.\1/g' \

        -e 's/\.(approver|creator|modifier|publisher)_/.\1/g' \

        `# Entferne den Zugriff auf den Namen, im GSB/OS sind die Felder einfache Strings` \

        -e 's/(approver|creator|modifier|publisher)\.name/\1/g' \

        `# Ersetze cms:getContentBean durch cms:getContentBeanFromPath` \

        -e 's/cms\:getContentBean\(/cms\:getContentBeanFromPath(/g' \

        `# Ersetze <logic:... > und <html:...> Tags durch <cms:... > Tags` \

        -e 's/<(logic|html)\:/<cms\:/g' \

        -e 's/<\/(logic|html)\:/<\/cms\:/g' \

        `# Ersetze .customers.<Mandant>.cae durch .customers.<Mandant>.site` \

        -e 's/\.customers\.([^.]*)\.cae/.customers.\1.site/g' \

        `# Ersetze HTML-Entities durch UTF-8 Zeichen oder XML-Entities` \

        -e 's/&auml;/ä/g' \

        -e 's/&ouml;/ö/g' \

        -e 's/&uuml;/ü/g' \

        -e 's/&Auml;/Ä/g' \

        -e 's/&Ouml;/Ö/g' \

        -e 's/&Uuml;/Ü/g' \

        -e 's/&szlig;/ß/g' \

        -e 's/\&nbsp;/\&#160;/g' \

        -e 's/\&hellip;/\&#8230;/g' \

        -e 's/\&euro;/\&#8364;/g' {} \;

 

echo "Done."