Aus Linux-Magazin 11/2013

Das in Java implementierte Magnolia CMS

© Vira Dobosh, 123RF.com

Magnolia CMS nutzen große Anwender wie Lovefilm oder EADS für ihre Webpräsenz. Das freie Java-System importiert externe Daten in ein zentrales Content Repository und macht sie damit auch bearbeitbar.

Das freie Contentmanagement-System Magnolia CMS existiert seit zehn Jahren und dient bei einer Reihe von bekannten Firmen zur Umsetzung des Internetauftritts. Sein Kerngedanke ist die Trennung in ein Autorensystem zum Eingeben und Erfassen von Texten und Bildern auf der einen Seite und ein Publikationssystem, das die Inhalte den Nutzern in einem einheitlichen Layout präsentiert, auf der anderen.

Der dahinterliegende Server ist in Java implementiert und fußt auf einer Reihe von Open-Source-Komponenten wie dem Servlet-Container Apache Tomcat und dem Content-Repository Jackrabbit. Magnolia CMS [1] ist sowohl in einer freien wie in einer Enterprise-Edition verfügbar. Die freie Version steht unter der GPL, für die kommerzielle Ausgabe bietet die hinter Magnolia CMS stehende gleichnamige Firma nicht nur die üblichen Dienstleistungen, sondern auch zusätzliche Features wie die Unterstützung von mehreren Internetdomains oder kommerziellen JEE-Servern wie Websphere und Weblogic.

Der Programmierer

Der Code für die Lösung der Aufgabe in diesem Artikel stammt aus dem Hause Magnolia International Ltd. in Basel [1], dem Hersteller des gleichnamigen CMS. Der Entwickler Lars Fischer aus der Abteilung Professional Services hat ihn geschrieben.

DELUG-DVD

Die Delug-DVD zu diesem Magazin enthält eine lauffähige Tomcat-Instanz mit Magnolia CMS und der Lösung der Programmieraufgabe.

Dieser Artikel zeigt, wie ein Entwickler Daten aus einer externen Quelle mit Hilfe von Magnolia CMS 5.0.2 auf den eigenen Webseiten publiziert. Als Beispiel dienen die unter [2] verfügbaren Daten über die Berliner und Brandenburger Volks- und Straßenfeste. Hierfür kommt der in Abbildung 1 illustrierte Ablauf zum Einsatz: Der eigentliche Import und die gegebenenfalls notwendige Transformation geschehen im Autorensystem. Hier müssen die Fremddaten lokal verfügbar sein, entweder durch regelmäßiges Abfragen (Pull-Prinzip) oder das Empfangen externer Events wie JMS-Nachrichten (Push-Prinzip).

Abbildung 1: Import und Ausgabe von Fremddaten in Magnolia CMS: Das Java Content Repository bildet den Mittelpunkt des Systems.

Abbildung 1: Import und Ausgabe von Fremddaten in Magnolia CMS: Das Java Content Repository bildet den Mittelpunkt des Systems.

Content Repository

In seiner Lösung der Aufgabe hat der Programmierer die Daten als Json regelmäßig vom Berliner Server abgefragt und in Magnolia CMS importiert. Für die Datenspeicherung nutzt Magnolia ein Java Content Repository (JCR). Dahinter steckt ein baumartiger Datenspeicher, dessen Knoten beliebige Metadaten tragen sowie auf Massendaten (Texte, Bilder, Filme oder Ähnliches) verweisen.

Die Grundidee dieser Speicherform besteht darin, kleinere Datenmengen wie hier Veranstaltungsort oder -zeit jeweils als Metadaten eines Knotens abzuspeichern und erst so spät wie möglich Text, XML oder HTML zu erzeugen. Damit stehen die einzelnen Metadaten-Attribute für Abfragen über die JCR-Schnittstellen bereit und müssen nicht aufwändig aus einem Text extrahiert werden.

Bei Magnolia CMS nutzen sowohl die Autoreninstanz als auch die für die Auslieferung an die Endnutzer zuständige öffentliche Instanz das gemeinsame Java Content Repository. Damit der Endanwender keine in Bearbeitung befindlichen Daten zu sehen bekommt, muss ein Redakteur die Einträge explizit für die Veröffentlichung freigeben.

