This is an automated email from the ASF dual-hosted git repository. andysch pushed a commit to branch feature/SLING-7768 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-resourceresolver.git
commit 7e03737261186acd3d5b381d44543f249ef4af26 Author: Andreas Schaefer <[email protected]> AuthorDate: Fri Jul 27 11:55:51 2018 -0700 Adding Resource Resolver based path resolution tests and revamping some of the unit tests to consolidate them --- .../impl/MockedResourceResolverImplTest.java | 21 +- .../mapping/AbstractMappingMapEntriesTest.java | 315 +++++++++++++++++++++ .../impl/mapping/EtcMappingMapEntriesTest.java | 252 +++++++++++++++++ .../impl/mapping/MapEntriesTest.java | 177 ++---------- .../mapping/StringInterpolationMapEntriesTest.java | 173 ++--------- .../sling/resourceresolver/util/MockTestUtil.java | 124 ++++++++ 6 files changed, 754 insertions(+), 308 deletions(-) diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java index 330b103..d84b714 100644 --- a/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java +++ b/src/test/java/org/apache/sling/resourceresolver/impl/MockedResourceResolverImplTest.java @@ -17,6 +17,7 @@ */ package org.apache.sling.resourceresolver.impl; +import static org.apache.sling.resourceresolver.util.MockTestUtil.getResourceName; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -405,7 +406,7 @@ public class MockedResourceResolverImplTest { @SuppressWarnings("unchecked") private Resource buildResource(String fullpath, Iterable<Resource> children, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) { Resource resource = Mockito.mock(Resource.class); - Mockito.when(resource.getName()).thenReturn(getName(fullpath)); + Mockito.when(resource.getName()).thenReturn(getResourceName(fullpath)); Mockito.when(resource.getPath()).thenReturn(fullpath); ResourceMetadata resourceMetadata = new ResourceMetadata(); Mockito.when(resource.getResourceMetadata()).thenReturn(resourceMetadata); @@ -434,15 +435,15 @@ public class MockedResourceResolverImplTest { } - /** - * extract the name from a path. - * @param fullpath - * @return - */ - private String getName(String fullpath) { - int n = fullpath.lastIndexOf("/"); - return fullpath.substring(n+1); - } +// /** +// * extract the name from a path. +// * @param fullpath +// * @return +// */ +// private String getName(String fullpath) { +// int n = fullpath.lastIndexOf("/"); +// return fullpath.substring(n+1); +// } /** * Test getting a resolver. diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java new file mode 100644 index 0000000..0f462e0 --- /dev/null +++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/AbstractMappingMapEntriesTest.java @@ -0,0 +1,315 @@ +/* + * 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 junit.framework.TestCase; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.resource.path.Path; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.service.event.EventAdmin; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.Semaphore; + +import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +/** + * These are tests that are testing the Sling Interpolation Feature (SLING-7768) + * on the MapEntries level + */ +public abstract class AbstractMappingMapEntriesTest { + static final String PROP_REG_EXP = "sling:match"; + + @Mock + MapConfigurationProvider resourceResolverFactory; + + @Mock + BundleContext bundleContext; + + @Mock + Bundle bundle; + + @Mock + EventAdmin eventAdmin; + + @Mock + ResourceResolver resourceResolver; + + @Mock + StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration; + + StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl(); + MapEntries mapEntries; + + File vanityBloomFilterFile; + + Resource map; + Resource http; + + Map<String, Map<String, String>> aliasMap; + + @SuppressWarnings({"unchecked"}) + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + + List<MapConfigurationProvider.VanityPathConfig> configs = getVanityPathConfigs(); + vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt"); + when(bundle.getSymbolicName()).thenReturn("TESTBUNDLE"); + when(bundleContext.getBundle()).thenReturn(bundle); + when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile); + when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver); + when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true); + when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs); + when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true); + when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true); + when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")}); + when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT); + when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L); + when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true); + when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn( + Collections.<Resource> emptySet().iterator()); + + map = setupEtcMapResource("/etc", "map"); + http = setupEtcMapResource("http", map); + + setupStringInterpolationProvider(new String[] {}); + mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider); + + final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap"); + aliasMapField.setAccessible(true); + this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries); + } + + List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() { + return new ArrayList<>(); + } + + @After + public void tearDown() throws Exception { + vanityBloomFilterFile.delete(); + } + + + // -------------------------- private methods ---------- + + ValueMap buildValueMap(Object... string) { + final Map<String, Object> data = new HashMap<>(); + for (int i = 0; i < string.length; i = i + 2) { + data.put((String) string[i], string[i+1]); + } + return new ValueMapDecorator(data); + } + + Resource getVanityPathResource(final String path) { + Resource rsrc = mock(Resource.class); + when(rsrc.getPath()).thenReturn(path); + when(rsrc.getName()).thenReturn(ResourceUtil.getName(path)); + when(rsrc.getValueMap()).thenReturn(buildValueMap("sling:vanityPath", "/vanity" + path)); + return rsrc; + } + + Resource setupEtcMapResource(String parentPath, String name, String...valueMapPairs) { + return setupEtcMapResource0(parentPath, name, null, valueMapPairs); + } + Resource setupEtcMapResource(String name, Resource parent, String...valueMapPairs) { + return setupEtcMapResource0(null, name, parent, valueMapPairs); + } + private Resource setupEtcMapResource0(String parentPath, String name, Resource parent, String...valueMapPairs) { + Resource resource = mock(Resource.class, withSettings().name(name).extraInterfaces(ResourceDecorator.class)); + String path = (parent == null ? parentPath : parent.getPath()) + "/" + name; + when(resource.getPath()).thenReturn(path); + when(resource.getName()).thenReturn(name); + ValueMap valueMap = buildValueMap(valueMapPairs); + when(resource.getValueMap()).thenReturn(valueMap); + when(resource.adaptTo(ValueMap.class)).thenReturn(valueMap); + when(resourceResolver.getResource(resource.getPath())).thenReturn(resource); + if(parent != null) { + List<Resource> childList = ((ResourceDecorator) parent).getChildrenList(); + childList.add(resource); + } + final List<Resource> childrenList = new ArrayList<>(); + when(((ResourceDecorator) resource).getChildrenList()).thenReturn(childrenList); + // Delay the children list iterator to make sure all children are added beforehand + // Iterators have a modCount that is set when created. Any changes to the underlying list will + // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception + when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() { + @Override + public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable { + return childrenList.iterator(); + } + }); + ResourceMetadata resourceMetadata = mock(ResourceMetadata.class); + when(resource.getResourceMetadata()).thenReturn(resourceMetadata); + doNothing().when(resourceMetadata).setResolutionPath(anyString()); + doNothing().when(resourceMetadata).setParameterMap(anyMap()); + + return resource; + } + + void setupStringInterpolationProvider(final String[] placeholderValues) { + when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(placeholderValues); + stringInterpolationProvider.activate(bundleContext, stringInterpolationProviderConfiguration); + } + + MapEntriesTest.DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) { + + Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() { + @Override + public Iterator<MapEntry> call() throws Exception { + return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath"); + } + }); + return new MapEntriesTest.DataFuture(future); + } + + void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException { + if (!sessionLock.tryAcquire()) { + fail("concurrent session access detected"); + } + try{ + Thread.sleep(1); + } finally { + sessionLock.release(); + } + } + + static class ExpectedEtcMapping { + List<ExpectedEtcMapEntry> expectedEtcMapEntries = new ArrayList<>(); + + public ExpectedEtcMapping() {} + + public ExpectedEtcMapping(String...expectedMapping) { + if(expectedMapping.length % 2 != 0) { + throw new IllegalArgumentException("Expect an even number of strings with pattern / redirect"); + } + int size = expectedMapping.length / 2; + for(int i = 0; i < size; i++ ) { + expectedEtcMapEntries.add(new ExpectedEtcMapEntry(expectedMapping[2 * i], expectedMapping[2 * i + 1])); + } + } + + public ExpectedEtcMapping addEtcMapEntry(String pattern, String redirect) { + addEtcMapEntry(pattern, false, redirect); + return this; + } + public ExpectedEtcMapping addEtcMapEntry(String pattern, boolean internal, String redirect) { + expectedEtcMapEntries.add(new ExpectedEtcMapEntry(pattern, internal, redirect)); + return this; + } + + public void assertEtcMap(String title, List<MapEntry> mapEntries) { + assertEquals("Wrong Number of Mappings for: " + title, expectedEtcMapEntries.size(), mapEntries.size()); + ArrayList<MapEntry> actual = new ArrayList<>(mapEntries); + ArrayList<ExpectedEtcMapEntry> expected = new ArrayList<>(expectedEtcMapEntries); + for(MapEntry actualMapEntry: actual) { + ExpectedEtcMapEntry expectedFound = null; + for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) { + if(expectedEtcMapEntry.pattern.equals(actualMapEntry.getPattern())) { + expectedFound = expectedEtcMapEntry; + break; + } + } + if(expectedFound == null) { + TestCase.fail("This pattern (" + actualMapEntry.getPattern() + ") is not expected for: " + title); + } + expectedFound.assertEtcMap(title, actualMapEntry); + expected.remove(expectedFound); + } + for(ExpectedEtcMapEntry expectedEtcMapEntry: expected) { + TestCase.fail("Expected Map Entry (" + expectedEtcMapEntry.pattern + ") not provided for: " + title); + } + } + } + + static class ExpectedEtcMapEntry { + private String pattern; + private boolean internal; + private String redirect; + + public ExpectedEtcMapEntry(String pattern, String redirect) { + this(pattern, false, redirect); + } + + public ExpectedEtcMapEntry(String pattern, boolean internal, String redirect) { + this.pattern = pattern; + this.internal = internal; + this.redirect = redirect; + } + + public void assertEtcMap(String title, MapEntry mapEntry) { + assertEquals("Wrong Pattern for " + title, pattern, mapEntry.getPattern()); + List<String> givenRedirects = new ArrayList<>(Arrays.asList(mapEntry.getRedirect())); + assertEquals("Wrong Number of Redirects for: " + title, 1, givenRedirects.size()); + assertEquals("Wrong Redirect for: " + title, this.redirect, givenRedirects.get(0)); + assertEquals("Wrong Redirect Type (ext/int) for: " + title, this.internal, mapEntry.isInternal()); + } + } + + /** + * Iterator to piggyback the list of Resources onto a Resource Mock + * so that we can add children to them and create the iterators after + * everything is setup + */ + static interface ResourceDecorator { + public List<Resource> getChildrenList(); + } + + static class DataFuture { + public Future<Iterator<?>> future; + + public DataFuture(Future<Iterator<?>> future) { + super(); + this.future = future; + } + } + +} diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java new file mode 100644 index 0000000..5526ea0 --- /dev/null +++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/EtcMappingMapEntriesTest.java @@ -0,0 +1,252 @@ +/* + * 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 org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.path.Path; +import org.apache.sling.resourceresolver.impl.CommonResourceResolverFactoryImpl; +import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker; +import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator; +import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryImpl; +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.serviceusermapping.ServiceUserMapper; +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.Test; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Method; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static java.util.Arrays.asList; +import static org.apache.sling.resourceresolver.impl.MockedResourceResolverImplTest.createRPHandler; +import static org.apache.sling.resourceresolver.impl.ResourceResolverImpl.PROP_REDIRECT_INTERNAL; +import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyMap; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +public class EtcMappingMapEntriesTest extends AbstractMappingMapEntriesTest { + + @Test + public void root_node_to_content_mapping() throws Exception { + setupEtcMapResource("localhost.8080", http,PROP_REDIRECT_EXTERNAL, "/content/simple-node"); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-node/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for simple node", mapEntries.getResolveMaps()); + } + + @Test + public void match_to_content_mapping() throws Exception { + setupEtcMapResource("test-node", http, + PROP_REG_EXP, "localhost.8080/", + PROP_REDIRECT_EXTERNAL, "/content/simple-match/" + ); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/localhost.8080/", "/content/simple-match/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for simple match", mapEntries.getResolveMaps()); + } + + // The following tests are based on the example from the https://sling.apache.org/documentation/the-sling-engine/mappings-for-resource-resolution.html page + + @Test + public void internal_to_external_node_mapping() throws Exception { + setupEtcMapResource("example.com.80", http,PROP_REDIRECT_EXTERNAL, "http://www.example.com/"); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/example.com.80/", "http://www.example.com/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for internal to external based on node", mapEntries.getResolveMaps()); + } + + @Test + public void internal_root_to_content_node_mapping() throws Exception { + setupEtcMapResource("www.example.com.80", http,PROP_REDIRECT_INTERNAL, "/example"); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/www.example.com.80/", true, "/example/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for internal root to content", mapEntries.getResolveMaps()); + } + + @Test + public void host_redirect_match_mapping() throws Exception { + setupEtcMapResource("any_example.com.80", http, + PROP_REG_EXP, ".+\\.example\\.com\\.80", + PROP_REDIRECT_EXTERNAL, "http://www.example.com/" + ); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping().addEtcMapEntry("^http/.+\\.example\\.com\\.80", false, "http://www.example.com/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for host redirect match mapping", mapEntries.getResolveMaps()); + } + + @Test + public void nested_internal_mixed_mapping() throws Exception { + Resource localhost = setupEtcMapResource("localhost_any", http, + PROP_REG_EXP, "localhost\\.\\d*", + PROP_REDIRECT_INTERNAL, "/content" + ); + setupEtcMapResource("cgi-bin", localhost, PROP_REDIRECT_INTERNAL, "/scripts"); + setupEtcMapResource("gateway", localhost, PROP_REDIRECT_INTERNAL, "http://gbiv.com"); + setupEtcMapResource("(stories)", localhost, PROP_REDIRECT_INTERNAL, "/anecdotes/$1"); + + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping() + .addEtcMapEntry("^http/localhost\\.\\d*", true, "/content") + .addEtcMapEntry("^http/localhost\\.\\d*/cgi-bin/", true, "/scripts/") + .addEtcMapEntry("^http/localhost\\.\\d*/gateway/", true, "http://gbiv.com/") + .addEtcMapEntry("^http/localhost\\.\\d*/(stories)/", true, "/anecdotes/$1/"); + expectedEtcMapping.assertEtcMap("Etc Mapping for nested internal mixed mapping", mapEntries.getResolveMaps()); + + // Not really an etc-map resource but it is good for now + final Resource test = setupEtcMapResource("/scripts", "test"); + ResourceProvider<?> rp = new ResourceProvider<Object>() { + @Override + public Resource getResource(ResolveContext<Object> ctx, String path, ResourceContext rCtx, Resource parent) { + if(path.equals("/scripts/test")) { + return test; + } + if(path.startsWith(map.getPath())) { + return findMapping(map, path); + } + return null; + } + + private Resource findMapping(Resource parent, String path) { + if(parent.getPath().equals(path)) { + return parent; + } + Iterator<Resource> i = parent.listChildren(); + while(i.hasNext()) { + Resource child = i.next(); + if(path.equals(child.getPath())) { + return child; + } else { + return findMapping(child, path); + } + } + return null; + } + + @Override + public Iterator<Resource> listChildren(ResolveContext<Object> ctx, Resource parent) { + if(parent.getPath().startsWith(map.getPath())) { + return parent.listChildren(); + } + return null; + } + }; + + List<ResourceProviderHandler> handlers = asList(createRPHandler(rp, "rp1", 0, "/")); + ResourceProviderTracker resourceProviderTracker = mock(ResourceProviderTracker.class); + ResourceProviderStorage storage = new ResourceProviderStorage(handlers); + when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage); + ResourceResolverFactoryActivator activator = spy(new ResourceResolverFactoryActivator()); + // Both 'resourceProviderTracker' and 'resourceAccessSecurityTracker' are package private and so we cannot + // set them here. Intercept the call to obtain them and provide the desired value + when(activator.getResourceProviderTracker()).thenReturn(resourceProviderTracker); + when(activator.getResourceAccessSecurityTracker()).thenReturn(new ResourceAccessSecurityTracker()); + when(activator.getBundleContext()).thenReturn(bundleContext); + when(activator.getStringInterpolationProvider()).thenReturn(stringInterpolationProvider); + when(activator.getMapRoot()).thenReturn("/etc/map"); + when(activator.getObservationPaths()).thenReturn(new Path[] {new Path("/")}); + CommonResourceResolverFactoryImpl commonFactory = spy(new CommonResourceResolverFactoryImpl(activator)); + when(bundleContext.getBundle()).thenReturn(bundle); + ServiceUserMapper serviceUserMapper = mock(ServiceUserMapper.class); + when(activator.getServiceUserMapper()).thenReturn(serviceUserMapper); + when(serviceUserMapper.getServiceUserID(any(Bundle.class),anyString())).thenReturn("mapping"); + Method method = CommonResourceResolverFactoryImpl.class.getDeclaredMethod("activate", BundleContext.class); + method.setAccessible(true); + method.invoke(commonFactory, bundleContext); + final Bundle usingBundle = mock(Bundle.class); + ResourceResolverFactoryImpl resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null); + ResourceResolver resResolver = resFac.getAdministrativeResourceResolver(null); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getScheme()).thenReturn("http"); + when(request.getServerName()).thenReturn("localhost"); + when(request.getServerPort()).thenReturn(80); + Resource mappedResource = resResolver.resolve(request, "/cgi-bin/test.html"); + String path = mappedResource.getPath(); + assertEquals("Wrong Resolved Path", "/scripts/test", path); + } + +// @Test +// public void regex_map_internal_mapping() throws Exception { +// setupEtcMapResource("regexmap", http, +// PROP_REG_EXP, "$1.example.com/$2", +// PROP_REDIRECT_INTERNAL, "/content/([^/]+)/(.*)" +// ); +// +// mapEntries.doInit(); +// // Regex Mappings are ignored for the Resolve Map +// ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping(); +//// .addEtcMapEntry("^http/$1.example.com/$2", true, "/content/([^/]+)/(.*)"); +// expectedEtcMapping.assertEtcMap("Etc Mapping for regex map internal mapping", mapEntries.getResolveMaps()); +// +// 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 resourceProviderTracker = mock(ResourceProviderTracker.class); +// ResourceProviderStorage storage = new ResourceProviderStorage(handlers); +// when(resourceProviderTracker.getResourceProviderStorage()).thenReturn(storage); +// ResourceResolverFactoryActivator activator = spy(new ResourceResolverFactoryActivator()); +// when(activator.getResourceProviderTracker()).thenReturn(resourceProviderTracker); +//// activator.resourceProviderTracker = resourceProviderTracker; +// when(activator.getResourceAccessSecurityTracker()).thenReturn(new ResourceAccessSecurityTracker()); +//// activator.resourceAccessSecurityTracker = new ResourceAccessSecurityTracker(); +// CommonResourceResolverFactoryImpl commonFactory = new CommonResourceResolverFactoryImpl(activator); +// final Bundle usingBundle = mock(Bundle.class); +// ResourceResolverFactoryImpl resFac = new ResourceResolverFactoryImpl(commonFactory, usingBundle, null); +// ResourceResolver resResolver = resFac.getAdministrativeResourceResolver(null); +// +// HttpServletRequest request = mock(HttpServletRequest.class); +// when(request.getScheme()).thenReturn("http"); +// when(request.getServerName()).thenReturn("a.example.com"); +// when(request.getServerPort()).thenReturn(80); +// Resource mappedResource = resResolver.resolve(request, "/b.html"); +// String path = mappedResource.getPath(); +// } +} diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java index 682dacf..b61ac20 100644 --- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java +++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/MapEntriesTest.java @@ -16,32 +16,26 @@ */ package org.apache.sling.resourceresolver.impl.mapping; -import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL; -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.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import org.apache.sling.api.SlingException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.observation.ResourceChange; +import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.resourceresolver.impl.ResourceResolverImpl; +import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.VanityPathConfig; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; -import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.text.ParseException; 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.Iterator; import java.util.LinkedList; @@ -49,73 +43,30 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import org.apache.sling.api.SlingException; -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.ValueMap; -import org.apache.sling.api.resource.observation.ResourceChange; -import org.apache.sling.api.resource.observation.ResourceChange.ChangeType; -import org.apache.sling.api.resource.path.Path; -import org.apache.sling.api.wrappers.ValueMapDecorator; -import org.apache.sling.resourceresolver.impl.ResourceResolverImpl; -import org.apache.sling.resourceresolver.impl.mapping.MapConfigurationProvider.VanityPathConfig; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.service.event.EventAdmin; - -public class MapEntriesTest { - - private MapEntries mapEntries; - - File vanityBloomFilterFile; - - @Mock - private MapConfigurationProvider resourceResolverFactory; - - @Mock - private BundleContext bundleContext; - - @Mock - private Bundle bundle; - - @Mock - private ResourceResolver resourceResolver; - - @Mock - private EventAdmin eventAdmin; - - @Mock - private StringInterpolationProvider stringInterpolationProvider; - - private Map<String, Map<String, String>> aliasMap; +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.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; - @SuppressWarnings({ "unchecked" }) - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); +public class MapEntriesTest extends AbstractMappingMapEntriesTest { + List<MapConfigurationProvider.VanityPathConfig> getVanityPathConfigs() { final List<VanityPathConfig> configs = new ArrayList<>(); configs.add(new VanityPathConfig("/libs/", false)); configs.add(new VanityPathConfig("/libs/denied", true)); @@ -129,35 +80,10 @@ public class MapEntriesTest { configs.add(new VanityPathConfig("/vanityPathOnJcrContent", false)); Collections.sort(configs); - vanityBloomFilterFile = new File("src/main/resourcesvanityBloomFilter.txt"); - when(bundle.getSymbolicName()).thenReturn("TESTBUNDLE"); - when(bundleContext.getBundle()).thenReturn(bundle); - when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile); - when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver); - when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true); - when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs); - when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true); - when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true); - when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")}); - when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT); - when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L); - when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true); - when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn( - Collections.<Resource> emptySet().iterator()); - - mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider); - final Field aliasMapField = MapEntries.class.getDeclaredField("aliasMap"); - aliasMapField.setAccessible(true); - - this.aliasMap = ( Map<String, Map<String, String>>) aliasMapField.get(mapEntries); - } - @After - public void tearDown() throws Exception { - vanityBloomFilterFile.delete(); + return configs; } - @Test(timeout = 1000) public void test_simple_alias_support() throws InterruptedException { Resource parent = mock(Resource.class); @@ -395,22 +321,6 @@ public class MapEntriesTest { assertTrue( mapEntries.getResolveMaps().isEmpty()); } - private ValueMap buildValueMap(Object... string) { - final Map<String, Object> data = new HashMap<>(); - for (int i = 0; i < string.length; i = i + 2) { - data.put((String) string[i], string[i+1]); - } - return new ValueMapDecorator(data); - } - - private Resource getVanityPathResource(final String path) { - Resource rsrc = mock(Resource.class); - when(rsrc.getPath()).thenReturn(path); - when(rsrc.getName()).thenReturn(ResourceUtil.getName(path)); - when(rsrc.getValueMap()).thenReturn(buildValueMap("sling:vanityPath", "/vanity" + path)); - return rsrc; - } - @Test public void test_vanity_path_registration_include_exclude() throws IOException { final String[] validPaths = {"/libs/somewhere", "/libs/a/b", "/foo/a", "/baa/a"}; @@ -2280,39 +2190,4 @@ public class MapEntriesTest { } } } - - // -------------------------- private methods ---------- - private DataFuture createDataFuture(ExecutorService pool, final MapEntries mapEntries) { - - Future<Iterator<?>> future = pool.submit(new Callable<Iterator<?>>() { - @Override - public Iterator<MapEntry> call() throws Exception { - return mapEntries.getResolveMapsIterator("http/localhost.8080/target/justVanityPath"); - } - }); - return new DataFuture(future); - } - - private void simulateSomewhatSlowSessionOperation(final Semaphore sessionLock) throws InterruptedException { - if (!sessionLock.tryAcquire()) { - fail("concurrent session access detected"); - } - try{ - Thread.sleep(1); - } finally { - sessionLock.release(); - } - } - - // -------------------------- inner classes ------------ - - private static class DataFuture { - public Future<Iterator<?>> future; - - public DataFuture(Future<Iterator<?>> future) { - super(); - this.future = future; - } - } - } \ No newline at end of file diff --git a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java index 8392637..f1635a0 100644 --- a/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java +++ b/src/test/java/org/apache/sling/resourceresolver/impl/mapping/StringInterpolationMapEntriesTest.java @@ -17,101 +17,31 @@ package org.apache.sling.resourceresolver.impl.mapping; import org.apache.sling.api.resource.Resource; -import org.apache.sling.api.resource.ResourceResolver; -import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.api.resource.path.Path; -import org.apache.sling.api.wrappers.ValueMapDecorator; -import org.junit.Before; import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.osgi.framework.BundleContext; -import org.osgi.service.event.EventAdmin; -import java.io.File; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; +import java.util.Arrays; import java.util.List; -import java.util.Map; +import static junit.framework.TestCase.fail; import static org.apache.sling.resourceresolver.impl.mapping.MapEntries.PROP_REDIRECT_EXTERNAL; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; /** * These are tests that are testing the Sling Interpolation Feature (SLING-7768) * on the MapEntries level */ -public class StringInterpolationMapEntriesTest { - private static final String PROP_REG_EXP = "sling:match"; - - @Mock - private MapConfigurationProvider resourceResolverFactory; - - @Mock - private BundleContext bundleContext; - - @Mock - private EventAdmin eventAdmin; - - @Mock - private ResourceResolver resourceResolver; - - @Mock - private StringInterpolationProviderConfiguration stringInterpolationProviderConfiguration; - - File vanityBloomFilterFile; - - private Resource map; - private Resource http; - - @SuppressWarnings({"unchecked"}) - @Before - public void setup() throws Exception { - MockitoAnnotations.initMocks(this); - - when(resourceResolverFactory.getServiceResourceResolver(any(Map.class))).thenReturn(resourceResolver); - when(resourceResolverFactory.isVanityPathEnabled()).thenReturn(true); - final List<MapConfigurationProvider.VanityPathConfig> configs = new ArrayList<>(); - when(resourceResolverFactory.getVanityPathConfig()).thenReturn(configs); - when(resourceResolverFactory.isOptimizeAliasResolutionEnabled()).thenReturn(true); - when(resourceResolverFactory.isForceNoAliasTraversal()).thenReturn(true); - when(resourceResolverFactory.getObservationPaths()).thenReturn(new Path[] {new Path("/")}); - when(resourceResolverFactory.getMapRoot()).thenReturn(MapEntries.DEFAULT_MAP_ROOT); - when(resourceResolverFactory.getMaxCachedVanityPathEntries()).thenReturn(-1L); - when(resourceResolverFactory.isMaxCachedVanityPathEntriesStartup()).thenReturn(true); - when(resourceResolver.findResources(anyString(), eq("sql"))).thenReturn( - Collections.<Resource> emptySet().iterator()); - vanityBloomFilterFile = new File("target/test-classes/resourcesvanityBloomFilter.txt"); - when(bundleContext.getDataFile("vanityBloomFilter.txt")).thenReturn(vanityBloomFilterFile); - - map = setupEtcMapResource("/etc", "map"); - http = setupEtcMapResource("http", map); - } +public class StringInterpolationMapEntriesTest extends AbstractMappingMapEntriesTest { @Test public void simple_node_string_interpolation() throws Exception { // To avoid side effects the String Interpolation uses its own Resource Resolver - Resource sivOne = setupEtcMapResource("${siv.one}", http,PROP_REDIRECT_EXTERNAL, "/content/test-me"); - StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"}); + Resource sivOne = setupEtcMapResource("${siv.one}", http,PROP_REDIRECT_EXTERNAL, "/content/simple-node"); + setupStringInterpolationProvider(new String[] {"siv.one=test-simple-node"}); - MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider); - List<MapEntry> mapMaps = mapEntries.getResolveMaps(); - assertEquals("Expected one mapping", 1, mapMaps.size()); - MapEntry mapEntry = mapMaps.get(0); - assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern()); - String[] redirects = mapEntry.getRedirect(); - assertEquals("Expected one redirect", 1, redirects.length); - assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]); + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-node/", "/content/simple-node/"); + expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps()); } @Test @@ -119,77 +49,26 @@ public class StringInterpolationMapEntriesTest { // To avoid side effects the String Interpolation uses its own Resource Resolver Resource sivOne = setupEtcMapResource("test-node", http, PROP_REG_EXP, "${siv.one}/", - PROP_REDIRECT_EXTERNAL, "/content/test-me/" + PROP_REDIRECT_EXTERNAL, "/content/simple-match/" ); - StringInterpolationProvider stringInterpolationProvider = setupStringInterpolationProvider(new String[] {"siv.one=test-value"}); + setupStringInterpolationProvider(new String[] {"siv.one=test-simple-match"}); - MapEntries mapEntries = new MapEntries(resourceResolverFactory, bundleContext, eventAdmin, stringInterpolationProvider); - List<MapEntry> mapMaps = mapEntries.getResolveMaps(); - assertEquals("Expected one mapping", 1, mapMaps.size()); - MapEntry mapEntry = mapMaps.get(0); - assertEquals("Wrong String Interpolation for siv.one", "^http/test-value/", mapEntry.getPattern()); - String[] redirects = mapEntry.getRedirect(); - assertEquals("Expected one redirect", 1, redirects.length); - assertEquals("Wrong Mapping found for siv.one", "/content/test-me/", redirects[0]); + mapEntries.doInit(); + ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match/", "/content/simple-match/"); + expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps()); } - // -------------------------- private methods ---------- - - private ValueMap buildValueMap(Object... string) { - final Map<String, Object> data = new HashMap<>(); - for (int i = 0; i < string.length; i = i + 2) { - data.put((String) string[i], string[i+1]); - } - return new ValueMapDecorator(data); - } - - private Resource setupEtcMapResource(String parentPath, String name, String...valueMapPairs) { - return setupEtcMapResource0(parentPath, name, null, valueMapPairs); - } - private Resource setupEtcMapResource(String name, Resource parent, String...valueMapPairs) { - return setupEtcMapResource0(null, name, parent, valueMapPairs); - } - private Resource setupEtcMapResource0(String parentPath, String name, Resource parent, String...valueMapPairs) { - Resource resource = mock(Resource.class, withSettings().name(name).extraInterfaces(ResourceDecorator.class)); - String path = (parent == null ? parentPath : parent.getPath()) + "/" + name; - when(resource.getPath()).thenReturn(path); - when(resource.getName()).thenReturn(name); - ValueMap valueMap = buildValueMap(valueMapPairs); - when(resource.getValueMap()).thenReturn(valueMap); - when(resource.adaptTo(ValueMap.class)).thenReturn(valueMap); - when(resourceResolver.getResource(resource.getPath())).thenReturn(resource); - if(parent != null) { - List<Resource> childList = ((ResourceDecorator) parent).getChildrenList(); - childList.add(resource); - } - final List<Resource> childrenList = new ArrayList<>(); - when(((ResourceDecorator) resource).getChildrenList()).thenReturn(childrenList); - // Delay the children list iterator to make sure all children are added beforehand - // Iterators have a modCount that is set when created. Any changes to the underlying list will - // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception - when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() { - @Override - public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable { - return childrenList.iterator(); - } - }); - - return resource; - } - - private StringInterpolationProvider setupStringInterpolationProvider(final String[] placeholderValues) { - when(stringInterpolationProviderConfiguration.place_holder_key_value_pairs()).thenReturn(placeholderValues); - StringInterpolationProviderImpl stringInterpolationProvider = new StringInterpolationProviderImpl(); - stringInterpolationProvider.activate(bundleContext, stringInterpolationProviderConfiguration); - return stringInterpolationProvider; - } - - /** - * Iterator to piggyback the list of Resources onto a Resource Mock - * so that we can add children to them and create the iterators after - * everything is setup - */ - private static interface ResourceDecorator { - public List<Resource> getChildrenList(); - } +// @Test +// public void simple_nested_match_string_interpolation() throws Exception { +// // To avoid side effects the String Interpolation uses its own Resource Resolver +// Resource sivOne = setupEtcMapResource("test-node", http, +// PROP_REG_EXP, "${siv.one}/", +// PROP_REDIRECT_EXTERNAL, "/content/simple-match/" +// ); +// setupStringInterpolationProvider(new String[] {"siv.one=test-simple-match"}); +// +// mapEntries.doInit(); +// ExpectedEtcMapping expectedEtcMapping = new ExpectedEtcMapping("^http/test-simple-match/", "/content/simple-match/"); +// expectedEtcMapping.assertEtcMap("String Interpolation for simple match", mapEntries.getResolveMaps()); +// } } diff --git a/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java new file mode 100644 index 0000000..7db3831 --- /dev/null +++ b/src/test/java/org/apache/sling/resourceresolver/util/MockTestUtil.java @@ -0,0 +1,124 @@ +/* + * 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.util; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; +import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl; +import org.apache.sling.resourceresolver.impl.mapping.AbstractMappingMapEntriesTest; +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.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static java.util.Arrays.asList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; + +public class MockTestUtil { + + /** + * Extract the name from a resource path + * @param fullPath Full / Aboslute path to the resource + * @return Name of the resource + */ + public static String getResourceName(String fullPath) { + int n = fullPath.lastIndexOf("/"); + return fullPath.substring(n+1); + } + + /** + * Build a resource with path, parent, provider and resource resolver. + * @param fullPath Full Path of the Resource + * @param parent Parent of this resource but it can be null + * @param resourceResolver Resource Resolver of this resource + * @param provider Resource Provider Instance + * @param properties Key / Value pair for resource properties (the number of strings must be even) + * @return + */ + @SuppressWarnings("unchecked") + private Resource buildResource(String fullPath, Resource parent, ResourceResolver resourceResolver, ResourceProvider<?> provider, String ... properties) { + if(properties != null && properties.length % 2 != 0) { throw new IllegalArgumentException("List of Resource Properties must be an even number: " + asList(properties)); } + Resource resource = mock(Resource.class, withSettings().name(getResourceName(fullPath)).extraInterfaces(ResourceChildrenAccessor.class)); + when(resource.getName()).thenReturn(getResourceName(fullPath)); + when(resource.getPath()).thenReturn(fullPath); + ResourceMetadata resourceMetadata = new ResourceMetadata(); + when(resource.getResourceMetadata()).thenReturn(resourceMetadata); + when(resource.getResourceResolver()).thenReturn(resourceResolver); + + if(parent != null) { + List<Resource> childList = ((ResourceChildrenAccessor) parent).getChildrenList(); + childList.add(resource); + } + final List<Resource> childrenList = new ArrayList<>(); + when(((ResourceChildrenAccessor) resource).getChildrenList()).thenReturn(childrenList); + // Delay the children list iterator to make sure all children are added beforehand + // Iterators have a modCount that is set when created. Any changes to the underlying list will + // change that modCount and the usage of the iterator will fail due to Concurrent Modification Exception + when(resource.listChildren()).thenAnswer(new Answer<Iterator<Resource>>() { + @Override + public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable { + return childrenList.iterator(); + } + }); + + // register the resource with the provider + if ( provider != null ) { + when(provider.listChildren(Mockito.any(ResolveContext.class), Mockito.eq(resource))).thenAnswer(new Answer<Iterator<Resource>>() { + @Override + public Iterator<Resource> answer(InvocationOnMock invocation) throws Throwable { + return childrenList.iterator(); + } + }); + when(provider.getResource(Mockito.any(ResolveContext.class), Mockito.eq(fullPath), Mockito.any(ResourceContext.class), Mockito.any(Resource.class))).thenReturn(resource); + } + if ( properties != null ) { + ValueMap vm = new SimpleValueMapImpl(); + for ( int i=0; i < properties.length; i+=2) { + resourceMetadata.put(properties[i], properties[i+1]); + vm.put(properties[i], properties[i+1]); + } + when(resource.getValueMap()).thenReturn(vm); + when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(vm); + } else { + when(resource.getValueMap()).thenReturn(ValueMapDecorator.EMPTY); + when(resource.adaptTo(Mockito.eq(ValueMap.class))).thenReturn(ValueMapDecorator.EMPTY); + } + + return resource; + } + + /** + * Iterator to piggyback the list of Resources onto a Resource Mock + * so that we can add children to them and create the iterators after + * everything is setup + */ + static interface ResourceChildrenAccessor { + public List<Resource> getChildrenList(); + } + +}
