| /* |
| * 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 org.apache.commons.lang3.StringUtils; |
| import org.jetbrains.annotations.Nullable; |
| import javax.jcr.Session; |
| import javax.servlet.http.HttpServletRequest; |
| |
| 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.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.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()); |
| |
| 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; |
| } |
| } |