Sobald die Daten freigegeben sind, stehen sie in der öffentlichen Instanz bereit. Darauf greifen Endanwender mit ihrem Browser zu und fragen HTML-Seiten an. Diese generiert Magnolia durch Kombination der im JCR enthaltenen Rohdaten mit einem Template. Die Trennung von Inhalt und Template hat den Vorteil, dass sich Inhalt und Aussehen einer Webseite unabhängig voneinander ändern lassen.

Datenimport

Die eigentlich Umsetzung für die Berliner Volksfeste gestaltet sich recht schlank, die Implementation des Imports enthalten die Listings 1 bis 3. Die kompletten Quellen für die gelungene und ansprechende Umsetzung stehen als Maven-Projekt unter [6] zur Verfügung.

Für Hintergrundprozesse kennt Magnolia CMS die Basisklasse »MgnlCommand« , deren Kinder sich über einen Cron-ähnlichen Mechanismus starten lassen. Die Konfiguration der zu startenden Klassen und Startzeiten geschieht bequem über die Weboberfläche. Für den Import der Berliner Daten hat der Anwendungsentwickler die Klasse »EventImporterCommand« abgeleitet (Listing 1).

Listing 1

Laden der Daten und Hauptschleife

01 public class EventImporterCommand extends MgnlCommand {
02
03 [...]
04
05   /**
06    * Ausfuehrungs-Methode des Magnolia Commands zum Import von
07    * Berliner Volksfesten
08    * @param context Magnolia Context
09    * @return Ergebnis der Import-Methode (true/false)
10    */
11   @Override
12   public boolean execute(Context context) throws Exception {
13     log.debug("Starte Event Import... ");
14
15     boolean importResult = true;
16
17     try {
18       // Start des Daten-Imports
19       InputStream input = new URL(importURL).openStream();
20       BufferedReader br = new BufferedReader(new InputStreamReader(input, "UTF-8"));
21
22       Gson gson = new Gson();
23
24       VolksfestData data = gson.fromJson(br, VolksfestData.class);
25
26       // Ende des eigentlichen Daten-Imports, importierte Events werden verarbeitet
27       for (Index event : data.getIndex()) {
28         if (validEvent(event)) {
29           try {
30           // Erstellung einer Seite im Website-Tree, falls das Event
31           // gueltige Daten hat und noch nicht vorhanden ist
32             session = MgnlContext.getJCRSession(repository);
33
34             Node parentNode = NodeUtil.createPath(session.getRootNode(),
35               path, NodeTypes.Page.NAME, false);
36             PropertyUtil.setProperty(parentNode, "mgnl:template", EVENT_OVERVIEW_TEMPLATE);
37             PropertyUtil.setProperty(parentNode, "title", "Berlin Events");
38
39             if (!eventExists(parentNode.getPath(), event.getId(), repository)) {
40               createEventNode(parentNode, event, NodeTypes.Page.NAME);
41               }
42
43             session.save();
44             } catch (Exception e) {
45           log.error("Problem beim erstellen von Content fuer ein Event: " + e.getMessage());
46           }
47         } else {
48           log.error("Fehlende Pflichtfelder bei Event (kein Import):");
49           log.error(event.toString());
50           }
51         }
52
53       } catch (Exception e) {
54           log.error("Problem beim Import von Events:" + e.getMessage());
55           importResult = false;
56         }
57
58         log.debug("Event-Import beendet.");
59
60         return importResult;
61   }
62 [...]
63 }

Startpunkt des Imports ist die Methode »execute()« in Zeile 12. Sie ruft zuerst die Json-Datei ab und wandelt sie mit Hilfe der Java-Bibliothek Gson [3] in Java-Objekte um. Dies sind Plain Old Java Objects (Pojos), einfache Datenobjekte, die eine Eins-zu-eins-Umsetzung des Json-Datenmodells in Java darstellen. Entsprechend sind alle Veranstaltungen in einem Objekt der Klasse »Index« enthalten, über die die For-Schleife (Zeilen 27 bis 51) iteriert.

