Java tutorial
/******************************************************************************* * Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved. * * This file is part of the OpenWGA server platform. * * OpenWGA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * In addition, a special exception is granted by the copyright holders * of OpenWGA called "OpenWGA plugin exception". You should have received * a copy of this exception along with OpenWGA in file COPYING. * If not, see <http://www.openwga.com/gpl-plugin-exception>. * * OpenWGA 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenWGA in file COPYING. * If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package de.innovationgate.wgpublisher.url; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.httpclient.URIException; import org.apache.log4j.Logger; import de.innovationgate.utils.WGUtils; import de.innovationgate.webgate.api.WGAPIException; import de.innovationgate.webgate.api.WGArea; import de.innovationgate.webgate.api.WGContent; import de.innovationgate.webgate.api.WGDatabase; import de.innovationgate.webgate.api.WGDatabaseConnectListener; import de.innovationgate.webgate.api.WGDatabaseEvent; import de.innovationgate.webgate.api.WGDatabaseEventListener; import de.innovationgate.webgate.api.WGDocument; import de.innovationgate.webgate.api.WGDocumentKey; import de.innovationgate.webgate.api.WGLanguage; import de.innovationgate.webgate.api.WGLanguageChooser; import de.innovationgate.webgate.api.WGStructEntry; import de.innovationgate.webgate.api.WGStructEntryIterator; import de.innovationgate.wgpublisher.ManagedDBAttribute; import de.innovationgate.wgpublisher.WGACore; import de.innovationgate.wgpublisher.WGPDispatcher; import de.innovationgate.wgpublisher.lang.LanguageBehaviourTools; /** * Object that takes care of various tasks around the "title path URL" new in WGA 4.1 * <ul> * <li> Indicator that title path is active for a database, if it is present as database attribute * <li> Determine if a path is a title path URL * <li> Transforming normal titles to URL-compatible titles * </ul> */ public class TitlePathManager implements ManagedDBAttribute, WGDatabaseEventListener, WGDatabaseConnectListener { public static String SHARE_NAME_SPECIAL_CHARS = " _-?!(),.%&*#'"; public static String MODE_URL = "url"; public static String MODE_SHARE = "share"; public static String EXTDATA_TITLEPATH_CONFLICT = "titlepathconflict"; public class RemainingPathElementException extends Exception { private static final long serialVersionUID = 1L; private WGContent _content; private PathTitle _remainingElement; public RemainingPathElementException(WGContent content, PathTitle remainingElement) { _content = content; _remainingElement = remainingElement; } public WGContent getContent() { return _content; } public PathTitle getRemainingElement() { return _remainingElement; } } public class TitlePathRoot { public static final int TYPE_AREA = 1; public static final int TYPE_CONTENT = 2; private int _type; private WGDocumentKey _key; public TitlePathRoot(WGDocumentKey key) { _key = key; _type = key.getDocType(); } protected int getType() { return _type; } protected WGDocumentKey getKey() { return _key; } public WGDocument getDocument(WGDatabase db) throws WGAPIException { return db.getDocumentByKey(_key); } } public abstract class PathTitle { public static final String INDEX_DIVIDER = "~"; private Pattern _regexPattern; public abstract String getTitle(); public abstract String getNormalizedTitle(); public boolean matches(WGContent content) throws WGAPIException, UnsupportedEncodingException { Iterator contentTitles = getContentTitles(content).iterator(); while (contentTitles.hasNext()) { String normalizedContentTitle = normalizeTitle((String) contentTitles.next()); if (getNormalizedTitle().equals(normalizedContentTitle)) { if (getIndex() != -1) { if (content.getStructEntry().getSiblingIndex() == getIndex()) { return true; } } else { return true; } } } return false; } protected abstract List getContentTitles(WGContent content) throws WGAPIException; public boolean matchesWithWildcards(WGContent content) throws WGAPIException, UnsupportedEncodingException { Iterator contentTitles = getContentTitles(content).iterator(); while (contentTitles.hasNext()) { String title = (String) contentTitles.next(); if (matchesWithWildcards(title)) { return true; } } return false; } public boolean matchesWithWildcards(String title) { Pattern titlePattern = getRegexPattern(); return titlePattern.matcher(title).matches(); } private Pattern getRegexPattern() { if (_regexPattern == null) { String regexPattern = getTitle(); // Convert the * pattern to .* for regex regexPattern = WGUtils.strReplace(regexPattern, "*", ".*", true); // Escape all characters that have special meanings in RegEx regexPattern = WGUtils.strReplace(regexPattern, ".", "\\.", true); regexPattern = WGUtils.strReplace(regexPattern, "(", "\\(", true); regexPattern = WGUtils.strReplace(regexPattern, ")", "\\)", true); _regexPattern = Pattern.compile(regexPattern); } return _regexPattern; } public abstract int getIndex(); public String toString() { if (getIndex() != -1) { return getNormalizedTitle() + INDEX_DIVIDER + getIndex(); } else { return getNormalizedTitle(); } } /** * Indirection method that should redirect to normalizeURLTitle or normalizeJCRTitle, what is appropriate for the concrete PathTitle implementation * @param title * @return * @throws UnsupportedEncodingException */ public abstract String normalizeTitle(String title) throws UnsupportedEncodingException; } public class URLPathTitle extends PathTitle { private String _title; private int _index = -1; private String _normalizedTitle; public URLPathTitle(String title) { int tildePos = title.indexOf(INDEX_DIVIDER); if (tildePos != -1) { _title = title.substring(0, tildePos); _index = Integer.parseInt(title.substring(tildePos + 1)); } else { _title = title; } _normalizedTitle = normalizeTitle(_title); } public URLPathTitle(String title, int index) { _title = title; _index = index; _normalizedTitle = normalizeTitle(_title); } /* (non-Javadoc) * @see de.innovationgate.wgpublisher.url.PathTitle#getTitle() */ public String getTitle() { return _title; } public int getIndex() { return _index; } public String getNormalizedTitle() { return _normalizedTitle; } public String normalizeTitle(String title) { return normalizeURLTitle(title); } protected List getContentTitles(WGContent content) throws WGAPIException { return Collections.singletonList(content.getTitle()); } } public class SharePathTitle extends PathTitle { private String _title; private int _index = -1; private String _normalizedTitle; public SharePathTitle(String title) throws UnsupportedEncodingException { _title = title; _normalizedTitle = normalizeTitle(_title); } /* (non-Javadoc) * @see de.innovationgate.wgpublisher.url.PathTitle#getTitle() */ public String getTitle() { return _title; } public int getIndex() { return _index; } public String getNormalizedTitle() { return _normalizedTitle; } public String normalizeTitle(String title) throws UnsupportedEncodingException { return normalizeShareTitle(title); } protected List getContentTitles(WGContent content) throws WGAPIException { List titles = new ArrayList(); titles.add(content.getTitle()); if (content.getFileNames().size() > 0) { titles.add(content.getFileNames().get(0)); } return titles; } } public class TitlePath { private TitlePathRoot _root; private String _rootKey; private String _mediaKey; private String _structKey = null; private String _language = null; private List<String> _titles; private List<String> _encodedTitles; public TitlePathRoot getTrigger() { return _root; } public String getMediaKey() { return _mediaKey; } public List<String> getTitles() { return _titles; } public void setRoot(TitlePathRoot trigger) { _root = trigger; } public void setMediaKey(String mediaKey) { _mediaKey = mediaKey; } public void setTitles(List<String> titles) { _titles = titles; _encodedTitles = new ArrayList(); for (String title : titles) { try { _encodedTitles.add(_core.getURLEncoder().encodePathPart(title)); } catch (URIException e) { _core.getLog().error("Exception encoding URL titles", e); } } } public String getPath() { if (_structKey != null) { return _structKey; } else { StringBuffer path = new StringBuffer(); path.append(getRootKey().toString()).append("/"); path.append(WGUtils.serializeCollection(_titles, "/")); return path.toString(); } } public String getLanguage() { return _language; } protected void setLanguage(String language) { _language = language; } public String getRootKey() { return _rootKey; } public void setRootKey(String rootKey) { _rootKey = rootKey; } public String getStructKey() { return _structKey; } public void setStructKey(String structKey) { _structKey = structKey; } public List<String> getEncodedTitles() { return _encodedTitles; } } private Map triggerNames = new HashMap(); private WGACore _core; private String _shortcutArea; private boolean _generateTitlePathURLs = false; private boolean _allowAllCharacters = true; private boolean _allowMixedLang = false; private boolean _contentIndexing; private WGDatabase _db; private boolean _allowUmlaute; private boolean _includeKeys; private boolean _useStructKeysInPath; public PathTitle parseURLPathTitle(String title) throws UnsupportedEncodingException { return new URLPathTitle(title); } public PathTitle parseSharePathTitle(String title) throws UnsupportedEncodingException { return new SharePathTitle(title); } public TitlePathManager(WGDatabase db, WGACore core, boolean generateTitlePathURLs) throws WGAPIException, UnsupportedEncodingException { _core = core; _shortcutArea = (String) db.getAttribute(WGACore.DBATTRIB_TITLEPATHURL_SHORTCUTAREA); _allowAllCharacters = db.getBooleanAttribute(WGACore.DBATTRIB_SHARE_ALLOWALLCHARS, true); _allowMixedLang = db.getBooleanAttribute(WGACore.DBATTRIB_TITLEPATHURL_MIXEDLANGUAGES, false); _generateTitlePathURLs = generateTitlePathURLs; _contentIndexing = db.getBooleanAttribute(WGACore.DBATTRIB_TITLEPATHURL_CONTENTINDEXING, false); _includeKeys = db.getBooleanAttribute(WGACore.DBATTRIB_TITLEPATHURL_INCLUDEKEYS, false); _useStructKeysInPath = db.getBooleanAttribute(WGACore.DBATTRIB_TITLEPATHURL_USESTRUCTKEYS, false); _allowUmlaute = db.getBooleanAttribute(WGACore.DBATTRIB_TITLEPATHURL_ALLOW_UMLAUTE, false); _db = db; if (_shortcutArea != null && db.getArea(_shortcutArea) == null) { // this warning should only logged once. _core.getLog().warn("Title path URL shortcut area '" + _shortcutArea + "' does not (yet) exist in database " + db.getDbReference()); } db.addDatabaseEventListener(this); if (generateTitlePathURLs) { if (db.isConnected()) { updateRootNames(); } else { db.addDatabaseConnectListener(this); } } } public void close() { } public void databaseUpdate(WGDatabaseEvent event) { try { WGDatabase db = event.getDatabase(); if (event.getEditedDocument() == null) { updateRootNames(); } else if (event.getEditedDocument() instanceof WGArea) { updateRootNames(); } else if (_shortcutArea != null) { if (event.getEditedDocument() instanceof WGStructEntry) { WGStructEntry entry = (WGStructEntry) event.getEditedDocument(); if (entry.isRoot() && entry.getArea().getName().equals(_shortcutArea)) { updateRootNames(); } } else if (event.getEditedDocument() instanceof WGContent) { WGContent content = (WGContent) event.getEditedDocument(); WGStructEntry entry = content.getStructEntry(); if (entry.isRoot() && entry.getArea().getName().equals(_shortcutArea)) { updateRootNames(); } } } } catch (Exception e) { Logger.getLogger("wga.titlepath").error("Error updating title path manager", e); } } private void updateRootNames() throws WGAPIException, UnsupportedEncodingException { Map newNames = new HashMap(); Iterator namesIt = _db.getAreas().keySet().iterator(); while (namesIt.hasNext()) { String name = (String) namesIt.next(); newNames.put(normalizeURLTitle(name), new TitlePathRoot(_db.getArea(name).getDocumentKeyObj())); } // Eventually parse shortcut area if (_shortcutArea != null) { parseShortcutArea(_db, newNames); } triggerNames = newNames; } private void parseShortcutArea(WGDatabase db, Map newNames) throws WGAPIException, UnsupportedEncodingException { WGArea area = db.getArea(_shortcutArea); if (area == null) { return; } Iterator rootStructs = area.getRootEntries().iterator(); while (rootStructs.hasNext()) { WGStructEntry root = (WGStructEntry) rootStructs.next(); Iterator contents = root.getContentSet(false).getReleasedContent().values().iterator(); while (contents.hasNext()) { WGContent content = (WGContent) contents.next(); newNames.put(normalizeURLTitle(content.getTitle()) + "/" + content.getLanguage().getName(), new TitlePathRoot(content.getDocumentKeyObj())); newNames.put(normalizeURLTitle(content.getTitle()), new TitlePathRoot(content.getDocumentKeyObj())); } } } public String normalizeURLTitle(String title) { title = title.toLowerCase(); StringBuffer name = new StringBuffer(); for (int i = 0; i < title.length(); i++) { char c = title.charAt(i); // Modified chars if (c == ' ') { name.append(_includeKeys ? '-' : '_'); // In openwga 7.5.1 we changed this from '_' to '-'. But we only do it if keys are included to avoid 404s from search engines. } else if (c == '' && !_allowUmlaute) { name.append("ae"); } else if (c == '' && !_allowUmlaute) { name.append("oe"); } else if (c == '' && !_allowUmlaute) { name.append("ue"); } else if (c == '' && !_allowUmlaute) { name.append("ss"); } // Unmodified chars else if (Character.isLetterOrDigit(c)) { name.append(c); } else if (c == '-') { name.append(c); } else if (c == '_') { name.append(c); } // All other chars are omitted } try { return _core.getURLEncoder().encodePathPart(name.toString()); } catch (Exception e) { throw new RuntimeException(e); } } public String normalizeShareTitle(String title) throws UnsupportedEncodingException { // Need to "normalize" String cause MacOS-DAV uses "decomposed" UTF-8 which breaks strings on database storage // title = com.ibm.icu.text.Normalizer.compose(title, true); title = WGUtils.normalizeUnicode(title); StringBuffer name = new StringBuffer(); for (int i = 0; i < title.length(); i++) { char c = title.charAt(i); // Unmodified chars if (Character.isLetterOrDigit(c)) { name.append(c); } else if (_allowAllCharacters || SHARE_NAME_SPECIAL_CHARS.indexOf(c) != -1) { name.append(c); } else { name.append('?'); } } return name.toString(); } public boolean isTemporary() { return false; } public TitlePath parseTitlePathURL(List path) throws UnsupportedEncodingException, WGAPIException { // Look if minimum path length is reached if (path.size() < 1) { return null; } // Look if we have a valid URLID on the last element String lastElement = (String) path.get(path.size() - 1); WGPDispatcher.URLID urlid = new WGPDispatcher.URLID(lastElement, _db); if (urlid.getSuffix() == null || _core.getMediaKey(urlid.getSuffix()) == null) { return null; } if (urlid.isCompleteFormat()) { WGLanguage lang = _db.getLanguage(urlid.getLanguage()); if (lang == null) { urlid.setCompleteFormat(false); } } // Look if an extra key is provided. If so we must skip the whole rest, as there is no need for a (valid) trigger title if (urlid.getResourceExtraKey() != null) { TitlePath url = new TitlePath(); url.setMediaKey(urlid.getSuffix()); url.setStructKey(urlid.getResourceExtraKey()); url.setTitles(path); if (urlid.isCompleteFormat()) { url.setLanguage(urlid.getLanguage()); } return url; } // Look if first element matches a trigger, first language qualified (contents of shortcut URLs) then unqualified String triggerText; if (path.size() == 1) { triggerText = urlid.getResourceId(); } else { triggerText = (String) path.get(0); } TitlePathRoot trigger = null; if (urlid.getLanguage() != null && path.size() == 1) { trigger = (TitlePathRoot) triggerNames.get(normalizeURLTitle(triggerText) + "/" + urlid.getLanguage()); } else trigger = (TitlePathRoot) triggerNames.get(normalizeURLTitle(triggerText)); if (trigger == null) { return null; } // We have found a non-content trigger but have no further path to specify: This is no titlepath URL if (path.size() == 1 && trigger.getType() != WGDocument.TYPE_CONTENT) { return null; } // So we have a title path URL. Now we collect the data TitlePath url = new TitlePath(); url.setRoot(trigger); url.setRootKey(triggerText); url.setMediaKey(urlid.getSuffix()); url.setStructKey(urlid.getResourceExtraKey()); if (urlid.isCompleteFormat()) { url.setLanguage(urlid.getLanguage()); } // Build list of pure document titles, excluding area and mediaKey/language suffix List docTitles = new ArrayList(); if (path.size() > 1) { if (path.size() > 2) { docTitles.addAll(path.subList(1, path.size() - 1)); } docTitles.add(urlid.getResourceId()); } url.setTitles(docTitles); return url; } public static WGContent findContent(PathTitle title, WGDocument parent, WGLanguageChooser chooser) throws WGAPIException, UnsupportedEncodingException { WGStructEntryIterator entries = fetchCandidateEntriesIterator(parent); WGContent foundContent = null; while (entries.hasNext()) { WGStructEntry entry = (WGStructEntry) entries.next(); WGContent content = chooser.selectContentForPage(entry, false); if (content != null) { if (title.matches(content)) { foundContent = content; break; } } } return foundContent; } public PathTitle createPathTitle(WGContent content) throws WGAPIException { String title = content.getTitle(); String normalizedTitle = normalizeURLTitle(title); int index = -1; if (_contentIndexing) { // Search if there are other contents on earlier siblings with identical // title // If so we must add the sibling index to the path title to make it // unique WGContent prevContent = content.getPreviousContent(); while (prevContent != null) { if (normalizeURLTitle(prevContent.getTitle()).equals(normalizedTitle)) { index = content.getStructEntry().getSiblingIndex(); break; } prevContent = prevContent.getPreviousContent(); } } return new URLPathTitle(title, index); } protected String getShortcutArea() { return _shortcutArea; } public WGContent findContentByTitlePath(TitlePath titlePathURL, WGDatabase database, WGLanguageChooser chooser, String mode) throws WGAPIException, UnsupportedEncodingException, RemainingPathElementException { // If we have a struct key (by tilde suffix) we just will load the doc by its content key if (titlePathURL.getStructKey() != null) { WGStructEntry entry = database .getStructEntryByKey(database.parseStructKey(titlePathURL.getStructKey())); if (entry == null) { // may be a sequence? try { long seq = Long.parseLong(titlePathURL.getStructKey(), 16); entry = database.getStructEntryBySequence(seq); } catch (Exception e) { // may be unable to parse structkey as long. Ignore any errors here. } } if (entry != null) { WGContent content = LanguageBehaviourTools.getRelevantContent(entry, titlePathURL.getLanguage(), false); if (content != null) { return content; } } // We may not use the other information. We would eventually land on the wrong document by titlepaths. return null; } // Find trigger document WGDocument triggerDoc = titlePathURL.getTrigger().getDocument(database); if (triggerDoc == null) { return null; } // No further path. Return trigger document (if it can be used) if (titlePathURL.getTitles().isEmpty()) { if (triggerDoc instanceof WGContent) { return (WGContent) triggerDoc; } else { return null; } } // Otherwise find entries by titles, relative to the trigger doc: Mode JCR else if (mode.equals(MODE_SHARE)) { return findSharePathContent(titlePathURL, chooser, mode, triggerDoc); } // Mode URL else { return findURLPathContent(titlePathURL, 0, chooser, triggerDoc); } } /** * Recursive variant to parse an Title Path URL. We must try each and every possible variant, so we must proceed recursively until we find a complete match * @param pathTitles * @param index * @param acceptedLanguages * @param parent * @return * @throws WGAPIException * @throws UnsupportedEncodingException * @throws RemainingPathElementException */ private WGContent findURLPathContent(TitlePath titlePath, int index, WGLanguageChooser chooser, WGDocument parent) throws WGAPIException, UnsupportedEncodingException, RemainingPathElementException { PathTitle pathTitle = parseURLPathTitle((String) titlePath.getTitles().get(index)); WGContent foundContent = null; // First try: Search for specific language if available if (titlePath.getLanguage() != null) { WGStructEntryIterator entries = fetchCandidateEntriesIterator(parent); while (entries.hasNext()) { WGStructEntry entry = (WGStructEntry) entries.next(); // If language available: Try the addressed language from the path WGContent content = null; content = LanguageBehaviourTools.getRelevantContent(entry, titlePath.getLanguage(), false); if (content != null && pathTitle.matches(content)) { WGContent descent = descendOnContent(titlePath, index, chooser, content); if (descent != null) { return descent; } } } } // Second try: Search all languages, if no language given or we may have mixed paths if ((_allowMixedLang && index != titlePath.getTitles().size() - 1) || titlePath.getLanguage() == null) { WGStructEntryIterator entries = fetchCandidateEntriesIterator(parent); while (entries.hasNext()) { WGStructEntry entry = (WGStructEntry) entries.next(); // Try all languages, priorized by language chooser for (WGContent content : chooser.selectContentPriorityOrder(entry, false)) { if (pathTitle.matches(content)) { WGContent descent = descendOnContent(titlePath, index, chooser, content); if (descent != null) { return descent; } } } } } // Nothing found on this sub-path. Calling methods should try remaining paths return null; } private static WGStructEntryIterator fetchCandidateEntriesIterator(WGDocument parent) throws WGAPIException { WGStructEntryIterator entries = null; if (parent instanceof WGArea) { entries = ((WGArea) parent).getRootEntryIterator(10); } else if (parent instanceof WGStructEntry) { entries = ((WGStructEntry) parent).getChildEntryIterator(10); } else if (parent instanceof WGContent) { entries = ((WGContent) parent).getStructEntry().getChildEntryIterator(10); } return entries; } private WGContent descendOnContent(TitlePath titlePath, int index, WGLanguageChooser chooser, WGContent content) throws WGAPIException, UnsupportedEncodingException, RemainingPathElementException { // We have a match. Look if the path goes further and we must recurse deeper if (index < (titlePath.getTitles().size() - 1)) { return findURLPathContent(titlePath, index + 1, chooser, content.getStructEntry()); } // Else we have found a matching content for the complete path and we return it else { return content; } } private WGContent findSharePathContent(TitlePath titlePathURL, WGLanguageChooser chooser, String mode, WGDocument triggerDoc) throws UnsupportedEncodingException, WGAPIException, RemainingPathElementException { WGContent pathContent = null; Iterator pathTitles = titlePathURL.getTitles().iterator(); WGDocument parent = null; while (pathTitles.hasNext()) { PathTitle pathTitle = parseSharePathTitle((String) pathTitles.next()); if (pathContent != null) { parent = pathContent.getStructEntry(); } else { parent = triggerDoc; } WGContent newContent = TitlePathManager.findContent(pathTitle, parent, chooser); if (newContent == null) { // In JCR mode the last element may address a JCR property. We throw a RemainingPathElementException then if (pathTitles.hasNext()) { pathContent = null; } else { throw new RemainingPathElementException(pathContent, pathTitle); } break; } else { pathContent = newContent; } } return pathContent; } public boolean isGenerateTitlePathURLs() { return _generateTitlePathURLs; } public void databaseConnected(WGDatabaseEvent event) { try { updateRootNames(); } catch (Exception e) { _core.getLog().error("Exception updating root names for title path manager", e); } } public void databaseConnectionError(WGDatabaseEvent event) { } public List<String> buildTitlePath(WGContent baseContent, String mediaKey, WGLanguageChooser chooser) throws WGAPIException { List<String> path = new ArrayList<String>(); // Build a path of all titles up to the root content WGContent content = baseContent; while (true) { // Title path conflict marked. We cannot use titlepath if (Boolean.TRUE.equals(content.getExtensionData(TitlePathManager.EXTDATA_TITLEPATH_CONFLICT))) { path = null; break; } String title = createPathTitle(content).toString(); // Add the language and media key suffix if we are on first level String language = content.getLanguage().getName(); if (!LanguageBehaviourTools.isMultiLanguageDB(content.getDatabase()) && language.equals(content.getDatabase().getDefaultLanguage())) { language = WGPDispatcher.DEFAULT_LANGUAGE_TOKEN; } if (content == baseContent) { String customTitlepath = (String) content.getMetaData("titlepath"); boolean hasCustomTitlepath = (customTitlepath != null && !customTitlepath.equals("")); StringBuffer baseContentSuffix = new StringBuffer(); if (_includeKeys) { if (hasCustomTitlepath) { title = normalizeURLTitle(customTitlepath); } if (_useStructKeysInPath) baseContentSuffix.append("~").append(String.valueOf(content.getStructKey())); else { // use page sequence as key long seq = content.getStructEntry().getPageSequence(); if (seq != 0) baseContentSuffix.append("~").append(Long.toHexString(seq)); else baseContentSuffix.append("~").append(String.valueOf(content.getStructKey())); } } baseContentSuffix.append(".").append(language).append(".").append(mediaKey); title += baseContentSuffix.toString(); // if custom titlepath is set, we are finished: if (hasCustomTitlepath && _includeKeys) { path.add(title); break; } } path.add(title); if (content.getStructEntry().isRoot()) { break; } // Go up in hierarchy // If we cannot build a complete hierarchy path (b.c. of no released contents on a node) we must cancel the title path creation WGContent parentContent = content.getParentContent(false); // Fallback to a language chosen by the language chooser if (parentContent == null && _allowMixedLang && chooser != null) { parentContent = chooser.selectContentForPage(content.getStructEntry().getParentEntry(), false); } // Cannot find parent content bc. no valid language. We cannot use titlepath. if (parentContent == null) { path = null; break; } content = parentContent; } if (path != null) { // Add area if the content is not a member of the shortcut area String areaName = baseContent.getStructEntry().getArea().getName(); if (getShortcutArea() == null || !getShortcutArea().equals(areaName)) { path.add(normalizeURLTitle(areaName)); } Collections.reverse(path); } return path; } public boolean isTitlePathAllowed(WGContent content) throws WGAPIException { // Deactivated title path if (!isGenerateTitlePathURLs()) { return false; } // Non-released content if (!content.getStatus().equals(WGContent.STATUS_RELEASE)) { return false; } return true; } public boolean isIncludeKeys() { return _includeKeys; } }