From 82597bc384dcff76fd6135db6da65266894f06e8 Mon Sep 17 00:00:00 2001 From: jensp Date: Wed, 25 Oct 2017 12:36:23 +0000 Subject: [PATCH] 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 --- .../article/ArticlePropertiesStep.java | 64 +++++ .../libreccm/theming/StaticThemeProvider.java | 238 +++++++++++++++--- .../org/libreccm/theming/ThemeConstants.java | 10 +- .../org/libreccm/theming/ThemeFileInfo.java | 19 ++ .../libreccm/theming/ThemeFileInfoUtil.java | 65 +++++ .../java/org/libreccm/theming/ThemeInfo.java | 49 ++-- .../org/libreccm/theming/ThemeProcessor.java | 25 +- .../org/libreccm/theming/ThemeProcessors.java | 14 ++ .../org/libreccm/theming/ThemeProvider.java | 13 +- .../java/org/libreccm/theming/ThemeType.java | 29 --- .../java/org/libreccm/theming/Themes.java | 109 ++++---- .../theming/manifest/ThemeManifest.java | 20 +- .../theming/manifest/ThemeManifestUtil.java | 90 +++++++ .../theming/manifest/ThemeTemplate.java | 15 +- 14 files changed, 605 insertions(+), 155 deletions(-) create mode 100644 ccm-cms/src/main/java/org/librecms/ui/authoring/article/ArticlePropertiesStep.java create mode 100644 ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java delete mode 100644 ccm-core/src/main/java/org/libreccm/theming/ThemeType.java create mode 100644 ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifestUtil.java diff --git a/ccm-cms/src/main/java/org/librecms/ui/authoring/article/ArticlePropertiesStep.java b/ccm-cms/src/main/java/org/librecms/ui/authoring/article/ArticlePropertiesStep.java new file mode 100644 index 000000000..7024cb8ed --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/ui/authoring/article/ArticlePropertiesStep.java @@ -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 Jens Pelzetter + */ +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"); + + } + +} 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 2ba8616b4..589833ccc 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/StaticThemeProvider.java @@ -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 Jens Pelzetter */ +@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 getThemes() { LOGGER.debug("Retrieving info about all static themes..."); - final List themeInfos = new ArrayList<>(); try (final FileSystem jarFileSystem = FileSystems.newFileSystem( getJarUri(), Collections.emptyMap())) { @@ -85,31 +101,195 @@ public class StaticThemeProvider implements ThemeProvider { } @Override - public Optional getThemeInfo(String theme, ThemeVersion version) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + public Optional 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 listThemeFiles(String theme, ThemeVersion version, - String path) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + public List 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 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 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 getThemeFileAsStream(String theme, - ThemeVersion version, - String path) { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + public Optional 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; } } 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 f027318db..0d95b47bb 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeConstants.java @@ -23,10 +23,14 @@ package org.libreccm.theming; * @author Jens Pelzetter */ 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"; + } 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 0f69aedac..252b736a4 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfo.java @@ -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() { diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java new file mode 100644 index 000000000..55a1c606e --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeFileInfoUtil.java @@ -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 Jens Pelzetter + */ +@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); + } + } + +} 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 9ca338fd7..01b61df91 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeInfo.java @@ -23,18 +23,27 @@ import org.libreccm.theming.manifest.ThemeManifest; import java.util.Objects; /** + * Informations about a theme. * * @author Jens Pelzetter */ 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 provider; + /** + * The {@link ThemeProvider} implementation which is responsible for the + * theme. + */ + private Class 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 getProvider() { + public Class getProvider() { return provider; } - public void setProvider(final Class provider) { + public void setProvider(final Class 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); } 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 79f9f5685..40847044a 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessor.java @@ -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 Jens Pelzetter */ public interface ThemeProcessor { - - String process(Map 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 page, ThemeInfo theme, ThemeProvider themeProvider); - + } diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessors.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessors.java index d31c04781..c0d7ade2c 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessors.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProcessors.java @@ -29,6 +29,7 @@ import javax.enterprise.util.AnnotationLiteral; import javax.inject.Inject; /** + * Provides access to the available implementations of {@link ThemeProcessor}. * * @author Jens Pelzetter */ @@ -37,9 +38,22 @@ public class ThemeProcessors implements Serializable { private static final long serialVersionUID = -2019759931022734946L; + /** + * All available implementations of {@link ThemeProcessor}. + */ @Inject private Instance 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 findThemeProcessorForType(final String type) { final ProcessesThemeLiteral literal = new ProcessesThemeLiteral(type); 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 3d2e5c2e2..ae0262191 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java +++ b/ccm-core/src/main/java/org/libreccm/theming/ThemeProvider.java @@ -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 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. diff --git a/ccm-core/src/main/java/org/libreccm/theming/ThemeType.java b/ccm-core/src/main/java/org/libreccm/theming/ThemeType.java deleted file mode 100644 index 374211ef6..000000000 --- a/ccm-core/src/main/java/org/libreccm/theming/ThemeType.java +++ /dev/null @@ -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 Jens Pelzetter - */ -public @interface ThemeType { - - String value(); - -} 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 7850ff50c..48c366ed0 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/Themes.java +++ b/ccm-core/src/main/java/org/libreccm/theming/Themes.java @@ -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 Jens Pelzetter */ @@ -43,10 +46,19 @@ public class Themes { @Inject private Instance providers; +// +// @Inject +// private Instance processors; @Inject - private Instance processors; + private ThemeProcessors themeProcessors; + /** + * Retrieve all available themes. + * + * @return A list with information about all available themes (draft + * versions). + */ public List getAvailableThemes() { final List 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 getLiveThemes() { final List themes = new ArrayList<>(); @@ -66,52 +83,41 @@ public class Themes { return themes; } - - public Optional 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 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 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 page, + final ThemeInfo theme) { - final ThemeTypeLiteral themeType = new ThemeTypeLiteral(theme.getType()); - - final Instance 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 forTheme = providers.select(theme - .getProvider()); + final Instance 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 - 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; - } - - } } 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 ca44830d3..8a45d63bf 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 @@ -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 Jens Pelzetter */ @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 templates; diff --git a/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifestUtil.java b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifestUtil.java new file mode 100644 index 000000000..1f96a18be --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeManifestUtil.java @@ -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 Jens Pelzetter + */ +@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; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeTemplate.java b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeTemplate.java index 3deb7be58..236862e77 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeTemplate.java +++ b/ccm-core/src/main/java/org/libreccm/theming/manifest/ThemeTemplate.java @@ -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 Jens Pelzetter */ @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;