diff --git a/ccm-bundle-devel-thorntail/pom.xml b/ccm-bundle-devel-thorntail/pom.xml
index 6178bfbf9..8882c9e6a 100644
--- a/ccm-bundle-devel-thorntail/pom.xml
+++ b/ccm-bundle-devel-thorntail/pom.xml
@@ -123,6 +123,12 @@
undertow
+
+ org.libreccm
+ ccm-wildfly
+ 7.0.0-SNAPSHOT
+
+
com.h2databaseh2
diff --git a/ccm-bundle-devel-wildfly/pom.xml b/ccm-bundle-devel-wildfly/pom.xml
index 3b5274fa5..fe0fefee9 100644
--- a/ccm-bundle-devel-wildfly/pom.xml
+++ b/ccm-bundle-devel-wildfly/pom.xml
@@ -24,6 +24,12 @@
http://www.libreccm.org/modules/web/wildfly
+
+ org.libreccm
+ ccm-wildfly
+ 7.0.0-SNAPSHOT
+
+
org.webjarsfont-awesome
@@ -202,6 +208,22 @@
assets/
+
+ org.libreccm
+ ccm-core
+ jar
+
+ views/
+
+
+
+ org.libreccm
+ ccm-core
+ jar
+
+ resources/
+
+ org.librecmsccm-cms
@@ -244,6 +266,9 @@
false${project.basedir}/wildfly.properties
+
+ -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8787
+
diff --git a/ccm-bundle-devel-wildfly/src/main/webapp/WEB-INF/faces-config.xml b/ccm-bundle-devel-wildfly/src/main/webapp/WEB-INF/faces-config.xml
index 59d6547c7..a1999da81 100644
--- a/ccm-bundle-devel-wildfly/src/main/webapp/WEB-INF/faces-config.xml
+++ b/ccm-bundle-devel-wildfly/src/main/webapp/WEB-INF/faces-config.xml
@@ -4,4 +4,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
+
+ org.libreccm.ui.CcmFaceletsResourceHandler
+
diff --git a/ccm-core/pom.xml b/ccm-core/pom.xml
index e4b86ea4f..1c5e2aae8 100644
--- a/ccm-core/pom.xml
+++ b/ccm-core/pom.xml
@@ -102,6 +102,21 @@
provided
+
+ javax.mvc
+ javax.mvc-api
+
+
+ org.eclipse.krazo
+ krazo-core
+ provided
+
+
+ org.eclipse.krazo.ext
+ krazo-freemarker
+ provided
+
+
@@ -398,6 +413,7 @@
truetruefalse
+ --frames
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlConnection.java b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlConnection.java
new file mode 100644
index 000000000..3006ab4c4
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlConnection.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2020 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.mvc.facelets;
+
+import org.libreccm.theming.ThemeInfo;
+import org.libreccm.theming.ThemeVersion;
+import org.libreccm.theming.Themes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Special {@link URLConnection} for loading Facelets templates from a LibreCCM
+ * theme.
+ *
+ * @author Jens Pelzetter
+ */
+class CcmThemeUrlConnection extends URLConnection {
+
+ /**
+ * Themes instance used as interface to the theme system
+ */
+ private final Themes themes;
+
+ /**
+ * Path of the template to load.
+ */
+ private final String path;
+
+ /**
+ * Theme to use. Initialized by {@link #connect()}.
+ */
+ private ThemeInfo themeInfo;
+
+ /**
+ * Path of the file relative to the theme root. Initialized by
+ * {@link #connect()}
+ */
+ private String filePath;
+
+ /**
+ * Constructor for initalizing the instance, providing the required
+ * parameters.
+ *
+ * @param themes Themes instance to use.
+ * @param url URL of the template to load.
+ */
+ public CcmThemeUrlConnection(final Themes themes, final URL url) {
+ super(url);
+ this.themes = themes;
+
+ final String urlPath = url.getPath();
+
+ if (urlPath.startsWith("/")) {
+ path = urlPath.substring(1);
+ } else {
+ path = urlPath;
+ }
+ }
+
+ /**
+ * Called by Java to connect to the source of the URL. In this case we
+ * retrieve the {@link ThemeInfo} for the theme to use, and initalize the
+ * {@link #filePath} property.
+ *
+ * @throws IOException If the theme is not found or if the URL is malformed.
+ */
+ @Override
+ public void connect() throws IOException {
+ final String[] tokens = path.split("/");
+ if (tokens.length >= 4) {
+ final String themeName = tokens[1];
+ final ThemeVersion version = ThemeVersion.valueOf(tokens[2]);
+ filePath = String.join(
+ "/",
+ Arrays.copyOfRange(
+ tokens, 3, tokens.length, String[].class
+ )
+ );
+
+ themeInfo = themes.getTheme(themeName, version)
+ .orElseThrow(() -> new IOException(
+ String.format(
+ "Theme %s is available as %s version.",
+ themeName,
+ Objects.toString(version)
+ )));
+ } else {
+ throw new IOException(
+ "Malformed URL for loading a facelets template from a theme."
+ );
+ }
+
+ }
+
+ /**
+ * Get an {@link InputStream} for the resource to which the URL points. In
+ * this case we delagate the retrieval of the the template to
+ * {@link Themes#getFileFromTheme(org.libreccm.theming.ThemeInfo, java.lang.String)}.
+ *
+ * @return An {@code InputStream} for the requested template.
+ *
+ * @throws IOException If the template was not found in the theme.
+ */
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return themes
+ .getFileFromTheme(themeInfo, filePath)
+ .orElseThrow(
+ () -> new IOException(
+ String.format(
+ "Template %s not found in %s version of the theme %s.",
+ filePath,
+ themeInfo.getVersion(),
+ themeInfo.getName()
+ )
+ )
+ );
+
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlStreamHandler.java b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlStreamHandler.java
new file mode 100644
index 000000000..b25face03
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeUrlStreamHandler.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 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.mvc.facelets;
+
+import org.libreccm.theming.Themes;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * Implementation of {@link URLStreamHandler} for handling URLs to LibreCCM
+ * theme templates.
+ *
+ * @author Jens Pelzetter
+ */
+class CcmThemeUrlStreamHandler extends URLStreamHandler {
+
+ private final Themes themes;
+
+ public CcmThemeUrlStreamHandler(final Themes themes) {
+ this.themes = themes;
+ }
+
+ @Override
+ protected URLConnection openConnection(final URL url) throws IOException {
+ return new CcmThemeUrlConnection(themes, url);
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeViewResource.java b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeViewResource.java
new file mode 100644
index 000000000..5e005b679
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmThemeViewResource.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2020 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.mvc.facelets;
+
+import org.libreccm.core.UnexpectedErrorException;
+import org.libreccm.theming.Themes;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import javax.faces.application.ViewResource;
+
+/**
+ * A {@link ViewResource} implementation for templates from LibreCCM themes.
+ *
+ * @author Jens Pelzetter
+ */
+class CcmThemeViewResource extends ViewResource {
+
+ private final String path;
+
+ private final Themes themes;
+
+ /**
+ * Constructor for the {@code ViewResource} instance.
+ *
+ * @param themes Interface to the theme system
+ * @param path The path of the template.
+ */
+ public CcmThemeViewResource(final Themes themes, final String path) {
+ this.themes = themes;
+ this.path = path;
+ }
+
+ /**
+ * Gets the URL of the URL of the template as {@code ViewResource}.
+ *
+ * @return The URL of the {@code ViewResource}.
+ */
+ @Override
+ public URL getURL() {
+ // The URL build here is a "pseudo" URL. Most of the parts of the URL
+ // are no relevant. An special URLStreamHandler implementation is
+ // also passed to the URL. This URLStreamHandler implementation takes
+ // care of retrieving the template from the theme.
+ try {
+ return new URL(
+ "libreccm",
+ null,
+ 0,
+ path,
+ new CcmThemeUrlStreamHandler(themes)
+ );
+ } catch (MalformedURLException ex) {
+ throw new UnexpectedErrorException(ex);
+ }
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmViewResourceHandler.java b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmViewResourceHandler.java
new file mode 100644
index 000000000..88332b49e
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/facelets/CcmViewResourceHandler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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.mvc.facelets;
+
+import org.libreccm.theming.Themes;
+
+import javax.faces.application.ResourceHandler;
+import javax.faces.application.ResourceHandlerWrapper;
+import javax.faces.application.ViewResource;
+import javax.faces.context.FacesContext;
+import javax.inject.Inject;
+
+/**
+ * A Facelets resource handler that loads Facelets templates from LibreCCM
+ * themes.
+ *
+ * This handler only works for view resources. Only resource path which are
+ * starting with {@code @themes} or {@code /@themes} are used processed. All
+ * other paths are delegated to the wrapped resource handler.
+ *
+ * To enable this resource handler to following snippet must be present in the
+ * {@code faces-config.xml} file of the WAR (bundle):
+ *
+ *
+ *
+ * @author Jens Pelzetter
+ */
+public class CcmViewResourceHandler extends ResourceHandlerWrapper {
+
+ @Inject
+ private Themes themes;
+
+ private final ResourceHandler wrapped;
+
+ public CcmViewResourceHandler(final ResourceHandler wrapped) {
+ super(wrapped);
+ this.wrapped = wrapped;
+ }
+
+ @Override
+ public ViewResource createViewResource(
+ final FacesContext context, final String path
+ ) {
+ if (path.startsWith("@themes") || path.startsWith("/@themes")) {
+ return new CcmThemeViewResource(themes, path);
+ } else {
+ return super.createViewResource(context, path);
+ }
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/facelets/package-info.java b/ccm-core/src/main/java/org/libreccm/mvc/facelets/package-info.java
new file mode 100644
index 000000000..04b4a3562
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/facelets/package-info.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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
+ */
+/**
+ * Integration of the LibreCCM theme system with the Facelet ViewEngine of
+ * Eclipse Krazo. The integration allows it to load Facelets either from
+ * a theme using a {@link Themes} or from the default locations. To enable the
+ * integration the following snippet has to be added to the
+ * {@code faces-config.xml}:
+ *
+ *
+ */
+package org.libreccm.mvc.facelets;
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/KrazoTemplateLoader.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/KrazoTemplateLoader.java
new file mode 100644
index 000000000..6b31bbc11
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/KrazoTemplateLoader.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 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.mvc.freemarker;
+
+import freemarker.cache.MultiTemplateLoader;
+import freemarker.cache.TemplateLoader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.servlet.ServletContext;
+
+/**
+ * A copy of the {@link TemplateLoader} used by Krazo.
+ *
+ * The {@code TemplateLoader} used by Krazo is defined as inner class. This
+ * class provides the same behaviour as this inner class so that we can use it
+ * with Freemarker's {@link MultiTemplateLoader}.
+ *
+ * As extension to Krazo's implementation this implementation of the
+ * {@code TemplateLoader} interface will not process template paths which start
+ * with {@code @themes/} or {@code /@themes/}. These path are processed by the
+ * {@link ThemesTemplateLoader} for the theming system.
+ *
+ * @author Jens Pelzetter
+ */
+class KrazoTemplateLoader implements TemplateLoader {
+
+ private final ServletContext servletContext;
+
+ public KrazoTemplateLoader(final ServletContext servletContext) {
+ super();
+ this.servletContext = servletContext;
+ }
+
+ @Override
+ public Object findTemplateSource(final String name) throws IOException {
+ if (name.startsWith("@themes") || name.startsWith("/@themes")) {
+ return null;
+ } else {
+ // Freemarker drops "/"
+ return servletContext.getResourceAsStream(
+ String.format("/%s", name)
+ );
+ }
+ }
+
+ @Override
+ public long getLastModified(final Object templateSource) {
+ return -1;
+ }
+
+ @Override
+ public Reader getReader(
+ final Object templateSource, final String encoding
+ ) throws IOException {
+ return new InputStreamReader((InputStream) templateSource, encoding);
+ }
+
+ @Override
+ public void closeTemplateSource(
+ final Object templateSource
+ ) throws IOException {
+ final InputStream inputStream = (InputStream) templateSource;
+ inputStream.close();
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/MvcFreemarkerConfigurationProducer.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/MvcFreemarkerConfigurationProducer.java
new file mode 100644
index 000000000..a209a462a
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/MvcFreemarkerConfigurationProducer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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.mvc.freemarker;
+
+import freemarker.cache.ClassTemplateLoader;
+import freemarker.cache.MultiTemplateLoader;
+import freemarker.cache.TemplateLoader;
+import freemarker.cache.WebappTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.TemplateExceptionHandler;
+import org.eclipse.krazo.engine.ViewEngineConfig;
+import org.eclipse.krazo.ext.freemarker.DefaultConfigurationProducer;
+import org.libreccm.theming.Themes;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Specializes;
+import javax.inject.Inject;
+import javax.servlet.ServletContext;
+import javax.ws.rs.Produces;
+
+/**
+ * Extends the default configuration for Freemarker of Eclipse Krazo to
+ * support Freemarker templates in CCM themes.
+ *
+ * @author Jens Pelzetter
+ */
+@ApplicationScoped
+public class MvcFreemarkerConfigurationProducer
+ extends DefaultConfigurationProducer {
+
+ @Inject
+ private ServletContext servletContext;
+
+ @Inject
+ private Themes themes;
+
+ @Produces
+ @ViewEngineConfig
+ @Specializes
+ @Override
+ public Configuration getConfiguration() {
+ final Configuration configuration = new Configuration(
+ Configuration.VERSION_2_3_30
+ );
+
+ configuration.setDefaultEncoding("UTF-8");
+ configuration.setTemplateExceptionHandler(
+ TemplateExceptionHandler.RETHROW_HANDLER
+ );
+ configuration.setLogTemplateExceptions(false);
+ configuration.setWrapUncheckedExceptions(false);
+ configuration.setLocalizedLookup(false);
+ configuration.setTemplateLoader(
+ new MultiTemplateLoader(
+ new TemplateLoader[]{
+ new KrazoTemplateLoader(servletContext),
+ new ThemesTemplateLoader(themes),
+ // For loading Freemarker macro libraries from WEB-INF
+ // resources
+ new WebappTemplateLoader(
+ servletContext, "/themes/freemarker"
+ ),
+ // For loading Freemarker macro libraries from classpath
+ // resources
+ new ClassTemplateLoader(getClass(), "/themes/freemarker")
+ }
+ )
+ );
+
+ return configuration;
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemesTemplateLoader.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemesTemplateLoader.java
new file mode 100644
index 000000000..478a505a5
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemesTemplateLoader.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2020 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.mvc.freemarker;
+
+import freemarker.cache.TemplateLoader;
+import org.libreccm.theming.ThemeInfo;
+import org.libreccm.theming.ThemeVersion;
+import org.libreccm.theming.Themes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * Loads Freemarker templates from a theme.
+ *
+ * @author Jens Pelzetter
+ */
+class ThemesTemplateLoader implements TemplateLoader {
+
+ private final Themes themes;
+
+ public ThemesTemplateLoader(final Themes themes) {
+ this.themes = themes;
+ }
+
+ /**
+ * Loads the template from a theme. The path of the theme file must follow
+ * the following format:
+ *
+ * {@code @themes/$themeName/$version/$path/$to/$file}
+ *
+ * The {@code @themes} prefix is mandantory. {@code $themeName} is the name
+ * of the theme from which the template is loaded. {@code $version} is the
+ * version of the theme to use. This token is converted to
+ * {@link ThemeVersion}. Valid values are therefore {@code DRAFT} and
+ * {@code LIVE}. The remainder of the path is the path to the file inside the
+ * theme.
+ *
+ * @param path The path of the file. The path must include the theme and its
+ * version.
+ *
+ * @return An {@link InputStream} for the template if the template was found
+ * in the theme. Otherwise {@code null} is returned.
+ *
+ * @throws IOException
+ */
+ @Override
+ public Object findTemplateSource(final String path) throws IOException {
+ if (path.startsWith("@themes") || path.startsWith("/@themes")) {
+ final String[] tokens;
+ if (path.startsWith("/")) {
+ tokens = path.substring(1).split("/");
+ } else {
+ tokens = path.split("/");
+ }
+ return findTemplateSource(tokens);
+ } else {
+ return null;
+ }
+ }
+
+ private InputStream findTemplateSource(final String[] tokens) {
+ if (tokens.length >= 4) {
+ final String themeName = tokens[1];
+ final ThemeVersion themeVersion = ThemeVersion
+ .valueOf(tokens[2]);
+ final String filePath = String.join(
+ "/",
+ Arrays.copyOfRange(
+ tokens, 3, tokens.length, String[].class
+ )
+ );
+
+ return findTemplateSource(themeName, themeVersion, filePath);
+ } else {
+ return null;
+ }
+ }
+
+ private InputStream findTemplateSource(
+ final String themeName,
+ final ThemeVersion themeVersion,
+ final String filePath
+ ) {
+ final Optional themeInfo = themes.getTheme(
+ themeName, themeVersion
+ );
+ if (themeInfo.isPresent()) {
+ return findTemplateSource(themeInfo.get(), filePath);
+ } else {
+ return null;
+ }
+ }
+
+ private InputStream findTemplateSource(
+ final ThemeInfo themeInfo, final String filePath
+ ) {
+ final Optional source = themes.getFileFromTheme(
+ themeInfo, filePath
+ );
+ if (source.isPresent()) {
+ return source.get();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public long getLastModified(Object templateSource) {
+ return -1;
+ }
+
+ @Override
+ public Reader getReader(
+ final Object templateSource, final String encoding
+ ) throws IOException {
+ final InputStream inputStream = (InputStream) templateSource;
+ return new InputStreamReader(inputStream, encoding);
+ }
+
+ @Override
+ public void closeTemplateSource(
+ final Object templateSource
+ ) throws IOException {
+ final InputStream inputStream = (InputStream) templateSource;
+ inputStream.close();
+ }
+
+}
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/package-info.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/package-info.java
new file mode 100644
index 000000000..d3d2157ad
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2020 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
+ */
+/**
+ * Integration of the Freemarker View Engine of Eclipse Krazo with LibreCCM.
+ */
+package org.libreccm.mvc.freemarker;
diff --git a/ccm-core/src/main/java/org/libreccm/mvc/package-info.java b/ccm-core/src/main/java/org/libreccm/mvc/package-info.java
new file mode 100644
index 000000000..f5b36a939
--- /dev/null
+++ b/ccm-core/src/main/java/org/libreccm/mvc/package-info.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2020 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
+ */
+/**
+ * The classes in this package integrate LibreCCM with Jakarta EE MVC and its
+ * reference implementation Eclipse Krazo.
+ *
+ * And the the moment the ViewEngines for Facelets and Freemarker are supported.
+ * The integration allows it to load templates for these ViewEngines either from
+ * the default locations or from a theme. If the path of template starts with
+ * {@code @themes/} or {@code /@themes/} the integration will delegate loading of
+ * the template to {@link Themes} of the theme. The path must follow the following
+ * pattern:
+ *
+ *