Open Source im professionellen Einsatz
Linux-Magazin 11/2013
© Vira Dobosh, 123RF.com

© Vira Dobosh, 123RF.com

Das in Java implementierte Magnolia CMS

Blühender Content-Baum

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.

1498

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.

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.

Diesen Artikel als PDF kaufen

Express-Kauf als PDF

Umfang: 5 Heftseiten

Preis € 0,99
(inkl. 19% MwSt.)

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • Kostenlose Kurse zu Magnolia CMS

    Das Softwareunternehmen Magnolia International Ltd. bietet kostenlose Online-Kurse zu seinem Content Management System (CMS) an.

  • Programmieren mit Speed

    Velocity aus dem Jakarta-Projekt hilft nicht nur den Webprogrammierern dabei, statische Vorlagen mit dynamischen Inhalten zu füllen. Das spart viel Zeit beim Entwickeln einander ähnelnder Beans.

  • Magnolia 5.4 mit vielen neuen Features

    Magnolia, das sich als Businessplattform und CMS versteht, legt jetzt mit der Version 5.4 etliche neue und verbesserte Features nach.

  • Ruby on Rails

    Das Webframework Ruby on Rails nimmt dem Entwickler viel Arbeit ab und lässt sich doch weitgehend anpassen. In der Beispielanwendung liefert es lediglich die Daten im Json-Format, das Anzeigen im Browser übernimmt die Javascript-Bibliothek Angular.js.

  • Magnolia 4.0: CMS barrierefrei und mobil

    Das Content Management System (CMS) Magnolia ist in Version 4.0 verfügbar.

comments powered by Disqus

Ausgabe 11/2017

Digitale Ausgabe: Preis € 6,40
(inkl. 19% MwSt.)

Stellenmarkt

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.