/* * 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 javax.annotation.CheckForNull; import javax.annotation.Nonnull; 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.spi.resource.provider.ResourceProvider; 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. */ public void refresh(@Nonnull final ResourceResolverContext context) { for (final AuthenticatedResourceProvider p : context.getProviderManager().getAllUsedRefreshable()) { p.refresh(); } } /** * Returns {@code true} if all providers are live. */ public boolean isLive(@Nonnull 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(@Nonnull final ResourceResolverContext context, @Nonnull final String parentPath, @Nonnull 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 checkSourceAndDest(final ResourceResolverContext context, final String srcAbsPath, final String destAbsPath) throws PersistenceException { // check source final Node<ResourceProviderHandler> srcNode = getResourceProviderStorage().getTree().getBestMatchingNode(srcAbsPath); if ( srcNode == null ) { throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null); } AuthenticatedResourceProvider srcProvider = null; try { srcProvider = context.getProviderManager().getOrCreateProvider(srcNode.getValue(), this); } catch (LoginException e) { // ignore } if ( srcProvider == null ) { throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null); } final Resource srcResource = srcProvider.getResource(srcAbsPath, null, null); if ( srcResource == null ) { throw new PersistenceException("Source resource does not exist.", null, srcAbsPath, null); } // check destination final Node<ResourceProviderHandler> destNode = getResourceProviderStorage().getTree().getBestMatchingNode(destAbsPath); if ( destNode == null ) { throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null); } AuthenticatedResourceProvider destProvider = null; try { destProvider = context.getProviderManager().getOrCreateProvider(destNode.getValue(), this); } catch (LoginException e) { // ignore } if ( destProvider == null ) { throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null); } final Resource destResource = destProvider.getResource(destAbsPath, null, null); if ( destResource == null ) { throw new PersistenceException("Destination resource does not exist.", null, destAbsPath, null); } // check for sub providers of src and dest if ( srcProvider == destProvider && !collectProviders(context, srcNode) && !collectProviders(context, destNode) ) { return srcProvider; } return null; } private boolean collectProviders(final ResourceResolverContext context, final Node<ResourceProviderHandler> parent) { boolean hasMoreProviders = false; for (final Entry<String, Node<ResourceProviderHandler>> entry : parent.getChildren().entrySet()) { if ( entry.getValue().getValue() != null ) { try { context.getProviderManager().getOrCreateProvider(entry.getValue().getValue(), this); hasMoreProviders = true; } catch ( final LoginException ignore) { // ignore } } if ( collectProviders(context, entry.getValue())) { hasMoreProviders = true; } } return hasMoreProviders; } 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 @CheckForNull 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 @CheckForNull 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(@Nonnull ResourceProviderHandler handler, @CheckForNull Object providerState) { this.authenticatedProviders.put(handler, providerState); } public void clearAuthenticatedProviders() { this.authenticatedProviders.clear(); } }