blob: 6a565b4eca69e4a7102b0aaff2eb2e11f94cd604 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.sling.resourceresolver.impl;
import static org.apache.commons.lang3.StringUtils.defaultString;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.adapter.annotations.Adaptable;
import org.apache.sling.adapter.annotations.Adapter;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.adapter.SlingAdaptable;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.NonExistingResource;
import org.apache.sling.api.resource.PersistenceException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceNotFoundException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.mapping.PathToUriMappingService;
import org.apache.sling.api.resource.mapping.ResourceMapper;
import org.apache.sling.resourceresolver.impl.helper.RedirectResource;
import org.apache.sling.resourceresolver.impl.helper.ResourceIteratorDecorator;
import org.apache.sling.resourceresolver.impl.helper.ResourcePathIterator;
import org.apache.sling.resourceresolver.impl.helper.ResourceResolverContext;
import org.apache.sling.resourceresolver.impl.helper.ResourceResolverControl;
import org.apache.sling.resourceresolver.impl.helper.StarResource;
import org.apache.sling.resourceresolver.impl.helper.URI;
import org.apache.sling.resourceresolver.impl.helper.URIException;
import org.apache.sling.resourceresolver.impl.mapping.MapEntry;
import org.apache.sling.resourceresolver.impl.mapping.ResourceMapperImpl;
import org.apache.sling.resourceresolver.impl.params.ParsedParameters;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Adaptable(adaptableClass = ResourceResolver.class, adapters = { @Adapter(Session.class), @Adapter(ResourceMapper.class) })
public class ResourceResolverImpl extends SlingAdaptable implements ResourceResolver {
/** Default logger */
private static final Logger logger = LoggerFactory.getLogger(ResourceResolverImpl.class);
private static final Map<String, String> EMPTY_PARAMETERS = Collections.emptyMap();
public static final String PROP_REDIRECT_INTERNAL = "sling:internalRedirect";
public static final String PROP_ALIAS = "sling:alias";
// The suffix of a resource being a content node of some parent
// such as nt:file. The slash is included to prevent false
// positives for the String.endsWith check for names like
// "xyzjcr:content"
public static final String JCR_CONTENT_LEAF = "/jcr:content";
/** The factory which created this resource resolver. */
private final CommonResourceResolverFactoryImpl factory;
/** Resource resolver control. */
private final ResourceResolverControl control;
/** Resource resolver context. */
private final ResourceResolverContext context;
private volatile Exception closedResolverException;
public ResourceResolverImpl(final CommonResourceResolverFactoryImpl factory, final boolean isAdmin, final Map<String, Object> authenticationInfo) throws LoginException {
this(factory, isAdmin, authenticationInfo, factory.getResourceProviderTracker());
}
ResourceResolverImpl(final CommonResourceResolverFactoryImpl factory, final boolean isAdmin, final Map<String, Object> authenticationInfo, final ResourceProviderStorageProvider resourceProviderTracker) throws LoginException {
this.factory = factory;
this.context = new ResourceResolverContext(this, factory.getResourceAccessSecurityTracker());
this.control = createControl(resourceProviderTracker, authenticationInfo, isAdmin);
this.factory.register(this, control);
}
/**
* Constructor for cloning the resource resolver
* @param resolver The resolver to clone
* @param authenticationInfo The auth info
* @throws LoginException if auth to a required provider fails
*/
private ResourceResolverImpl(final ResourceResolverImpl resolver, final Map<String, Object> authenticationInfo) throws LoginException {
this.factory = resolver.factory;
Map<String, Object> authInfo = new HashMap<>();
if (resolver.control.getAuthenticationInfo() != null) {
authInfo.putAll(resolver.control.getAuthenticationInfo());
}
if (authenticationInfo != null) {
authInfo.putAll(authenticationInfo);
}
authInfo.put(ResourceProvider.AUTH_CLONE, true);
this.context = new ResourceResolverContext(this, factory.getResourceAccessSecurityTracker());
this.control = createControl(factory.getResourceProviderTracker(), authInfo, resolver.control.isAdmin());
this.factory.register(this, control);
}
/**
* Create the resource resolver control
* @param storage The provider storage
* @param authenticationInfo Current auth info
* @param isAdmin Is this admin?
* @return A control
* @throws LoginException If auth to the required providers fails.
*/
private ResourceResolverControl createControl(final ResourceProviderStorageProvider resourceProviderTracker,
final Map<String, Object> authenticationInfo,
final boolean isAdmin)
throws LoginException {
final ResourceResolverControl control = new ResourceResolverControl(isAdmin, authenticationInfo, resourceProviderTracker);
this.context.getProviderManager().authenticateAll(resourceProviderTracker.getResourceProviderStorage().getAuthRequiredHandlers(), control);
return control;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#clone(Map)
*/
@Override
public ResourceResolver clone(final Map<String, Object> authenticationInfo)
throws LoginException {
// ensure resolver is still live
checkClosed();
// create a regular resource resolver
return new ResourceResolverImpl(this, authenticationInfo);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#isLive()
*/
@Override
public boolean isLive() {
return !this.control.isClosed() && this.control.isLive(this.context) && this.factory.isLive();
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#close()
*/
@Override
public void close() {
if (factory.shouldLogResourceResolverClosing()) {
closedResolverException = new Exception("Stack Trace");
}
this.factory.unregister(this, this.control);
}
/**
* Check if the resource resolver is already closed or the factory which created this resolver is no longer live.
*
* @throws IllegalStateException
* If the resolver is already closed or the factory is no longer live.
*/
public void checkClosed() {
if (this.control.isClosed()) {
if (closedResolverException != null) {
logger.error("The ResourceResolver has already been closed.", closedResolverException);
}
throw new IllegalStateException("Resource resolver is already closed.");
}
if (!this.factory.isLive()) {
throw new IllegalStateException("Resource resolver factory which created this resolver is no longer active.");
}
}
// ---------- attributes
/**
* @see org.apache.sling.api.resource.ResourceResolver#getAttributeNames()
*/
@Override
public Iterator<String> getAttributeNames() {
checkClosed();
return this.control.getAttributeNames(this.context).iterator();
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#getAttribute(String)
*/
@Override
public Object getAttribute(final String name) {
checkClosed();
if (name == null) {
throw new NullPointerException("name");
}
return this.control.getAttribute(this.context, name);
}
// ---------- resolving resources
/**
* @see org.apache.sling.api.resource.ResourceResolver#resolve(java.lang.String)
*/
@Override
public Resource resolve(final String path) {
checkClosed();
final Resource rsrc = this.resolveInternal(null, path);
return rsrc;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#resolve(javax.servlet.http.HttpServletRequest)
*/
@Override
public Resource resolve(final HttpServletRequest request) {
checkClosed();
// throws NPE if request is null as required
final Resource rsrc = this.resolveInternal(request, request.getPathInfo());
return rsrc;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#resolve(javax.servlet.http.HttpServletRequest,
* java.lang.String)
*/
@Override
public Resource resolve(final HttpServletRequest request, String path) {
checkClosed();
final Resource rsrc = this.resolveInternal(request, path);
return rsrc;
}
private Resource resolveInternal(final HttpServletRequest request, String absPath) {
// make sure abspath is not null and is absolute
if (absPath == null) {
absPath = "/";
} else if (!absPath.startsWith("/")) {
absPath = "/" + absPath;
}
// check for special namespace prefix treatment
absPath = unmangleNamespaces(absPath);
// Assume http://localhost:80 if request is null
String[] realPathList = { absPath };
String requestPath;
if (request != null) {
requestPath = getMapPath(request.getScheme(), request.getServerName(), request.getServerPort(), absPath);
} else {
requestPath = getMapPath("http", "localhost", 80, absPath);
}
logger.debug("resolve: Resolving request path {}", requestPath);
// loop while finding internal or external redirect into the
// content out of the virtual host mapping tree
// the counter is to ensure we are not caught in an endless loop here
// TODO: might do better to be able to log the loop and help the user
for (int i = 0; i < 100; i++) {
String[] mappedPath = null;
final Iterator<MapEntry> mapEntriesIterator = this.factory.getMapEntries().getResolveMapsIterator(requestPath);
while (mapEntriesIterator.hasNext()) {
final MapEntry mapEntry = mapEntriesIterator.next();
mappedPath = mapEntry.replace(requestPath);
if (mappedPath != null) {
if (logger.isDebugEnabled()) {
logger.debug("resolve: MapEntry {} matches, mapped path is {}", mapEntry, Arrays.toString(mappedPath));
}
if (mapEntry.isInternal()) {
// internal redirect
logger.debug("resolve: Redirecting internally");
break;
}
// external redirect
logger.debug("resolve: Returning external redirect");
return this.factory.getResourceDecoratorTracker().decorate(
new RedirectResource(this, absPath, mappedPath[0], mapEntry.getStatus()));
}
}
// if there is no virtual host based path mapping, abort
// and use the original realPath
if (mappedPath == null) {
logger.debug("resolve: Request path {} does not match any MapEntry", requestPath);
break;
}
// if the mapped path is not an URL, use this path to continue
if (!mappedPath[0].contains("://")) {
logger.debug("resolve: Mapped path is for resource tree");
realPathList = mappedPath;
break;
}
// otherwise the mapped path is an URI and we have to try to
// resolve that URI now, using the URI's path as the real path
try {
final URI uri = new URI(mappedPath[0], false);
requestPath = getMapPath(uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath());
realPathList = new String[] { uri.getPath() };
logger.debug("resolve: Mapped path is an URL, using new request path {}", requestPath);
} catch (final URIException use) {
// TODO: log and fail
throw new ResourceNotFoundException(absPath);
}
}
// now we have the real path resolved from virtual host mapping
// this path may be absolute or relative, in which case we try
// to resolve it against the search path
Resource res = null;
for (int i = 0; res == null && i < realPathList.length; i++) {
final ParsedParameters parsedPath = new ParsedParameters(realPathList[i]);
final String realPath = parsedPath.getRawPath();
// first check whether the requested resource is a StarResource
if (StarResource.appliesTo(realPath)) {
logger.debug("resolve: Mapped path {} is a Star Resource", realPath);
res = new StarResource(this, ensureAbsPath(realPath));
} else {
if (realPath.startsWith("/")) {
// let's check it with a direct access first
logger.debug("resolve: Try absolute mapped path {}", realPath);
res = resolveInternal(realPath, parsedPath.getParameters());
} else {
for(final String path : factory.getSearchPath()) {
logger.debug("resolve: Try relative mapped path with search path entry {}", path);
res = resolveInternal(path + realPath, parsedPath.getParameters());
if ( res != null ) {
break;
}
}
}
}
}
// if no resource has been found, use a NonExistingResource
if (res == null) {
final ParsedParameters parsedPath = new ParsedParameters(realPathList[0]);
final String resourcePath = ensureAbsPath(parsedPath.getRawPath());
logger.debug("resolve: Path {} does not resolve, returning NonExistingResource at {}", absPath, resourcePath);
res = new NonExistingResource(this, resourcePath);
// SLING-864: if the path contains a dot we assume this to be
// the start for any selectors, extension, suffix, which may be
// used for further request processing.
// the resolution path must be the full path and is already set within
// the non existing resource
final int index = resourcePath.indexOf('.');
if (index != -1) {
res.getResourceMetadata().setResolutionPathInfo(resourcePath.substring(index));
}
res.getResourceMetadata().setParameterMap(parsedPath.getParameters());
} else {
logger.debug("resolve: Path {} resolves to Resource {}", absPath, res);
}
return this.factory.getResourceDecoratorTracker().decorate(res);
}
/**
* calls map(HttpServletRequest, String) as map(null, resourcePath)
*
* @see org.apache.sling.api.resource.ResourceResolver#map(java.lang.String)
*/
@Override
public String map(final String resourcePath) {
checkClosed();
return map(null, resourcePath);
}
/**
* full implementation - apply sling:alias from the resource path - apply
* /etc/map mappings (inkl. config backwards compat) - return absolute uri
* if possible
*
* @see org.apache.sling.api.resource.ResourceResolver#map(javax.servlet.http.HttpServletRequest,
* java.lang.String)
*/
@Override
public String map(final HttpServletRequest request, final String resourcePath) {
return adaptTo(ResourceMapper.class).getMapping(resourcePath, request);
}
// ---------- search path for relative resoures
/**
* @see org.apache.sling.api.resource.ResourceResolver#getSearchPath()
*/
@Override
public String[] getSearchPath() {
checkClosed();
final List<String> searchPath = factory.getSearchPath();
return searchPath.toArray(new String[searchPath.size()]);
}
// ---------- direct resource access without resolution
/**
* @see org.apache.sling.api.resource.ResourceResolver#getResource(java.lang.String)
*/
@Override
public Resource getResource(String path) {
checkClosed();
final Resource result = this.getResourceInternal(null, path);
return result;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#getResource(org.apache.sling.api.resource.Resource,
* java.lang.String)
*/
@Override
public Resource getResource(final Resource base, final String path) {
checkClosed();
String absolutePath = path;
if (absolutePath != null && !absolutePath.startsWith("/") && base != null && base.getPath() != null ) {
absolutePath = appendToPath(base.getPath(), absolutePath);
}
final Resource result = getResourceInternal(base, absolutePath);
return result;
}
/**
* Methods concatenates two paths. If the first path contains parameters separated semicolon, they are
* moved at the end of the result.
*
* @param pathWithParameters
* @param segmentToAppend
* @return
*/
private static String appendToPath(final String pathWithParameters, final String segmentToAppend) {
final ParsedParameters parsed = new ParsedParameters(pathWithParameters);
if (parsed.getParametersString() == null) {
return new StringBuilder(parsed.getRawPath()).append('/').append(segmentToAppend).toString();
} else {
return new StringBuilder(parsed.getRawPath()).append('/').append(segmentToAppend).append(parsed.getParametersString()).toString();
}
}
private Resource getResourceInternal(Resource parent, String path) {
Resource result = null;
if ( path != null ) {
// if the path is absolute, normalize . and .. segments and get res
if (path.startsWith("/")) {
final ParsedParameters parsedPath = new ParsedParameters(path);
path = ResourceUtil.normalize(parsedPath.getRawPath());
result = (path != null) ? getAbsoluteResourceInternal(parent, path, parsedPath.getParameters(), false) : null;
if (result != null) {
result = this.factory.getResourceDecoratorTracker().decorate(result);
}
} else {
// otherwise we have to apply the search path
// (don't use this.getSearchPath() to save a few cycle for not cloning)
for (final String prefix : factory.getSearchPath()) {
result = getResource(prefix + path);
if (result != null) {
break;
}
}
}
}
return result;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#listChildren(org.apache.sling.api.resource.Resource)
*/
@Override
public Iterator<Resource> listChildren(final Resource parent) {
checkClosed();
if (parent instanceof ResourceWrapper) {
return listChildren(((ResourceWrapper) parent).getResource());
}
return new ResourceIteratorDecorator(this.factory.getResourceDecoratorTracker(), this.control.listChildren(this.context, parent));
}
/**
* @see org.apache.sling.api.resource.Resource#getChildren()
*/
@Override
public Iterable<Resource> getChildren(final Resource parent) {
return new Iterable<Resource>() {
@Override
public Iterator<Resource> iterator() {
return listChildren(parent);
}
};
}
// ---------- Querying resources
private static final String DEFAULT_QUERY_LANGUAGE = "xpath";
/**
* @see org.apache.sling.api.resource.ResourceResolver#findResources(java.lang.String,
* java.lang.String)
*/
@Override
public Iterator<Resource> findResources(final String query, final String language) throws SlingException {
checkClosed();
return new ResourceIteratorDecorator(this.factory.getResourceDecoratorTracker(),
control.findResources(this.context, query, defaultString(language, DEFAULT_QUERY_LANGUAGE)));
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#queryResources(java.lang.String,
* java.lang.String)
*/
@Override
public Iterator<Map<String, Object>> queryResources(final String query, final String language)
throws SlingException {
checkClosed();
return control.queryResources(this.context, query, defaultString(language, DEFAULT_QUERY_LANGUAGE));
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#getUserID()
*/
@Override
public String getUserID() {
checkClosed();
// Try auth info first
if ( this.control.getAuthenticationInfo() != null ) {
final Object impUser = this.control.getAuthenticationInfo().get(ResourceResolverFactory.USER_IMPERSONATION);
if ( impUser != null ) {
return impUser.toString();
}
final Object user = this.control.getAuthenticationInfo().get(ResourceResolverFactory.USER);
if ( user != null ) {
return user.toString();
}
}
// Try attributes
final Object impUser = this.getAttribute(ResourceResolverFactory.USER_IMPERSONATION);
if ( impUser != null ) {
return impUser.toString();
}
final Object user = this.getAttribute(ResourceResolverFactory.USER);
if ( user != null ) {
return user.toString();
}
return null;
}
/** Cached session object, fetched on demand. */
private Object cachedSession;
/** Flag indicating if a searching has already been searched. */
private boolean searchedSession = false;
/**
* Try to get a session from one of the resource providers.
*/
@SuppressWarnings("unchecked")
private <AdapterType> AdapterType getSession(final Class<AdapterType> type) {
if ( !this.searchedSession ) {
this.searchedSession = true;
this.cachedSession = this.control.adaptTo(this.context, type);
}
return (AdapterType) this.cachedSession;
}
// ---------- Adaptable interface
/**
* @see org.apache.sling.api.adapter.SlingAdaptable#adaptTo(java.lang.Class)
*/
@SuppressWarnings("unchecked")
@Override
public <AdapterType> AdapterType adaptTo(final Class<AdapterType> type) {
checkClosed();
if (type.getName().equals("javax.jcr.Session")) {
return getSession(type);
}
if ( type == ResourceMapper.class )
return (AdapterType) new ResourceMapperImpl(this, factory.getResourceDecoratorTracker(), factory.getMapEntries(),
factory.isOptimizeAliasResolutionEnabled(), factory.getNamespaceMangler(), factory.getPathToUriMappingService());
final AdapterType result = this.control.adaptTo(this.context, type);
if ( result != null ) {
return result;
}
// fall back to default behaviour
return super.adaptTo(type);
}
// ---------- internal
/**
* Returns a string used for matching map entries against the given request
* or URI parts.
*
* @param scheme
* The URI scheme
* @param host
* The host name
* @param port
* The port number. If this is negative, the default value used
* is 80 unless the scheme is "https" in which case the default
* value is 443.
* @param path
* The (absolute) path
* @return The request path string {scheme}/{host}.{port}{path}.
*/
private static String getMapPath(final String scheme, final String host, int port, final String path) {
if (port < 0) {
port = ("https".equals(scheme)) ? 443 : 80;
}
return scheme + "/" + host + "." + port + path;
}
/**
* Internally resolves the absolute path. The will almost always contain
* request selectors and an extension. Therefore this method uses the
* {@link ResourcePathIterator} to cut off parts of the path to find the
* actual resource.
* <p>
* This method operates in two steps:
* <ol>
* <li>Check the path directly
* <li>Drill down the resource tree from the root down to the resource trying to get the child
* as per the respective path segment or finding a child whose <code>sling:alias</code> property
* is set to the respective name.
* </ol>
* <p>
* If neither mechanism (direct access and drill down) resolves to a resource this method
* returns <code>null</code>.
*
* @param absPath
* The absolute path of the resource to return.
* @return The resource found or <code>null</code> if the resource could not
* be found. The
* {@link org.apache.sling.api.resource.ResourceMetadata#getResolutionPathInfo()
* resolution path info} field of the resource returned is set to
* the part of the <code>absPath</code> which has been cut off by
* the {@link ResourcePathIterator} to resolve the resource.
*/
public Resource resolveInternal(final String absPath, final Map<String, String> parameters) {
Resource resource = null;
if (absPath != null && !absPath.isEmpty() && !absPath.startsWith("/")) {
logger.debug("resolveInternal: absolute path expected {} ",absPath);
return resource; // resource is null at this point
}
String curPath = absPath;
try {
final ResourcePathIterator it = new ResourcePathIterator(absPath);
while (it.hasNext() && resource == null) {
curPath = it.next();
resource = getAbsoluteResourceInternal(null, curPath, parameters, true);
}
} catch (final Exception ex) {
throw new SlingException("Problem trying " + curPath + " for request path " + absPath, ex);
}
// SLING-627: set the part cut off from the uriPath as
// sling.resolutionPathInfo property such that
// uriPath = curPath + sling.resolutionPathInfo
if (resource != null) {
final String rpi = absPath.substring(curPath.length());
resource.getResourceMetadata().setResolutionPath(absPath.substring(0, curPath.length()));
resource.getResourceMetadata().setResolutionPathInfo(rpi);
resource.getResourceMetadata().setParameterMap(parameters);
logger.debug("resolveInternal: Found resource {} with path info {} for {}", new Object[] { resource, rpi, absPath });
} else {
String tokenizedPath = absPath;
// no direct resource found, so we have to drill down into the
// resource tree to find a match
resource = getAbsoluteResourceInternal(null, "/", parameters, true);
//no read access on / drilling further down
//SLING-5638
if (resource == null) {
resource = getAbsoluteResourceInternal(absPath, parameters, true);
if (resource != null) {
tokenizedPath = tokenizedPath.substring(resource.getPath().length());
}
}
final StringBuilder resolutionPath = new StringBuilder();
final StringTokenizer tokener = new StringTokenizer(tokenizedPath, "/");
final int delimCount = StringUtils.countMatches(tokenizedPath,'/');
final int redundantDelimCount = delimCount - tokener.countTokens();
while (resource != null && tokener.hasMoreTokens()) {
final String childNameRaw = tokener.nextToken();
Resource nextResource = getChildInternal(resource, childNameRaw);
if (nextResource != null) {
resource = nextResource;
resolutionPath.append("/").append(childNameRaw);
} else {
String childName = null;
final ResourcePathIterator rpi = new ResourcePathIterator(childNameRaw);
while (rpi.hasNext() && nextResource == null) {
childName = rpi.next();
nextResource = getChildInternal(resource, childName);
}
// switch the currentResource to the nextResource (may be
// null)
resource = nextResource;
resolutionPath.append("/").append(childName);
// terminate the search if a resource has been found
// with the extension cut off
if (nextResource != null) {
break;
}
}
}
// SLING-627: set the part cut off from the uriPath as
// sling.resolutionPathInfo property such that
// uriPath = curPath + sling.resolutionPathInfo
if (resource != null) {
final String path = resolutionPath.toString();
final String pathInfo = absPath.substring(path.length() + redundantDelimCount);
resource.getResourceMetadata().setResolutionPath(path);
resource.getResourceMetadata().setResolutionPathInfo(pathInfo);
resource.getResourceMetadata().setParameterMap(parameters);
logger.debug("resolveInternal: Found resource {} with path info {} for {}", new Object[] { resource, pathInfo,
absPath });
}
}
return resource;
}
private Resource getChildInternal(final Resource parent, final String childName) {
final String path;
if ( childName.startsWith("/") ) {
path = childName;
} else {
path = parent.getPath() + '/' + childName;
}
Resource child = getAbsoluteResourceInternal(parent, ResourceUtil.normalize(path), EMPTY_PARAMETERS, true );
if (child != null) {
final String alias = ResourceResolverControl.getProperty(child, PROP_REDIRECT_INTERNAL);
if (alias != null) {
// TODO: might be a redirect ??
logger.warn("getChildInternal: Internal redirect to {} for Resource {} is not supported yet, ignoring", alias,
child);
}
// we have the resource name, continue with the next level
return child;
}
// we do not have a child with the exact name, so we look for
// a child, whose alias matches the childName
if (factory.isOptimizeAliasResolutionEnabled()){
logger.debug("getChildInternal: Optimize Alias Resolution is Enabled");
//optimization made in SLING-2521
final Map<String, String> aliases = factory.getMapEntries().getAliasMap(parent.getPath());
if (aliases != null) {
final String aliasName = aliases.get(childName);
if (aliasName != null ) {
final String aliasPath;
if ( aliasName.startsWith("/") ) {
aliasPath = aliasName;
} else {
aliasPath = parent.getPath() + '/' + aliasName;
}
final Resource aliasedChild = getAbsoluteResourceInternal(parent, ResourceUtil.normalize(aliasPath), EMPTY_PARAMETERS, true );
logger.debug("getChildInternal: Found Resource {} with alias {} to use", aliasedChild, childName);
return aliasedChild;
}
}
} else {
logger.debug("getChildInternal: Optimize Alias Resolution is Disabled");
final Iterator<Resource> children = listChildren(parent);
while (children.hasNext()) {
child = children.next();
if (!child.getPath().endsWith(JCR_CONTENT_LEAF)) {
final String[] aliases = ResourceResolverControl.getProperty(child, PROP_ALIAS, String[].class);
if (aliases != null) {
for (final String alias : aliases) {
if (childName.equals(alias)) {
logger.debug("getChildInternal: Found Resource {} with alias {} to use", child, childName);
final Resource aliasedChild = getAbsoluteResourceInternal(parent, ResourceUtil.normalize(child.getPath()) , EMPTY_PARAMETERS, true);
return aliasedChild;
}
}
}
}
}
}
// no match for the childName found
logger.debug("getChildInternal: Resource {} has no child {}", parent, childName);
return null;
}
/**
* Creates a resource with the given path if existing
*/
private Resource getAbsoluteResourceInternal(@Nullable final Resource parent, @Nullable final String path, final Map<String, String> parameters, final boolean isResolve) {
if (path == null || path.length() == 0 || path.charAt(0) != '/') {
logger.debug("getResourceInternal: Path must be absolute {}", path);
return null; // path must be absolute
}
final Resource parentToUse;
if (parent != null && path.startsWith(parent.getPath())) {
parentToUse = parent;
} else {
parentToUse = null;
}
final Resource resource = this.control.getResource(this.context, path, parentToUse, parameters, isResolve);
if (resource != null) {
resource.getResourceMetadata().setResolutionPath(path);
resource.getResourceMetadata().setParameterMap(parameters);
return resource;
}
logger.debug("getResourceInternal: Cannot resolve path '{}' to a resource", path);
return null;
}
/**
* Creates a resource, traversing bottom up, to the highest readable resource.
*
*/
private Resource getAbsoluteResourceInternal(String absPath, final Map<String, String> parameters, final boolean isResolved) {
if (!absPath.contains("/") || "/".equals(absPath)) {
return null;
}
absPath = absPath.substring(absPath.indexOf("/"));
Resource resource = getAbsoluteResourceInternal(null, absPath, parameters, isResolved);
absPath = absPath.substring(0, absPath.lastIndexOf("/"));
while (!absPath.equals("")) {
Resource r = getAbsoluteResourceInternal(null, absPath, parameters, true);
if (r != null) {
resource = r;
}
absPath = absPath.substring(0, absPath.lastIndexOf("/"));
}
return resource;
}
/**
* Returns the <code>path</code> as an absolute path. If the path is already
* absolute it is returned unmodified (the same instance actually). If the
* path is relative it is made absolute by prepending the first entry of the
* {@link #getSearchPath() search path}.
*
* @param path
* The path to ensure absolute
* @return The absolute path as explained above
*/
private String ensureAbsPath(String path) {
if (!path.startsWith("/")) {
path = factory.getSearchPath().get(0) + path;
}
return path;
}
private String unmangleNamespaces(String absPath) {
if (absPath != null && factory.getNamespaceMangler() != null ) {
absPath = ((JcrNamespaceMangler)factory.getNamespaceMangler()).unmangleNamespaces(this, logger, absPath);
}
return absPath;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#delete(org.apache.sling.api.resource.Resource)
*/
@Override
public void delete(final Resource resource)
throws PersistenceException {
// check if the resource is non existing - throws NPE if resource is null as stated in the API
if ( ResourceUtil.isNonExistingResource(resource) ) {
// nothing to do
return;
}
// if resource is null, we get an NPE as stated in the API
this.control.delete(this.context, resource);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#create(org.apache.sling.api.resource.Resource, java.lang.String, Map)
*/
@Override
public Resource create(final Resource parent, final String name, final Map<String, Object> properties)
throws PersistenceException {
// if parent or name is null, we get an NPE as stated in the API
if ( name == null ) {
throw new NullPointerException("name");
}
// name should be a name not a path
if ( name.indexOf("/") != -1 ) {
throw new IllegalArgumentException("Name should not contain a slash: " + name);
}
final String path;
if ( parent.getPath().equals("/") ) {
path = parent.getPath() + name;
} else {
path = parent.getPath() + "/" + name;
}
// experimental code for handling synthetic resources
if ( ResourceUtil.isSyntheticResource(parent) ) {
Resource grandParent = parent.getParent();
if (grandParent != null) {
this.create(grandParent, parent.getName(), null);
} else {
throw new IllegalArgumentException("Can't create child on a synthetic root");
}
}
final Resource rsrc = this.control.create(this.context, path, properties);
rsrc.getResourceMetadata().setResolutionPath(rsrc.getPath());
return this.factory.getResourceDecoratorTracker().decorate(rsrc);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#revert()
*/
@Override
public void revert() {
this.control.revert(this.context);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#commit()
*/
@Override
public void commit() throws PersistenceException {
this.control.commit(this.context);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#hasChanges()
*/
@Override
public boolean hasChanges() {
return this.control.hasChanges(this.context);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#hasChildren()
*/
@Override
public boolean hasChildren(Resource resource) {
return listChildren(resource).hasNext();
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#getParentResourceType(org.apache.sling.api.resource.Resource)
*/
@Override
public String getParentResourceType(final Resource resource) {
String resourceSuperType = null;
if ( resource != null ) {
resourceSuperType = resource.getResourceSuperType();
if (resourceSuperType == null) {
resourceSuperType = this.getParentResourceType(resource.getResourceType());
}
}
return resourceSuperType;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#getParentResourceType(java.lang.String)
*/
@Override
public String getParentResourceType(final String resourceType) {
return this.control.getParentResourceType(this.factory, this, resourceType);
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#isResourceType(org.apache.sling.api.resource.Resource, java.lang.String)
*/
@Override
public boolean isResourceType(final Resource resource, final String resourceType) {
boolean result = false;
if ( resource != null && resourceType != null ) {
// Check if the resource is of the given type. This method first checks the
// resource type of the resource, then its super resource type and continues
// to go up the resource super type hierarchy.
if (ResourceTypeUtil.areResourceTypesEqual(resourceType, resource.getResourceType(), factory.getSearchPath())) {
result = true;
} else {
Set<String> superTypesChecked = new HashSet<>();
String superType = this.getParentResourceType(resource);
while (!result && superType != null) {
if (ResourceTypeUtil.areResourceTypesEqual(resourceType, superType, factory.getSearchPath())) {
result = true;
} else {
superTypesChecked.add(superType);
superType = this.getParentResourceType(superType);
if (superType != null && superTypesChecked.contains(superType)) {
throw new SlingException("Cyclic dependency for resourceSuperType hierarchy detected on resource " + resource.getPath(), null);
}
}
}
}
}
return result;
}
/**
* @see org.apache.sling.api.resource.ResourceResolver#refresh()
*/
@Override
public void refresh() {
this.control.refresh(this.context);
}
@Override
public Resource getParent(final Resource child) {
Resource rsrc = null;
final String parentPath = ResourceUtil.getParent(child.getPath());
if ( parentPath != null ) {
// if the parent path is relative, resolve using search paths.
if ( !parentPath.startsWith("/") ) {
rsrc = context.getResourceResolver().getResource(parentPath);
} else {
rsrc = this.control.getParent(this.context, parentPath, child);
if (rsrc != null ) {
rsrc.getResourceMetadata().setResolutionPath(rsrc.getPath());
rsrc = this.factory.getResourceDecoratorTracker().decorate(rsrc);
}
}
}
return rsrc;
}
@Override
public Resource copy(final String srcAbsPath, final String destAbsPath) throws PersistenceException {
Resource rsrc = this.control.copy(this.context, srcAbsPath, destAbsPath);
if (rsrc != null ) {
rsrc.getResourceMetadata().setResolutionPath(rsrc.getPath());
rsrc = this.factory.getResourceDecoratorTracker().decorate(rsrc);
}
return rsrc;
}
@Override
public Resource move(final String srcAbsPath, final String destAbsPath) throws PersistenceException {
Resource rsrc = this.control.move(this.context, srcAbsPath, destAbsPath);
if (rsrc != null ) {
rsrc.getResourceMetadata().setResolutionPath(rsrc.getPath());
rsrc = this.factory.getResourceDecoratorTracker().decorate(rsrc);
}
return rsrc;
}
}