| /* |
| * 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 SF 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.helper; |
| |
| import java.io.Closeable; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| |
| import org.apache.commons.collections4.iterators.IteratorChain; |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.sling.api.resource.LoginException; |
| import org.apache.sling.api.resource.PersistenceException; |
| import org.apache.sling.api.resource.Resource; |
| 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.SyntheticResource; |
| import org.apache.sling.api.resource.ValueMap; |
| import org.apache.sling.api.resource.path.PathBuilder; |
| import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler; |
| import org.apache.sling.resourceresolver.impl.providers.ResourceProviderInfo; |
| import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage; |
| import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorageProvider; |
| import org.apache.sling.resourceresolver.impl.providers.stateful.AuthenticatedResourceProvider; |
| import org.apache.sling.resourceresolver.impl.providers.tree.Node; |
| import org.apache.sling.resourceresolver.impl.providers.tree.PathTree; |
| import org.apache.sling.spi.resource.provider.ResourceProvider; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.FrameworkUtil; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * This class takes a number of {@link AuthenticatedResourceProvider} objects and |
| * exposes it as one such object. Provider appropriate for the given operation |
| * is chosen basing on its {@link ResourceProviderInfo#getPath()} (more specific |
| * first) and service ranking. |
| * |
| * Like a resource resolver itself, this class is not thread safe. |
| */ |
| public class ResourceResolverControl { |
| |
| private static final Logger logger = LoggerFactory.getLogger(ResourceResolverControl.class); |
| |
| private static final String[] FORBIDDEN_ATTRIBUTES = new String[] { |
| ResourceResolverFactory.PASSWORD, |
| ResourceProvider.AUTH_SERVICE_BUNDLE, |
| ResourceResolverFactory.SUBSERVICE}; |
| |
| /** Is this a resource resolver for an admin? */ |
| private final boolean isAdmin; |
| |
| /** The authentication info. */ |
| private final Map<String, Object> authenticationInfo; |
| |
| /** Resource type resource resolver (admin resolver) */ |
| private volatile ResourceResolver resourceTypeResourceResolver; |
| |
| /** Flag for handling multiple calls to close. */ |
| private final AtomicBoolean isClosed = new AtomicBoolean(false); |
| |
| private final ResourceProviderStorageProvider resourceProviderTracker; |
| |
| private final Map<ResourceProviderHandler, Object> authenticatedProviders; |
| |
| /** |
| * Create a new resource resolver context. |
| */ |
| public ResourceResolverControl(final boolean isAdmin, |
| final Map<String, Object> authenticationInfo, |
| final ResourceProviderStorageProvider resourceProviderTracker) { |
| this.authenticatedProviders = new IdentityHashMap<>(); |
| this.authenticationInfo = authenticationInfo; |
| this.isAdmin = isAdmin; |
| this.resourceProviderTracker = resourceProviderTracker; |
| } |
| |
| /** |
| * Is this an admin resource resolver? |
| * @return{@code true} if it is an admin resource resolver |
| */ |
| public boolean isAdmin() { |
| return isAdmin; |
| } |
| |
| /** |
| * The authentication info |
| * @return The map with the auth info |
| */ |
| public Map<String, Object> getAuthenticationInfo() { |
| return this.authenticationInfo; |
| } |
| |
| /** |
| * Is this already closed? |
| * @return {@code true} if it is closed. |
| */ |
| public boolean isClosed() { |
| return this.isClosed.get(); |
| } |
| |
| /** |
| * Logs out from all providers. |
| */ |
| private void logout() { |
| for(final Map.Entry<ResourceProviderHandler, Object> entry : this.authenticatedProviders.entrySet()) { |
| try { |
| final ResourceProvider<Object> rp = entry.getKey().getResourceProvider(); |
| if ( rp != null ) { |
| rp.logout(entry.getValue()); |
| } else if ( entry.getValue() instanceof Closeable ) { |
| ((Closeable)entry.getValue()).close(); |
| } |
| } catch ( final Throwable t ) { |
| // we ignore everything from there to not stop this thread |
| } |
| } |
| this.authenticatedProviders.clear(); |
| } |
| |
| /** |
| * Refreshes all refreshable providers as well as the resolver used for resource types. |
| */ |
| public void refresh(@NotNull final ResourceResolverContext context) { |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllUsedRefreshable()) { |
| p.refresh(); |
| } |
| if (this.resourceTypeResourceResolver != null) { |
| this.resourceTypeResourceResolver.refresh(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if all providers are live. |
| */ |
| public boolean isLive(@NotNull final ResourceResolverContext context) { |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllAuthenticated()) { |
| if (!p.isLive()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Returns parent from the most appropriate resource provider accepting the |
| * given children. |
| * |
| * In some cases the {@link SyntheticResource} can be returned if no |
| * resource provider returns parent for this child. See |
| * {@link #getResource(String, Resource, Map, boolean)} for more details |
| */ |
| public Resource getParent(@NotNull final ResourceResolverContext context, @NotNull final String parentPath, @NotNull final Resource child) { |
| final AuthenticatedResourceProvider childProvider = getBestMatchingProvider(context, child.getPath()); |
| final AuthenticatedResourceProvider parentProvider = getBestMatchingProvider(context, parentPath); |
| if ( parentProvider != null ) { |
| final Resource parentCandidate; |
| if ( childProvider == parentProvider ) { |
| parentCandidate = parentProvider.getParent(child); |
| } else { |
| parentCandidate = parentProvider.getResource(parentPath, null, null); |
| } |
| if (parentCandidate != null) { |
| return parentCandidate; |
| } |
| } |
| |
| if (isIntermediatePath(parentPath)) { |
| return new SyntheticResource(context.getResourceResolver(), parentPath, ResourceProvider.RESOURCE_TYPE_SYNTHETIC); |
| } |
| return null; |
| } |
| |
| /** |
| * Returns resource from the most appropriate resource provider. |
| * <br/><br/> |
| * If there's no such provider and the path is a part of some resource |
| * provider path, then the {@link SyntheticResource} will be returned. For |
| * instance, if we have resource provider under |
| * {@code /libs/sling/servlet/default/GET.servlet} and no resource provider |
| * returns a resource for {@code /libs/sling/servlet/default}, then the |
| * {@link SyntheticResource} will be returned to provide a consistent |
| * resource tree. |
| * <br/><br/> |
| * The same behaviour occurs in {@link #getParent(Resource)} and |
| * {@link #listChildren(Resource)}. |
| */ |
| public Resource getResource(final ResourceResolverContext context, |
| String path, Resource parent, Map<String, String> parameters, |
| boolean isResolve) { |
| if (path == null || path.length() == 0 || path.charAt(0) != '/') { |
| logger.debug("Not absolute {}", path); |
| return null; // path must be absolute |
| } |
| |
| final AuthenticatedResourceProvider provider = this.getBestMatchingProvider(context, path); |
| if ( provider != null ) { |
| final Resource resourceCandidate = provider.getResource(path, parent, parameters); |
| if (resourceCandidate != null) { |
| return resourceCandidate; |
| } |
| } |
| |
| // query: /libs/sling/servlet/default |
| // resource Provider: libs/sling/servlet/default/GET.servlet |
| // list will match libs, sling, servlet, default |
| // and there will be no resource provider at the end |
| // SLING-3482 : this is only done for getResource but not resolve |
| // as it is important e.g. for servlet resolution |
| // to get the parent resource for resource traversal. |
| if (!isResolve && isIntermediatePath(path)) { |
| logger.debug("Resolved Synthetic {}", path); |
| return new SyntheticResource(context.getResourceResolver(), path, ResourceProvider.RESOURCE_TYPE_SYNTHETIC); |
| } |
| logger.debug("Resource null {} ", path); |
| return null; |
| } |
| |
| private boolean isIntermediatePath(final String fullPath) { |
| return getResourceProviderStorage().getTree().getNode(fullPath) != null; |
| } |
| |
| /** |
| * This method asks all matching resource providers for the children iterators, |
| * merges them, adds {@link SyntheticResource}s (see |
| * {@link #getResource(String, Resource, Map, boolean)} for more details), |
| * filters out the duplicates and returns the resulting iterator. All |
| * transformations are done lazily, during the {@link Iterator#hasNext()} |
| * invocation on the result. |
| */ |
| @SuppressWarnings("unchecked") |
| public Iterator<Resource> listChildren(final ResourceResolverContext context, final Resource parent) { |
| final String parentPath = parent.getPath(); |
| |
| // 3 sources are combined: children of the provider which owns 'parent', |
| // providers which are directly mounted at a child path, |
| // synthetic resources for providers mounted at a lower level |
| |
| // children of the 'parent' provider |
| Iterator<Resource> realChildren = null; |
| final AuthenticatedResourceProvider provider = this.getBestMatchingProvider(context, parentPath); |
| if ( provider != null ) { |
| realChildren = provider.listChildren(parent); |
| } |
| |
| final Set<String> visitedNames = new HashSet<>(); |
| |
| IteratorChain chain = new IteratorChain(); |
| if ( realChildren != null ) { |
| chain.addIterator(realChildren); |
| } |
| |
| // synthetic and providers are done in one loop |
| final Node<ResourceProviderHandler> node = getResourceProviderStorage().getTree().getNode(parent.getPath()); |
| if (node != null) { |
| final List<Resource> syntheticList = new ArrayList<>(); |
| final List<Resource> providerList = new ArrayList<>(); |
| |
| for (final Entry<String, Node<ResourceProviderHandler>> entry : node.getChildren().entrySet()) { |
| final String name = entry.getKey(); |
| final ResourceProviderHandler handler = entry.getValue().getValue(); |
| PathBuilder pathBuilder = new PathBuilder(parent.getPath()); |
| pathBuilder.append(name); |
| final String childPath = pathBuilder.toString(); |
| if (handler == null) { |
| syntheticList.add(new SyntheticResource(context.getResourceResolver(), childPath, ResourceProvider.RESOURCE_TYPE_SYNTHETIC)); |
| } else { |
| Resource rsrc = null; |
| try { |
| final AuthenticatedResourceProvider rp = context.getProviderManager().getOrCreateProvider(handler, this); |
| rsrc = rp == null ? null : rp.getResource(childPath, parent, null); |
| } catch ( final LoginException ignore) { |
| // ignore |
| } |
| if ( rsrc != null ) { |
| providerList.add(rsrc); |
| } else { |
| // if there is a child provider underneath, we need to create a synthetic resource |
| // otherwise we need to make sure that no one else is providing this child |
| if ( entry.getValue().getChildren().isEmpty() ) { |
| syntheticList.add(new SyntheticResource(context.getResourceResolver(), childPath, ResourceProvider.RESOURCE_TYPE_SYNTHETIC)); |
| } else { |
| visitedNames.add(name); |
| } |
| } |
| } |
| } |
| if ( !providerList.isEmpty() ) { |
| chain.addIterator(providerList.iterator()); |
| } |
| if ( !syntheticList.isEmpty() ) { |
| chain.addIterator(syntheticList.iterator()); |
| } |
| } |
| if ( chain.size() == 0 ) { |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| return new UniqueResourceIterator(visitedNames, chain); |
| } |
| |
| /** |
| * Returns the union of all attribute names. |
| */ |
| public Collection<String> getAttributeNames(final ResourceResolverContext context) { |
| final Set<String> names = new LinkedHashSet<>(); |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllBestEffort(getResourceProviderStorage().getAttributableHandlers(), this)) { |
| p.getAttributeNames(names); |
| } |
| if ( this.authenticationInfo != null ) { |
| names.addAll(authenticationInfo.keySet()); |
| } |
| for(final String key : FORBIDDEN_ATTRIBUTES) { |
| names.remove(key); |
| } |
| return names; |
| } |
| |
| /** |
| * Returns the first non-null result of the |
| * {@link AuthenticatedResourceProvider#getAttribute(String)} invocation on |
| * the providers. |
| */ |
| public Object getAttribute(final ResourceResolverContext context, final String name) { |
| for(final String key : FORBIDDEN_ATTRIBUTES) { |
| if (key.equals(name)) { |
| return null; |
| } |
| } |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllBestEffort(getResourceProviderStorage().getAttributableHandlers(), this)) { |
| final Object attribute = p.getAttribute(name); |
| if (attribute != null) { |
| return attribute; |
| } |
| } |
| return this.authenticationInfo != null ? this.authenticationInfo.get(name) :null; |
| } |
| |
| /** |
| * Create a resource. |
| * |
| * @throws UnsupportedOperationException |
| * If creation is not allowed/possible |
| * @throws PersistenceException |
| * If creation fails |
| * @return The new resource |
| */ |
| public Resource create(final ResourceResolverContext context, |
| final String path, final Map<String, Object> properties) |
| throws PersistenceException { |
| final AuthenticatedResourceProvider provider = getBestMatchingModifiableProvider(context, path); |
| if ( provider != null ) { |
| final Resource creationResultResource = provider.create(context.getResourceResolver(), path, properties); |
| if (creationResultResource != null) { |
| return creationResultResource; |
| } |
| } |
| throw new UnsupportedOperationException("create '" + ResourceUtil.getName(path) + "' at " + ResourceUtil.getParent(path)); |
| } |
| |
| /** |
| * Delete the resource. Iterate over all modifiable ResourceProviders |
| * giving each an opportunity to delete the resource if they are able. |
| * |
| * @throws NullPointerException |
| * if resource is null |
| * @throws UnsupportedOperationException |
| * If deletion is not allowed/possible |
| * @throws PersistenceException |
| * If deletion fails |
| */ |
| public void delete(final ResourceResolverContext context, final Resource resource) throws PersistenceException { |
| final String path = resource.getPath(); |
| final AuthenticatedResourceProvider provider = getBestMatchingModifiableProvider(context, path); |
| if ( provider != null ) { |
| provider.delete(resource); |
| return; |
| } |
| throw new UnsupportedOperationException("delete at '" + path + "'"); |
| } |
| |
| /** |
| * Revert changes on all modifiable ResourceProviders. |
| */ |
| public void revert(final ResourceResolverContext context) { |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllUsedModifiable()) { |
| p.revert(); |
| } |
| } |
| |
| /** |
| * Commit changes on all modifiable ResourceProviders. |
| */ |
| public void commit(final ResourceResolverContext context) throws PersistenceException { |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllUsedModifiable()) { |
| p.commit(); |
| } |
| } |
| |
| /** |
| * Check if any modifiable ResourceProvider has uncommited changes. |
| */ |
| public boolean hasChanges(final ResourceResolverContext context) { |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllUsedModifiable()) { |
| if (p.hasChanges()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return the union of query languages supported by the providers. |
| */ |
| public String[] getSupportedLanguages(final ResourceResolverContext context) { |
| final Set<String> supportedLanguages = new LinkedHashSet<>(); |
| for (AuthenticatedResourceProvider p : context.getProviderManager().getAllBestEffort(getResourceProviderStorage().getLanguageQueryableHandlers(), this)) { |
| supportedLanguages.addAll(Arrays.asList(p.getSupportedLanguages())); |
| } |
| return supportedLanguages.toArray(new String[supportedLanguages.size()]); |
| } |
| |
| /** |
| * Queries all resource providers and combines the results. |
| */ |
| public Iterator<Resource> findResources(final ResourceResolverContext context, |
| final String query, final String language) { |
| final List<AuthenticatedResourceProvider> queryableRP = getQueryableProviders(context, language); |
| final List<Iterator<Resource>> iterators = new ArrayList<>(queryableRP.size()); |
| for (AuthenticatedResourceProvider p : queryableRP) { |
| iterators.add(p.findResources(query, language)); |
| } |
| return new ChainedIterator<>(iterators.iterator()); |
| } |
| |
| private List<AuthenticatedResourceProvider> getQueryableProviders( |
| final ResourceResolverContext context, |
| final String language) { |
| final List<AuthenticatedResourceProvider> queryableProviders = new ArrayList<>(); |
| for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllBestEffort(getResourceProviderStorage().getLanguageQueryableHandlers(), this)) { |
| if (ArrayUtils.contains(p.getSupportedLanguages(), language)) { |
| queryableProviders.add(p); |
| } |
| } |
| return queryableProviders; |
| } |
| |
| /** |
| * Queries all resource providers and combines the results. |
| */ |
| public Iterator<Map<String, Object>> queryResources(final ResourceResolverContext context, |
| final String query, final String language) { |
| final List<AuthenticatedResourceProvider> queryableRP = getQueryableProviders(context, language); |
| final List<Iterator<Map<String, Object>>> iterators = new ArrayList<>(queryableRP.size()); |
| for (AuthenticatedResourceProvider p : queryableRP) { |
| iterators.add(p.queryResources(query, language)); |
| } |
| return new ChainedIterator<>(iterators.iterator()); |
| } |
| |
| /** |
| * Returns the first non-null result of the adaptTo() method invoked on the |
| * providers. |
| */ |
| @SuppressWarnings("unchecked") |
| public <AdapterType> AdapterType adaptTo(final ResourceResolverContext context, Class<AdapterType> type) { |
| for (AuthenticatedResourceProvider p : context.getProviderManager().getAllBestEffort(getResourceProviderStorage().getAdaptableHandlers(), this)) { |
| final Object adaptee = p.adaptTo(type); |
| if (adaptee != null) { |
| return (AdapterType) adaptee; |
| } |
| } |
| return null; |
| } |
| |
| private AuthenticatedResourceProvider checkExistence(final ResourceResolverContext context, final String path, |
| final String type) throws PersistenceException { |
| final Node<ResourceProviderHandler> node = getResourceProviderStorage().getTree().getBestMatchingNode(path); |
| if (node == null) { |
| throw new PersistenceException(type.concat(" resource does not exist."), null, path, null); |
| } |
| AuthenticatedResourceProvider provider = null; |
| try { |
| provider = context.getProviderManager().getOrCreateProvider(node.getValue(), this); |
| } catch (LoginException e) { |
| // ignore |
| } |
| if (provider == null) { |
| throw new PersistenceException(type.concat(" resource does not exist."), null, path, null); |
| } |
| final Resource rsrc = provider.getResource(path, null, null); |
| if (rsrc == null) { |
| throw new PersistenceException(type.concat(" resource does not exist."), null, path, null); |
| } |
| return provider; |
| } |
| |
| public AuthenticatedResourceProvider checkSourceAndDest(final ResourceResolverContext context, |
| final String srcAbsPath, final String destAbsPath) throws PersistenceException { |
| // check source |
| final AuthenticatedResourceProvider srcProvider = checkExistence(context, srcAbsPath, "Source"); |
| // check destination |
| final AuthenticatedResourceProvider destProvider = checkExistence(context, destAbsPath, "Destination"); |
| |
| // check for sub providers of src and dest |
| if (srcProvider == destProvider) { |
| final PathTree<ResourceProviderHandler> providerTree = getResourceProviderStorage().getTree(); |
| final boolean sourceHasProvider = hasSubProvider(context, providerTree.getNode(srcAbsPath)); |
| final boolean destHasProvider = hasSubProvider(context, providerTree.getNode(destAbsPath)); |
| if (!(sourceHasProvider || destHasProvider)) { |
| return srcProvider; |
| } |
| } |
| return null; |
| } |
| |
| private boolean hasSubProvider(ResourceResolverContext context, Node<ResourceProviderHandler> node) { |
| if (node != null) { |
| for (final Node<ResourceProviderHandler> childNode : node.getChildren().values()) { |
| final ResourceProviderHandler handler = childNode.getValue(); |
| if (handler != null) { |
| try { |
| context.getProviderManager().getOrCreateProvider(handler, this); |
| return true; |
| } catch (final LoginException ignore) { |
| // ignore |
| } |
| } |
| if (hasSubProvider(context, childNode)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private void copy(final ResourceResolverContext context, final Resource src, final String dstPath, final List<Resource> newNodes) throws PersistenceException { |
| final ValueMap vm = src.getValueMap(); |
| final String createPath = new PathBuilder(dstPath).append(src.getName()).toString(); |
| newNodes.add(this.create(context, createPath, vm)); |
| for(final Resource c : src.getChildren()) { |
| copy(context, c, createPath, newNodes); |
| } |
| } |
| |
| /** |
| * Tries to find a resource provider accepting both paths and invokes |
| * {@link AuthenticatedResourceProvider#copy(String, String)} method on it. |
| * Returns false if there's no such provider. |
| */ |
| public Resource copy(final ResourceResolverContext context, |
| final String srcAbsPath, final String destAbsPath) throws PersistenceException { |
| final AuthenticatedResourceProvider optimizedSourceProvider = checkSourceAndDest(context, srcAbsPath, destAbsPath); |
| if ( optimizedSourceProvider != null && optimizedSourceProvider.copy(srcAbsPath, destAbsPath) ) { |
| return this.getResource(context, destAbsPath + '/' + ResourceUtil.getName(srcAbsPath), null, null, false); |
| } |
| |
| final Resource srcResource = this.getResource(context, srcAbsPath, null, null, false); |
| final List<Resource> newResources = new ArrayList<>(); |
| boolean rollback = true; |
| try { |
| this.copy(context, srcResource, destAbsPath, newResources); |
| rollback = false; |
| return newResources.get(0); |
| } finally { |
| if ( rollback ) { |
| for(final Resource rsrc : newResources) { |
| this.delete(context, rsrc); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tries to find a resource provider accepting both paths and invokes |
| * {@link AuthenticatedResourceProvider#move(String, String)} method on it. |
| * Returns false if there's no such provider. |
| */ |
| public Resource move(final ResourceResolverContext context, |
| String srcAbsPath, String destAbsPath) throws PersistenceException { |
| final AuthenticatedResourceProvider optimizedSourceProvider = checkSourceAndDest(context, srcAbsPath, destAbsPath); |
| if ( optimizedSourceProvider != null && optimizedSourceProvider.move(srcAbsPath, destAbsPath) ) { |
| return this.getResource(context, destAbsPath + '/' + ResourceUtil.getName(srcAbsPath), null, null, false); |
| } |
| final Resource srcResource = this.getResource(context, srcAbsPath, null, null, false); |
| final List<Resource> newResources = new ArrayList<>(); |
| boolean rollback = true; |
| try { |
| this.copy(context, srcResource, destAbsPath, newResources); |
| this.delete(context, srcResource); |
| rollback = false; |
| return newResources.get(0); |
| } finally { |
| if ( rollback ) { |
| for(final Resource rsrc : newResources) { |
| this.delete(context, rsrc); |
| } |
| } |
| } |
| } |
| |
| public ResourceProviderStorage getResourceProviderStorage() { |
| return this.resourceProviderTracker.getResourceProviderStorage(); |
| } |
| |
| /** |
| * @param path |
| * @return |
| */ |
| private @Nullable AuthenticatedResourceProvider getBestMatchingProvider(final ResourceResolverContext context, |
| final String path) { |
| try { |
| final Node<ResourceProviderHandler> node = resourceProviderTracker.getResourceProviderStorage().getTree().getBestMatchingNode(path); |
| return node == null ? null : context.getProviderManager().getOrCreateProvider(node.getValue(), this); |
| } catch ( final LoginException le ) { |
| // ignore |
| return null; |
| } |
| } |
| |
| /** |
| * @param path |
| * @return The modifiable provider or {@code null} |
| */ |
| private @Nullable AuthenticatedResourceProvider getBestMatchingModifiableProvider( |
| final ResourceResolverContext context, |
| final String path) { |
| final Node<ResourceProviderHandler> node = resourceProviderTracker.getResourceProviderStorage().getTree().getBestMatchingNode(path); |
| if ( node != null && node.getValue().getInfo().isModifiable() ) { |
| try { |
| return context.getProviderManager().getOrCreateProvider(node.getValue(), this); |
| } catch ( final LoginException le ) { |
| // ignore |
| return null; |
| } |
| } |
| return null; |
| } |
| |
| |
| /** |
| * Close all dynamic resource providers. |
| */ |
| public void close() { |
| if (this.isClosed.compareAndSet(false, true)) { |
| this.logout(); |
| if ( this.resourceTypeResourceResolver != null ) { |
| try { |
| this.resourceTypeResourceResolver.close(); |
| } catch ( final Throwable t) { |
| // the resolver (or the underlying provider) might already be terminated (bundle stopped etc.) |
| // so we ignore anything from here |
| } |
| this.resourceTypeResourceResolver = null; |
| } |
| } |
| } |
| |
| private ResourceResolver getResourceTypeResourceResolver( |
| final ResourceResolverFactory factory, |
| final ResourceResolver resolver) { |
| if ( this.isAdmin ) { |
| return resolver; |
| } else { |
| if ( this.resourceTypeResourceResolver == null ) { |
| try { |
| // make sure we're getting the resourceTypeResourceResolver on behalf of |
| // the resourceresolver bundle |
| final Bundle bundle = FrameworkUtil.getBundle(ResourceResolverControl.class); |
| final Map<String, Object> authenticationInfo = new HashMap<>(); |
| authenticationInfo.put(ResourceProvider.AUTH_SERVICE_BUNDLE, bundle); |
| authenticationInfo.put(ResourceResolverFactory.SUBSERVICE, "hierarchy"); |
| this.resourceTypeResourceResolver = factory.getServiceResourceResolver(authenticationInfo); |
| } catch (final LoginException e) { |
| throw new IllegalStateException("Failed to create resource-type ResourceResolver", e); |
| } |
| } |
| return this.resourceTypeResourceResolver; |
| } |
| } |
| |
| /** |
| * Get the parent resource type |
| * |
| * @see org.apache.sling.api.resource.ResourceResolver#getParentResourceType(java.lang.String) |
| */ |
| public String getParentResourceType( |
| final ResourceResolverFactory factory, |
| final ResourceResolver resolver, |
| final String resourceType) { |
| // normalize resource type to a path string |
| final String rtPath = (resourceType == null ? null : ResourceUtil.resourceTypeToPath(resourceType)); |
| // get the resource type resource and check its super type |
| String resourceSuperType = null; |
| |
| if ( rtPath != null ) { |
| ResourceResolver adminResolver = this.getResourceTypeResourceResolver(factory, resolver); |
| if ( adminResolver != null ) { |
| final Resource rtResource = adminResolver.getResource(rtPath); |
| if (rtResource != null) { |
| resourceSuperType = rtResource.getResourceSuperType(); |
| } |
| } |
| } |
| return resourceSuperType; |
| } |
| |
| /** |
| * Returns {@link #getProperty(Resource, String, Class) getProperty(res, |
| * propName, String.class)} |
| * |
| * @param res The resource to access the property from |
| * @param propName The name of the property to access |
| * @return The property as a {@code String} or {@code null} if the property |
| * does not exist or cannot be converted into a {@code String} |
| */ |
| public static String getProperty(final Resource res, final String propName) { |
| return getProperty(res, propName, String.class); |
| } |
| |
| /** |
| * Returns the value of the name property of the resource converted to the |
| * requested {@code type}. |
| * <p> |
| * If the resource itself does not have the property, the property is looked |
| * up in the {@code jcr:content} child node. This access is done through the |
| * same {@code ValueMap} as is used to access the property directly. This |
| * generally only works for JCR based {@code ValueMap} instances which |
| * provide access to relative path property names. This may not work in non |
| * JCR {@code ValueMap}, however in non JCR envs there is usually no |
| * "jcr:content" child node anyway |
| * |
| * @param res The resource to access the property from |
| * @param propName The name of the property to access |
| * @param type The type into which to convert the property |
| * @return The property converted to the requested {@code type} or |
| * {@code null} if the property does not exist or cannot be |
| * converted into the requested {@code type} |
| */ |
| public static <Type> Type getProperty(final Resource res, final String propName, final Class<Type> type) { |
| |
| // check the property in the resource itself |
| final ValueMap props = res.adaptTo(ValueMap.class); |
| if (props != null) { |
| Type prop = props.get(propName, type); |
| if (prop != null) { |
| return prop; |
| } |
| // otherwise, check it in the jcr:content child resource |
| // This is a special case checking for JCR based resources |
| // we directly use the deep resolution of properties of the |
| // JCR value map implementation - this does not work |
| // in non JCR environments, however in non JCR envs there |
| // is usually no "jcr:content" child node anyway |
| prop = props.get("jcr:content/" + propName, type); |
| return prop; |
| } |
| |
| return null; |
| } |
| |
| public void registerAuthenticatedProvider(@NotNull ResourceProviderHandler handler, |
| @Nullable Object providerState) { |
| this.authenticatedProviders.put(handler, providerState); |
| } |
| |
| public void clearAuthenticatedProviders() { |
| this.authenticatedProviders.clear(); |
| } |
| } |