CCM NG: ThemeProcessor for XSLT based themes

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5081 8810af33-2d31-482b-a856-94f89814c4df

Former-commit-id: 66c1c8baeb
pull/2/head
jensp 2017-10-25 14:48:05 +00:00
parent 497458059e
commit 7646284f47
11 changed files with 234 additions and 27 deletions

View File

@ -62,6 +62,7 @@ public class StaticThemeProvider implements ThemeProvider {
* Path the the static themes. * Path the the static themes.
*/ */
private static final String THEMES_DIR = "/themes"; private static final String THEMES_DIR = "/themes";
private static final long serialVersionUID = -8701021965452233811L;
@Inject @Inject
private ThemeFileInfoUtil themeFileInfoUtil; private ThemeFileInfoUtil themeFileInfoUtil;

View File

@ -24,6 +24,8 @@ package org.libreccm.theming;
*/ */
public final class ThemeConstants { 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_JSON = "theme.json";
public final static String THEME_MANIFEST_XML = "theme.xml"; public final static String THEME_MANIFEST_XML = "theme.xml";

View File

@ -18,6 +18,7 @@
*/ */
package org.libreccm.theming; package org.libreccm.theming;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
/** /**
@ -25,28 +26,30 @@ import java.util.Objects;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
public class ThemeFileInfo { public class ThemeFileInfo implements Serializable {
private static final long serialVersionUID = 2880986115955856570L;
/** /**
* The name of the file. * The name of the file.
*/ */
private String name; private String name;
/** /**
* Is the file a directory? * Is the file a directory?
*/ */
private boolean directory; private boolean directory;
/** /**
* The type of the file (for example {@code text/xml} or {@code image/jpeg}. * The type of the file (for example {@code text/xml} or {@code image/jpeg}.
*/ */
private String mimeType; private String mimeType;
/** /**
* The size of the file. For directories this will be {@code 0}. * The size of the file. For directories this will be {@code 0}.
*/ */
private long size; private long size;
/** /**
* Is the file writable? * Is the file writable?
*/ */

View File

@ -21,6 +21,7 @@ package org.libreccm.theming;
import org.libreccm.core.UnexpectedErrorException; import org.libreccm.core.UnexpectedErrorException;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Objects; import java.util.Objects;
@ -33,7 +34,9 @@ import javax.enterprise.context.RequestScoped;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@RequestScoped @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 * Build a {@link ThemeFileInfo} object for a file. Before calling this

View File

@ -20,6 +20,7 @@ package org.libreccm.theming;
import org.libreccm.theming.manifest.ThemeManifest; import org.libreccm.theming.manifest.ThemeManifest;
import java.io.Serializable;
import java.util.Objects; import java.util.Objects;
/** /**
@ -27,7 +28,9 @@ import java.util.Objects;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
public class ThemeInfo { public class ThemeInfo implements Serializable {
private static final long serialVersionUID = -518244930947022256L;
/** /**
* The manifest of the theme. * The manifest of the theme.

View File

@ -18,6 +18,7 @@
*/ */
package org.libreccm.theming; package org.libreccm.theming;
import java.io.Serializable;
import java.util.Map; import java.util.Map;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
@ -32,16 +33,17 @@ import javax.enterprise.context.RequestScoped;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
public interface ThemeProcessor { public interface ThemeProcessor extends Serializable {
/** /**
* Process the provided {@link PageModel} {@code page} and convert into HTML * Process the provided {@link PageModel} {@code page} and convert into HTML
* using the theme {@code theme} provided by the * using the theme {@code theme} provided by the
* {@link ThemeProvider} {@code themeProvider}. * {@link ThemeProvider} {@code themeProvider}.
* *
* @param page The page to convert the HTML. * @param page The page to convert the HTML.
* @param theme The theme to use. * @param theme The theme to use.
* @param themeProvider The {@link ThemeProvider} which provides the the theme. * @param themeProvider The {@link ThemeProvider} which provides the the
* theme.
* *
* @return The HTML for the provided {@code page}. * @return The HTML for the provided {@code page}.
*/ */

View File

@ -21,6 +21,7 @@ package org.libreccm.theming;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -30,7 +31,7 @@ import java.util.Optional;
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
public interface ThemeProvider { public interface ThemeProvider extends Serializable {
/** /**
* Provides a list of all themes provided by this theme provider. The list * Provides a list of all themes provided by this theme provider. The list

View File

@ -23,6 +23,7 @@ import org.apache.logging.log4j.Logger;
import org.libreccm.core.UnexpectedErrorException; import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.pagemodel.PageModel; import org.libreccm.pagemodel.PageModel;
import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -40,7 +41,9 @@ import javax.inject.Inject;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@RequestScoped @RequestScoped
public class Themes { public class Themes implements Serializable {
private static final long serialVersionUID = 6861457919635241221L;
private static final Logger LOGGER = LogManager.getLogger(Themes.class); private static final Logger LOGGER = LogManager.getLogger(Themes.class);
@ -139,5 +142,4 @@ public class Themes {
return processor.process(page, theme, provider); return processor.process(page, theme, provider);
} }
} }

View File

@ -33,23 +33,27 @@ import javax.xml.bind.annotation.XmlRootElement;
import static org.libreccm.theming.ThemeConstants.*; import static org.libreccm.theming.ThemeConstants.*;
import java.io.Serializable;
/** /**
* Each theme contains a Manifest (either in XML or JSON format) which provides * Each theme contains a Manifest (either in XML or JSON format) which provides
* informations about the theme. * informations about the theme.
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
@XmlRootElement(name = "theme", namespace = THEMES_XML_NS) @XmlRootElement(name = "theme", namespace = THEMES_XML_NS)
@XmlAccessorType(XmlAccessType.FIELD) @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. * contains the theme.
*/ */
@XmlElement(name = "name", namespace = THEMES_XML_NS) @XmlElement(name = "name", namespace = THEMES_XML_NS)
private String name; private String name;
/** /**
* The type of the theme, for example XSLT. * The type of the theme, for example XSLT.
*/ */
@ -75,6 +79,12 @@ public class ThemeManifest {
@XmlElement(name = "template", namespace = THEMES_XML_NS) @XmlElement(name = "template", namespace = THEMES_XML_NS)
private List<ThemeTemplate> templates; private List<ThemeTemplate> templates;
/**
* Path of the default template.
*/
@XmlElement(name = "default-template", namespace = THEMES_XML_NS)
private String defaultTemplate;
public ThemeManifest() { public ThemeManifest() {
templates = new ArrayList<>(); templates = new ArrayList<>();
} }
@ -90,10 +100,11 @@ public class ThemeManifest {
public String getType() { public String getType() {
return type; return type;
} }
public void setType(final String type) { public void setType(final String type) {
this.type =type; this.type = type;
} }
public LocalizedString getTitle() { public LocalizedString getTitle() {
return title; return title;
} }
@ -126,6 +137,14 @@ public class ThemeManifest {
templates.remove(template); templates.remove(template);
} }
public String getDefaultTemplate() {
return defaultTemplate;
}
public void setDefaultTemplate(final String defaultTemplate) {
this.defaultTemplate = defaultTemplate;
}
@Override @Override
public int hashCode() { public int hashCode() {
int hash = 7; int hash = 7;
@ -134,6 +153,7 @@ public class ThemeManifest {
hash = 83 * hash + Objects.hashCode(title); hash = 83 * hash + Objects.hashCode(title);
hash = 83 * hash + Objects.hashCode(description); hash = 83 * hash + Objects.hashCode(description);
hash = 83 * hash + Objects.hashCode(templates); hash = 83 * hash + Objects.hashCode(templates);
hash = 83 * hash + Objects.hashCode(defaultTemplate);
return hash; return hash;
} }
@ -164,7 +184,10 @@ public class ThemeManifest {
if (!Objects.equals(description, other.getDescription())) { if (!Objects.equals(description, other.getDescription())) {
return false; 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) { public boolean canEqual(final Object obj) {
@ -182,13 +205,15 @@ public class ThemeManifest {
+ "name = \"%s\", " + "name = \"%s\", "
+ "title = \"%s\", " + "title = \"%s\", "
+ "description = \"%s\", " + "description = \"%s\", "
+ "templates = %s%s" + "templates = %s, "
+ "defaultTemplate%s"
+ " }", + " }",
super.toString(), super.toString(),
name, name,
Objects.toString(title), Objects.toString(title),
Objects.toString(description), Objects.toString(description),
Objects.toString(templates), Objects.toString(templates),
defaultTemplate,
data); data);
} }

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class XsltThemeProcessor implements ThemeProcessor {
private static final long serialVersionUID = -3883625727845105417L;
@Override
public String process(final Map<String, Object> 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<ThemeTemplate> 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();
}
}

10
pom.xml
View File

@ -115,7 +115,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version> <version>3.7.0</version>
<configuration> <configuration>
<source>1.8</source> <source>1.8</source>
<target>1.8</target> <target>1.8</target>
@ -255,7 +255,7 @@
<plugin> <plugin>
<groupId>com.vaadin</groupId> <groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId> <artifactId>vaadin-maven-plugin</artifactId>
<version>8.1.3</version> <version>8.1.5</version>
</plugin> </plugin>
</plugins> </plugins>
</pluginManagement> </pluginManagement>
@ -405,7 +405,7 @@
<dependency> <dependency>
<groupId>com.vaadin</groupId> <groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId> <artifactId>vaadin-bom</artifactId>
<version>8.1.3</version> <version>8.1.5</version>
<scope>import</scope> <scope>import</scope>
<type>pom</type> <type>pom</type>
</dependency> </dependency>
@ -442,7 +442,7 @@
<dependency> <dependency>
<groupId>org.apache.logging.log4j</groupId> <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId> <artifactId>log4j-bom</artifactId>
<version>2.8.2</version> <version>2.9.1</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -520,7 +520,7 @@
<dependency> <dependency>
<groupId>net.sf.saxon</groupId> <groupId>net.sf.saxon</groupId>
<artifactId>Saxon-HE</artifactId> <artifactId>Saxon-HE</artifactId>
<version>9.8.0-3</version> <version>9.8.0-5</version>
</dependency> </dependency>
<dependency> <dependency>