blob: d63ca1494718cb981deb625ff972917aa11ed7a3 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.resourceresolver.impl;
import static java.util.Arrays.asList;
import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import org.apache.sling.api.SlingException;
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.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.resourceresolver.impl.mappingchain.ResourceUriMappingChain;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderHandler;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderStorage;
import org.apache.sling.resourceresolver.impl.providers.ResourceProviderTracker;
import org.apache.sling.spi.resource.provider.ResolveContext;
import org.apache.sling.spi.resource.provider.ResourceContext;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.util.reflection.Whitebox;
import org.osgi.framework.Bundle;
public class ResourceResolverImplTest {
private CommonResourceResolverFactoryImpl commonFactory;
private ResourceResolver resResolver;
private ResourceResolverFactoryImpl resFac;
private ResourceProviderTracker resourceProviderTracker;
@Before public void setup() throws LoginException {
ResourceProvider<?> rp = new ResourceProvider<Object>() {
@Override
public Resource getResource(ResolveContext<Object> ctx, String path, ResourceContext rCtx, Resource parent) {
return null;
}
@Override
public Iterator<Resource> listChildren(ResolveContext<Object> ctx, Resource parent) {
return null;
}
};
List<ResourceProviderHandler> handlers = asList(createRPHandler(rp, "rp1", 0, "/"));
resourceProviderTracker = mock(ResourceProviderTracker.class);
ResourceProviderStorage storage = new ResourceProviderStorage(handlers);
when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage);
ResourceResolverFactoryActivator activator = new ResourceResolverFactoryActivator();
activator.resourceProviderTracker = resourceProviderTracker;
activator.resourceAccessSecurityTracker = new ResourceAccessSecurityTracker();
activator.resourceUriMappingChain = new ResourceUriMappingChain();
commonFactory = new CommonResourceResolverFactoryImpl(activator);
final Bundle usingBundle = mock(Bundle.class);
resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null);
resResolver = resFac.getAdministrativeResourceResolver(null);
}
@SuppressWarnings("deprecation")
@Test public void testClose() throws Exception {
final ResourceResolver rr = new ResourceResolverImpl(commonFactory, false, null, resourceProviderTracker);
assertTrue(rr.isLive());
rr.close();
assertFalse(rr.isLive());
// close is always allowed to be called
rr.close();
assertFalse(rr.isLive());
// now check all public method - they should all throw!
try {
rr.adaptTo(Session.class);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.clone(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.findResources("a", "b");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getAttribute("a");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getAttributeNames();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getResource(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getResource(null, "/a");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getSearchPath();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getUserID();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.listChildren(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.map("/somepath");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.map(null, "/somepath");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.queryResources("a", "b");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve((HttpServletRequest)null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve("/path");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve(null, "/path");
fail();
} catch (final IllegalStateException ise) {
// expected
}
}
@SuppressWarnings("deprecation")
@Test
public void testCloseWithStackTraceLogging() throws Exception {
ResourceResolverFactoryConfig config = mock(ResourceResolverFactoryConfig.class);
when(config.resource_resolver_log_closing()).thenReturn(true);
ResourceResolverFactoryActivator rrfa = spy(new ResourceResolverFactoryActivator());
Whitebox.setInternalState(rrfa, "config", config);
CommonResourceResolverFactoryImpl crrfi = new CommonResourceResolverFactoryImpl(rrfa);
final ResourceResolver rr = new ResourceResolverImpl(crrfi, false, null, resourceProviderTracker);
assertTrue(rr.isLive());
rr.close();
assertFalse(rr.isLive());
// close is always allowed to be called
rr.close();
assertFalse(rr.isLive());
// now check all public method - they should all throw!
try {
rr.adaptTo(Session.class);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.clone(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.findResources("a", "b");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getAttribute("a");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getAttributeNames();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getResource(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getResource(null, "/a");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getSearchPath();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.getUserID();
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.listChildren(null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.map("/somepath");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.map(null, "/somepath");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.queryResources("a", "b");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve((HttpServletRequest)null);
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve("/path");
fail();
} catch (final IllegalStateException ise) {
// expected
}
try {
rr.resolve(null, "/path");
fail();
} catch (final IllegalStateException ise) {
// expected
}
}
@SuppressWarnings("deprecation")
@Test public void testBasicAPIAssumptions() throws Exception {
// null resource is accessing /, which exists of course
final Resource res00 = resResolver.resolve((String) null);
assertNotNull(res00);
assertTrue("Resource must be NonExistingResource",
res00 instanceof NonExistingResource);
assertEquals("Null path is expected to return root", "/",
res00.getPath());
// relative paths are treated as if absolute
final String path01 = "relPath/relPath";
final Resource res01 = resResolver.resolve(path01);
assertNotNull(res01);
assertEquals("Expecting absolute path for relative path", "/" + path01,
res01.getPath());
assertTrue("Resource must be NonExistingResource",
res01 instanceof NonExistingResource);
final String no_resource_path = "/no_resource/at/this/location";
final Resource res02 = resResolver.resolve(no_resource_path);
assertNotNull(res02);
assertEquals("Expecting absolute path for relative path",
no_resource_path, res02.getPath());
assertTrue("Resource must be NonExistingResource",
res01 instanceof NonExistingResource);
try {
resResolver.resolve((HttpServletRequest) null);
fail("Expected NullPointerException trying to resolve null request");
} catch (NullPointerException npe) {
// expected
}
final Resource res0 = resResolver.resolve(null, no_resource_path);
assertNotNull("Expecting resource if resolution fails", res0);
assertTrue("Resource must be NonExistingResource",
res0 instanceof NonExistingResource);
assertEquals("Path must be the original path", no_resource_path,
res0.getPath());
final HttpServletRequest req1 = mock(HttpServletRequest.class);
when(req1.getProtocol()).thenReturn("http");
when(req1.getServerName()).thenReturn("localhost");
when(req1.getPathInfo()).thenReturn(no_resource_path);
final Resource res1 = resResolver.resolve(req1);
assertNotNull("Expecting resource if resolution fails", res1);
assertTrue("Resource must be NonExistingResource",
res1 instanceof NonExistingResource);
assertEquals("Path must be the original path", no_resource_path,
res1.getPath());
final HttpServletRequest req2 = mock(HttpServletRequest.class);
when(req2.getProtocol()).thenReturn("http");
when(req2.getServerName()).thenReturn("localhost");
when(req2.getPathInfo()).thenReturn(null);
final Resource res2 = resResolver.resolve(req2);
assertNotNull("Expecting resource if resolution fails", res2);
assertTrue("Resource must be NonExistingResource",
res2 instanceof NonExistingResource);
assertEquals("Path must be the the root path", "/", res2.getPath());
final Resource res3 = resResolver.getResource(null);
assertNull("Expected null resource for null path", res3);
final Resource res4 = resResolver.getResource(null, null);
assertNull("Expected null resource for null path", res4);
final Resource res5 = resResolver.getResource(res01, null);
assertNull("Expected null resource for null path", res5);
}
@Test public void test_clone_based_on_anonymous() throws Exception {
final ResourceResolver anon0 = resFac.getResourceResolver((Map<String, Object>) null);
// no session
final Session anon0Session = anon0.adaptTo(Session.class);
assertNull("Session should not be available", anon0Session);
// no user information, so user id is null
assertEquals(null, anon0.getUserID());
// same user and workspace
final ResourceResolver anon1 = anon0.clone(null);
final Session anon1Session = anon1.adaptTo(Session.class);
assertEquals(anon0.getUserID(), anon1.getUserID());
assertNull("Session should not be available", anon1Session);
anon1.close();
// same workspace but admin user
final Map<String, Object> admin0Cred = new HashMap<>();
admin0Cred.put(ResourceResolverFactory.USER, "admin");
admin0Cred.put(ResourceResolverFactory.PASSWORD, "admin".toCharArray());
final ResourceResolver admin0 = anon0.clone(admin0Cred);
assertEquals("admin", admin0.getUserID());
admin0.close();
anon0.close();
}
@Test public void test_clone_based_on_admin() throws Exception {
final ResourceResolver admin0 = resFac.getAdministrativeResourceResolver((Map<String, Object>) null);
// no user information, so user id is null
assertEquals(null, admin0.getUserID());
// same user and workspace
final ResourceResolver admin1 = admin0.clone(null);
assertEquals(admin0.getUserID(), admin1.getUserID());
admin1.close();
// same workspace but anonymous user
final Map<String, Object> anon0Cred = new HashMap<>();
anon0Cred.put(ResourceResolverFactory.USER, "anonymous");
final ResourceResolver anon0 = admin0.clone(anon0Cred);
assertEquals("anonymous", anon0.getUserID());
anon0.close();
admin0.close();
}
@Test public void test_attributes_from_authInfo() throws Exception {
final Map<String, Object> authInfo = new HashMap<>();
authInfo.put(ResourceResolverFactory.USER, "admin");
authInfo.put(ResourceResolverFactory.PASSWORD, "admin".toCharArray());
authInfo.put("testAttributeString", "AStringValue");
authInfo.put("testAttributeNumber", 999);
final ResourceResolver rr = resFac.getResourceResolver(authInfo);
assertEquals("AStringValue", rr.getAttribute("testAttributeString"));
assertEquals(999, rr.getAttribute("testAttributeNumber"));
assertEquals("admin", rr.getAttribute(ResourceResolverFactory.USER));
assertNull(rr.getAttribute(ResourceResolverFactory.PASSWORD));
final HashSet<String> validNames = new HashSet<>();
validNames.add(ResourceResolverFactory.USER);
validNames.add("testAttributeString");
validNames.add("testAttributeNumber");
final Iterator<String> names = rr.getAttributeNames();
assertTrue(validNames.remove(names.next()));
assertTrue(validNames.remove(names.next()));
assertTrue(validNames.remove(names.next()));
assertFalse("Expect no more names", names.hasNext());
assertTrue("Expect validNames set to be empty now",
validNames.isEmpty());
rr.close();
}
@Test public void testBasicCrud() throws Exception {
final Resource r = mock(Resource.class);
when(r.getPath()).thenReturn("/some");
try {
this.resResolver.create(null, "a", null);
fail("Null parent resource should throw NPE");
} catch (final NullPointerException npe) {
// correct
}
try {
this.resResolver.create(r, null, null);
fail("Null name should throw NPE");
} catch (final NullPointerException npe) {
// correct
}
try {
this.resResolver.create(r, "a/b", null);
fail("Slash in name should throw illegal argument exception");
} catch (final IllegalArgumentException pe) {
// correct
}
try {
this.resResolver.create(r, "a", null);
fail("This should be unsupported.");
} catch (final PersistenceException uoe) {
// correct
}
}
@Test public void test_getResourceSuperType() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver();
// the resources to test
final Resource r = resolver.add(new SyntheticResource(resolver, "/a", "a:b"));
final Resource r2 = resolver.add(new SyntheticResource(resolver, "/a2", "a:c"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/a/b", "x:y", "t:c"));
assertEquals("t:c", resolver.getParentResourceType(r.getResourceType()));
assertNull(resolver.getParentResourceType(r2.getResourceType()));
}
@Test public void testIsResourceType() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver();
final Resource r = resolver.add(new SyntheticResourceWithSupertype(resolver, "/a", "a:b", "d:e"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/d/e", "x:y", "t:c"));
assertTrue(resolver.isResourceType(r, "a:b"));
assertTrue(resolver.isResourceType(r, "d:e"));
assertFalse(resolver.isResourceType(r, "x:y"));
assertTrue(resolver.isResourceType(r, "t:c"));
assertFalse(resolver.isResourceType(r, "h:p"));
}
@Test public void testIsResourceTypeWithPaths() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver();
/**
* prepare resource type hierarchy
* /types/1
* +- /types/2
* +- /types/3
*/
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/1", "/types/component", "/types/2"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/2", "/types/component", "/types/3"));
resolver.add(new SyntheticResource(resolver, "/types/3", "/types/component"));
Resource resourceT1 = resolver.add(new SyntheticResource(resolver, "/resourceT1", "/types/1"));
Resource resourceT2 = resolver.add(new SyntheticResource(resolver, "/resourceT2", "/types/2"));
Resource resourceT3 = resolver.add(new SyntheticResource(resolver, "/resourceT3", "/types/3"));
assertTrue(resolver.isResourceType(resourceT1, "/types/1"));
assertTrue(resolver.isResourceType(resourceT1, "/types/2"));
assertTrue(resolver.isResourceType(resourceT1, "/types/3"));
assertFalse(resolver.isResourceType(resourceT1, "/types/component"));
assertFalse(resolver.isResourceType(resourceT1, "/types/unknown"));
assertFalse(resolver.isResourceType(resourceT2, "/types/1"));
assertTrue(resolver.isResourceType(resourceT2, "/types/2"));
assertTrue(resolver.isResourceType(resourceT2, "/types/3"));
assertFalse(resolver.isResourceType(resourceT2, "/types/component"));
assertFalse(resolver.isResourceType(resourceT2, "/types/unknown"));
assertFalse(resolver.isResourceType(resourceT3, "/types/1"));
assertFalse(resolver.isResourceType(resourceT3, "/types/2"));
assertTrue(resolver.isResourceType(resourceT3, "/types/3"));
assertFalse(resolver.isResourceType(resourceT3, "/types/component"));
assertFalse(resolver.isResourceType(resourceT3, "/types/unknown"));
}
/**
* @see <a href="https://issues.apache.org/jira/browse/SLING-6327">SLING-6327</a>
*/
@Test public void testIsResourceTypeWithMixedAbsoluteAndRelativePaths() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver(new String[] {"/apps/", "/libs/"});
Resource resourceT1 = resolver.add(new SyntheticResource(resolver, "/resourceT1", "types/1"));
Resource resourceT2 = resolver.add(new SyntheticResource(resolver, "/resourceT2", "/apps/types/2"));
Resource resourceT3 = resolver.add(new SyntheticResource(resolver, "/resourceT3", "/libs/types/3"));
Resource resourceT4 = resolver.add(new SyntheticResource(resolver, "/resourceT3", "/someprefix/types/4"));
assertTrue(resolver.isResourceType(resourceT1, "/libs/types/1"));
assertTrue(resolver.isResourceType(resourceT1, "/apps/types/1"));
assertTrue(resolver.isResourceType(resourceT1, "types/1"));
assertTrue(resolver.isResourceType(resourceT2, "/apps/types/2"));
assertTrue(resolver.isResourceType(resourceT2, "types/2"));
assertTrue(resolver.isResourceType(resourceT2, "/libs/types/2"));
assertTrue(resolver.isResourceType(resourceT3, "/apps/types/3"));
assertTrue(resolver.isResourceType(resourceT3, "types/3"));
assertTrue(resolver.isResourceType(resourceT3, "/libs/types/3"));
assertFalse(resolver.isResourceType(resourceT4, "/apps/types/4"));
assertFalse(resolver.isResourceType(resourceT4, "types/4"));
assertFalse(resolver.isResourceType(resourceT4, "/libs/types/4"));
assertTrue(resolver.isResourceType(resourceT4, "/someprefix/types/4"));
}
@Test(expected=SlingException.class) public void testIsResourceCyclicHierarchyDirect() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver();
/**
* prepare resource type hierarchy
* /types/1 <---+
* +- /types/2 -+
*/
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/1", "/types/component", "/types/2"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/2", "/types/component", "/types/1"));
Resource resource = resolver.add(new SyntheticResource(resolver, "/resourceT1", "/types/1"));
assertTrue(resolver.isResourceType(resource, "/types/1"));
assertTrue(resolver.isResourceType(resource, "/types/2"));
// this should throw a SlingException when detecting the cyclic hierarchy
resolver.isResourceType(resource, "/types/unknown");
}
@Test(expected=SlingException.class) public void testIsResourceCyclicHierarchyIndirect() {
final PathBasedResourceResolverImpl resolver = getPathBasedResourceResolver();
/**
* prepare resource type hierarchy
* /types/1 <----+
* +- /types/2 |
* +- /types/3 -+
*/
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/1", "/types/component", "/types/2"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/2", "/types/component", "/types/3"));
resolver.add(new SyntheticResourceWithSupertype(resolver, "/types/3", "/types/component", "/types/1"));
Resource resource = resolver.add(new SyntheticResource(resolver, "/resourceT1", "/types/1"));
assertTrue(resolver.isResourceType(resource, "/types/1"));
assertTrue(resolver.isResourceType(resource, "/types/2"));
assertTrue(resolver.isResourceType(resource, "/types/3"));
// this should throw a SlingException when detecting the cyclic hierarchy
resolver.isResourceType(resource, "/types/unknown");
}
private PathBasedResourceResolverImpl getPathBasedResourceResolver() {
return getPathBasedResourceResolver(new String[] {""});
}
private PathBasedResourceResolverImpl getPathBasedResourceResolver(String[] searchPaths) {
try {
final List<ResourceResolver> resolvers = new ArrayList<>();
final PathBasedResourceResolverImpl resolver = new PathBasedResourceResolverImpl(resolvers, resourceProviderTracker, searchPaths);
resolvers.add(resolver);
return resolver;
}
catch (LoginException ex) {
throw new RuntimeException(ex);
}
}
private static class PathBasedResourceResolverImpl extends ResourceResolverImpl {
private final Map<String, Resource> resources = new HashMap<>();
public PathBasedResourceResolverImpl(final List<ResourceResolver> resolvers, final ResourceProviderTracker resourceProviderTracker, final String[] searchPaths) throws LoginException {
this(new CommonResourceResolverFactoryImpl(new ResourceResolverFactoryActivator()) {
@Override
public ResourceResolver getAdministrativeResourceResolver(
Map<String, Object> authenticationInfo) throws LoginException {
return resolvers.get(0);
}
@Override
public ResourceResolver getServiceResourceResolver(
Map<String, Object> authenticationInfo) throws LoginException {
return resolvers.get(0);
}
@Override
public List<String> getSearchPath() {
return Arrays.asList(searchPaths);
}
}, resourceProviderTracker);
}
public PathBasedResourceResolverImpl(CommonResourceResolverFactoryImpl factory, ResourceProviderTracker resourceProviderTracker) throws LoginException {
super(factory, false, null, resourceProviderTracker);
}
public Resource add(final Resource r) {
this.resources.put(r.getPath(), r);
return r;
}
@Override
public Resource getResource(final String path) {
final String p = (path.startsWith("/") ? path : "/" + path);
return this.resources.get(p);
}
}
private static class SyntheticResourceWithSupertype extends SyntheticResource {
private final String resourceSuperType;
public SyntheticResourceWithSupertype(ResourceResolver resourceResolver, String path,
String resourceType, String resourceSuperType) {
super(resourceResolver, path, resourceType);
this.resourceSuperType = resourceSuperType;
}
@Override
public String getResourceSuperType() {
return this.resourceSuperType;
}
}
}