Integration of EE MVC and CCM themes

ccm-docs
Jens Pelzetter 2021-01-05 19:21:23 +01:00
parent 5c2c4e583f
commit 5f155a754f
13 changed files with 709 additions and 165 deletions

View File

@ -19,16 +19,27 @@
package org.libreccm.mvc.freemarker; package org.libreccm.mvc.freemarker;
import freemarker.template.Configuration; import freemarker.template.Configuration;
import freemarker.template.SimpleNumber;
import freemarker.template.Template; import freemarker.template.Template;
import freemarker.template.TemplateException; 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.ViewEngineBase;
import org.eclipse.krazo.engine.ViewEngineConfig; 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.IOException;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.Writer; import java.io.Writer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -64,6 +75,18 @@ public class FreemarkerViewEngine extends ViewEngineBase {
@Inject @Inject
private MvcContext mvc; private MvcContext mvc;
@Inject
private L10NUtils l10nUtils;
@Inject
private SettingsUtils settingsUtils;
@Inject
private TextUtils textUtils;
@Inject
private ThemeTemplateUtil themeTemplateUtil;
@Override @Override
public boolean supports(String view) { public boolean supports(String view) {
return view.endsWith(".ftl"); return view.endsWith(".ftl");
@ -86,6 +109,23 @@ public class FreemarkerViewEngine extends ViewEngineBase {
model.put("mvc", mvc); model.put("mvc", mvc);
model.put("request", context.getRequest(HttpServletRequest.class)); model.put("request", context.getRequest(HttpServletRequest.class));
final Optional<TemplateInfo> 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<String, Object> namedBeans = beanManager final Map<String, Object> namedBeans = beanManager
.getBeans(Object.class) .getBeans(Object.class)
.stream() .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.");
}
}
}
} }

View File

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

View File

