diff --git a/ccm-cms/src/main/java/org/librecms/pages/PagesController.java b/ccm-cms/src/main/java/org/librecms/pages/PagesController.java index 1615825ab..253284c24 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/PagesController.java +++ b/ccm-cms/src/main/java/org/librecms/pages/PagesController.java @@ -31,17 +31,25 @@ import org.libreccm.theming.ThemeInfo; import org.libreccm.theming.ThemeVersion; import org.libreccm.theming.Themes; import org.libreccm.theming.mvc.ThemesMvc; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemL10NManager; +import org.librecms.contentsection.ContentItemManager; import org.librecms.contentsection.ContentItemVersion; import org.librecms.pages.models.CategoryModel; import org.librecms.pages.models.ContentItemModel; import org.librecms.pages.models.SiteInfoModel; import java.net.URI; +import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Optional; import java.util.Set; +import java.util.StringJoiner; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.PostConstruct; @@ -173,6 +181,12 @@ public class PagesController { @Inject private ConfigurationManager confManager; + @Inject + private ContentItemManager contentItemManager; + + @Inject + private ContentItemL10NManager contentItemL10NManager; + @Inject private ContentItemModel contentItemModel; @@ -182,6 +196,9 @@ public class PagesController { @Inject private PagesRepository pagesRepo; + @Inject + private PagesService pagesService; + @Inject private SiteInfoModel siteInfoModel; @@ -206,13 +223,27 @@ public class PagesController { @GET @Path("/") @Transactional(Transactional.TxType.REQUIRED) - public Response redirectToIndexPage(@Context final UriInfo uriInfo) { + public Response redirectToIndexPage( + @Context + final UriInfo uriInfo, + @QueryParam("theme") + @DefaultValue(ThemesMvc.DEFAULT_THEME_PARAM) + final String theme, + @QueryParam("preview") + @DefaultValue("") + final String preview + ) { final String domain = uriInfo.getBaseUri().getHost(); final Pages pages = getPages(domain); final Category category = getCategory(domain, pages, "/"); - final String language = determineLanguage(category); + final Versions versions = generateFromPreviewParam(preview); + final String language = determineLanguage(category, versions); - final String indexPage = String.format("/index.%s.html", language); + final String indexPage = String.format( + "/index.%s.html%s", + language, + buildQueryParamsStr(preview, theme) + ); final URI uri = uriInfo.getBaseUriBuilder().path(indexPage).build(); return Response.temporaryRedirect(uri).build(); } @@ -221,16 +252,27 @@ public class PagesController { @Path("/{name:[\\w\\-]+}") @Transactional(Transactional.TxType.REQUIRED) public Response getRootPage( - @Context final UriInfo uriInfo, - @PathParam("name") final String itemName + @Context + final UriInfo uriInfo, + @PathParam("name") final String itemName, + @QueryParam("theme") + @DefaultValue(ThemesMvc.DEFAULT_THEME_PARAM) + final String theme, + @QueryParam("preview") + @DefaultValue("") + final String preview ) { final String domain = uriInfo.getBaseUri().getHost(); final Pages pages = getPages(domain); final Category category = getCategory(domain, pages, "/"); - final String language = determineLanguage(category); + final Versions versions = generateFromPreviewParam(preview); + final String language = determineLanguage(category, versions); final String itemPage = String.format( - "/%s.%s.html", itemName, language + "/%s.%s.html%s", + itemName, + language, + buildQueryParamsStr(preview, theme) ); final URI uri = uriInfo.getBaseUriBuilder().path(itemPage).build(); return Response.temporaryRedirect(uri).build(); @@ -241,12 +283,20 @@ public class PagesController { @Transactional(Transactional.TxType.REQUIRED) public Response getRootPageAsHtml( @Context final UriInfo uriInfo, - @PathParam("name") final String itemName) { - + @QueryParam("theme") + @DefaultValue(ThemesMvc.DEFAULT_THEME_PARAM) + final String theme, + @QueryParam("preview") + @DefaultValue("") + final String preview, + @PathParam("name") + final String itemName + ) { final String domain = uriInfo.getBaseUri().getHost(); final Pages pages = getPages(domain); final Category category = getCategory(domain, pages, "/"); - final String language = determineLanguage(category); + final Versions versions = generateFromPreviewParam(preview); + final String language = determineLanguage(category, itemName, versions); final String itemPage = String.format( "/%s.%s.html", itemName, language @@ -284,7 +334,14 @@ public class PagesController { contentItemModel.setItemName(itemName); contentItemModel.setItemVersion(versions.getContentItemVersion()); - return themesMvc.getMvcTemplate(uriInfo, "pages", "page"); + final String domain = uriInfo.getBaseUri().getHost(); + final Pages pages = getPages(domain); + final Category category = getCategory(domain, pages, "/"); + final Page page = pageManager.findPageForCategory(category); + page.getDisplayName(); + return themesMvc.getMvcTemplate( + uriInfo, "pages", page.getDisplayName() + ); } /** @@ -297,6 +354,8 @@ public class PagesController { * @param uriInfo * @param page * @param itemName + * @param theme + * @param preview * * @return */ @@ -306,12 +365,21 @@ public class PagesController { public Response getPage( @Context final UriInfo uriInfo, @PathParam("page") final String page, - @PathParam("name") final String itemName + @PathParam("name") final String itemName, + @QueryParam("theme") + @DefaultValue("--DEFAULT--") + final String theme, + @QueryParam("preview") + @DefaultValue("") + final String preview ) { + // ToDo Check!!! + final String domain = uriInfo.getBaseUri().getHost(); final Pages pages = getPages(domain); final Category category = getCategory(domain, pages, page); - final String language = determineLanguage(category); + final Versions versions = generateFromPreviewParam(preview); + final String language = determineLanguage(category, versions); final String redirectTo; if (uriInfo.getPath().endsWith("/")) { @@ -346,12 +414,21 @@ public class PagesController { public Response getPageAsHtml( @Context final UriInfo uriInfo, @PathParam("page") final String page, - @PathParam("name") final String itemName + @PathParam("name") final String itemName, + @QueryParam("theme") + @DefaultValue("--DEFAULT--") + final String theme, + @QueryParam("preview") + @DefaultValue("") + final String preview ) { + //ToDo Check! + final String domain = uriInfo.getBaseUri().getHost(); final Pages pages = getPages(domain); final Category category = getCategory(domain, pages, page); - final String language = determineLanguage(category); + final Versions versions = generateFromPreviewParam(preview); + final String language = determineLanguage(category, versions); final String redirectTo; if (uriInfo.getPath().endsWith("/")) { @@ -461,20 +538,91 @@ public class PagesController { ); } - private String determineLanguage(final Category category) { + private String determineLanguage( + final Category category, final Versions versions + ) { final Locale negoiatedLocale = globalizationHelper .getNegotiatedLocale(); - final String language; - if (category.getTitle().hasValue(negoiatedLocale)) { - language = negoiatedLocale.toString(); - } else if (category.getTitle().hasValue(defaultLocale)) { - language = defaultLocale.toString(); + if (categoryManager.hasIndexObject(category)) { + final ContentItem indexItem = getIndexObject(category, versions) + .get(); + if (contentItemL10NManager.hasLanguage(indexItem, negoiatedLocale)) { + return negoiatedLocale.toString(); + } else { + return confManager + .findConfiguration(KernelConfig.class) + .getDefaultLanguage(); + } } else { - throw new NotFoundException(); + if (category.getTitle().hasValue(negoiatedLocale)) { + return negoiatedLocale.toString(); + } else { + return defaultLocale.toString(); + } } + } - return language; + private String determineLanguage( + final Category category, + final String itemName, + final Versions versions + ) { + final Locale negoiatedLocale = globalizationHelper + .getNegotiatedLocale(); + + final ContentItem contentItem = pagesService.findCategorizedItem( + category, itemName, versions.getContentItemVersion() + ) + .orElseThrow( + () -> new NotFoundException( + String.format( + "No item %s found in category %s.", + itemName, + categoryManager.getCategoryPath(category) + ) + ) + ); + + final KernelConfig kernelConfig = confManager.findConfiguration( + KernelConfig.class + ); + + if (contentItemL10NManager.hasLanguage(contentItem, negoiatedLocale)) { + return negoiatedLocale.toString(); + } else if (contentItemL10NManager.hasLanguage(contentItem, kernelConfig + .getDefaultLocale())) { + return kernelConfig.getDefaultLanguage(); + } else { + throw new NotFoundException( + String.format( + "No item %s found in category %s.", + itemName, + categoryManager.getCategoryPath(category) + ) + ); + } + } + + private String buildQueryParamsStr( + final String previewParam, final String themeParam + ) { + return List + .of( + Optional + .of(themeParam) + .filter(String::isBlank) + .map(param -> String.format("theme=%s", param)) + .orElse(""), + Optional + .of(previewParam) + .filter(String::isBlank) + .map(param -> String.format("preview=%s", param)) + .orElse("") + ) + .stream() + .filter(String::isBlank) + .collect(Collectors.joining("&", "?", "")); } private ThemeInfo getTheme( @@ -508,6 +656,26 @@ public class PagesController { } } + private Optional getIndexObject( + final Category category, final Versions versions + ) { + final Predicate filter; + if (versions.getContentItemVersion() == ContentItemVersion.DRAFT) { + filter = (ContentItem item) -> !contentItemManager.isLive(item); + } else { + filter = (ContentItem item) -> contentItemManager.isLive(item); + } + + return categoryManager + .getIndexObject(category) + .stream() + .filter(object -> object instanceof ContentItem) + .map(object -> (ContentItem) object) + .filter(filter) + .findFirst(); + + } + private Page findPage( final UriInfo uriInfo, final String pagePath, final String language ) { diff --git a/ccm-cms/src/main/java/org/librecms/pages/PagesService.java b/ccm-cms/src/main/java/org/librecms/pages/PagesService.java new file mode 100644 index 000000000..5e9884f15 --- /dev/null +++ b/ccm-cms/src/main/java/org/librecms/pages/PagesService.java @@ -0,0 +1,106 @@ +/* + * 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.librecms.pages; + +import org.libreccm.categorization.Categorization; +import org.libreccm.categorization.Category; +import org.libreccm.categorization.CategoryManager; +import org.librecms.contentsection.ContentItem; +import org.librecms.contentsection.ContentItemVersion; + +import java.util.Optional; + +import javax.enterprise.context.RequestScoped; +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.NoResultException; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Root; +import javax.transaction.Transactional; + +/** + * + * @author Jens Pelzetter + */ +@RequestScoped +public class PagesService { + + @Inject + private CategoryManager categoryManager; + + @Inject + private EntityManager entityManager; + + @Transactional(Transactional.TxType.REQUIRED) + public Optional findIndexItem( + final Category category, final ContentItemVersion version + ) { + return categoryManager + .getIndexObject(category) + .stream() + .filter(object -> object instanceof ContentItem) + .map(object -> (ContentItem) object) + .filter(item -> item.getVersion() == version) + .findFirst(); + } + + @Transactional(Transactional.TxType.REQUIRED) + public Optional findCategorizedItem( + final Category category, + final String itemName, + final ContentItemVersion version + ) { + final CriteriaBuilder builder = entityManager + .getCriteriaBuilder(); + final CriteriaQuery criteriaQuery = builder + .createQuery(ContentItem.class); + final Root from = criteriaQuery.from( + ContentItem.class + ); + final Join join = from.join( + "categories" + ); + + final TypedQuery query = entityManager + .createQuery( + criteriaQuery + .select(from) + .where( + builder.and( + builder.equal(from.get("displayName"), itemName), + builder.equal( + from.get("version"), + version + ), + builder.equal(join.get("category"), category) + ) + ) + ); + + try { + return Optional.of(query.getSingleResult()); + } catch (NoResultException ex) { + return Optional.empty(); + } + } + +} diff --git a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java index 11f725503..dc0021070 100644 --- a/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java +++ b/ccm-cms/src/main/java/org/librecms/pages/models/ContentItemModel.java @@ -20,33 +20,25 @@ package org.librecms.pages.models; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.libreccm.categorization.Categorization; -import org.libreccm.categorization.Category; import org.libreccm.categorization.CategoryManager; import org.libreccm.l10n.GlobalizationHelper; import org.librecms.contentsection.AttachmentList; import org.librecms.contentsection.ContentItem; import org.librecms.contentsection.ContentItemVersion; import org.librecms.pages.PagesRouter; +import org.librecms.pages.PagesService; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.List; -import java.util.Objects; import java.util.Optional; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.inject.Named; import javax.persistence.EntityManager; -import javax.persistence.NoResultException; -import javax.persistence.TypedQuery; -import javax.persistence.criteria.CriteriaBuilder; -import javax.persistence.criteria.CriteriaQuery; -import javax.persistence.criteria.Join; -import javax.persistence.criteria.Root; import javax.ws.rs.NotFoundException; /** @@ -77,6 +69,9 @@ public class ContentItemModel { @Inject private GlobalizationHelper globalizationHelper; + @Inject + private PagesService pagesService; + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault()); @@ -93,11 +88,11 @@ public class ContentItemModel { public void setItemName(final String itemName) { this.itemName = itemName; } - + public ContentItemVersion getItemVersion() { return itemVersion; } - + public void setItemVersion(final ContentItemVersion itemVersion) { this.itemVersion = itemVersion; } @@ -221,66 +216,13 @@ public class ContentItemModel { private void retrieveContentItem() { if (itemName == null) { - retrieveIndexItem(); + contentItem = pagesService.findIndexItem( + categoryModel.getCategory(), itemVersion + ); } else { - retrieveCategorizedItem(); - } - } - - private void retrieveIndexItem() { - final Category category = categoryModel.getCategory(); - - contentItem = categoryManager - .getIndexObject(category) - .stream() - .filter(object -> object instanceof ContentItem) - .map(object -> (ContentItem) object) - .filter(item -> item.getVersion() == itemVersion) - .findFirst(); - } - - private void retrieveCategorizedItem() { - final Category category = categoryModel.getCategory(); - final CriteriaBuilder builder = entityManager - .getCriteriaBuilder(); - final CriteriaQuery criteriaQuery = builder - .createQuery(ContentItem.class); - - final Root from = criteriaQuery.from( - ContentItem.class - ); - final Join join = from.join( - "categories" - ); - - final TypedQuery query = entityManager - .createQuery(criteriaQuery - .select(from) - .where(builder.and( - builder.equal(from.get("displayName"), itemName), - builder.equal( - from.get("version"), - itemVersion - ), - builder.equal(join.get("category"), category) - ))); - - try { - contentItem = Optional.of(query.getSingleResult()); - } catch (NoResultException ex) { - LOGGER.warn( - "No ContentItem with name \"{}\" in Category \"{}\".", - itemName, - Objects.toString(category) - ); - throw new NotFoundException( - String.format( - "No ContentItem with name \"%s\" in Category \"%s\".", - itemName, - Objects.toString(category) - ) + contentItem = pagesService.findCategorizedItem( + categoryModel.getCategory(), itemName, itemVersion ); } } - } 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 51915d464..d57848a7e 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 @@ -49,6 +49,8 @@ import javax.ws.rs.core.UriInfo; @RequestScoped public class ThemesMvc { + public static final String DEFAULT_THEME_PARAM = "--DEFAULT--"; + @Inject private Models models; @@ -89,15 +91,15 @@ public class ThemesMvc { ); } else { final Site site = getSite(uriInfo); - final String theme = parseThemeParam(uriInfo); - themeVersion = parsePreviewParam(uriInfo); - themeInfo = getTheme( - site, - theme, - themeVersion - ); + final String theme = parseThemeParam(uriInfo); + themeVersion = parsePreviewParam(uriInfo); + themeInfo = getTheme( + site, + theme, + themeVersion + ); } - + final ThemeManifest manifest = themeInfo.getManifest(); final Map views = manifest.getViewsOfApplication( application @@ -203,7 +205,7 @@ public class ThemesMvc { final Site site, final String theme, final ThemeVersion themeVersion) { - if ("--DEFAULT--".equals(theme)) { + if (DEFAULT_THEME_PARAM.equals(theme)) { return themes .getTheme(site.getDefaultTheme(), themeVersion) .orElseThrow( @@ -239,13 +241,14 @@ public class ThemesMvc { * @param uriInfo Information about the current URI. * * @return The value of the {@link theme} query parameter if present, or - * {@code --DEFAULT--} if the query parameter is not present. + * {@link #DEFAULT_THEME_PARAM} if the query parameter is not + * present. */ private String parseThemeParam(final UriInfo uriInfo) { if (uriInfo.getQueryParameters().containsKey("theme")) { return uriInfo.getQueryParameters().getFirst("theme"); } else { - return "--DEFAULT--"; + return DEFAULT_THEME_PARAM; } }