806 lines
30 KiB
Java
806 lines
30 KiB
Java
/*
|
|
* Copyright (C) 2017 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.dispatcher;
|
|
|
|
import com.arsdigita.bebop.PageState;
|
|
import com.arsdigita.cms.CMS;
|
|
import com.arsdigita.cms.dispatcher.CMSDispatcher;
|
|
import com.arsdigita.cms.dispatcher.CMSPage;
|
|
import com.arsdigita.cms.dispatcher.MasterPage;
|
|
import com.arsdigita.cms.ui.ContentItemPage;
|
|
import com.arsdigita.kernel.KernelConfig;
|
|
import com.arsdigita.util.Assert;
|
|
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
import org.libreccm.core.CcmObject;
|
|
import org.librecms.CmsConstants;
|
|
import org.librecms.contentsection.ContentItem;
|
|
import org.librecms.contentsection.ContentItemManager;
|
|
import org.librecms.contentsection.ContentItemRepository;
|
|
import org.librecms.contentsection.ContentItemVersion;
|
|
import org.librecms.contentsection.ContentSection;
|
|
import org.librecms.contentsection.Folder;
|
|
import org.librecms.contentsection.FolderManager;
|
|
import org.librecms.contentsection.FolderRepository;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.StringTokenizer;
|
|
|
|
import javax.enterprise.context.RequestScoped;
|
|
import javax.inject.Inject;
|
|
import javax.servlet.ServletException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.transaction.Transactional;
|
|
|
|
/**
|
|
* Resolves items to URLs and URLs to items for multiple language variants.
|
|
*
|
|
* For version 7.0.0 this call has been moved from the
|
|
* {@code com.arsdigita.cms.dispatcher} package to the
|
|
* {@code org.librecms.dispatcher} package and refactored to an CDI bean. This
|
|
* was necessary to avoid several problems when accessing the entity beans for
|
|
* {@link Category} and {@link ContentItem}, primarily the infamous
|
|
* {@code LazyInitializationException}. Also several checks for null parameters
|
|
* were added to avoid {@code NullPointerExcpetions}.
|
|
*
|
|
* <strong>
|
|
* AS of version 7.0.0 this class not longer part of the public API. It is left
|
|
* here to keep the changes to the UI classes as minimal as possible. For new
|
|
* code other methods, for example from the {@link ContentItemManager} or the
|
|
* {@link ContentItemRepository} should be used. Because this class is no longer
|
|
* part of the public API the will might be removed or changed in future
|
|
* releases without prior warning.
|
|
* </strong>
|
|
*
|
|
*
|
|
*
|
|
* @author <a href="mailto:mhanisch@redhat.com">Michael Hanisch</a>
|
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
|
*/
|
|
@RequestScoped
|
|
public class MultilingualItemResolver implements ItemResolver {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger(
|
|
MultilingualItemResolver.class);
|
|
|
|
private static final String ADMIN_PREFIX = "admin";
|
|
|
|
/**
|
|
* The string identifying an item's ID in the query string of a URL.
|
|
*/
|
|
protected static final String ITEM_ID = "item_id";
|
|
|
|
/**
|
|
* The separator used in URL query strings; should be either "&" or ";".
|
|
*/
|
|
protected static final String SEPARATOR = "&";
|
|
|
|
@Inject
|
|
private FolderRepository folderRepo;
|
|
|
|
@Inject
|
|
private FolderManager folderManager;
|
|
|
|
@Inject
|
|
private ContentItemRepository itemRepo;
|
|
|
|
@Inject
|
|
private ContentItemManager itemManager;
|
|
|
|
/**
|
|
* Returns a content item based on section, url, and use context.
|
|
*
|
|
* @param section The current content section
|
|
* @param itemUrl The section-relative URL
|
|
* @param context The use context, e.g. <code>ContentItem.LIVE</code>,
|
|
* <code>CMSDispatcher.PREVIEW</code> or
|
|
* <code>ContentItem.DRAFT</code>. See {@link
|
|
* #getCurrentContext}.
|
|
*
|
|
* @return The content item, or null if no such item exists
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public ContentItem getItem(final ContentSection section,
|
|
final String itemUrl,
|
|
final String context) {
|
|
if (section == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't get item from section null.");
|
|
}
|
|
if (itemUrl == null) {
|
|
throw new IllegalArgumentException("Can't get item for URL null.");
|
|
}
|
|
if (context == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't get item for context null.");
|
|
}
|
|
|
|
LOGGER.debug("Resolving the item in content section \"{}\" at URL "
|
|
+ "\"{}\" for context \"{}\"...",
|
|
section.getLabel(),
|
|
itemUrl,
|
|
context);
|
|
|
|
final Folder rootFolder = section.getRootDocumentsFolder();
|
|
String url = stripTemplateFromURL(itemUrl);
|
|
|
|
if (rootFolder == null) {
|
|
// nothing to do, if root folder is null
|
|
LOGGER.debug("The root folder is null; returning no item");
|
|
} else {
|
|
LOGGER.debug("Using root folder {}...", rootFolder.getName());
|
|
|
|
if (ContentItemVersion.LIVE.toString().equals(context)) {
|
|
LOGGER.debug("The use context is 'live'");
|
|
|
|
LOGGER.debug("The root folder has a live version; recursing");
|
|
|
|
final String prefix = String.join(
|
|
"",
|
|
section.getPrimaryUrl(),
|
|
folderManager.getFolderPath(rootFolder));
|
|
|
|
if (url.startsWith(prefix)) {
|
|
LOGGER.
|
|
debug("The starts with prefix \"{}\"; removing it...",
|
|
prefix);
|
|
|
|
url = url.substring(prefix.length());
|
|
}
|
|
|
|
final ContentItem item = getItemFromLiveURL(url, rootFolder);
|
|
|
|
LOGGER.debug("Resolved URL \"{}\" to item {}...",
|
|
url,
|
|
Objects.toString(item));
|
|
|
|
return item;
|
|
|
|
} else if (ContentItemVersion.DRAFT.toString().equals(context)) {
|
|
LOGGER.debug("The use context is 'draft'");
|
|
|
|
// For 'draft' items, 'generateUrl()' method returns
|
|
// URL like this
|
|
// '/acs/wcms/admin/item.jsp?item_id=10201&set_tab=1'
|
|
// Check if URL contains any match of string
|
|
// 'item_id', then try to instanciate item_id value
|
|
// and return FIXME: Please hack this if there is
|
|
// more graceful solution. [aavetyan]
|
|
if (Assert.isEnabled()) {
|
|
Assert.isTrue(url.contains(ITEM_ID),
|
|
String.format("url must contain parameter %s",
|
|
ITEM_ID));
|
|
}
|
|
|
|
final ContentItem item = getItemFromDraftURL(url);
|
|
|
|
LOGGER.debug("Resolved URL \"{}\" to item {}.",
|
|
url,
|
|
Objects.toString(item));
|
|
|
|
return item;
|
|
} else if (CMSDispatcher.PREVIEW.equals(context)) {
|
|
LOGGER.debug("The use context is 'preview'");
|
|
|
|
final String prefix = CMSDispatcher.PREVIEW + "/";
|
|
|
|
if (url.startsWith(prefix)) {
|
|
LOGGER.debug(
|
|
"The URL starts with prefix \"{}\"; removing it",
|
|
prefix);
|
|
|
|
url = url.substring(prefix.length());
|
|
}
|
|
|
|
final ContentItem item = getItemFromLiveURL(url, rootFolder);
|
|
|
|
LOGGER.debug("Resolved URL \"{}\" to item {}.",
|
|
url,
|
|
Objects.toString(item));
|
|
|
|
return item;
|
|
} else {
|
|
throw new IllegalArgumentException(String.format(
|
|
"Invalid item resolver context \"%s\".", context));
|
|
}
|
|
}
|
|
|
|
LOGGER.debug("No item resolved; returning null");
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Fetches the current context based on the page state.
|
|
*
|
|
* @param state the current page state
|
|
*
|
|
* @return the context of the current URL, such as
|
|
* <code>ContentItem.LIVE</code> or <code>ContentItem.DRAFT</code>
|
|
*
|
|
* @see ContentItem#LIVE
|
|
* @see ContentItem#DRAFT
|
|
*
|
|
* ToDo: Refactor to use the {@link ContentItemVersion} directly.
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public String getCurrentContext(final PageState state) {
|
|
LOGGER.debug("Getting the current context");
|
|
|
|
// XXX need to use Web.getWebContext().getRequestURL() here.
|
|
String url = state.getRequest().getRequestURI();
|
|
|
|
final ContentSection section = CMS.getContext().getContentSection();
|
|
|
|
// If this page is associated with a content section,
|
|
// transform the URL so that it is relative to the content
|
|
// section site node.
|
|
if (section != null) {
|
|
final String sectionURL = section.getPrimaryUrl();
|
|
|
|
if (url.startsWith(sectionURL)) {
|
|
url = url.substring(sectionURL.length());
|
|
}
|
|
}
|
|
|
|
// Remove any template-specific URL components (will only work
|
|
// if they're first in the URL at this point; verify). XXX but
|
|
// we don't actually verify?
|
|
url = stripTemplateFromURL(url);
|
|
|
|
// Determine if we are under the admin UI.
|
|
if (url.startsWith(ADMIN_PREFIX)
|
|
|| url.startsWith(CmsConstants.CONTENT_CENTER_URL)) {
|
|
return ContentItemVersion.DRAFT.toString();
|
|
} else {
|
|
return ContentItemVersion.LIVE.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a URL for a content item.
|
|
*
|
|
* @param itemId The item ID
|
|
* @param name The name of the content page
|
|
* @param state The page state
|
|
* @param section the content section to which the item belongs
|
|
* @param context the context of the URL, such as "live" or "admin"
|
|
*
|
|
* @return The URL of the item
|
|
*
|
|
* @see #getCurrentContext
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public String generateItemURL(final PageState state,
|
|
final Long itemId,
|
|
final String name,
|
|
final ContentSection section,
|
|
final String context) {
|
|
return generateItemURL(state, itemId, name, section, context, null);
|
|
}
|
|
|
|
/**
|
|
* Generates a URL for a content item.
|
|
*
|
|
* @param itemId The item ID
|
|
* @param name The name of the content page
|
|
* @param state The page state
|
|
* @param section the content section to which the item belongs
|
|
* @param context the context of the URL, such as "live" or "admin"
|
|
* @param templateContext the context for the URL, such as "public"
|
|
*
|
|
* @return The URL of the item
|
|
*
|
|
* @see #getCurrentContext
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public String generateItemURL(final PageState state,
|
|
final Long itemId,
|
|
final String name,
|
|
final ContentSection section,
|
|
final String context,
|
|
final String templateContext) {
|
|
if (itemId == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't generate item URL for item id null.");
|
|
}
|
|
if (context == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't generate item URL for context null.");
|
|
}
|
|
if (section == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't generate item URL for section null.");
|
|
}
|
|
|
|
LOGGER.debug("Generating an item URL for item id {}, section \"{}\" "
|
|
+ "and context \"{}\" with name \"{}\"...",
|
|
itemId,
|
|
section.getLabel(),
|
|
context,
|
|
name);
|
|
|
|
if (ContentItemVersion.DRAFT.toString().equals(context)) {
|
|
// No template context here.
|
|
return generateDraftURL(section, itemId);
|
|
} else if (CMSDispatcher.PREVIEW.equals(context)) {
|
|
final ContentItem item = itemRepo.findById(itemId).get();
|
|
return generatePreviewURL(section, item, templateContext);
|
|
} else if (ContentItemVersion.LIVE.toString().equals(context)) {
|
|
final ContentItem item = itemRepo.findById(itemId).get();
|
|
|
|
return generateLiveURL(section, item, templateContext);
|
|
} else {
|
|
throw new IllegalArgumentException("Unknown context '" + context
|
|
+ "'");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a URL for a content item.
|
|
*
|
|
* @param item The item
|
|
* @param state The page state
|
|
* @param section the content section to which the item belongs
|
|
* @param context the context of the URL, such as "live" or "admin"
|
|
*
|
|
* @return The URL of the item
|
|
*
|
|
* @see #getCurrentContext
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public String generateItemURL(final PageState state,
|
|
final ContentItem item,
|
|
final ContentSection section,
|
|
final String context) {
|
|
return generateItemURL(state, item, section, context, null);
|
|
}
|
|
|
|
/**
|
|
* Generates a URL for a content item.
|
|
*
|
|
* @param item The item
|
|
* @param state The page state
|
|
* @param section the content section to which the item belongs
|
|
* @param context the context of the URL, such as "live" or "admin"
|
|
* @param templateContext the context for the URL, such as "public"
|
|
*
|
|
* @return The URL of the item
|
|
*
|
|
* @see #getCurrentContext
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public String generateItemURL(final PageState state,
|
|
final ContentItem item,
|
|
final ContentSection section,
|
|
final String context,
|
|
final String templateContext) {
|
|
if (item == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't generate URL for item null.");
|
|
}
|
|
if (context == null) {
|
|
throw new IllegalArgumentException(
|
|
"Can't generate URL for context null.");
|
|
}
|
|
|
|
final ContentSection contentSection;
|
|
if (section == null) {
|
|
contentSection = item.getContentType().getContentSection();
|
|
} else {
|
|
contentSection = section;
|
|
}
|
|
|
|
LOGGER.debug("Generating an item URL for item \"{}\", section \"{}\" "
|
|
+ "and context \"{}\".",
|
|
item.getDisplayName(),
|
|
contentSection.getLabel(),
|
|
context);
|
|
|
|
if (ContentItemVersion.DRAFT.toString().equals(context)) {
|
|
return generateDraftURL(section, item.getObjectId());
|
|
} else if (CMSDispatcher.PREVIEW.equals(context)) {
|
|
return generatePreviewURL(section, item, templateContext);
|
|
} else if (ContentItemVersion.LIVE.toString().equals(context)) {
|
|
return generateLiveURL(contentSection, item, templateContext);
|
|
} else {
|
|
throw new IllegalArgumentException(String.format(
|
|
"Unknown context \"%s\".", context));
|
|
}
|
|
}
|
|
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
@Override
|
|
public CMSPage getMasterPage(final ContentItem item,
|
|
final HttpServletRequest request)
|
|
throws ServletException {
|
|
|
|
LOGGER.debug("Getting the master page for item {}",
|
|
item.getDisplayName());
|
|
|
|
final MasterPage masterPage = new MasterPage();
|
|
masterPage.init();
|
|
|
|
return masterPage;
|
|
}
|
|
|
|
/**
|
|
* Returns content item's draft version URL
|
|
*
|
|
* @param section The content section to which the item belongs
|
|
* @param itemId The content item's ID
|
|
*
|
|
* @return generated URL string
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected String generateDraftURL(final ContentSection section,
|
|
final Long itemId) {
|
|
if (LOGGER.isDebugEnabled()) {
|
|
LOGGER.debug("Generating draft URL for item ID " + itemId
|
|
+ " and section " + section);
|
|
}
|
|
|
|
if (Assert.isEnabled()) {
|
|
Assert.isTrue(section != null && itemId != null,
|
|
"get draft url: neither secion nor item "
|
|
+ "can be null");
|
|
}
|
|
|
|
final String url = ContentItemPage.getItemURL(
|
|
String.format("%s/", section.getPrimaryUrl()),
|
|
itemId,
|
|
ContentItemPage.AUTHORING_TAB);
|
|
|
|
if (LOGGER.isDebugEnabled()) {
|
|
LOGGER.debug("Generated draft URL " + url);
|
|
}
|
|
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* Generate a <em>language-independent</em> URL to the <code>item</code> in
|
|
* the given section.<p>
|
|
* When a client retrieves this URL, the URL is resolved to point to a
|
|
* specific language instance of the item referenced here, i.e. this URL
|
|
* will be resolved to a <em>language-specific</em> URL internally.
|
|
*
|
|
* @param section the <code>ContentSection</code> that contains this
|
|
* item
|
|
* @param item <code>ContentItem</code> for which a URL should be
|
|
* constructed.
|
|
* @param templateContext template context; will be ignored if
|
|
* <code>null</code>
|
|
*
|
|
* @return a <em>language-independent</em> URL to the <code>item</code> in
|
|
* the given <code>section</code>, which will be presented within
|
|
* the given <code>templateContext</code>
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected String generateLiveURL(final ContentSection section,
|
|
final ContentItem item,
|
|
final String templateContext) {
|
|
LOGGER.debug("Generating live URL for item {} in section {}",
|
|
Objects.toString(item),
|
|
Objects.toString(section));
|
|
|
|
/*
|
|
* URL = URL of section + templateContext + path to the ContentBundle
|
|
* which contains the item
|
|
*/
|
|
final StringBuffer url = new StringBuffer(400);
|
|
//url.append(section.getURL());
|
|
url
|
|
.append(section.getPrimaryUrl())
|
|
.append("/");
|
|
|
|
/*
|
|
* add template context, if one is given
|
|
*/
|
|
// This is breaking URL's...not sure why it's here. XXX
|
|
// this should work with the appropriate logic. trying again.
|
|
if (!(templateContext == null || templateContext.length() == 0)) {
|
|
url
|
|
.append(TEMPLATE_CONTEXT_PREFIX)
|
|
.append(templateContext)
|
|
.append("/");
|
|
}
|
|
|
|
url.append(itemManager.getItemPath(item));
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
/**
|
|
* Generate a URL which can be used to preview the <code>item</code>, using
|
|
* the given <code>templateContext</code>.<p>
|
|
* Only a specific language instance can be previewed, meaning there
|
|
* <em>no</em> language negotiation is involved when a request is made to a
|
|
* URL that has been generated by this method.
|
|
*
|
|
* @param section The <code>ContentSection</code> which contains the
|
|
* <code>item</code>
|
|
* @param item The <code>ContentItem</code> for which a URL
|
|
* should be generated.
|
|
* @param templateContext the context that determines which template should
|
|
* render the item when it is previewed; ignored if
|
|
* the argument given here is <code>null</code>
|
|
*
|
|
* @return a URL which can be used to preview the given <code>item</code>
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected String generatePreviewURL(final ContentSection section,
|
|
final ContentItem item,
|
|
final String templateContext) {
|
|
Assert.exists(section, "ContentSection section");
|
|
Assert.exists(item, "ContentItem item");
|
|
|
|
final StringBuffer url = new StringBuffer(100);
|
|
url
|
|
.append(section.getPrimaryUrl())
|
|
.append("/")
|
|
.append(CMSDispatcher.PREVIEW)
|
|
.append("/");
|
|
/*
|
|
* add template context, if one is given
|
|
*/
|
|
// This is breaking URL's...not sure why it's here. XXX
|
|
// this should work with the appropriate logic. trying again.
|
|
if (!(templateContext == null || templateContext.length() == 0)) {
|
|
url
|
|
.append(TEMPLATE_CONTEXT_PREFIX)
|
|
.append(templateContext)
|
|
.append("/");
|
|
}
|
|
|
|
url.append(itemManager.getItemPath(item));
|
|
|
|
return url.toString();
|
|
}
|
|
|
|
/**
|
|
* Retrieves <code>ITEM_ID</code> parameter from URL and instantiates item
|
|
* according to this ID.
|
|
*
|
|
* @param url URL that indicates which item should be retrieved; must
|
|
* contain the <code>ITEM_ID</code> parameter
|
|
*
|
|
* @return the <code>ContentItem</code> the given <code>url</code> points
|
|
* to, or <code>null</code> if no ID has been found in the
|
|
* <code>url</code>
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected ContentItem getItemFromDraftURL(final String url) {
|
|
LOGGER.debug("Looking up the item from draft URL ", url);
|
|
|
|
int pos = url.indexOf(ITEM_ID);
|
|
|
|
String item_id = url.substring(pos); // item_id == ITEM_ID=.... ?
|
|
|
|
pos = item_id.indexOf("="); // should be exactly after the ITEM_ID string
|
|
|
|
if (pos != ITEM_ID.length()) {
|
|
// item_id seems to be something like ITEM_IDFOO=
|
|
|
|
LOGGER.debug("No suitable item_id parameter found; returning null");
|
|
|
|
return null; // no ID found
|
|
}
|
|
|
|
pos++; // skip the "="
|
|
|
|
// ID is the string between the equal (at pos) and the next separator
|
|
int i = item_id.indexOf(SEPARATOR);
|
|
item_id = item_id.substring(pos, Math.min(i, item_id.length() - 1));
|
|
|
|
LOGGER.debug("Looking up item using item ID {}", item_id);
|
|
|
|
final Optional<ContentItem> item = itemRepo.findById(Long.parseLong(
|
|
item_id));
|
|
|
|
if (item.isPresent()) {
|
|
LOGGER.debug("Returning item {}", Objects.toString(item));
|
|
return item.get();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a content item based on URL relative to the root folder.
|
|
*
|
|
* @param url The content item url
|
|
* @param parentFolder The parent folder object, url must be relevant to it
|
|
*
|
|
* @return The Content Item instance, it can also be either Bundle or Folder
|
|
* objects, depending on URL and file language extension
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected ContentItem getItemFromLiveURL(final String url,
|
|
final Folder parentFolder) {
|
|
|
|
if (parentFolder == null || url == null || url.equals("")) {
|
|
LOGGER.debug("The url is null or parent folder was null "
|
|
+ "or something else is wrong, so just return "
|
|
+ "null.");
|
|
|
|
return null;
|
|
}
|
|
|
|
LOGGER.debug("Resolving the item for live URL {}"
|
|
+ " and parent folder {}...",
|
|
url,
|
|
parentFolder.getName());
|
|
|
|
int len = url.length();
|
|
int index = url.indexOf('/');
|
|
|
|
if (index >= 0) {
|
|
LOGGER.debug("The URL starts with a slash; paring off the first "
|
|
+ "URL element and recursing");
|
|
|
|
final String liveUrl = index + 1 < len ? url.substring(index + 1)
|
|
: "";
|
|
|
|
return getItemFromLiveURL(liveUrl, parentFolder);
|
|
} else {
|
|
LOGGER.debug("Found a file element in the URL");
|
|
|
|
final String[] nameAndLang = getNameAndLangFromURLFrag(url);
|
|
final String name = nameAndLang[0];
|
|
|
|
final Optional<ContentItem> item = itemRepo.findByNameInFolder(
|
|
parentFolder, name);
|
|
|
|
if (item.isPresent()) {
|
|
return item.get();
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns an array containing the the item's name and lang based on the URL
|
|
* fragment.
|
|
*
|
|
* @param url
|
|
*
|
|
* @return a two-element string array, the first element containing the
|
|
* bundle name, and the second element containing the lang string
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected String[] getNameAndLangFromURLFrag(final String url) {
|
|
String name;
|
|
String lang = null;
|
|
|
|
/*
|
|
* Try to find out if there's an extension with the language code
|
|
* 1 Get a list of all "extensions", i.e. parts of the url
|
|
* which are separated by colons
|
|
* 2 If one or more extensions have been found, compare them against
|
|
* the list of known language codes
|
|
* 2a if a match is found, this language is used to retrieve an instance
|
|
* from a bundle
|
|
* 2b if no match is found
|
|
*/
|
|
final List<String> exts = new ArrayList<>(5);
|
|
final StringTokenizer tok = new StringTokenizer(url, ".");
|
|
|
|
while (tok.hasMoreTokens()) {
|
|
exts.add(tok.nextToken());
|
|
}
|
|
|
|
if (exts.size() > 0) {
|
|
LOGGER.debug("Found some file extensions to look at; they "
|
|
+ "are {}",
|
|
exts);
|
|
|
|
/*
|
|
* We have found at least one extension, so we can
|
|
* proceed. Now try to find out if one of the
|
|
* extensions looks like a language code (we only
|
|
* support 2-letter language codes!).
|
|
* If so, use this as the language to look for.
|
|
*/
|
|
/*
|
|
* First element is the NAME of the item, not an extension!
|
|
*/
|
|
name = exts.get(0);
|
|
String ext;
|
|
final Collection<String> supportedLangs = KernelConfig.getConfig()
|
|
.getSupportedLanguages();
|
|
Iterator<String> supportedLangIt;
|
|
|
|
for (int i = 1; i < exts.size(); i++) {
|
|
ext = exts.get(i);
|
|
|
|
LOGGER.debug("Examining extension {}", ext);
|
|
|
|
/*
|
|
* Loop through all extensions, but discard the
|
|
* first one, which is the name of the item.
|
|
*/
|
|
if (ext != null && ext.length() == 2) {
|
|
/* Only check extensions consisting of 2
|
|
* characters.
|
|
*
|
|
* Compare current extension with known
|
|
* languages; if it matches, we've found the
|
|
* language we should use!
|
|
*/
|
|
supportedLangIt = supportedLangs.iterator();
|
|
while (supportedLangIt.hasNext()) {
|
|
if (ext.equals(supportedLangIt.next())) {
|
|
lang = ext;
|
|
LOGGER.debug("Found a match; using "
|
|
+ "language {}", lang);
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
LOGGER.debug("Discarding extension {}; it is too short",
|
|
ext);
|
|
}
|
|
}
|
|
} else {
|
|
LOGGER.debug("The file has no extensions; no language was encoded");
|
|
name = url; // no extension, so we just have a name here
|
|
lang = null; // no extension, so we cannot guess the language
|
|
}
|
|
|
|
LOGGER.debug("File name resolved to {}", name);
|
|
LOGGER.debug("File language resolved to {}", lang);
|
|
|
|
final String[] returnArray = new String[2];
|
|
returnArray[0] = name;
|
|
returnArray[1] = lang;
|
|
return returnArray;
|
|
}
|
|
|
|
/**
|
|
* Finds a language instance of a content item given the bundle, name, and
|
|
* lang string. Note: Because there not ContentBundles anymore this method
|
|
* simply returns the provided item.
|
|
*
|
|
* @param lang The lang string from the URL
|
|
* @param item The content bundle
|
|
*
|
|
* @return The negotiated lang instance for the current request.
|
|
*/
|
|
@Transactional(Transactional.TxType.REQUIRED)
|
|
protected ContentItem getItemFromLangAndBundle(final String lang,
|
|
final ContentItem item) {
|
|
return item;
|
|
}
|
|
|
|
}
|