Path.java
/*
* #%L
* wcm.io
* %%
* Copyright (C) 2018 wcm.io
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
package io.wcm.wcm.commons.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.tenant.Tenant;
import org.jetbrains.annotations.NotNull;
import org.osgi.annotation.versioning.ProviderType;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
/**
* Handling of paths and absolute parents in AEM.
* <p>
* The methods implement special handling for AEM features:
* </p>
* <ul>
* <li>Side-by-side version comparison (at <code>/tmp/versionhistory</code> or
* <code>/content/versionhistory</code>)</li>
* <li>Launches (at <code>/content/launches</code>)</li>
* </ul>
* Paths starting with one of these special paths are treated in a special way so code relying on the original path
* structure still works.
*/
@ProviderType
public final class Path {
// VERSION_HISTORY_PATH is used since AEM 6.5, AEM 6.4.3, AEM 6.3.3.2, LEGACY_VERSION_HISTORY_PATH in the versions before
private static final String VERSION_HISTORY_PATH = "/tmp/versionhistory";
private static final String LEGACY_VERSION_HISTORY_PATH = "/content/versionhistory";
private static final String LAUNCHES_PATH = "/content/launches";
private static final String EXPERIENCE_FRAGMENTS_PATH = "/content/experience-fragments";
private static final Pattern VERSION_HISTORY_PATTERN = Pattern.compile(VERSION_HISTORY_PATH + "/[^/]+/[^/]+(/.*)?");
private static final Pattern LEGACY_VERSION_HISTORY_PATTERN = Pattern.compile(LEGACY_VERSION_HISTORY_PATH + "/[^/]+(/.*)?");
private static final Pattern LEGACY_VERSION_HISTORY_TENANT_PATTERN = Pattern.compile(LEGACY_VERSION_HISTORY_PATH + "/[^/]+/[^/]+(/.*)?");
private static final Pattern LAUNCHES_PATTERN = Pattern.compile(LAUNCHES_PATH + "/\\d+/\\d+/\\d+/[^/]+(/.*)?");
private static final Pattern EXPERIENCE_FRAGMENTS_PATTERN = Pattern.compile("^" + EXPERIENCE_FRAGMENTS_PATH + "/.*$");
private static final Pattern EDITABLE_TEMPLATE_PATTERN = Pattern.compile("^/conf/.*/settings/wcm/templates/.*$");
private Path() {
// static methods only
}
/**
* Get absolute parent of given path.
* If the path is a version history or launch path the path level is adjusted accordingly.
* This is a replacement for {@link com.day.text.Text#getAbsoluteParent(String, int)}.
* @param path Path
* @param parentLevel Parent level
* @param resourceResolver Resource resolver
* @return Absolute parent path or empty string if path is invalid
*/
public static String getAbsoluteParent(@NotNull String path, int parentLevel, @NotNull ResourceResolver resourceResolver) {
if (parentLevel < 0) {
return "";
}
int level = parentLevel + getAbsoluteLevelOffset(path, resourceResolver);
return Text.getAbsoluteParent(path, level);
}
/**
* Get absolute parent of given path.
* If the path is a version history or launch path the path level is adjusted accordingly.
* This is a replacement for {@link Page#getAbsoluteParent(int)}.
* @param page Page
* @param parentLevel Parent level
* @param resourceResolver Resource resolver
* @return Absolute parent page or null if path is invalid
*/
@SuppressWarnings("null")
public static Page getAbsoluteParent(@NotNull Page page, int parentLevel, @NotNull ResourceResolver resourceResolver) {
PageManager pageManager = resourceResolver.adaptTo(PageManager.class);
String absoluteParentPath = getAbsoluteParent(page.getPath(), parentLevel, resourceResolver);
if (StringUtils.isEmpty(absoluteParentPath)) {
return null;
}
return pageManager.getPage(absoluteParentPath);
}
/**
* Gets level from parent use same logic (but reverse) as {@link #getAbsoluteParent(Page, int, ResourceResolver)}.
* If the path is a version history or launch path the original path is returned.
* @param path Path
* @param resourceResolver Resource resolver
* @return level >= 0 if path is valid, -1 if path is invalid
*/
public static int getAbsoluteLevel(@NotNull String path, @NotNull ResourceResolver resourceResolver) {
if (StringUtils.isEmpty(path) || StringUtils.equals(path, "/")) {
return -1;
}
String originalPath = getOriginalPath(path, resourceResolver);
return StringUtils.countMatches(originalPath, "/") - 1;
}
/**
* Resolve original path if the path is a version history or launch path.
* If the path does not point to any of these locations it is returned unchanged.
* @param path Path
* @param resourceResolver Resource resolver
* @return Path that is not a version history or launch path
*/
public static String getOriginalPath(@NotNull String path, @NotNull ResourceResolver resourceResolver) {
if (StringUtils.isEmpty(path)) {
return null;
}
Matcher versionHistoryMatcher = VERSION_HISTORY_PATTERN.matcher(path);
if (versionHistoryMatcher.matches()) {
return "/content" + versionHistoryMatcher.group(1);
}
Matcher legacyVersionHistoryMatcher = LEGACY_VERSION_HISTORY_PATTERN.matcher(path);
if (legacyVersionHistoryMatcher.matches()) {
if (isTenant(resourceResolver)) {
legacyVersionHistoryMatcher = LEGACY_VERSION_HISTORY_TENANT_PATTERN.matcher(path);
}
if (legacyVersionHistoryMatcher.matches()) {
return "/content" + legacyVersionHistoryMatcher.group(1);
}
}
Matcher launchesMatcher = LAUNCHES_PATTERN.matcher(path);
if (launchesMatcher.matches()) {
return launchesMatcher.group(1);
}
return path;
}
/**
* Calculates offset for absolute level if path is a version history or launch path.
* @param path Path
* @param resourceResolver Resource resolver
* @return 0 or offset if a version history or launch path.
*/
private static int getAbsoluteLevelOffset(@NotNull String path, @NotNull ResourceResolver resourceResolver) {
Matcher versionHistoryMatcher = VERSION_HISTORY_PATTERN.matcher(path);
if (versionHistoryMatcher.matches()) {
return 3;
}
Matcher legacyVersionHistoryMatcher = LEGACY_VERSION_HISTORY_PATTERN.matcher(path);
if (legacyVersionHistoryMatcher.matches()) {
if (isTenant(resourceResolver)) {
legacyVersionHistoryMatcher = LEGACY_VERSION_HISTORY_TENANT_PATTERN.matcher(path);
if (legacyVersionHistoryMatcher.matches()) {
return 3;
}
}
return 2;
}
Matcher launchesMatcher = LAUNCHES_PATTERN.matcher(path);
if (launchesMatcher.matches()) {
return 6;
}
return 0;
}
/**
* Checks if a tenant is active for the current user.
* @param resourceResolver Resource resolver
* @return true if tenant is active
*/
private static boolean isTenant(@NotNull ResourceResolver resourceResolver) {
Tenant tenant = resourceResolver.adaptTo(Tenant.class);
return tenant != null;
}
/**
* @param path Content path
* @return true if content path is inside experience fragements path.
*/
public static boolean isExperienceFragmentPath(@NotNull String path) {
return EXPERIENCE_FRAGMENTS_PATTERN.matcher(path).matches();
}
/**
* @param path Content path
* @return true if content path is inside an editable template definition.
*/
public static boolean isEditableTemplatePath(@NotNull String path) {
return EDITABLE_TEMPLATE_PATTERN.matcher(path).matches();
}
}