libreccm/ccm-cms/src/main/java/org/librecms/pages/PagesController.java

908 lines
31 KiB
Java

/*
* 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 com.arsdigita.kernel.KernelConfig;
import org.libreccm.categorization.Category;
import org.libreccm.categorization.CategoryManager;
import org.libreccm.categorization.CategoryRepository;
import org.libreccm.configuration.ConfigurationManager;
import org.libreccm.l10n.GlobalizationHelper;
import org.libreccm.sites.Site;
import org.libreccm.sites.SiteRepository;
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.PagePropertiesModel;
import org.librecms.pages.models.PageUrlModel;
import org.librecms.pages.models.SiteInfoModel;
import java.net.URI;
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.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.mvc.Controller;
import javax.transaction.Transactional;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
/**
* Controller for the pages MVC application responsible for initializing the
* models used by themes for displaying the pages.
*
* This controller is the main entry point for the Pages application, which is
* the primary entry point for most public sites powered by LibreCMS. Based on
* the provided path it looks up the matching category and initalizes the models
* used by the themes to create the public pages. The application is ,as most
* other frontends of LibreCCM/LibreCMS, based on the EE MVC framework.
*
* In the following description, <code>{placeholder}</code> stands for a
* placeholder for a parameter. The placeholder <code>{path}</code> stands for
* the requested path. The path might be empty, in this case the root category
* of the category system associated with the instance of the pages application
* is used.
*
* Depending on the provided path in the URL this controller does the following:
*
* <ul>
* <li>
* If the requested path does <b>not</b> end with <code>.html</code> or
* <code>.{lang}.html</code>: Determine the language to use, select the most
* appropriate language. Redirect the request the
* <code>/@pages/{path}/index.{lang}.de</code>, where lang is the most
* approbriate language determined (also called <i>negotiated</i> language).
* </li>
* <li>
* If the requested path ends matches <code>/@pages/{path}/index.html</code>
* (the exact regex pattern can be found in the documentation of the responsible
* methods) determine the most appropriate of the available languages and
* redirect the request <code>/@pages/{path}/index.{lang}.html</code>, where
* <code>{lang}</code> is the negotiated language.
* </li>
* <li>
* If the requested path ends matches
* <code>/@pages/{path}/{itemname}.html</code> (the exact regex patterns for
* <code>{path}</code> and <code>itemname</code> can be found in the
* documentation of the responsible methods) determine the most appropriate of
* the available languages and redirect the request to
* <code>/@pages/{path}/{itemname}.{lang}.html</code>, , where
* <code>{lang}</code> is the negotiated language.
* </li>
* <li>
* If the request path matches <code>/@pages/{path}/index.{lang}.html</code>
* display the index page of the category together with the index item of the
* category, if the category has an index item. If the category has an index
* item and the index item is not available in the language requested by the
* <code>{lang}</code> parameter in the path, raise a <code>404 Not Found</code>
* error using a {@link WebApplicationException}.
* </li>
* * <li>
* If the request path matches
* <code>/@pages/{path}/{itemname}.{lang}.html</code> display the item with the
* name provided in the <code>{itemname}</code> parameter. If no item with a
* matching name is assigned to the category raise a <code>404 Not Found</code>
* using a {@link WebApplicationException}. Also, if there is a matching item,
* but it is not available in the language requested by the <code>{lang}</code>
* parameter, raise a <code>404 Not Found Error</code> using a
* {@link WebApplicationException}.
* </li>
* </ul>
*
* In the last two cases this controller will initialize the basic models to
* provide soem data for displaying theme, and delegate to the theme by calling
* {@link ThemesMvc#getMvcTemplate(javax.ws.rs.core.UriInfo, java.lang.String, java.lang.String)}.
* {@link ThemesMvc} will determine the theme to use from the requested URL. For
* details please refer to the documentation of {@link ThemesMvc}.
*
* The following models are initialized by this controller:
* <ul>
* <li>{@link CategoryModel}</li>
* <li>{@link ContentItemModel}</li>
* <li>{@link SiteInfoModel}</li>
* </ul>
*
* The language to use is determined using the following algorithm:
* <ol type="1">
* <li>
* Check if one of the languages sent by the user agent in the
* <code>Accept-Language</code> header is a supported language (see
* {@link KernelConfig#supportedLanguages}. If not fall back to the default
* language (see {@link KernelConfig#defaultLanguage}.
* </li>
* <li>
* Check if the requested item is available in the selected language, or if a
* category without an index item is requested, if the category is avaiable for
* the selected language. If yes use the selected language otherwise fallback to
* the default default configured in {@link KernelConfig#defaultLanguage}.
* </li>
* </ol>
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@Controller
@RequestScoped
@Path("/")
public class PagesController {
@Inject
private CategoryManager categoryManager;
@Inject
private CategoryModel categoryModel;
@Inject
private CategoryRepository categoryRepo;
@Inject
private GlobalizationHelper globalizationHelper;
@Inject
private ConfigurationManager confManager;
@Inject
private ContentItemManager contentItemManager;
@Inject
private ContentItemL10NManager contentItemL10NManager;
@Inject
private ContentItemModel contentItemModel;
@Inject
private PageManager pageManager;
@Inject
private PagePropertiesModel pagePropertiesModel;
@Inject
private PageUrlModel pageUrlModel;
@Inject
private PagesRepository pagesRepo;
@Inject
private PagesService pagesService;
@Inject
private SiteInfoModel siteInfoModel;
@Inject
private SiteRepository siteRepo;
@Inject
private Themes themes;
@Inject
private ThemesMvc themesMvc;
private Locale defaultLocale;
@PostConstruct
private void init() {
final KernelConfig kernelConfig = confManager
.findConfiguration(KernelConfig.class);
defaultLocale = kernelConfig.getDefaultLocale();
}
@GET
@Path("/")
@Transactional(Transactional.TxType.REQUIRED)
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 Versions versions = generateFromPreviewParam(preview);
final String language = determineLanguage(category, versions);
initPageUrlModel(uriInfo);
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();
}
@GET
@Path("/{name:[\\w\\-]+}")
@Transactional(Transactional.TxType.REQUIRED)
public Response getRootPage(
@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 Versions versions = generateFromPreviewParam(preview);
final String language = determineLanguage(category, versions);
initPageUrlModel(uriInfo);
final String itemPage = String.format(
"/%s.%s.html%s",
itemName,
language,
buildQueryParamsStr(preview, theme)
);
final URI uri = uriInfo.getBaseUriBuilder().path(itemPage).build();
return Response.temporaryRedirect(uri).build();
}
@GET
@Path("/{name:[\\w\\-]+}.html")
@Transactional(Transactional.TxType.REQUIRED)
public Response getRootPageAsHtml(
@Context final UriInfo uriInfo,
@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 Versions versions = generateFromPreviewParam(preview);
final String language = determineLanguage(category, itemName, versions);
initPageUrlModel(uriInfo);
final String itemPage = String.format(
"/%s.%s.html", itemName, language
);
final String path = uriInfo
.getPath()
.replace(String.format("%s.html", itemName), itemPage);
final URI uri = uriInfo.getBaseUriBuilder().path(path).build();
return Response.temporaryRedirect(uri).build();
}
@GET
@Path("/{name:[\\w\\-]+}.{lang:\\w+}.html")
@Produces("text/html")
@Transactional(Transactional.TxType.REQUIRED)
public String getRootPageAsHtml(
@Context
final UriInfo uriInfo,
@PathParam("name")
final String itemName,
@PathParam("lang")
final String language,
@QueryParam("theme")
@DefaultValue("--DEFAULT--")
final String theme,
@QueryParam("preview")
@DefaultValue("")
final String preview
) {
// final Versions versions = generateFromPreviewParam(preview);
//
// globalizationHelper.setSelectedLocale(new Locale(language));
//
// contentItemModel.setItemName(itemName);
// contentItemModel.setItemVersion(versions.getContentItemVersion());
//
// final String domain = uriInfo.getBaseUri().getHost();
// final Pages pages = getPages(domain);
// final Site site = pages.getSite();
// siteInfoModel.setAvailableLanguages(
// confManager
// .findConfiguration(KernelConfig.class)
// .getSupportedLanguages()
// .stream()
// .sorted()
// .collect(Collectors.toList())
// );
// siteInfoModel.setDomain(site.getDomainOfSite());
// siteInfoModel.setHost(domain);
// siteInfoModel.setName(
// Optional
// .ofNullable(site.getDisplayName())
// .orElse("")
// );
// final Category category = getCategory(domain, pages, "/");
// categoryModel.init(pages.getCategoryDomain(), category);
// final Page page = pageManager.findPageForCategory(category);
// pagePropertiesModel.setProperties(page.getProperties());
// return themesMvc.getMvcTemplate(
// uriInfo, "pages", page.getDisplayName()
// );
return getPageAsHtml(
uriInfo,
"/",
itemName,
language,
theme,
preview
);
}
/**
* Retrieve the item page for a category and the content item associated
* with the category and identified by {@code itemName}.
*
* Redirects to
* {@link #getPageAsHtml(javax.ws.rs.core.UriInfo, java.lang.String, java.lang.String)}.
*
* @param uriInfo
* @param page
* @param itemName
* @param theme
* @param preview
*
* @return
*/
@GET
@Path("/{page:[\\w\\-/]+}/{name:[\\w\\-]+}")
@Transactional(Transactional.TxType.REQUIRED)
public Response getPage(
@Context final UriInfo uriInfo,
@PathParam("page") final String page,
@PathParam("name") final String itemName,
@QueryParam("theme")
@DefaultValue("--DEFAULT--")
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, page);
final Versions versions = generateFromPreviewParam(preview);
final String language = determineLanguage(category, versions);
initPageUrlModel(uriInfo);
final String redirectTo;
if (uriInfo.getPath().endsWith("/")) {
redirectTo = String.format(
"%sindex.%s.html", uriInfo.getPath(), language
);
} else {
final String itemPath = String.format(
"%s.%s.html%s",
itemName,
language,
buildQueryParamsStr(preview, theme)
);
redirectTo = uriInfo.getPath().replace(itemName, itemPath);
}
final URI uri = uriInfo.getBaseUriBuilder().path(redirectTo).build();
return Response.temporaryRedirect(uri).build();
}
/**
* Retrieve the item page for a category and the content item associated
* with the category and identified by {@code itemName}. Redirects to
* {@link #getPageAsHtml(javax.ws.rs.core.UriInfo, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)}.
*
* @param uriInfo
* @param page
* @param itemName
* @param theme
* @param preview
*
* @return
*/
@GET
@Path("/{page:[\\w\\-/]+}/{name:[\\w\\-]+}.html")
@Transactional(Transactional.TxType.REQUIRED)
public Response getPageAsHtml(
@Context final UriInfo uriInfo,
@PathParam("page") final String page,
@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 Versions versions = generateFromPreviewParam(preview);
final String language = determineLanguage(category, versions);
initPageUrlModel(uriInfo);
final String redirectTo;
if (uriInfo.getPath().endsWith("/")) {
redirectTo = String.format(
"%sindex.%s.html%s",
uriInfo.getPath(),
language,
buildQueryParamsStr(preview, theme)
);
} else {
final String itemPath = String.format(
"%s.%s.html%s",
itemName,
language,
buildQueryParamsStr(preview, theme)
);
redirectTo = uriInfo.getPath().replace(itemName, itemPath);
}
final URI uri = uriInfo.getBaseUriBuilder().path(redirectTo).build();
return Response.temporaryRedirect(uri).build();
}
/**
* Retrieve the item page as HTML for a category and the content item
* associated with the category and identified by {@code itemName}.
*
* @param uriInfo
* @param pagePath
* @param itemName
* @param language
* @param theme
* @param preview
*
* @return
*/
@GET
@Path("/{pagePath:[\\w\\-/]+}/{name:[\\w\\-]+}.{lang:\\w+}.html")
@Produces("text/html")
@Transactional(Transactional.TxType.REQUIRED)
public String getPageAsHtml(
@Context
final UriInfo uriInfo,
@PathParam("pagePath")
final String pagePath,
@PathParam("name")
final String itemName,
@PathParam("lang")
final String language,
@QueryParam("theme")
@DefaultValue("--DEFAULT--")
final String theme,
@QueryParam("preview")
@DefaultValue("")
final String preview
) {
final Versions versions = generateFromPreviewParam(preview);
globalizationHelper.setSelectedLocale(new Locale(language));
contentItemModel.setItemName(itemName);
final ContentItemVersion version = versions.getContentItemVersion();
contentItemModel.setItemVersion(versions.getContentItemVersion());
final String domain = uriInfo.getBaseUri().getHost();
final Pages pages = getPages(domain);
final Site site = pages.getSite();
siteInfoModel.setAvailableLanguages(
confManager
.findConfiguration(KernelConfig.class)
.getSupportedLanguages()
.stream()
.sorted()
.collect(Collectors.toList())
);
initPageUrlModel(uriInfo);
siteInfoModel.setDomain(site.getDomainOfSite());
siteInfoModel.setHost(domain);
siteInfoModel.setName(
Optional
.ofNullable(site.getDisplayName())
.orElse("")
);
final Category category = getCategory(domain, pages, pagePath);
categoryModel.init(pages.getCategoryDomain(), category, version);
final Page page = pageManager.findPageForCategory(category);
pagePropertiesModel.setProperties(page.getProperties());
if (itemName.equals("index") || itemName.isBlank()) {
return themesMvc.getMvcTemplate(
uriInfo, "pages", page.getDisplayName()
);
} else {
return themesMvc.getMvcTemplate(
uriInfo, "pages", "item-page"
);
}
}
private void initSiteInfoModel(
final Site site, final String host
) {
siteInfoModel.setAvailableLanguages(
confManager
.findConfiguration(KernelConfig.class)
.getSupportedLanguages()
.stream()
.sorted()
.collect(Collectors.toList())
);
siteInfoModel.setDomain(site.getDomainOfSite());
siteInfoModel.setHost(host);
siteInfoModel.setName(
Optional
.ofNullable(site.getDisplayName())
.orElse("")
);
}
private Site getSite(final UriInfo uriInfo) {
final String domain = Objects
.requireNonNull(uriInfo)
.getBaseUri()
.getHost();
final Site site;
if (siteRepo.hasSiteForDomain(domain)) {
site = siteRepo.findByDomain(domain).get();
} else {
site = siteRepo
.findDefaultSite()
.orElseThrow(
() -> new NotFoundException(
"No matching Site and no default Site."
)
);
}
return site;
}
private Pages getPages(final String domain) {
return pagesRepo
.findPagesForSite(domain)
.orElseThrow(
() -> new NotFoundException(
String.format(
"No Pages for domain \"%s\" available.",
domain
)
)
);
}
private Category getCategory(
final String domain, final Pages pages, final String pagePath
) {
return categoryRepo
.findByPath(pages.getCategoryDomain(), pagePath)
.orElseThrow(
() -> new NotFoundException(
String.format(
"No Page for path \"%s\" in site \"%s\"",
pagePath,
domain
)
)
);
}
private String determineLanguage(
final Category category, final Versions versions
) {
final Locale negoiatedLocale = globalizationHelper
.getNegotiatedLocale();
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 {
if (category.getTitle().hasValue(negoiatedLocale)) {
return negoiatedLocale.toString();
} else {
return defaultLocale.toString();
}
}
}
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(
final Site site, final String theme, final ThemeVersion themeVersion
) {
if ("--DEFAULT--".equals(theme)) {
return themes
.getTheme(site.getDefaultTheme(), themeVersion)
.orElseThrow(
() -> new WebApplicationException(
String.format(
"The configured default theme \"%s\" for "
+ "site \"%s\" is not available.",
site.getDefaultTheme(),
site.getDomainOfSite()
),
Response.Status.INTERNAL_SERVER_ERROR
)
);
} else {
return themes.getTheme(theme, themeVersion)
.orElseThrow(
() -> new WebApplicationException(
String.format(
"The theme \"%s\" is not available.",
theme
),
Response.Status.BAD_REQUEST
)
);
}
}
private Optional<ContentItem> getIndexObject(
final Category category, final Versions versions
) {
final Predicate<ContentItem> 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();
}
/**
* Parse the value of the {@code preview} query parameter.
*
* @param value The value of the {@code preview} query parameter to parse.
*
* @return If the provided value is {@code all} a {@link Versions} object
* with all versions set to the draft versions is created and
* returned. If the provided value is {@code null} or empty a
* {@link Versions} object with fields set the the live versions is
* returned. Otherwise the values is split into tokens (separated by
* commas). The values of the returned {@link Versions} depend on
* presence of certain tokens. At the moment to following tokens are
* recognised:
* <dl>
* <dt>{@code content}</dt>
* <dd>{@link Versions#contentVersion} is set to
* {@link ContentItemVersion#DRAFT}</dd>
* <dt>{@code theme}</dt>
* <dd>{@link Versions#themeVersion} is set to
* {@link ThemeVersion#DRAFT}.</dd>
* </dl>
*
*/
private Versions generateFromPreviewParam(final String value) {
if (value == null || value.isEmpty() || value.matches("\\s*")) {
return new Versions(ContentItemVersion.LIVE,
ThemeVersion.LIVE);
} else if ("all".equals(value.toLowerCase(Locale.ROOT))) {
return new Versions(ContentItemVersion.DRAFT,
ThemeVersion.DRAFT);
} else {
final Set<String> values = new HashSet<>();
Collections.addAll(values,
value.toLowerCase(Locale.ROOT).split(","));
final Versions result = new Versions();
if (values.contains("content")) {
result.setContentVersion(ContentItemVersion.DRAFT);
}
if (values.contains("theme")) {
result.setThemeVersion(ThemeVersion.DRAFT);
}
return result;
}
}
private void initPageUrlModel(final UriInfo uriInfo) {
pageUrlModel.setHost(uriInfo.getRequestUri().getHost());
pageUrlModel.setPath(uriInfo.getPath());
pageUrlModel.setPort(uriInfo.getRequestUri().getPort());
pageUrlModel.setProtocol(uriInfo.getRequestUri().getScheme());
pageUrlModel.setQueryParameters(
uriInfo
.getQueryParameters()
.entrySet()
.stream()
.collect(
Collectors.toMap(
entry -> entry.getKey(),
entry -> entry.getValue().get(0)
)
)
);
}
/**
* Encapsulate the result of converting the value of the {@code preview}
* query parameter.
*/
private class Versions {
/**
* Version of content to use
*/
private ContentItemVersion contentVersion;
/**
* Version of theme to use.
*/
private ThemeVersion themeVersion;
/**
* Creates a new {@code Versions} object with all fields set to
* {@code live} versions.
*/
public Versions() {
this.contentVersion = ContentItemVersion.LIVE;
this.themeVersion = ThemeVersion.LIVE;
}
/**
* Create a new {@code Versions} object with the provided parameters.
*
* @param contentVersion
* @param themeVersion
*/
public Versions(
final ContentItemVersion contentVersion,
final ThemeVersion themeVersion
) {
this.contentVersion = contentVersion;
this.themeVersion = themeVersion;
}
public ContentItemVersion getContentItemVersion() {
return contentVersion;
}
public void setContentVersion(final ContentItemVersion contentVersion) {
this.contentVersion = contentVersion;
}
public ThemeVersion getThemeVersion() {
return themeVersion;
}
public void setThemeVersion(final ThemeVersion themeVersion) {
this.themeVersion = themeVersion;
}
}
}