From f02721df90f0a2d9df173503ba8d2a36387a294a Mon Sep 17 00:00:00 2001 From: Jens Pelzetter Date: Tue, 5 Jan 2021 19:21:23 +0100 Subject: [PATCH] Integration of EE MVC and CCM themes Former-commit-id: f0e152822df97cb4661dd00e0ee26fe56cb9e2b1 --- .../mvc/freemarker/FreemarkerViewEngine.java | 150 +++++++++++++++++- .../freemarker/FreemarkerViewEngine.java.off | 83 ---------- .../MvcFreemarkerConfigurationProducer.java | 33 ++-- .../libreccm/mvc/freemarker/TemplateInfo.java | 46 ++++++ .../mvc/freemarker/ThemeTemplateUtil.java | 128 +++++++++++++++ .../mvc/freemarker/ThemesTemplateLoader.java | 83 +++------- .../theming/mvc/ThemeResourceProvider.java | 139 ++++++++++++++++ .../libreccm/theming/mvc/ThemeResources.java | 43 +++++ .../org/libreccm/theming/mvc/ThemesMvc.java | 25 ++- .../libreccm/ui/login/LoginController.java | 1 + .../ccm-freemarker/login/login-form.html.ftl | 14 +- .../themes/ccm-freemarker/theme-index.json | 70 ++++++++ .../mvc/ThemeResourceProviderTest.java | 59 +++++++ 13 files changed, 709 insertions(+), 165 deletions(-) delete mode 100644 ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java.off create mode 100644 ccm-core/src/main/java/org/libreccm/mvc/freemarker/TemplateInfo.java create mode 100644 ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemeTemplateUtil.java create mode 100644 ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResourceProvider.java create mode 100644 ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResources.java create mode 100644 ccm-core/src/main/resources/themes/ccm-freemarker/theme-index.json create mode 100644 ccm-core/src/test/java/org/libreccm/theming/mvc/ThemeResourceProviderTest.java diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java index 861ecdac8..14e41ce92 100644 --- a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java +++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java @@ -19,16 +19,27 @@ package org.libreccm.mvc.freemarker; import freemarker.template.Configuration; +import freemarker.template.SimpleNumber; import freemarker.template.Template; import freemarker.template.TemplateException; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; import org.eclipse.krazo.engine.ViewEngineBase; import org.eclipse.krazo.engine.ViewEngineConfig; +import org.libreccm.theming.ThemeInfo; +import org.libreccm.theming.ThemeProvider; +import org.libreccm.theming.freemarker.FreemarkerThemeProcessor; +import org.libreccm.theming.utils.L10NUtils; +import org.libreccm.theming.utils.SettingsUtils; +import org.libreccm.theming.utils.TextUtils; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.Charset; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -60,10 +71,22 @@ public class FreemarkerViewEngine extends ViewEngineBase { @Inject @ViewEngineConfig private Configuration configuration; - + @Inject private MvcContext mvc; + @Inject + private L10NUtils l10nUtils; + + @Inject + private SettingsUtils settingsUtils; + + @Inject + private TextUtils textUtils; + + @Inject + private ThemeTemplateUtil themeTemplateUtil; + @Override public boolean supports(String view) { return view.endsWith(".ftl"); @@ -86,6 +109,23 @@ public class FreemarkerViewEngine extends ViewEngineBase { model.put("mvc", mvc); model.put("request", context.getRequest(HttpServletRequest.class)); + final Optional templateInfo = themeTemplateUtil + .getTemplateInfo(context.getView()); + final ThemeProvider themeProvider = themeTemplateUtil + .findThemeProvider(templateInfo.get().getThemeInfo()); + if (templateInfo.isPresent()) { + final ThemeInfo themeInfo = templateInfo.get().getThemeInfo(); + model.put("getSetting", + new GetSettingMethod(themeInfo, themeProvider) + ); + model.put("localize", + new LocalizeMethod(themeInfo, themeProvider) + ); + } + model.put("truncateText", new TruncateTextMethod()); + + + final Map namedBeans = beanManager .getBeans(Object.class) .stream() @@ -147,4 +187,112 @@ public class FreemarkerViewEngine extends ViewEngineBase { } + private class GetSettingMethod implements TemplateMethodModelEx { + + private final ThemeInfo fromTheme; + + private final ThemeProvider themeProvider; + + public GetSettingMethod(final ThemeInfo fromTheme, + final ThemeProvider themeProvider) { + this.fromTheme = fromTheme; + this.themeProvider = themeProvider; + } + + @Override + public Object exec(final List arguments) throws TemplateModelException { + + switch (arguments.size()) { + case 2: { + final String filePath = ((TemplateScalarModel) arguments + .get(0)) + .getAsString(); + final String settingName = ((TemplateScalarModel) arguments + .get(0)) + .getAsString(); + + return settingsUtils.getSetting(fromTheme, + themeProvider, + filePath, + settingName); + } + case 3: { + final String filePath + = ((TemplateScalarModel) arguments.get(0)) + .getAsString(); + final String settingName + = ((TemplateScalarModel) arguments.get(1)) + .getAsString(); + final String defaultValue + = ((TemplateScalarModel) arguments.get(2)) + .getAsString(); + + return settingsUtils.getSetting(fromTheme, + themeProvider, + filePath, + settingName, + defaultValue); + } + default: + throw new TemplateModelException( + "Illegal number of arguments."); + } + } + + } + + private class LocalizeMethod implements TemplateMethodModelEx { + + private final ThemeInfo fromTheme; + + private final ThemeProvider themeProvider; + + public LocalizeMethod(final ThemeInfo fromTheme, + final ThemeProvider themeProvider) { + this.fromTheme = fromTheme; + this.themeProvider = themeProvider; + } + + @Override + public Object exec(final List arguments) throws TemplateModelException { + + if (arguments.isEmpty()) { + throw new TemplateModelException("No string to localize."); + } + + final String bundle; + if (arguments.size() > 1) { + bundle = ((TemplateScalarModel) arguments.get(1)).getAsString(); + } else { + bundle = "theme-bundle"; + } + + final String key = ((TemplateScalarModel) arguments.get(0)) + .getAsString(); + + return l10nUtils.getText(fromTheme, themeProvider, bundle, key); + } + + } + + private class TruncateTextMethod implements TemplateMethodModelEx { + + @Override + public Object exec(final List arguments) throws TemplateModelException { + + if (arguments.size() == 2) { + final String text = ((TemplateScalarModel) arguments.get(0)) + .getAsString(); + final int length = ((SimpleNumber) arguments.get(1)) + .getAsNumber() + .intValue(); + + return textUtils.truncateText(text, length); + } else { + throw new TemplateModelException("Illegal number of arguments."); + } + } + + } + } diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java.off b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java.off deleted file mode 100644 index 8472dd470..000000000 --- a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/FreemarkerViewEngine.java.off +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.template.Configuration; -import freemarker.template.Template; -import freemarker.template.TemplateException; -import org.eclipse.krazo.engine.ViewEngineBase; -import org.eclipse.krazo.engine.ViewEngineConfig; - -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.Map; - -import javax.annotation.Priority; -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.mvc.engine.ViewEngine; -import javax.mvc.engine.ViewEngineContext; -import javax.mvc.engine.ViewEngineException; -import javax.servlet.http.HttpServletRequest; - -/** - * - * @author Jens Pelzetter - */ -@ApplicationScoped -@Priority(ViewEngine.PRIORITY_APPLICATION) -public class FreemarkerViewEngine extends ViewEngineBase { - - @Inject - @ViewEngineConfig - private Configuration configuration; - - @Override - public boolean supports(String view) { - return view.endsWith(".ftl"); - } - - @Override - public void processView(final ViewEngineContext context) - throws ViewEngineException { - - final Charset charset = resolveCharsetAndSetContentType(context); - - try (final Writer writer = new OutputStreamWriter( - context.getOutputStream(), charset - )) { - final Template template = configuration.getTemplate( - resolveView(context) - ); - - final Map model = new HashMap<>( - context.getModels().asMap() - ); - model.put("request", context.getRequest(HttpServletRequest.class)); - - template.process(model, writer); - } catch (TemplateException | IOException e) { - throw new ViewEngineException(e); - } - } - -} 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 index 97b038240..83c5c6fd9 100644 --- a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/MvcFreemarkerConfigurationProducer.java +++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/MvcFreemarkerConfigurationProducer.java @@ -31,38 +31,39 @@ import org.libreccm.theming.Themes; import javax.enterprise.inject.Produces; import javax.enterprise.inject.Specializes; import javax.inject.Inject; +import javax.mvc.Models; import javax.servlet.ServletContext; - /** - * Extends the default configuration for Freemarker of Eclipse Krazo to - * support Freemarker templates in CCM themes. - * + * Extends the default configuration for Freemarker of Eclipse Krazo to support + * Freemarker templates in CCM themes. + * * @author Jens Pelzetter */ -//@ApplicationScoped -//@Alternative -//@Specializes -//@Priority(3000) -public class MvcFreemarkerConfigurationProducer +public class MvcFreemarkerConfigurationProducer extends DefaultConfigurationProducer { - + + @Inject + private Models models; + @Inject private ServletContext servletContext; - + @Inject private Themes themes; + @Inject + private ThemeTemplateUtil themeTemplateUtil; + @Produces @ViewEngineConfig -// @Alternative @Specializes @Override public Configuration getConfiguration() { final Configuration configuration = new Configuration( Configuration.VERSION_2_3_30 ); - + configuration.setDefaultEncoding("UTF-8"); configuration.setTemplateExceptionHandler( TemplateExceptionHandler.RETHROW_HANDLER @@ -74,7 +75,7 @@ public class MvcFreemarkerConfigurationProducer new MultiTemplateLoader( new TemplateLoader[]{ new KrazoTemplateLoader(servletContext), - new ThemesTemplateLoader(themes), + new ThemesTemplateLoader(themes, themeTemplateUtil), // For loading Freemarker macro libraries from WEB-INF // resources new WebappTemplateLoader( @@ -86,8 +87,8 @@ public class MvcFreemarkerConfigurationProducer } ) ); - + return configuration; } - + } diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/TemplateInfo.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/TemplateInfo.java new file mode 100644 index 000000000..3eda69687 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/TemplateInfo.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 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 org.libreccm.theming.ThemeInfo; + +/** + * + * @author Jens Pelzetter + */ +class TemplateInfo { + + private final ThemeInfo themeInfo; + + private final String filePath; + + public TemplateInfo(ThemeInfo themeInfo, String filePath) { + this.themeInfo = themeInfo; + this.filePath = filePath; + } + + public ThemeInfo getThemeInfo() { + return themeInfo; + } + + public String getFilePath() { + return filePath; + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemeTemplateUtil.java b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemeTemplateUtil.java new file mode 100644 index 000000000..80b693f6f --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemeTemplateUtil.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2021 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 org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.libreccm.core.UnexpectedErrorException; +import org.libreccm.theming.ThemeInfo; +import org.libreccm.theming.ThemeProvider; +import org.libreccm.theming.ThemeVersion; +import org.libreccm.theming.Themes; + +import java.util.Arrays; +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +class ThemeTemplateUtil { + + private static final Logger LOGGER = LogManager.getLogger(ThemeTemplateUtil.class); + + @Inject + private Instance themeProviders; + + @Inject + private Themes themes; + + public boolean isValidTemplatePath(final String templatePath) { + return templatePath.startsWith("@themes") + || templatePath.startsWith("/@themes"); + } + + public Optional getTemplateInfo(final String templatePath) { + if (!isValidTemplatePath(templatePath)) { + throw new IllegalArgumentException( + String.format( + "Provided template \"%s\" path does not start with " + + "\"@theme\" or \"/@theme\".", + templatePath + ) + ); + } + + final String[] tokens; + if (templatePath.startsWith("/")) { + tokens = templatePath.substring(1).split("/"); + } else { + tokens = templatePath.split("/"); + } + + return getTemplateInfo(tokens); + } + + public ThemeProvider findThemeProvider(final ThemeInfo forTheme) { + final Instance provider = themeProviders + .select(forTheme.getProvider()); + + if (provider.isUnsatisfied()) { + LOGGER.error("ThemeProvider \"{}\" not found.", + forTheme.getProvider().getName()); + throw new UnexpectedErrorException( + String.format( + "ThemeProvider \"%s\" not found.", + forTheme.getProvider().getName() + ) + ); + } + + return provider.get(); + } + + private Optional getTemplateInfo(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 + ) + ); + + final Optional themeInfo = themes.getTheme( + themeName, themeVersion + ); + + if (themeInfo.isPresent()) { + return Optional.of(new TemplateInfo(themeInfo.get(), filePath)); + } else { + return Optional.empty(); + } + } else { + throw new IllegalArgumentException( + String.format( + "Template path has wrong format. Expected at least " + + "four tokens separated by slashes, but found only %d", + tokens.length + ) + ); + } + } + +} 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 index e183c6bf8..999b90ebb 100644 --- a/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemesTemplateLoader.java +++ b/ccm-core/src/main/java/org/libreccm/mvc/freemarker/ThemesTemplateLoader.java @@ -39,8 +39,13 @@ class ThemesTemplateLoader implements TemplateLoader { private final Themes themes; - public ThemesTemplateLoader(final Themes themes) { + private final ThemeTemplateUtil themeTemplateUtil; + + public ThemesTemplateLoader( + final Themes themes, final ThemeTemplateUtil themeTemplateUtil + ) { this.themes = themes; + this.themeTemplateUtil = themeTemplateUtil; } /** @@ -53,75 +58,37 @@ class ThemesTemplateLoader implements TemplateLoader { * 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. + * {@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. + * 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") - || path.startsWith("WEB-INF/views/@themes")) { - final String[] tokens; - if (path.startsWith("/")) { - tokens = path.substring(1).split("/"); + if (themeTemplateUtil.isValidTemplatePath(path)) { + final Optional templateInfo = themeTemplateUtil + .getTemplateInfo(path); + + if (templateInfo.isPresent()) { + final Optional source = themes.getFileFromTheme( + templateInfo.get().getThemeInfo(), + templateInfo.get().getFilePath() + ); + + if (source.isPresent()) { + return source.get(); + } else { + return null; + } } else { - tokens = path.split("/"); + return null; } - 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; } diff --git a/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResourceProvider.java b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResourceProvider.java new file mode 100644 index 000000000..e9dcd7d3b --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResourceProvider.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 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.mvc; + +import org.libreccm.theming.ThemeFileInfo; +import org.libreccm.theming.ThemeProvider; +import org.libreccm.theming.ThemeVersion; +import org.libreccm.theming.manager.Themes; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Any; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.core.Response; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +@Path("/") +public class ThemeResourceProvider { + + @Inject + @Any + private Instance providers; + + @Inject + private Themes themes; + + @GET + @Path("/{theme}/{themeVersion}/{path:.+}") + public Response getThemeFile( + @PathParam("theme") final String themeName, + @PathParam("themeVersion") final String themeVersionParam, + @PathParam("path") final String pathParam + ) { + final Optional provider = findProvider(themeName); + final ThemeVersion themeVersion = ThemeVersion.valueOf( + themeVersionParam + ); + + if (provider.isPresent()) { + final Optional fileInfo = provider + .get() + .getThemeFileInfo(themeName, themeVersion, pathParam); + + if (fileInfo.isPresent()) { + final ThemeFileInfo themeFileInfo = fileInfo.get(); + if (themeFileInfo.isDirectory()) { + return Response.status(Response.Status.FORBIDDEN).build(); + } else { + final Optional inputStream = provider + .get() + .getThemeFileAsStream( + themeName, themeVersion, pathParam + ); + if (inputStream.isPresent()) { + final InputStream inStream = inputStream.get(); + return Response + .ok(inStream) + .type(themeFileInfo.getMimeType()) + .build(); + } else { + return Response + .status(Response.Status.NOT_FOUND) + .entity( + String.format( + "File \"%s\" does not exist in version of " + + "theme %s.", + pathParam, + themeVersion, + themeName + ) + ) + .build(); + } + } + } else { + return Response + .status(Response.Status.NOT_FOUND) + .entity( + String.format( + "File \"%s\" does not exist in the %s " + + "version of theme %s.", + pathParam, + themeVersion, + themeName + ) + ) + .build(); + } + } else { + return Response + .status(Response.Status.NOT_FOUND) + .entity(String.format("Theme \"%s\" does not exist.", + themeName)) + .build(); + } + } + + private Optional findProvider(final String forTheme) { + + final List providersList = new ArrayList<>(); + providers + .forEach(provider -> providersList.add(provider)); + + return providersList + .stream() + .filter(current -> current.providesTheme(forTheme, + ThemeVersion.DRAFT)) + .findAny(); + } + +} diff --git a/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResources.java b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResources.java new file mode 100644 index 000000000..bdac41633 --- /dev/null +++ b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemeResources.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 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.mvc; + +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * + * @author Jens Pelzetter + */ +@ApplicationPath("/@themes") +public class ThemeResources extends Application { + + @Override + public Set> getClasses() { + final Set> classes = new HashSet<>(); + classes.add(ThemeResourceProvider.class); + return classes; + } + + + +} diff --git a/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemesMvc.java b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemesMvc.java index 24620dd21..f999d9b2e 100644 --- a/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemesMvc.java +++ b/ccm-core/src/main/java/org/libreccm/theming/mvc/ThemesMvc.java @@ -33,6 +33,8 @@ import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; +import javax.mvc.Models; +import javax.servlet.ServletContext; import javax.ws.rs.NotFoundException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; @@ -45,6 +47,12 @@ import javax.ws.rs.core.UriInfo; @RequestScoped public class ThemesMvc { + @Inject + private Models models; + + @Inject + private ServletContext servletContext; + @Inject private SiteRepository siteRepo; @@ -77,7 +85,7 @@ public class ThemesMvc { ); final ThemeTemplate themeTemplate; if (applicationTemplates.containsKey(application)) { - themeTemplate = applicationTemplates.get(application); + themeTemplate = applicationTemplates.get(application); } else { themeTemplate = Optional.ofNullable( applicationTemplates.get("@default") @@ -94,8 +102,21 @@ public class ThemesMvc { ); } + models.put("contextPath", servletContext.getContextPath()); + models.put("themeName", themeInfo.getName()); + models.put("themeVersion", themeInfo.getVersion()); + models.put( + "themeUrl", + String.format( + "%s/@themes/%s/%s", + servletContext.getContextPath(), + themeInfo.getName(), + themeInfo.getVersion() + ) + ); + return String.format( - "@themes/%s/%s/%s", + "/@themes/%s/%s/%s", themeInfo.getName(), Objects.toString(themeVersion), themeTemplate.getPath() diff --git a/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java b/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java index 3767f846b..2617c4526 100644 --- a/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java +++ b/ccm-core/src/main/java/org/libreccm/ui/login/LoginController.java @@ -82,6 +82,7 @@ public class LoginController { models.put( "emailIsPrimaryIdentifier", isEmailPrimaryIdentifier() ); + models.put("loginFailed", false); models.put("returnUrl", redirectUrl); return themesMvc.getMvcTemplate(uriInfo, "login-form"); } diff --git a/ccm-core/src/main/resources/themes/ccm-freemarker/login/login-form.html.ftl b/ccm-core/src/main/resources/themes/ccm-freemarker/login/login-form.html.ftl index ff42eb3c3..9b52ffed1 100644 --- a/ccm-core/src/main/resources/themes/ccm-freemarker/login/login-form.html.ftl +++ b/ccm-core/src/main/resources/themes/ccm-freemarker/login/login-form.html.ftl @@ -2,17 +2,21 @@ Category page - - + + +
+            ${themeUrl}/style.css
+        

${LoginMessages['login.title']}

- <# if (loginFailed)> + <#if (loginFailed)>
${LoginMessages['login.errors.failed']}
-
${mvc.uri('LoginController#processLogin')} + @@ -30,6 +34,6 @@
- <#include "footer.html.ftl"> + <#include "../footer.html.ftl"> diff --git a/ccm-core/src/main/resources/themes/ccm-freemarker/theme-index.json b/ccm-core/src/main/resources/themes/ccm-freemarker/theme-index.json new file mode 100644 index 000000000..efed2fefc --- /dev/null +++ b/ccm-core/src/main/resources/themes/ccm-freemarker/theme-index.json @@ -0,0 +1,70 @@ +{ + "files": [ + { + "name": "login", + "isDirectory": true, + "files": [ + { + "name": "login-form.html.ftl", + "isDirectory": false, + "mimeType": "text/plain" + }, + { + "name": "login-password-recovered.html.ftl", + "isDirectory": false, + "mimeType": "text/plain" + }, { + "name": "login-recover-password.html.ftl", + "isDirectory": false, + "mimeType": "text/plain" + } + ] + }, + { + "name": "texts", + "isDirectory": true, + "files": [ + { + "name": "labels.properties", + "isDirectory": false, + "mimeType": "text/plain" + } + ] + }, + { + "name": "category-page.html.ftl", + "isDirectory": false, + "mimeType": "text/plain" + }, + { + "name": "footer.html.ftl", + "isDirectory": false, + "mimeType": "text/plain" + }, + { + "name": "settings.properties", + "isDirectory": false, + "mimeType": "text/plain" + }, + { + "name": "style.css", + "isDirectory": false, + "mimeType": "text/css" + }, + { + "name": "theme-bundle.properties", + "isDirectory": false, + "mimeType": "text/plain" + }, + { + "name": "theme-index.json", + "isDirectory": false, + "mimeType": "application/json" + }, + { + "name": "theme.json", + "isDirectory": false, + "mimeType": "application/json" + } + ] +} diff --git a/ccm-core/src/test/java/org/libreccm/theming/mvc/ThemeResourceProviderTest.java b/ccm-core/src/test/java/org/libreccm/theming/mvc/ThemeResourceProviderTest.java new file mode 100644 index 000000000..fa1b3dfaf --- /dev/null +++ b/ccm-core/src/test/java/org/libreccm/theming/mvc/ThemeResourceProviderTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 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.mvc; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Jens Pelzetter + */ +public class ThemeResourceProviderTest { + + public ThemeResourceProviderTest() { + } + + @BeforeClass + public static void setUpClass() { + } + + @AfterClass + public static void tearDownClass() { + } + + @Before + public void setUp() { + } + + @After + public void tearDown() { + } + + @Test + public void testSomeMethod() { + // TODO review the generated test code and remove the default call to fail. + fail("The test case is a prototype."); + } + +}