| /* |
| * 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 static java.util.Arrays.asList; |
| import static org.hamcrest.Matchers.instanceOf; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertThat; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.when; |
| import static org.mockito.Mockito.withSettings; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import javax.servlet.http.HttpServletRequest; |
| |
| import org.apache.sling.api.resource.NonExistingResource; |
| 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.resource.mapping.PathToUriMappingService; |
| import org.apache.sling.api.wrappers.ValueMapDecorator; |
| import org.apache.sling.resourceresolver.impl.SimpleValueMapImpl; |
| import org.apache.sling.resourceresolver.impl.helper.RedirectResource; |
| import org.apache.sling.resourceresolver.impl.mapping.MapEntry; |
| import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider; |
| import org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderConfiguration; |
| 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 org.osgi.framework.BundleContext; |
| |
| import junit.framework.TestCase; |
| |
| public class MockTestUtil { |
| |
| static final String PROP_SLING_TARGET = "sling:target"; |
| static final String PROP_SLING_STATUS = "sling:status"; |
| |
| public static void checkRedirectResource(Resource redirect, String target, int status) { |
| assertThat("Not a Redirect Resource", redirect, instanceOf(RedirectResource.class)); |
| RedirectResource redirectResource = (RedirectResource) redirect; |
| ValueMap values = redirectResource.adaptTo(ValueMap.class); |
| assertEquals("Redirect Target is wrong", target, values.get(PROP_SLING_TARGET, String.class)); |
| assertEquals("Redirect Status is wrong", new Integer(status), values.get(PROP_SLING_STATUS, Integer.class)); |
| } |
| |
| public static void checkNonExistingResource(Resource redirect, String path) { |
| assertThat("Not a Non Existing Resource", redirect, instanceOf(NonExistingResource.class)); |
| NonExistingResource nonExistingResource = (NonExistingResource) redirect; |
| if(path != null) { |
| assertEquals("Wrong Path for Non Existing Resource", path, nonExistingResource.getPath()); |
| } |
| } |
| |
| public static void checkInternalResource(Resource internal, String path) { |
| assertEquals("Wrong Path for Resource", path, internal.getPath()); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * Creates a Mock Http Servlet Request |
| * @param url Absolute URL to be used to get the method, host and port |
| * @return Http Servlet Request if the url is valid otherwise null |
| */ |
| public static HttpServletRequest createRequestFromUrl(String url) { |
| int index = url.indexOf("://"); |
| if(index > 0) { |
| String method = url.substring(0, index); |
| int port = 80; |
| int index2 = url.indexOf(":", index + 3); |
| int index3 = url.indexOf("/", index2 > index ? index2 : index + 3); |
| String host = ""; |
| if (index2 > 0) { |
| port = new Integer(url.substring(index2 + 1, index3)); |
| host = url.substring(index + 3, index2); |
| } else { |
| if(index3 > 0) { |
| host = url.substring(index + 3, index3); |
| } else { |
| host = url.substring(index + 3); |
| } |
| } |
| HttpServletRequest request = mock(HttpServletRequest.class); |
| when(request.getScheme()).thenReturn(method); |
| when(request.getServerName()).thenReturn(host); |
| when(request.getServerPort()).thenReturn(port); |
| return request; |
| } |
| return null; |
| } |
| |
| /** |
| * 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 Mock Resource able to handle addition of children later on |
| */ |
| @SuppressWarnings("unchecked") |
| public static 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; |
| } |
| |
| /** |
| * Calls a private method that has no parameter like a getter |
| * |
| * @param methodName Name of the method |
| * @param target Target instance |
| * @return Object that is returned from the method call |
| * |
| * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed |
| */ |
| public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target) { |
| return callInaccessibleMethod(methodName, returnType, target, new Class[] {}, new Object[] {}); |
| } |
| |
| /** |
| * Calls a private method that has one parameter like a setter method |
| * |
| * @param methodName Name of the method |
| * @param target Target instance |
| * @param paramsType Parameter Type which cannot be null |
| * @param param Parameter Value |
| * @return Object that is returned from the method call |
| * |
| * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed |
| */ |
| public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class paramsType, Object param) { |
| return callInaccessibleMethod(methodName, returnType, target, new Class[] {paramsType}, new Object[] {param}); |
| } |
| |
| /** |
| * Calls a private method that has none or one parameter like a setter method |
| * |
| * ATTENTION: If parameter types of values is null then both are set to null. Also the length of the arrays must |
| * be the same |
| * |
| * @param methodName Name of the method |
| * @param target Target instance |
| * @param parameterTypes Parameter Types which must not be null |
| * @param parameters Parameter Values which must not be null |
| * @return Object that is returned from the method call |
| * |
| * @throws IllegalArgumentException If the parameter types and values do not match |
| * @throws UnsupportedOperationException If the call failed because it method is not found, has no access or invocation failed |
| */ |
| public static <T> T callInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class[] parameterTypes, Object[] parameters) { |
| if(parameterTypes != null && parameters != null) { |
| if(parameters.length != parameterTypes.length) { throw new IllegalArgumentException("Number of Parameter Types and Values were not the same"); } |
| } else { |
| throw new IllegalArgumentException("Parameter Type and Value Array cannot be null"); |
| } |
| try { |
| return getInaccessibleMethod(methodName, returnType, target, parameterTypes).call(parameters); |
| } catch (IllegalAccessException e) { |
| throw new UnsupportedOperationException("Failed to access method: " + methodName, e); |
| } catch (InvocationTargetException e) { |
| throw new UnsupportedOperationException("Failed to invoke method: " + methodName, e); |
| } |
| } |
| |
| public static <T> MethodWrapper<T> getInaccessibleMethod(String methodName, Class<T> returnType, Object target, Class...parameterTypes) { |
| return new MethodWrapper(methodName, returnType, target, parameterTypes); |
| } |
| |
| public static class MethodWrapper<T> { |
| private Method method; |
| private Object target; |
| |
| public MethodWrapper(String methodName, Class<T> returnType, Object target, Class[] parameterTypes) { |
| try { |
| this.method = target.getClass().getDeclaredMethod(methodName, parameterTypes); |
| this.method.setAccessible(true); |
| this.target = target; |
| if(returnType == null && !this.method.getReturnType().equals(Void.TYPE)) { |
| throw new IllegalArgumentException("Return Type is null but method does not return Void but: " + this.method.getReturnType()); |
| } |
| if(returnType != null && !returnType.isAssignableFrom(this.method.getReturnType())) { |
| throw new IllegalArgumentException("Return Type is not assignable to: " + returnType + ", it returns this: " + this.method.getReturnType()); |
| } |
| } catch (NoSuchMethodException e) { |
| throw new UnsupportedOperationException("Failed to find method: " + methodName, e); |
| } |
| } |
| |
| public T call(Object...parameters) throws InvocationTargetException, IllegalAccessException { |
| return (T) method.invoke(target, parameters); |
| } |
| } |
| |
| /** |
| * Sets the value of a private field |
| * |
| * @param fieldName Name of the field to be set |
| * @param target Target instance |
| * @param fieldValue Value to be set |
| * |
| * @throws UnsupportedOperationException If the call failed because it field is not found or has no access |
| */ |
| public static void setInaccessibleField(String fieldName, Object target, Object fieldValue) throws NoSuchMethodException { |
| try { |
| getInaccessibleFieldWrapper(fieldName, target, Object.class).set(fieldValue); |
| } catch (IllegalAccessException e) { |
| throw new UnsupportedOperationException("Failed to access field: " + fieldName, e); |
| } |
| } |
| |
| public static <T> T getInaccessibleField(String fieldName, Object target, Class<T> type) { |
| try { |
| return getInaccessibleFieldWrapper(fieldName, target, type).get(); |
| } catch (IllegalAccessException e) { |
| throw new UnsupportedOperationException("Failed to access field: " + fieldName, e); |
| } |
| } |
| |
| public static <T> FieldWrapper<T> getInaccessibleFieldWrapper(String fieldName, Object target, Class<T> type) { |
| try { |
| return new FieldWrapper(fieldName, target, type); |
| } catch (NoSuchFieldException e) { |
| throw new UnsupportedOperationException("Failed to find field: " + fieldName, e); |
| } |
| } |
| |
| public static StringInterpolationProviderConfiguration createStringInterpolationProviderConfiguration() { |
| StringInterpolationProviderConfiguration answer = mock(StringInterpolationProviderConfiguration.class); |
| when(answer.placeHolderKeyValuePairs()).thenReturn(new String[] {}); |
| return answer; |
| } |
| |
| public static void setupStringInterpolationProvider( |
| StringInterpolationProvider provider, StringInterpolationProviderConfiguration configuration, final String[] placeholderValues |
| ) { |
| when(configuration.placeHolderKeyValuePairs()).thenReturn(placeholderValues); |
| BundleContext context = mock(BundleContext.class); |
| callInaccessibleMethod("activate", Void.TYPE, provider, |
| new Class[] {BundleContext.class, StringInterpolationProviderConfiguration.class}, |
| new Object[] {context, configuration} |
| ); |
| } |
| |
| public static class FieldWrapper<T> { |
| private Field field; |
| private Object target; |
| |
| public FieldWrapper(String fieldName, Object target, Class<T> type) throws NoSuchFieldException { |
| this.field = target.getClass().getDeclaredField(fieldName); |
| this.field.setAccessible(true); |
| this.target = target; |
| } |
| |
| public void set(T parameter) throws IllegalAccessException { |
| field.set(target, parameter); |
| } |
| |
| public T get() throws IllegalAccessException { |
| return (T) field.get(target); |
| } |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| /** |
| * Defines the Result of the Etc Mapping for easy testing |
| */ |
| public 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); |
| } |
| } |
| } |
| |
| public 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()); |
| } |
| } |
| |
| public static PathToUriMappingService createPathToUriMappingServiceMock(final ResourceResolver rr) { |
| return new TestPathToUriMappingService(); |
| } |
| |
| } |