Der Code in der Schleife fragt zunächst den Startknoten »Berlin Events« für die Ereignisse im JCR ab oder erzeugt ihn. Dann prüft er mit »eventExists()« , ob die Veranstaltung schon bekannt ist, andernfalls legt er mit »createEventNode()« einen neuen Knoten dafür an. Nach jeder Veranstaltung speichert »session.save()« (Zeile 43) die Änderungen im JCR. In Listing 2 findet das eigentliche Abspeichern der Veranstaltungsdaten in der Methode »createEventNode()« statt. Dort legt »NodeUtil.createPath()« pro Veranstaltung einen neuen JCR-Knoten an (Zeile 10), »PropertyUtil« speichert die Daten aus dem Json-Objekt im Knoten. Das JCR-API bietet nicht nur Schnittstellen zum direkten Auslesen der Attribute, sondern auch umfangreiche Abfragemöglichkeiten. Das nutzt die »eventExists()« -Methode in Listing 3: Bei mehrfachem Import soll das CMS selbstverständlich für bereits bekannte Veranstaltungen keine neuen Knoten anlegen.

Listing 3

Abfrage im JCR, ob Knoten bereits existiert

01     /**
02      * Prueft, ob ein Event bereits existiert
03      *
04      * @param eventPath Repository-Pfad, in dem das Event gesucht werden soll
05      * @param eventId eindeutige ID des Events aus dem REST-Import
06      * @param repo Repository, in dem gesucht werden soll
07      */
08     protected boolean eventExists(String eventPath, String eventId, String repo) {
09         boolean found = false;
10
11         String sql = "select * from [nt:base] as t where ISDESCENDANTNODE([" + eventPath + "]) " +
12                 "and (t.id='" + eventId + "')";
13
14         try {
15             NodeIterator posts = QueryUtil.search(repo, sql);
16             if (posts.hasNext() && posts.nextNode() != null) {
17                 found = true;
18             }
19         } catch (RepositoryException e) {
20             log.error("Problem bei der Suche nach einem Event: " + e.getMessage());
21         }
22
23         return found;
24     }

Listing 2

Mappen der Daten und Erzeugen der JCR-Knoten

01 /**
02  * Erstellt eine Event Page
03  *
04  * @param parentNode Parent-Knoten im Repository fuer das neue Event
05  * @param event einzelnes Event aus REST-Import
06  * @param eventType definiert den Typ des Events (Webseite oder Content)
07  */
08 protected void createEventNode(Node parentNode, Index event, String eventType) {
09   try {
10     Node eventNode = NodeUtil.createPath(parentNode, EVENT_PREFIX + event.getId(), eventType, false);
11     if (StringUtils.equals(NodeTypes.Page.NAME, eventType)) {
12       PropertyUtil.setProperty(eventNode, "mgnl:template", EVENT_TEMPLATE);
13       PropertyUtil.setProperty(eventNode, "abstract", event.getBezeichnung());
14       }
15     PropertyUtil.setProperty(eventNode, "id", event.getId());
16     PropertyUtil.setProperty(eventNode, "title", event.getBezeichnung());
17     PropertyUtil.setProperty(eventNode, "eventTitle", event.getBezeichnung());
18     PropertyUtil.setProperty(eventNode, "date", DateUtils.parseDate(event.getVon(), dateFormats));
19     PropertyUtil.setProperty(eventNode, "dateEnd", DateUtils.parseDate(event.getVon(), dateFormats));
20     PropertyUtil.setProperty(eventNode, "zeit", StringUtils.replace(event.getZeit(), "\n", " "));
21     PropertyUtil.setProperty(eventNode, "location", getLocation(event));
22     PropertyUtil.setProperty(eventNode, "bezirk", event.getBezirk());
23
24     PropertyUtil.setProperty(eventNode, "link", event.getWww());
25     PropertyUtil.setProperty(eventNode, "veranstalter", event.getVeranstalter());
26     PropertyUtil.setProperty(eventNode, "mail", event.getMail());
27     PropertyUtil.setProperty(eventNode, "bemerkungen", event.getBemerkungen());
28     session.save();
29     } catch (Exception e) {
30       log.error("Event konnte nicht erstellt werden, Event ID " + event.getId() + ": " + e.getMessage());
31       }
32 }
33
34  /**
35   * Baut die Event-Location aus Strasse und PLZ zusammen
36   *
37   * @param event Einzelnes Event aus REST-Import
38   * @return String mit der Event-Location
39   */
40   protected String getLocation(Index event) {
41     String location = event.getStrasse();
42     if (StringUtils.isNotEmpty(event.getPlz())) {
43       location += " (PLZ: " + event.getPlz() + ")";
44     }
45     return location;
46   }

