CCM NG: StaticThemeProvider for serving themes from the classpath

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5077 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2017-10-25 12:36:23 +00:00
parent 5c2387d4b3
commit 92e62ff6ec
14 changed files with 605 additions and 155 deletions

View File

@ -0,0 +1,64 @@
/*
* 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.librecms.ui.authoring.article;
import com.vaadin.ui.CustomComponent;
import com.vaadin.ui.TextField;
import org.librecms.contentsection.ContentItem;
import org.librecms.contenttypes.Article;
import org.librecms.ui.ContentSectionViewController;
import java.io.Serializable;
import java.util.Objects;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ArticlePropertiesStep
extends CustomComponent
implements Serializable {
private static final long serialVersionUID = 1587965921855375545L;
private final ContentSectionViewController controller;
private final Article article;
public ArticlePropertiesStep(final ContentSectionViewController controller,
final ContentItem item) {
Objects.requireNonNull(controller);
Objects.requireNonNull(item);
if (!(item instanceof Article)) {
throw new IllegalArgumentException(String
.format("The provided ContentItem is not an instance "
+ "of class \"%s\" but of class \"%s\".",
Article.class.getName(),
item.getClass().getName()));
}
this.controller = controller;
article = (Article) item;
final TextField titleField = new TextField("Title");
}
}

View File

@ -18,9 +18,13 @@
*/
package org.libreccm.theming;
import static org.libreccm.theming.ThemeConstants.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.theming.manifest.ThemeManifest;
import org.libreccm.theming.manifest.ThemeManifestUtil;
import java.io.IOException;
import java.io.InputStream;
@ -36,28 +40,40 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
/**
* A theme provider implementation which serves themes from the class path
* ({@code /themes)}.
* ({@code /themes)}. This implementation does not support changes to the
* theme(s) and files.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class StaticThemeProvider implements ThemeProvider {
private static final Logger LOGGER = LogManager
.getLogger(StaticThemeProvider.class);
/**
* Path the the static themes.
*/
private static final String THEMES_DIR = "/themes";
private static final String THEME_XML = "theme.xml";
private static final String THEME_JSON = "theme.json";
@Inject
private ThemeFileInfoUtil themeFileInfoUtil;
@Inject
private ThemeManifestUtil themeManifests;
@Override
public List<ThemeInfo> getThemes() {
LOGGER.debug("Retrieving info about all static themes...");
final List<ThemeInfo> themeInfos = new ArrayList<>();
try (final FileSystem jarFileSystem = FileSystems.newFileSystem(
getJarUri(), Collections.emptyMap())) {
@ -85,31 +101,195 @@ public class StaticThemeProvider implements ThemeProvider {
}
@Override
public Optional<ThemeInfo> getThemeInfo(String theme, ThemeVersion version) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
public Optional<ThemeInfo> getThemeInfo(final String theme,
final ThemeVersion version) {
Objects.requireNonNull(theme);
Objects.requireNonNull(version);
if (theme.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the theme can't be empty.");
}
LOGGER.debug("Trying to find static theme \"{}\"...",
theme);
try (final FileSystem jarFileSystem = FileSystems
.newFileSystem(getJarUri(), Collections.emptyMap())) {
final Path themePath = jarFileSystem
.getPath(String.format(THEMES_DIR + "/%s", theme));
if (isTheme(themePath)) {
return Optional.of(generateThemeInfo(themePath));
} else {
return Optional.empty();
}
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
@Override
public boolean providesTheme(String theme, ThemeVersion version) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
public boolean providesTheme(final String theme,
final ThemeVersion version) {
Objects.requireNonNull(theme);
Objects.requireNonNull(version);
if (theme.isEmpty() || theme.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the theme can't be empty.");
}
LOGGER.debug("Determining if there is static theme \"{}\"...",
theme);
try (final FileSystem jarFileSystem = FileSystems
.newFileSystem(getJarUri(), Collections.emptyMap())) {
final Path themePath = jarFileSystem
.getPath(String.format(THEMES_DIR + "/%s", theme));
LOGGER.debug("Is there a static theme \"{}\": {}",
theme,
isTheme(themePath));
return isTheme(themePath);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
@Override
public List<ThemeFileInfo> listThemeFiles(String theme, ThemeVersion version,
String path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
public List<ThemeFileInfo> listThemeFiles(final String theme,
final ThemeVersion version,
final String path) {
Objects.requireNonNull(theme);
Objects.requireNonNull(version);
Objects.requireNonNull(path);
if (theme.isEmpty() || theme.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the theme can't be empty.");
}
final String pathToDir;
if ("".equals(path)) {
pathToDir = "/";
} else {
pathToDir = path;
}
LOGGER.debug("Listing all files in path \"{]\" of theme \"{}\"...",
path,
theme);
final List<ThemeFileInfo> infos;
try (final FileSystem jarFileSystem = FileSystems
.newFileSystem(getJarUri(), Collections.emptyMap())) {
final Path themePath = jarFileSystem
.getPath(String.format(THEMES_DIR + "/%s", theme));
if (!isTheme(themePath)) {
throw new IllegalArgumentException(String
.format("Theme \"%s\" does not exist.",
theme));
}
final Path dirPath = themePath.resolve(pathToDir);
if (Files.exists(dirPath)) {
if (Files.isDirectory(dirPath)) {
try (final Stream<Path> stream = Files.list(dirPath)) {
infos = stream
.map(themeFileInfoUtil::buildThemeInfo)
.collect(Collectors.toList());
}
} else {
infos = new ArrayList<>();
infos.add(themeFileInfoUtil.buildThemeInfo(dirPath));
}
} else {
throw new IllegalArgumentException(String
.format("No file/directory \"%s\" in theme \"%s\".",
path,
theme));
}
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
LOGGER.debug("Files in path \"{}\" of static theme \"{}\": {}",
pathToDir,
theme,
Objects.toString(infos));
return infos;
}
@Override
public Optional<InputStream> getThemeFileAsStream(String theme,
ThemeVersion version,
String path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
public Optional<InputStream> getThemeFileAsStream(final String theme,
final ThemeVersion version,
final String path) {
Objects.requireNonNull(theme);
Objects.requireNonNull(version);
Objects.requireNonNull(path);
if (theme.isEmpty() || theme.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the theme can't be empty.");
}
if (path.isEmpty() || path.matches("\\s*")) {
throw new IllegalArgumentException(
"The name of the theme can't be empty.");
}
try (final FileSystem jarFileSystem = FileSystems
.newFileSystem(getJarUri(), Collections.emptyMap())) {
final Path themePath = jarFileSystem
.getPath(String.format(THEMES_DIR + "/%s", theme));
final Path filePath;
if (path.charAt(0) == '/') {
filePath = themePath.resolve(path.substring(1));
} else {
filePath = themePath.resolve(path);
}
if (!Files.isRegularFile(filePath)) {
throw new IllegalArgumentException(String
.format("The provided path \"%s\" in theme \"%s\" points "
+ "not to a regular file.",
path,
theme));
}
if (Files.exists(filePath)) {
return Optional.of(Files.newInputStream(filePath));
} else {
return Optional.empty();
}
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
@Override
public OutputStream getOutputStreamForThemeFile(String theme, String path) {
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
public OutputStream getOutputStreamForThemeFile(final String theme,
final String path) {
throw new UnsupportedOperationException("Not supported by this"
+ " implemetentation");
}
@Override
@ -152,8 +332,8 @@ public class StaticThemeProvider implements ThemeProvider {
return false;
}
final Path manifestPathJson = path.resolve(THEME_JSON);
final Path manifestPathXml = path.resolve(THEME_XML);
final Path manifestPathJson = path.resolve(THEME_MANIFEST_JSON);
final Path manifestPathXml = path.resolve(THEME_MANIFEST_XML);
return Files.exists(manifestPathJson) || Files.exists(manifestPathXml);
}
@ -169,27 +349,27 @@ public class StaticThemeProvider implements ThemeProvider {
path.toString()));
}
final Path manifestPathJson = path.resolve(THEME_JSON);
final Path manifestPathXml = path.resolve(THEME_XML);
final Path manifestPathJson = path.resolve(THEME_MANIFEST_JSON);
final Path manifestPathXml = path.resolve(THEME_MANIFEST_XML);
final ThemeManifest manifest;
if (Files.exists(manifestPathJson)) {
return generateThemeInfoFromJson(manifestPathJson);
manifest = themeManifests.loadManifest(manifestPathJson);
} else if (Files.exists(manifestPathXml)) {
return generateThemeInfoFromXml(manifestPathXml);
manifest = themeManifests.loadManifest(manifestPathXml);
} else {
throw new IllegalArgumentException(String
.format("The provided path \"%s\" does "
+ "contain a theme manifest file.",
path.toString()));
}
}
private ThemeInfo generateThemeInfoFromJson(final Path path) {
throw new UnsupportedOperationException();
}
final ThemeInfo themeInfo = new ThemeInfo();
themeInfo.setVersion(ThemeVersion.LIVE);
themeInfo.setProvider(getClass());
themeInfo.setManifest(manifest);
private ThemeInfo generateThemeInfoFromXml(final Path path) {
throw new UnsupportedOperationException();
return themeInfo;
}
}

View File

@ -23,10 +23,14 @@ package org.libreccm.theming;
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public final class ThemeConstants {
public final static String THEME_MANIFEST_JSON = "theme.json";
public final static String THEME_MANIFEST_XML = "theme.xml";
public final static String THEMES_XML_NS = "http://themes.libreccm.org";
private ThemeConstants() {
//Nothing
}
public final static String THEMES_XML_NS = "http://themes.libreccm.org";
}

View File

@ -27,10 +27,29 @@ import java.util.Objects;
*/
public class ThemeFileInfo {
/**
* 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?
*/
private boolean writable;
public String getName() {

View File

@ -0,0 +1,65 @@
/*
* 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;
import org.libreccm.core.UnexpectedErrorException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import javax.enterprise.context.RequestScoped;
/**
* Utility for building a {@link ThemeFileInfo} object for a file.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class ThemeFileInfoUtil {
/**
* Build a {@link ThemeFileInfo} object for a file. Before calling this
* method the caller should check if the file to {@code path} points exists.
*
* @param path The path of the file.
* @return A {@link ThemeFileInfo} object with informations about the file.
*/
public ThemeFileInfo buildThemeInfo(final Path path) {
Objects.requireNonNull(path);
try {
final ThemeFileInfo fileInfo = new ThemeFileInfo();
fileInfo.setName(path.getFileName().toString());
fileInfo.setDirectory(Files.isDirectory(path));
fileInfo.setWritable(Files.isWritable(path));
if (!Files.isDirectory(path)) {
fileInfo.setSize(Files.size(path));
}
fileInfo.setMimeType(Files.probeContentType(path));
return fileInfo;
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
}
}

View File

@ -23,18 +23,27 @@ import org.libreccm.theming.manifest.ThemeManifest;
import java.util.Objects;
/**
* Informations about a theme.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public class ThemeInfo {
/**
* The manifest of the theme.
*/
private ThemeManifest manifest;
// private String name;
/**
* The version of the theme.
*/
private ThemeVersion version;
// private String type;
private Class<ThemeProvider> provider;
/**
* The {@link ThemeProvider} implementation which is responsible for the
* theme.
*/
private Class<? extends ThemeProvider> provider;
public ThemeManifest getManifest() {
return manifest;
@ -47,16 +56,12 @@ public class ThemeInfo {
/**
* Convenient getter for name of theme.
*
* @return {@link ThemeManifest#getName()}
* @return {@link #manifest#getName()}
*/
public String getName() {
// return name;
return manifest.getName();
}
// public void setName(final String name) {
// this.name = name;
// }
public ThemeVersion getVersion() {
return version;
}
@ -68,21 +73,17 @@ public class ThemeInfo {
/**
* Convenient getter for type of theme.
*
* @return {@link ThemeManifest#getType()}
* @return {@link #manifest#getType()}
*/
public String getType() {
// return type;
return manifest.getType();
}
// public void setType(final String type) {
// this.type = type;
// }
public Class<ThemeProvider> getProvider() {
public Class<? extends ThemeProvider> getProvider() {
return provider;
}
public void setProvider(final Class<ThemeProvider> provider) {
public void setProvider(final Class<? extends ThemeProvider> provider) {
this.provider = provider;
}
@ -90,9 +91,7 @@ public class ThemeInfo {
public int hashCode() {
int hash = 5;
hash = 73 * hash + Objects.hashCode(manifest);
// hash = 73 * hash + Objects.hashCode(name);
hash = 73 * hash + Objects.hashCode(version);
// hash = 73 * hash + Objects.hashCode(type);
if (provider != null) {
hash = 73 * hash + Objects.hashCode(provider.getName());
}
@ -127,12 +126,6 @@ public class ThemeInfo {
return false;
}
}
// if (!Objects.equals(name, other.getName())) {
// return false;
// }
// if (!Objects.equals(type, other.getType())) {
// return false;
// }
return version == other.getVersion();
}
@ -146,27 +139,23 @@ public class ThemeInfo {
}
public String toString(final String data) {
final String providerClassName;
if (provider == null) {
providerClassName = "";
providerClassName = "";
} else {
providerClassName = provider.getName();
}
return String.format("%s{ "
+ "mainfest = %s, "
// + "name = \"%s\", "
+ "version = %s, "
+ "provider = %s, "
// + "type = \"%s\"%s"
+ "%s }",
super.toString(),
Objects.toString(manifest),
// name,
Objects.toString(version),
providerClassName,
// type,
data);
}

View File

@ -20,14 +20,33 @@ package org.libreccm.theming;
import java.util.Map;
import javax.enterprise.context.RequestScoped;
/**
* Interface for theme processors. A theme processor is responsible for
* converting the result of rendering a {@link PageModel} into HTML.
*
* An implementation must be a CDI bean (recommended scope:
* {@link RequestScoped}) which also annotated with the {@link ProcessesThemes}
* annotation.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public interface ThemeProcessor {
String process(Map<String, Object> page,
/**
* 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.
*
* @return The HTML for the provided {@code page}.
*/
String process(Map<String, Object> page,
ThemeInfo theme,
ThemeProvider themeProvider);
}

View File

@ -29,6 +29,7 @@ import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
/**
* Provides access to the available implementations of {@link ThemeProcessor}.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -37,9 +38,22 @@ public class ThemeProcessors implements Serializable {
private static final long serialVersionUID = -2019759931022734946L;
/**
* All available implementations of {@link ThemeProcessor}.
*/
@Inject
private Instance<ThemeProcessor> processors;
/**
* Find the implementation of {@link ThemeProcessor} for {@code type}.
*
* @param type The type of the theme to process.
*
* @return The implementation {@link ThemeProcessor} which can process
* themes of the provided {@code type} or an empty {@code Optional}
* if there is no suitable implementation of {@link ThemeProcessor}
* is available.
*/
public Optional<ThemeProcessor> findThemeProcessorForType(final String type) {
final ProcessesThemeLiteral literal = new ProcessesThemeLiteral(type);

View File

@ -19,6 +19,7 @@
package org.libreccm.theming;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.List;
import java.util.Optional;
@ -86,7 +87,7 @@ public interface ThemeProvider {
* will ignore this parameter.
* @param path The path of the directory of which the files are listed.
* The path is relative to the root of the theme.To get the
* root directory provided an empty string. Implementations
* root directory provide an empty string. Implementations
* should throw an NullPointerException if {@code null} is
* provided as path.
*
@ -94,6 +95,10 @@ public interface ThemeProvider {
* path in the theme the list is empty. If the path is the path of a
* file and not a directory the list should have one element, the
* data about the file itself.
*
* @throws IllegalArgumentException If {@code theme} is an empty string,
* if there is no theme with the name provided by {@code theme} or
* if there is no file/directory with the provided path in the theme.
*/
List<ThemeFileInfo> listThemeFiles(String theme,
ThemeVersion version,
@ -102,7 +107,11 @@ public interface ThemeProvider {
/**
* Retrieve a file from a theme. We use an {@link InputStream} here because
* that is the most universal interface in the Java API which works for all
* sorts of resources and is independent from any other API.
* sorts of resources and is independent from any other API. Hint: In most
* cases it is recommended to wrap the {@link InputStream} provided by this
* method in a {@link InputStreamReader} by using one of constructors of
* {@link InputStreamReader} which allows the caller to set the charset of
* the data read (which should be UTF-8 in most cases).
*
* @param theme The theme from which the file is retrieved.
* @param version The version of the theme from which the file is retrieved.

View File

@ -1,29 +0,0 @@
/*
* 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;
/**
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
public @interface ThemeType {
String value();
}

View File

@ -21,6 +21,7 @@ package org.libreccm.theming;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.libreccm.core.UnexpectedErrorException;
import org.libreccm.pagemodel.PageModel;
import java.util.ArrayList;
import java.util.List;
@ -29,10 +30,12 @@ import java.util.Optional;
import javax.enterprise.context.RequestScoped;
import javax.enterprise.inject.Instance;
import javax.enterprise.util.AnnotationLiteral;
import javax.inject.Inject;
/**
* Central interface for using themes. In most cases users of the theming system
* will use this class instead of directly working with {@link ThemeProvider}s
* and {@link ThemeProcessor}s.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -43,10 +46,19 @@ public class Themes {
@Inject
private Instance<ThemeProvider> providers;
//
// @Inject
// private Instance<ThemeProcessor> processors;
@Inject
private Instance<ThemeProcessor> processors;
private ThemeProcessors themeProcessors;
/**
* Retrieve all available themes.
*
* @return A list with information about all available themes (draft
* versions).
*/
public List<ThemeInfo> getAvailableThemes() {
final List<ThemeInfo> themes = new ArrayList<>();
@ -57,6 +69,11 @@ public class Themes {
return themes;
}
/**
* Retrieve all available live themes.
*
* @return A list with informations about all live themes.
*/
public List<ThemeInfo> getLiveThemes() {
final List<ThemeInfo> themes = new ArrayList<>();
@ -66,52 +83,41 @@ public class Themes {
return themes;
}
public Optional<ThemeInfo> getTheme(final String name,
/**
* Get information about a specific theme.
*
* @param name Then name of the theme.
* @param version The version of the theme.
*
* @return An {@link Optional} with informations about theme {@code theme}
* or an empty optional if there is no such theme.
*/
public Optional<ThemeInfo> getTheme(final String name,
final ThemeVersion version) {
for(final ThemeProvider provider : providers) {
for (final ThemeProvider provider : providers) {
if (provider.providesTheme(name, version)) {
return provider.getThemeInfo(name, version);
}
}
return Optional.empty();
}
public String process(Map<String, Object> page, ThemeInfo theme) {
/**
* Creates HTML from the result of rendering a {@link PageModel}.
*
* @param page The page to convert to HTML.
* @param theme The theme to use.
*
* @return The HTML representation of the page.
*/
public String process(final Map<String, Object> page,
final ThemeInfo theme) {
final ThemeTypeLiteral themeType = new ThemeTypeLiteral(theme.getType());
final Instance<ThemeProcessor> forType = processors.select(themeType);
if (forType.isUnsatisfied()) {
LOGGER.error("No ThemeProcessor implementation for type \"{}\" of "
+ "theme \"{}\".",
theme.getType(),
theme.getName());
throw new UnexpectedErrorException(String
.format("No ThemeProcessor implementation for type \"%s\" of "
+ "theme \"%s\".",
theme.getType(),
theme.getName()));
}
if (forType.isAmbiguous()) {
LOGGER.error(
"Mutiple ThemeProcessor implementations for type \"{}\" of "
+ "theme \"{}\".",
theme.getType(),
theme.getName());
throw new UnexpectedErrorException(String
.format(
"Mutiple ThemeProcessor implementations for type \"%s\" of "
+ "theme \"%s\".",
theme.getType(),
theme.getName()));
}
final Instance<ThemeProvider> forTheme = providers.select(theme
.getProvider());
final Instance<? extends ThemeProvider> forTheme = providers.select(
theme.getProvider());
if (forTheme.isUnsatisfied()) {
LOGGER.error("ThemeProvider \"{}\" not found.",
@ -121,28 +127,17 @@ public class Themes {
theme.getProvider().getName()));
}
final ThemeProcessor processor = forType.get();
final ThemeProcessor processor = themeProcessors
.findThemeProcessorForType(theme.getType())
.orElseThrow(() -> new UnexpectedErrorException(String
.format("No ThemeProcessor implementation for type \"%s\" of "
+ "theme \"%s\".",
theme.getType(),
theme.getName())));
final ThemeProvider provider = forTheme.get();
return processor.process(page, theme, provider);
}
private static class ThemeTypeLiteral extends AnnotationLiteral<ThemeType>
implements ThemeType {
private static final long serialVersionUID = 3377237291286175824L;
private final String value;
public ThemeTypeLiteral(final String value) {
this.value = value;
}
@Override
public String value() {
return value;
}
}
}

View File

@ -34,25 +34,43 @@ import javax.xml.bind.annotation.XmlRootElement;
import static org.libreccm.theming.ThemeConstants.*;
/**
*
* 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 {
/**
* 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.
*/
@XmlElement(name = "type", namespace = THEMES_XML_NS)
private String type;
/**
* The (localised) title of the theme.
*/
@XmlElement(name = "title", namespace = THEMES_XML_NS)
private LocalizedString title;
/**
* A (localised) description of the theme.
*/
@XmlElement(name = "description", namespace = THEMES_XML_NS)
private LocalizedString description;
/**
* The templates provided by the theme.
*/
@XmlElementWrapper(name = "templates", namespace = THEMES_XML_NS)
@XmlElement(name = "template", namespace = THEMES_XML_NS)
private List<ThemeTemplate> templates;

View File

@ -0,0 +1,90 @@
/*
* 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.manifest;
import static org.libreccm.theming.ThemeConstants.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule;
import org.libreccm.core.UnexpectedErrorException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import javax.enterprise.context.RequestScoped;
/**
* A Utility class for loading them manifest file of a theme.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
public class ThemeManifestUtil implements Serializable {
private static final long serialVersionUID = -7650437144515619682L;
/**
* Reads the manifest file at {@code path}.
*
* @param path The path of the manifest file.
* @return The parsed manifest file.
*/
public ThemeManifest loadManifest(final Path path) {
final String pathStr = path.toString().toLowerCase(Locale.ROOT);
final BufferedReader reader;
try {
reader = Files.newBufferedReader(path, Charset.forName("UTF-8"));
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
final ObjectMapper mapper;
if (pathStr.endsWith(THEME_MANIFEST_JSON)) {
mapper = new ObjectMapper();
} else if (pathStr.endsWith(THEME_MANIFEST_XML)) {
final JacksonXmlModule xmlModule = new JacksonXmlModule();
mapper = new XmlMapper(xmlModule);
} else {
throw new IllegalArgumentException(String
.format("The provided path \"%s\" does not point to a theme "
+ "manifest file.",
path.toString()));
}
mapper.registerModule(new JaxbAnnotationModule());
final ThemeManifest manifest;
try {
manifest = mapper.readValue(reader, ThemeManifest.class);
} catch (IOException ex) {
throw new UnexpectedErrorException(ex);
}
return manifest;
}
}

View File

@ -29,7 +29,8 @@ import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
/**
*
* Informations about a template provided by a theme.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@XmlRootElement(name = "template", namespace = "http://themes.libreccm.org")
@ -38,15 +39,27 @@ public class ThemeTemplate implements Serializable {
private static final long serialVersionUID = -9034588759798295569L;
/**
* The name of the template (usually the filename).
*/
@XmlElement(name = "name", namespace = "http://themes.libreccm.org")
private String name;
/**
* The (localised) title of the template.
*/
@XmlElement(name = "title", namespace = "http://themes.libreccm.org")
private LocalizedString title;
/**
* A (localised) description of the template.
*/
@XmlElement(name = "description", namespace = "http://themes.libreccm.org")
private LocalizedString description;
/**
* Path of template relative to the directory of the theme.
*/
@XmlElement(name = "path", namespace = "http://themes.libreccm.org")
private String path;