@ -31,31 +31,32 @@ import org.libreccm.theming.Themes;
import javax.enterprise.inject.Produces; import javax.enterprise.inject.Produces;
import javax.enterprise.inject.Specializes; import javax.enterprise.inject.Specializes;
import javax.inject.Inject; import javax.inject.Inject;
import javax.mvc.Models;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
/** /**
* Extends the default configuration for Freemarker of Eclipse Krazo to * Extends the default configuration for Freemarker of Eclipse Krazo to support
* support Freemarker templates in CCM themes. * Freemarker templates in CCM themes.
* *
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a> * @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/ */
//@ApplicationScoped
//@Alternative
//@Specializes
//@Priority(3000)
public class MvcFreemarkerConfigurationProducer public class MvcFreemarkerConfigurationProducer
extends DefaultConfigurationProducer { extends DefaultConfigurationProducer {
@Inject
private Models models;
@Inject @Inject
private ServletContext servletContext; private ServletContext servletContext;
@Inject @Inject
private Themes themes; private Themes themes;
@Inject
private ThemeTemplateUtil themeTemplateUtil;
@Produces @Produces
@ViewEngineConfig @ViewEngineConfig
// @Alternative
@Specializes @Specializes
@Override @Override
public Configuration getConfiguration() { public Configuration getConfiguration() {
@ -74,7 +75,7 @@ public class MvcFreemarkerConfigurationProducer
new MultiTemplateLoader( new MultiTemplateLoader(
new TemplateLoader[]{ new TemplateLoader[]{
new KrazoTemplateLoader(servletContext), new KrazoTemplateLoader(servletContext),
new ThemesTemplateLoader(themes), new ThemesTemplateLoader(themes, themeTemplateUtil),
// For loading Freemarker macro libraries from WEB-INF // For loading Freemarker macro libraries from WEB-INF
// resources // resources
new WebappTemplateLoader( new WebappTemplateLoader(

View File

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

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
class ThemeTemplateUtil {
private static final Logger LOGGER = LogManager.getLogger(ThemeTemplateUtil.class);
@Inject
private Instance<ThemeProvider> themeProviders;
@Inject
private Themes themes;
public boolean isValidTemplatePath(final String templatePath) {
return templatePath.startsWith("@themes")
|| templatePath.startsWith("/@themes");
}
public Optional<TemplateInfo> 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<? extends ThemeProvider> 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<TemplateInfo> 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> 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
)
);
}
}
}

View File

@ -39,8 +39,13 @@ class ThemesTemplateLoader implements TemplateLoader {
private final Themes themes; 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.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 * of the theme from which the template is loaded. {@code $version} is the
* version of the theme to use. This token is converted to * version of the theme to use. This token is converted to
* {@link ThemeVersion}. Valid values are therefore {@code DRAFT} and * {@link ThemeVersion}. Valid values are therefore {@code DRAFT} and
* {@code LIVE}. The remainder of the path is the path to the file inside the * {@code LIVE}. The remainder of the path is the path to the file inside
* theme. * the theme.
* *
* @param path The path of the file. The path must include the theme and its * @param path The path of the file. The path must include the theme and its
* version. * version.
* *
* @return An {@link InputStream} for the template if the template was found * @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 * @throws IOException
*/ */
@Override @Override
public Object findTemplateSource(final String path) throws IOException { public Object findTemplateSource(final String path) throws IOException {
if (path.startsWith("@themes") if (themeTemplateUtil.isValidTemplatePath(path)) {
|| path.startsWith("/@themes") final Optional<TemplateInfo> templateInfo = themeTemplateUtil
|| path.startsWith("WEB-INF/views/@themes")) { .getTemplateInfo(path);
final String[] tokens;
if (path.startsWith("/")) { if (templateInfo.isPresent()) {
tokens = path.substring(1).split("/"); final Optional<InputStream> source = themes.getFileFromTheme(
templateInfo.get().getThemeInfo(),
templateInfo.get().getFilePath()
);
if (source.isPresent()) {
return source.get();
} else {
return null;
}
} else { } 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> 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<InputStream> source = themes.getFileFromTheme(
themeInfo, filePath
);
if (source.isPresent()) {
return source.get();
} else { } else {
return null; return null;
} }

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@RequestScoped
@Path("/")
public class ThemeResourceProvider {
@Inject
@Any
private Instance<ThemeProvider> 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<ThemeProvider> provider = findProvider(themeName);
final ThemeVersion themeVersion = ThemeVersion.valueOf(
themeVersionParam
);
if (provider.isPresent()) {
final Optional<ThemeFileInfo> 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> 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<ThemeProvider> findProvider(final String forTheme) {
final List<ThemeProvider> providersList = new ArrayList<>();
providers
.forEach(provider -> providersList.add(provider));
return providersList
.stream()
.filter(current -> current.providesTheme(forTheme,
ThemeVersion.DRAFT))
.findAny();
}
}

View File

@ -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 <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ApplicationPath("/@themes")
public class ThemeResources extends Application {
@Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> classes = new HashSet<>();
classes.add(ThemeResourceProvider.class);
return classes;
}
}

View File

@ -33,6 +33,8 @@ import java.util.Optional;
import javax.enterprise.context.RequestScoped; import javax.enterprise.context.RequestScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.mvc.Models;
import javax.servlet.ServletContext;
import javax.ws.rs.NotFoundException; import javax.ws.rs.NotFoundException;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
@ -45,6 +47,12 @@ import javax.ws.rs.core.UriInfo;
@RequestScoped @RequestScoped
public class ThemesMvc { public class ThemesMvc {
@Inject
private Models models;
@Inject
private ServletContext servletContext;
@Inject @Inject
private SiteRepository siteRepo; private SiteRepository siteRepo;
@ -77,7 +85,7 @@ public class ThemesMvc {
); );
final ThemeTemplate themeTemplate; final ThemeTemplate themeTemplate;
if (applicationTemplates.containsKey(application)) { if (applicationTemplates.containsKey(application)) {
themeTemplate = applicationTemplates.get(application); themeTemplate = applicationTemplates.get(application);
} else { } else {
themeTemplate = Optional.ofNullable( themeTemplate = Optional.ofNullable(
applicationTemplates.get("@default") 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( return String.format(
"@themes/%s/%s/%s", "/@themes/%s/%s/%s",
themeInfo.getName(), themeInfo.getName(),
Objects.toString(themeVersion), Objects.toString(themeVersion),
themeTemplate.getPath() themeTemplate.getPath()

View File

@ -82,6 +82,7 @@ public class LoginController {
models.put( models.put(
"emailIsPrimaryIdentifier", isEmailPrimaryIdentifier() "emailIsPrimaryIdentifier", isEmailPrimaryIdentifier()
); );
models.put("loginFailed", false);
models.put("returnUrl", redirectUrl); models.put("returnUrl", redirectUrl);
return themesMvc.getMvcTemplate(uriInfo, "login-form"); return themesMvc.getMvcTemplate(uriInfo, "login-form");
} }

View File

@ -2,17 +2,21 @@
<html> <html>
<head> <head>
<title>Category page</title> <title>Category page</title>
<link rel="stylesheet" href="${getContextPath()}/theming/ccm/style.css" /> <link rel="stylesheet" href="${themeUrl}/style.css" />
</head> </head>
<body> <body>
<pre>
${themeUrl}/style.css
</pre>
<main> <main>
<h1>${LoginMessages['login.title']}</h1> <h1>${LoginMessages['login.title']}</h1>
<# if (loginFailed)> <#if (loginFailed)>
<div class="alert-error"> <div class="alert-error">
${LoginMessages['login.errors.failed']} ${LoginMessages['login.errors.failed']}
</div> </div>
</#if> </#if>
<form action="${mvc.url('LoginController#processLogin')}" <pre>${mvc.uri('LoginController#processLogin')}</pre>
<form action="${mvc.uri('LoginController#processLogin')}"
method="post"> method="post">
<label for="login">${LoginMessages['login.screenname.label']}</label> <label for="login">${LoginMessages['login.screenname.label']}</label>
<input id="login" name="login" required="true" type="text" /> <input id="login" name="login" required="true" type="text" />
@ -30,6 +34,6 @@
</button> </button>
</form> </form>
</main> </main>
<#include "footer.html.ftl"> <#include "../footer.html.ftl">
</body> </body>
</html> </html>

View File

@ -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"
}
]
}

View File

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