CCM NG: ThemeProcessor for XSLT based themes

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5081 8810af33-2d31-482b-a856-94f89814c4df
ccm-docs
jensp 2017-10-25 14:48:05 +00:00
parent de37d2beeb
commit 579a8fc869
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.
*/
private static final String THEMES_DIR = "/themes";
private static final long serialVersionUID = -8701021965452233811L;
@Inject
private ThemeFileInfoUtil themeFileInfoUtil;

View File

@ -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";

View File

@ -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 <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.
*/
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?
*/

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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

View File

@ -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 <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.

View File

@ -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 <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
* 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}.
*/

View File

@ -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 <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

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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);
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@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<ThemeTemplate> 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);
}

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