From 76c1f6371493404d2f812d886e70ebbde0214300 Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 25 Oct 2017 14:48:05 +0000 Subject: [PATCH] CCM NG: ThemeProcessor for XSLT based themes git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5081 8810af33-2d31-482b-a856-94f89814c4df --- .../libreccm/theming/StaticThemeProvider.java | 1 + .../org/libreccm/theming/ThemeConstants.java | 2 + .../org/libreccm/theming/ThemeFileInfo.java | 13 +- .../libreccm/theming/ThemeFileInfoUtil.java | 5 +- .../java/org/libreccm/theming/ThemeInfo.java | 5 +- .../org/libreccm/theming/ThemeProcessor.java | 10 +- .../org/libreccm/theming/ThemeProvider.java | 3 +- .../java/org/libreccm/theming/Themes.java | 6 +- .../theming/manifest/ThemeManifest.java | 41 ++++- .../theming/xslt/XsltThemeProcessor.java | 165 ++++++++++++++++++ pom.xml | 10 +- 11 files changed, 234 insertions(+), 27 deletions(-) create mode 100644 ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java diff --git a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java index 589833ccc..2932d8a3f 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java @@ -62,6 +62,7 @@ public class StaticThemeProvider implements ThemeProvider { * Path the the static themes. */ private static final String THEMES_DIR = "/themes"; + private static final long serialVersionUID = -8701021965452233811L; @Inject private ThemeFileInfoUtil themeFileInfoUtil; diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java index 0d95b47bb..3ed285aca 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java @@ -24,6 +24,8 @@ package org.libreccm.theming; */ public final class ThemeConstants { + public static final String PAGE_PARAMETER_TEMPLATE = "template"; + public final static String THEME_MANIFEST_JSON = "theme.json"; public final static String THEME_MANIFEST_XML = "theme.xml"; diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java index 252b736a4..3155347a1 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java @@ -18,6 +18,7 @@ */ package org.libreccm.theming; +import java.io.Serializable; import java.util.Objects; /** @@ -25,28 +26,30 @@ import java.util.Objects; * * @author Jens Pelzetter */ -public class ThemeFileInfo { +public class ThemeFileInfo implements Serializable { + + private static final long serialVersionUID = 2880986115955856570L; /** * The name of the file. */ private String name; - + /** * Is the file a directory? */ private boolean directory; - + /** * The type of the file (for example {@code text/xml} or {@code image/jpeg}. */ private String mimeType; - + /** * The size of the file. For directories this will be {@code 0}. */ private long size; - + /** * Is the file writable? */ diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java index 55a1c606e..0f32cbc93 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java @@ -21,6 +21,7 @@ package org.libreccm.theming; import org.libreccm.core.UnexpectedErrorException; import java.io.IOException; +import java.io.Serializable; import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; @@ -33,7 +34,9 @@ import javax.enterprise.context.RequestScoped; * @author Jens Pelzetter */ @RequestScoped -public class ThemeFileInfoUtil { +public class ThemeFileInfoUtil implements Serializable { + + private static final long serialVersionUID = -3382896567742774318L; /** * Build a {@link ThemeFileInfo} object for a file. Before calling this diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java index 01b61df91..5528f0f84 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java @@ -20,6 +20,7 @@ package org.libreccm.theming; import org.libreccm.theming.manifest.ThemeManifest; +import java.io.Serializable; import java.util.Objects; /** @@ -27,7 +28,9 @@ import java.util.Objects; * * @author Jens Pelzetter */ -public class ThemeInfo { +public class ThemeInfo implements Serializable { + + private static final long serialVersionUID = -518244930947022256L; /** * The manifest of the theme. diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java index 40847044a..c404777e3 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java @@ -18,6 +18,7 @@ */ package org.libreccm.theming; +import java.io.Serializable; import java.util.Map; import javax.enterprise.context.RequestScoped; @@ -32,16 +33,17 @@ import javax.enterprise.context.RequestScoped; * * @author Jens Pelzetter */ -public interface ThemeProcessor { +public interface ThemeProcessor extends Serializable { /** * Process the provided {@link PageModel} {@code page} and convert into HTML * using the theme {@code theme} provided by the * {@link ThemeProvider} {@code themeProvider}. * - * @param page The page to convert the HTML. - * @param theme The theme to use. - * @param themeProvider The {@link ThemeProvider} which provides the the theme. + * @param page The page to convert the HTML. + * @param theme The theme to use. + * @param themeProvider The {@link ThemeProvider} which provides the the + * theme. * * @return The HTML for the provided {@code page}. */ diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java index ae0262191..60a0e492b 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java @@ -21,6 +21,7 @@ package org.libreccm.theming; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.Serializable; import java.util.List; import java.util.Optional; @@ -30,7 +31,7 @@ import java.util.Optional; * * @author Jens Pelzetter */ -public interface ThemeProvider { +public interface ThemeProvider extends Serializable { /** * Provides a list of all themes provided by this theme provider. The list diff --git a/ccm-core/src/main/java/org/libreccm/theming/Themes.java b/ccm-core/src/main/java/org/libreccm/theming/Themes.java index 48c366ed0..08e856f29 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/Themes.java +++ b/ccm-core/src/main/java/org/libreccm/theming/Themes.java @@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger; import org.libreccm.core.UnexpectedErrorException; import org.libreccm.pagemodel.PageModel; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -40,7 +41,9 @@ import javax.inject.Inject; * @author Jens Pelzetter */ @RequestScoped -public class Themes { +public class Themes implements Serializable { + + private static final long serialVersionUID = 6861457919635241221L; private static final Logger LOGGER = LogManager.getLogger(Themes.class); @@ -139,5 +142,4 @@ public class Themes { return processor.process(page, theme, provider); } - } diff --git a/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifest.java b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifest.java index 8a45d63bf..9cea077ef 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifest.java +++ b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifest.java @@ -33,23 +33,27 @@ import javax.xml.bind.annotation.XmlRootElement; import static org.libreccm.theming.ThemeConstants.*; +import java.io.Serializable; + /** * Each theme contains a Manifest (either in XML or JSON format) which provides * informations about the theme. - * + * * @author Jens Pelzetter */ @XmlRootElement(name = "theme", namespace = THEMES_XML_NS) @XmlAccessorType(XmlAccessType.FIELD) -public class ThemeManifest { +public class ThemeManifest implements Serializable { + + private static final long serialVersionUID = 699497658459398231L; /** - * The name of the theme. Usually the same as the name of directory which + * The name of the theme. Usually the same as the name of directory which * contains the theme. */ @XmlElement(name = "name", namespace = THEMES_XML_NS) private String name; - + /** * The type of the theme, for example XSLT. */ @@ -75,6 +79,12 @@ public class ThemeManifest { @XmlElement(name = "template", namespace = THEMES_XML_NS) private List templates; + /** + * Path of the default template. + */ + @XmlElement(name = "default-template", namespace = THEMES_XML_NS) + private String defaultTemplate; + public ThemeManifest() { templates = new ArrayList<>(); } @@ -90,10 +100,11 @@ public class ThemeManifest { public String getType() { return type; } - + public void setType(final String type) { - this.type =type; + this.type = type; } + public LocalizedString getTitle() { return title; } @@ -126,6 +137,14 @@ public class ThemeManifest { templates.remove(template); } + public String getDefaultTemplate() { + return defaultTemplate; + } + + public void setDefaultTemplate(final String defaultTemplate) { + this.defaultTemplate = defaultTemplate; + } + @Override public int hashCode() { int hash = 7; @@ -134,6 +153,7 @@ public class ThemeManifest { hash = 83 * hash + Objects.hashCode(title); hash = 83 * hash + Objects.hashCode(description); hash = 83 * hash + Objects.hashCode(templates); + hash = 83 * hash + Objects.hashCode(defaultTemplate); return hash; } @@ -164,7 +184,10 @@ public class ThemeManifest { if (!Objects.equals(description, other.getDescription())) { return false; } - return Objects.equals(templates, other.getTemplates()); + if (!Objects.equals(templates, other.getTemplates())) { + return false; + } + return Objects.equals(defaultTemplate, other.getDefaultTemplate()); } public boolean canEqual(final Object obj) { @@ -182,13 +205,15 @@ public class ThemeManifest { + "name = \"%s\", " + "title = \"%s\", " + "description = \"%s\", " - + "templates = %s%s" + + "templates = %s, " + + "defaultTemplate%s" + " }", super.toString(), name, Objects.toString(title), Objects.toString(description), Objects.toString(templates), + defaultTemplate, data); } diff --git a/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java b/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java new file mode 100644 index 000000000..3a7f4d71d --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/xslt/XsltThemeProcessor.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2017 LibreCCM Foundation. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ +package org.libreccm.theming.xslt; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.theming.ThemeInfo; +import org.libreccm.theming.ThemeProcessor; +import org.libreccm.theming.ThemeProvider; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.util.Map; + +import javax.enterprise.context.RequestScoped; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import static org.libreccm.theming.ThemeConstants.*; + +import org.libreccm.theming.manifest.ThemeTemplate; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.util.Optional; + +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * A {@link ThemeProcessor} implementation for XSLT based themes. + * + * @author Jens Pelzetter + */ +@RequestScoped +public class XsltThemeProcessor implements ThemeProcessor { + + private static final long serialVersionUID = -3883625727845105417L; + + @Override + public String process(final Map page, + final ThemeInfo theme, + final ThemeProvider themeProvider) { + + //Convert page to XML + final JacksonXmlModule xmlModule = new JacksonXmlModule(); + final ObjectMapper mapper = new XmlMapper(xmlModule); + + final String pageAsXml; + try { + pageAsXml = mapper.writeValueAsString(page); + } catch (JsonProcessingException ex) { + throw new UnexpectedErrorException(ex); + } + + final DocumentBuilderFactory documentBuilderFactory + = DocumentBuilderFactory.newInstance(); + final DocumentBuilder documentBuilder; + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + } catch (ParserConfigurationException ex) { + throw new UnexpectedErrorException(ex); + } + + final Document document; + try { + document = documentBuilder.parse(pageAsXml); + } catch (SAXException | IOException ex) { + throw new UnexpectedErrorException(ex); + } + + final String pathToTemplate; + if (page.containsKey(PAGE_PARAMETER_TEMPLATE)) { + + final String templateName = (String) page + .get(PAGE_PARAMETER_TEMPLATE); + + final Optional template = theme + .getManifest() + .getTemplates() + .stream() + .filter(current -> current.getName().equals(templateName)) + .findAny(); + + if (template.isPresent()) { + pathToTemplate = template.get().getPath(); + } else { + throw new UnexpectedErrorException(String + .format("Theme \"%s\" does provide template \"%s\".", + theme.getName(), + templateName)); + } + } else { + pathToTemplate = theme.getManifest().getDefaultTemplate(); + } + + final InputStream xslFileInputStream = themeProvider + .getThemeFileAsStream(theme.getName(), + theme.getVersion(), + pathToTemplate) + .orElseThrow(() -> new UnexpectedErrorException(String + .format("Failed to open XSL file \"%s\" from theme \"%s\" for " + + "reading.", + pathToTemplate, + theme.getName()))); + + final Reader reader; + try { + reader = new InputStreamReader(xslFileInputStream, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + throw new UnexpectedErrorException(ex); + } + + final StreamSource xslFileStreamSource = new StreamSource(reader); + final TransformerFactory transformerFactory = TransformerFactory + .newInstance(); + final Transformer transformer; + try { + transformer = transformerFactory.newTransformer(xslFileStreamSource); + } catch (TransformerConfigurationException ex) { + throw new UnexpectedErrorException(ex); + } + + final StringWriter resultWriter = new StringWriter(); + final Result result = new StreamResult(resultWriter); + try { + transformer.transform(new DOMSource(document), result); + } catch (TransformerException ex) { + throw new UnexpectedErrorException(ex); + } + + return resultWriter.toString(); + } + +} diff --git a/pom.xml b/pom.xml index 0e921540a..d253c5748 100644 --- a/pom.xml +++ b/pom.xml @@ -115,7 +115,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.6.1 + 3.7.0 1.8 1.8 @@ -255,7 +255,7 @@ com.vaadin vaadin-maven-plugin - 8.1.3 + 8.1.5 @@ -405,7 +405,7 @@ com.vaadin vaadin-bom - 8.1.3 + 8.1.5 import pom @@ -442,7 +442,7 @@ org.apache.logging.log4j log4j-bom - 2.8.2 + 2.9.1 pom import @@ -520,7 +520,7 @@ net.sf.saxon Saxon-HE - 9.8.0-3 + 9.8.0-5