blob: fa71d15aa004861c66890b122db824af42f6ddf1 [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.mapping;
import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_ALIAS;
import static org.apache.sling.spi.resource.provider.ResourceProvider.PROPERTY_NAME;
import static org.apache.sling.spi.resource.provider.ResourceProvider.PROPERTY_ROOT;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.sling.api.resource.LoginException;
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.mapping.PathToUriMappingService;
import org.apache.sling.api.resource.mapping.ResourceMapper;
import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker;
import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator;
import org.apache.sling.resourceresolver.util.MockTestUtil;
import org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
/**
* Validates that the {@link ResourceMapperImpl} correctly queries all sources of mappings
*
* <p>This test ensures that in case more than one mappings is possible, for instance:
*
* <ol>
* <li>path for an existing resource</li>
* <li>alias</li>
* <li>/etc/map entries</li>
* </ol>
*
* all are correctly considered and included in the relevant method calls.
* </p>
*
* <p>This test should not exhaustively test all mapping scenarios, other tests in this
* module and the Sling ITs cover that.</p>
*
*/
@RunWith(Parameterized.class)
public class ResourceMapperImplTest {
@Parameters(name="optimized alias resolution → {0}")
public static Object[] data() {
return new Object[] { false, true};
}
@Rule
public final OsgiContext ctx = new OsgiContext();
private final boolean optimiseAliasResolution;
private HttpServletRequest req;
private ResourceResolver resolver;
public ResourceMapperImplTest(boolean optimiseAliasResolution) {
this.optimiseAliasResolution = optimiseAliasResolution;
}
@Before
public void prepare() throws LoginException {
ctx.registerInjectActivateService(new ServiceUserMapperImpl());
ctx.registerInjectActivateService(new ResourceAccessSecurityTracker());
ctx.registerInjectActivateService(new StringInterpolationProviderImpl());
ctx.registerService(PathToUriMappingService.class, MockTestUtil.createPathToUriMappingServiceMock(resolver));
InMemoryResourceProvider resourceProvider = new InMemoryResourceProvider();
resourceProvider.putResource("/"); // root
resourceProvider.putResource("/here"); // regular page
resourceProvider.putResource("/there", PROP_ALIAS, "alias-value"); // with alias
resourceProvider.putResource("/there-multiple", PROP_ALIAS, "alias-value-3", "alias-value-4"); // with multivalued alias
resourceProvider.putResource("/somewhere", PROP_ALIAS, "alias-value-2"); // with alias and also /etc/map
resourceProvider.putResource("/there/that"); // parent has alias
resourceProvider.putResource("/content");
resourceProvider.putResource("/content/virtual");
resourceProvider.putResource("/content/virtual/foo"); // matches virtual.host.com.80 mapping entry
resourceProvider.putResource("/parent", PROP_ALIAS, "alias-parent"); // parent has alias
resourceProvider.putResource("/parent/child", PROP_ALIAS, "alias-child"); // child has alias
resourceProvider.putResource("/parent/child-multiple", PROP_ALIAS, "alias-child-1", "alias-child-2"); // child has multiple alias
resourceProvider.putResource("/vain", "sling:vanityPath", "/vanity-a", "/vanity-b"); // vanity path
// build /etc/map structure
resourceProvider.putResource("/etc");
resourceProvider.putResource("/etc/map");
resourceProvider.putResource("/etc/map/http");
resourceProvider.putResource("/etc/map/http/localhost_any",
"sling:internalRedirect", "/somewhere",
"sling:match", "localhost.8080/everywhere");
resourceProvider.putResource("/etc/map/http/virtual.host.com.80",
"sling:internalRedirect", "/content/virtual");
// we fake the fact that we are the JCR resource provider since it's the required one
ctx.registerService(ResourceProvider.class, resourceProvider, PROPERTY_ROOT, "/", PROPERTY_NAME, "JCR");
// disable optimised alias resolution as it relies on JCR queries
ctx.registerInjectActivateService(new ResourceResolverFactoryActivator(),
"resource.resolver.optimize.alias.resolution", optimiseAliasResolution);
ResourceResolverFactory factory = ctx.getService(ResourceResolverFactory.class);
assertNotNull(factory);
resolver = factory.getResourceResolver(null);
req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("localhost");
when(req.getServerPort()).thenReturn(8080);
when(req.getContextPath()).thenReturn("/app");
}
@After
public void cleanup() {
if ( resolver != null )
resolver.close();
}
/**
* Validates that mappings for an empty return the root path
*
* @throws LoginException
*/
@Test
public void mapNonExistingEmptyPath() throws LoginException {
ExpectedMappings.nonExistingResource("")
.singleMapping("/")
.singleMappingWithRequest("/app/")
.allMappings("/")
.allMappingsWithRequest("/app/")
.verify(resolver, req);
}
/**
* Validates that mappings for a non-existing resource only contain that resource's path
*
* @throws LoginException
*/
@Test
public void mapNonExistingPath() throws LoginException {
ExpectedMappings.nonExistingResource("/not-here")
.singleMapping("/not-here")
.singleMappingWithRequest("/app/not-here")
.allMappings("/not-here")
.allMappingsWithRequest("/app/not-here")
.verify(resolver, req);
}
/**
* Validates that mappings for an existing resource only contain that resource's path
*
* @throws LoginException
*/
@Test
public void mapExistingPath() throws LoginException {
ExpectedMappings.existingResource("/here")
.singleMapping("/here")
.singleMappingWithRequest("/app/here")
.allMappings("/here")
.allMappingsWithRequest("/app/here")
.verify(resolver, req);
}
/**
* Validates that mappings for a existing resource with an alias contain the alias and the resource's path
*
* @throws LoginException
*/
@Test
public void mapResourceWithAlias() {
ExpectedMappings.existingResource("/there")
.singleMapping("/alias-value")
.singleMappingWithRequest("/app/alias-value")
.allMappings("/alias-value", "/there")
.allMappingsWithRequest("/app/alias-value", "/app/there")
.verify(resolver, req);
}
/**
* Validates that mappings for a existing resource with multiple alias contain the alias and the resource's path
*
* @throws LoginException
*/
@Test
public void mapResourceWithMultivaluedAlias() {
ExpectedMappings.existingResource("/there-multiple")
.singleMapping("/alias-value-3")
.singleMappingWithRequest("/app/alias-value-3")
.allMappings("/alias-value-3", "/alias-value-4", "/there-multiple")
.allMappingsWithRequest("/app/alias-value-3", "/app/alias-value-4", "/app/there-multiple")
.verify(resolver, req);
}
/**
* Validates that mappings for a existing resource with an alias and /etc/map entry
* contain the /etc/map entry, the alias and the resource's path
*
* @throws LoginException
*/
@Test
public void mapResourceWithAliasAndEtcMap() {
ExpectedMappings.existingResource("/somewhere")
.singleMapping("/alias-value-2")
.singleMappingWithRequest("/app/alias-value-2")
.allMappings("/alias-value-2", "http://localhost:8080/everywhere", "/somewhere")
.allMappingsWithRequest("/app/alias-value-2", "/app/everywhere", "/app/somewhere")
.verify(resolver, req);
}
/**
* Validates that a resource with an alias on parent has the parent path set
* to the alias value
*
* @throws LoginException
*/
@Test
public void mapResourceWithAliasOnParent() {
ExpectedMappings.existingResource("/there/that")
.singleMapping("/alias-value/that")
.singleMappingWithRequest("/app/alias-value/that")
.allMappings("/alias-value/that", "/there/that")
.allMappingsWithRequest("/app/alias-value/that","/app/there/that")
.verify(resolver, req);
}
@Test
public void priorityForVHostMappings() {
// override the default request
req = mock(HttpServletRequest.class);
when(req.getScheme()).thenReturn("http");
when(req.getServerName()).thenReturn("virtual.host.com");
when(req.getServerPort()).thenReturn(-1);
when(req.getContextPath()).thenReturn("");
when(req.getPathInfo()).thenReturn(null);
ExpectedMappings.existingResource("/content/virtual/foo")
.singleMapping("http://virtual.host.com/foo")
.singleMappingWithRequest("/foo")
.allMappings("http://virtual.host.com/foo", "/content/virtual/foo")
.allMappingsWithRequest("/foo", "/content/virtual/foo")
.verify(resolver, req);
}
/**
* Validates that a resource with an alias on parent and on child
*
* @throws LoginException
*/
@Test
public void mapResourceWithNestedAlias() {
ExpectedMappings.existingResource("/parent/child")
.singleMapping("/alias-parent/alias-child")
.singleMappingWithRequest("/app/alias-parent/alias-child")
.allMappings("/alias-parent/alias-child", "/alias-parent/child", "/parent/alias-child", "/parent/child")
.allMappingsWithRequest("/app/alias-parent/alias-child", "/app/alias-parent/child", "/app/parent/alias-child", "/app/parent/child")
.verify(resolver, req);
}
/**
* Validates that a resource with an alias on parent and multiple alias on child
*
* @throws LoginException
*/
@Test
public void mapResourceWithNestedMultipleAlias() {
ExpectedMappings.existingResource("/parent/child-multiple")
.singleMapping("/alias-parent/alias-child-1")
.singleMappingWithRequest("/app/alias-parent/alias-child-1")
.allMappings("/alias-parent/alias-child-1", "/alias-parent/alias-child-2", "/alias-parent/child-multiple", "/parent/alias-child-1", "/parent/alias-child-2", "/parent/child-multiple")
.allMappingsWithRequest("/app/alias-parent/alias-child-1", "/app/alias-parent/alias-child-2", "/app/alias-parent/child-multiple", "/app/parent/alias-child-1", "/app/parent/alias-child-2", "/app/parent/child-multiple")
.verify(resolver, req);
}
/**
* Validates that vanity paths are returned as mappings
*
* <p>As vanity paths are alternate paths rather than variations so they will not be returned
* from the singleMapping() methods.</p>
*/
@Test
public void mapResourceWithVanityPaths() {
ExpectedMappings.existingResource("/vain")
.singleMapping("/vain")
.singleMappingWithRequest("/app/vain")
.allMappings("/vanity-a", "/vanity-b", "/vain")
.allMappingsWithRequest("/app/vanity-a", "/app/vanity-b", "/app/vain")
.verify(resolver, req);
}
static class ExpectedMappings {
public static ExpectedMappings existingResource(String path) {
return new ExpectedMappings(path, true);
}
public static ExpectedMappings nonExistingResource(String path) {
return new ExpectedMappings(path, false);
}
private final String path;
private final boolean exists;
private String singleMapping;
private String singleMappingWithRequest;
private Set<String> allMappings;
private Set<String> allMappingsWithRequest;
private ExpectedMappings(String path, boolean exists) {
this.path = path;
this.exists = exists;
}
public ExpectedMappings singleMapping(String singleMapping) {
this.singleMapping = singleMapping;
return this;
}
public ExpectedMappings singleMappingWithRequest(String singleMappingWithRequest) {
this.singleMappingWithRequest = singleMappingWithRequest;
return this;
}
public ExpectedMappings allMappings(String... allMappings) {
this.allMappings = new HashSet<>(Arrays.asList(allMappings));
return this;
}
public ExpectedMappings allMappingsWithRequest(String... allMappingsWithRequest) {
this.allMappingsWithRequest = new HashSet<>(Arrays.asList(allMappingsWithRequest));
return this;
}
public void verify(ResourceResolver resolver, HttpServletRequest request) {
checkConfigured();
Resource res = resolver.getResource(path);
if ( exists ) {
assertThat("Resource was null but should exist", res, notNullValue());
assertThat("Resource is non-existing but should exist", ResourceUtil.isNonExistingResource(res), is(false));
} else {
assertThat("Resource is neither null nor non-existing", res == null || ResourceUtil.isNonExistingResource(res), is(true));
}
// downcast to ensure we're testing the right class
ResourceMapperImpl mapper = (ResourceMapperImpl) resolver.adaptTo(ResourceMapper.class);
assertThat("Single mapping without request", mapper.getMapping(path), is(singleMapping));
if ( !path.isEmpty() ) // an empty path is invalid, hence not testing with a request
assertThat("Single mapping with request", mapper.getMapping(path, request), is(singleMappingWithRequest));
assertThat("All mappings without request", mapper.getAllMappings(path), is(allMappings));
if ( !path.isEmpty() ) // an empty path is invalid, hence not testing with a request
assertThat("All mappings with request", mapper.getAllMappings(path, request), is(allMappingsWithRequest));
}
private void checkConfigured() {
if ( singleMapping == null )
throw new IllegalStateException("singleMapping is null");
if ( singleMappingWithRequest == null )
throw new IllegalStateException("singleMappingWithRequest is null");
if ( allMappings == null )
throw new IllegalStateException("allMappings is null");
if ( allMappingsWithRequest == null )
throw new IllegalStateException("allMappingsWithRequest is null");
}
}
}