Mit Hilfe einer SQL-ähnlichen Abfragesprache lassen sich JCR-Knoten anhand ihres Vaterknotens oder ihrer Metadaten finden. Das Attribut »id« aus den Originaldaten dient zur Identifizierung bereits importierter Veranstaltungen. Diese Suche stellt eine mächtige Grundlage für ein CMS dar, da sich neben Standardattributen (Änderungsdatum, Bearbeiter und so weiter) auch knotenspezifische Attribute wie ID oder der Stadtbezirk verwenden lassen. Auf der anderen Seite zeigt Listing 2 anhand des »location« -Attributs und der Methode »getLocation()« , wie einfach das Mapping von den Json-Quelldaten zu den Metadaten eines JCR-Knotens ist.

Freigeben und ändern

Nach dem Import kommt zunächst die Freigabe, sie erfolgt über die Autoreninstanz von Magnolia CMS. Die in Abbildung 2 mit einem roten Punkt markierten Veranstaltungen hat der Import neu angelegt, der Redakteur kann sie freigeben. Zudem lassen sich einzelne Veranstaltungen löschen, ändern (Abbildung 3) oder in einer Vorschau betrachten – Features, die Magnolia frei Haus mitliefert.

Abbildung 2: Im Magnolia-Seiteneditor kann der Benutzer Veranstaltungen freigeben oder löschen.

Abbildung 2: Im Magnolia-Seiteneditor kann der Benutzer Veranstaltungen freigeben oder löschen.

Abbildung 3: Einmal importiert, lassen sich die Daten der Veranstaltungen jederzeit ändern.

Abbildung 3: Einmal importiert, lassen sich die Daten der Veranstaltungen jederzeit ändern.

Bis jetzt existiert pro Veranstaltung nur ein Satz Metadaten im JCR, die Endanwender erwarten jedoch eine HTML-Seite im Browser. Zum Rendern der Daten bietet Magnolia CMS sein Standard Templating Kit (STK) an. Dessen Basis ist die weitverbreitete Template-Engine Freemarker [4]. Sie kümmert sich um das Rendern von Java-Objekten in Text. Die in Magnolia mitgelieferten Templates enthalten Standarddarstellungen, von Übersichtsseiten bis zu einzelnen Komponenten wie Kalender- oder Kommentarboxen. Für die Anzeige der Berliner Events kommen die existierenden Templates für »Event Overview« und »Event« zum Einsatz.

Der Entwickler hat die Standardseite mit einem geänderten CSS-Stylesheet versehen und vor allem die Suche von Veranstaltungen anhand des Start- und Enddatums umgesetzt (Abbildung 4). Die Suche ist Server-seitig schon unterstützt, nur das Übersichts-Template erhielt zusätzlich eine Referenz auf eine neue Komponente. Diese ist wiederum ein Template, das das Formular mit dem Start- und Enddatum enthält. Durch diesen modularen Aufbau kann man auf beliebigen Seiten immer wieder die gleichen Bausteine einfügen. Das Kalender-Widget selber zeichnet die Komponente »datepicker« aus der Bibliothek Jquery UI [5] ins Browserfenster.

Abbildung 4: Aus einem von Magnolia CMS bereits mitgelieferten Template wird mit kleinen Anpassungen die HTML-Seite mit der Veranstaltungsübersicht.

Abbildung 4: Aus einem von Magnolia CMS bereits mitgelieferten Template wird mit kleinen Anpassungen die HTML-Seite mit der Veranstaltungsübersicht.

Zusammenfassung

Magnolia CMS bietet offenbar eine solide Basis für den Aufbau einer Webpräsenz. Die Trennung der Daten von der Darstellung mittels Java Content Repository und Templates vermeidet Probleme beim Anpassen des Aussehens von Anfang an. Auch das Anbinden von Fremddaten mit eigenem Java-Quelltext lässt sich zügig umsetzen, durch den Einsatz des Content Repository ergibt sich zugleich ein Caching. Das Template-Konzept für die Darstellung ist in sich schlüssig, wer gravierende Änderungen an den vorhandenen Templates vornimmt, muss jedoch einiges an Lernaufwand vorsehen. Positiv fällt die umfangreiche Dokumentation auf der Webseite [1] auf. (mhu)

Der Autor

Carsten Zerbst arbeitet als Teamleiter im CAD- und PDM-Umfeld für die Automobil-, Luft- und Raumfahrtindustrie. Er konzentriert sich auf die Beratung und Erstellung von Java-basierter Individualsoftware zur optimalen Unterstützung der Arbeitsabläufe. Zur Zeit ist er auf der Suche nach neuen Teammitgliedern.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben