| /* |
| * 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.testing.mock.sling.context; |
| |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Function; |
| |
| import org.apache.sling.api.SlingHttpServletRequest; |
| import org.apache.sling.api.adapter.AdapterFactory; |
| 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.scripting.SlingBindings; |
| import org.apache.sling.api.scripting.SlingScriptHelper; |
| import org.apache.sling.commons.mime.MimeTypeService; |
| import org.apache.sling.featureflags.impl.FeatureManager; |
| import org.apache.sling.jcr.resource.internal.scripting.JcrObjectsBindingsValuesProvider; |
| import org.apache.sling.models.impl.ModelAdapterFactory; |
| import org.apache.sling.resourcebuilder.api.ResourceBuilder; |
| import org.apache.sling.resourcebuilder.api.ResourceBuilderFactory; |
| import org.apache.sling.resourcebuilder.impl.ResourceBuilderFactoryService; |
| import org.apache.sling.scripting.core.impl.BindingsValuesProvidersByContextImpl; |
| import org.apache.sling.settings.SlingSettingsService; |
| import org.apache.sling.testing.mock.osgi.MockOsgi; |
| import org.apache.sling.testing.mock.osgi.context.OsgiContextImpl; |
| import org.apache.sling.testing.mock.sling.MockResourceBundleProvider; |
| import org.apache.sling.testing.mock.sling.MockSling; |
| import org.apache.sling.testing.mock.sling.MockXSSFilter; |
| import org.apache.sling.testing.mock.sling.ResourceResolverType; |
| import org.apache.sling.testing.mock.sling.builder.ContentBuilder; |
| import org.apache.sling.testing.mock.sling.loader.ContentLoader; |
| import org.apache.sling.testing.mock.sling.services.MockMimeTypeService; |
| import org.apache.sling.testing.mock.sling.services.MockSlingSettingService; |
| import org.apache.sling.testing.mock.sling.servlet.MockRequestPathInfo; |
| import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletRequest; |
| import org.apache.sling.testing.mock.sling.servlet.MockSlingHttpServletResponse; |
| import org.apache.sling.xss.impl.XSSAPIImpl; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.osgi.annotation.versioning.ConsumerType; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.service.event.EventHandler; |
| |
| import static org.apache.sling.testing.mock.sling.context.MockSlingBindings.SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE; |
| import static org.osgi.service.event.EventConstants.EVENT_TOPIC; |
| |
| /** |
| * Defines Sling context objects with lazy initialization. |
| * Should not be used directly but via the SlingContext JUnit rule or extension. |
| */ |
| @ConsumerType |
| public class SlingContextImpl extends OsgiContextImpl { |
| |
| static { |
| // ugly hack: prevent default console logging from ESAPI on each initialization by setting system property |
| System.setProperty("org.owasp.esapi.logSpecial.discard", "true"); |
| } |
| |
| // default to publish instance run mode |
| static final @NotNull Set<String> DEFAULT_RUN_MODES = Collections.singleton("publish"); |
| |
| private static final @NotNull String RESOURCERESOLVERFACTORYACTIVATOR_PID = |
| "org.apache.sling.jcr.resource.internal.JcrResourceResolverFactoryImpl"; |
| |
| /** |
| * Resource resolver factory |
| */ |
| protected ResourceResolverFactory resourceResolverFactory; |
| |
| /** |
| * Resource resolver type |
| */ |
| protected ResourceResolverType resourceResolverType; |
| |
| /** |
| * Resource resolver |
| */ |
| protected ResourceResolver resourceResolver; |
| |
| /** |
| * Sling request |
| */ |
| protected MockSlingHttpServletRequest request; |
| |
| /** |
| * Sling response |
| */ |
| protected MockSlingHttpServletResponse response; |
| |
| /** |
| * Sling script helper |
| */ |
| protected SlingScriptHelper slingScriptHelper; |
| |
| /** |
| * Content loader |
| */ |
| protected ContentLoader contentLoader; |
| |
| /** |
| * Content loader with auto commit after loading content |
| */ |
| protected ContentLoader contentLoaderAutoCommit; |
| |
| /** |
| * Content builder |
| */ |
| protected ContentBuilder contentBuilder; |
| |
| /** |
| * Resource builder |
| */ |
| protected ResourceBuilder resourceBuilder; |
| |
| /** |
| * Unique root helper |
| */ |
| protected UniqueRoot uniqueRoot; |
| |
| private Map<String, Object> resourceResolverFactoryActivatorProps; |
| private boolean registerSlingModelsFromClassPath = true; |
| |
| /** |
| * @param resourceResolverType Resource resolver type |
| */ |
| protected void setResourceResolverType(@Nullable final ResourceResolverType resourceResolverType) { |
| if (resourceResolverType == null) { |
| this.resourceResolverType = MockSling.DEFAULT_RESOURCERESOLVER_TYPE; |
| } else { |
| this.resourceResolverType = resourceResolverType; |
| } |
| } |
| |
| /** |
| * @param props Resolver resolver factory activator properties |
| */ |
| protected void setResourceResolverFactoryActivatorProps(@Nullable Map<String, Object> props) { |
| this.resourceResolverFactoryActivatorProps = props; |
| } |
| |
| /** |
| * @param registerSlingModelsFromClassPath Register sling models from classpath |
| */ |
| protected void setRegisterSlingModelsFromClassPath(boolean registerSlingModelsFromClassPath) { |
| this.registerSlingModelsFromClassPath = registerSlingModelsFromClassPath; |
| } |
| |
| /** |
| * Setup actions before test method execution |
| */ |
| @Override |
| protected void setUp() { |
| super.setUp(); |
| MockSling.setAdapterManagerBundleContext(bundleContext()); |
| |
| if (this.resourceResolverFactoryActivatorProps != null) { |
| // use OSGi ConfigurationAdmin to pass over customized configuration to Resource Resolver Factory Activator |
| // service |
| MockOsgi.setConfigForPid( |
| bundleContext(), RESOURCERESOLVERFACTORYACTIVATOR_PID, this.resourceResolverFactoryActivatorProps); |
| } |
| |
| // automatically register resource resolver factory when ResourceResolverType != NONE, |
| // so the ResourceResolverFactory is available as OSGi service immediately |
| if (resourceResolverType != ResourceResolverType.NONE) { |
| resourceResolverFactory(); |
| } |
| |
| registerDefaultServices(); |
| } |
| |
| /** |
| * Initialize mocked resource resolver factory. |
| * @return Resource resolver factory |
| */ |
| protected @NotNull ResourceResolverFactory newResourceResolverFactory() { |
| return ContextResourceResolverFactory.get(this.resourceResolverType, bundleContext()); |
| } |
| |
| private @NotNull ResourceResolverFactory resourceResolverFactory() { |
| if (this.resourceResolverFactory == null) { |
| this.resourceResolverFactory = newResourceResolverFactory(); |
| } |
| return this.resourceResolverFactory; |
| } |
| |
| /** |
| * Default services that should be available for every unit test |
| */ |
| protected void registerDefaultServices() { |
| |
| // scripting services (required by sling models impl since 1.3.6) |
| registerInjectActivateServiceByClassName( |
| "org.apache.sling.scripting.core.impl.ScriptEngineManagerFactory", |
| "org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager"); |
| registerInjectActivateService(new BindingsValuesProvidersByContextImpl()); |
| |
| // sling models |
| registerInjectActivateService(new ModelAdapterFactory()); |
| registerInjectActivateServiceByClassName( |
| "org.apache.sling.models.impl.FirstImplementationPicker", |
| "org.apache.sling.models.impl.ResourceTypeBasedResourcePicker", |
| "org.apache.sling.models.impl.injectors.BindingsInjector", |
| "org.apache.sling.models.impl.injectors.ChildResourceInjector", |
| "org.apache.sling.models.impl.injectors.OSGiServiceInjector", |
| "org.apache.sling.models.impl.injectors.RequestAttributeInjector", |
| "org.apache.sling.models.impl.injectors.ResourcePathInjector", |
| "org.apache.sling.models.impl.injectors.SelfInjector", |
| "org.apache.sling.models.impl.injectors.SlingObjectInjector", |
| "org.apache.sling.models.impl.injectors.ValueMapInjector", |
| "org.apache.sling.models.impl.via.BeanPropertyViaProvider", |
| "org.apache.sling.models.impl.via.ChildResourceViaProvider", |
| "org.apache.sling.models.impl.via.ForcedResourceTypeViaProvider", |
| "org.apache.sling.models.impl.via.ResourceSuperTypeViaProvider"); |
| |
| // other services |
| registerService(SlingSettingsService.class, new MockSlingSettingService(DEFAULT_RUN_MODES)); |
| registerService(MimeTypeService.class, new MockMimeTypeService()); |
| registerInjectActivateService(new ResourceBuilderFactoryService()); |
| registerInjectActivateService( |
| new JcrObjectsBindingsValuesProvider(), SERVICE_PROPERTY_MOCK_SLING_BINDINGS_IGNORE, true); |
| registerInjectActivateService(new MockResourceBundleProvider()); |
| registerInjectActivateService(MockXSSFilter.class); |
| registerInjectActivateService(XSSAPIImpl.class); |
| registerInjectActivateService(new FeatureManager()); |
| |
| // scan for models defined via bundle headers in classpath |
| if (registerSlingModelsFromClassPath) { |
| ModelAdapterFactoryUtil.addModelsForManifestEntries(this.bundleContext()); |
| } |
| } |
| |
| @SuppressWarnings("null") |
| private void registerInjectActivateServiceByClassName(@NotNull String @NotNull ... classNames) { |
| for (String className : classNames) { |
| try { |
| Class<?> clazz = Class.forName(className); |
| registerInjectActivateService(clazz.newInstance()); |
| } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { |
| // ignore - probably not the latest sling models impl version |
| } |
| } |
| } |
| |
| /** |
| * Teardown actions after test method execution |
| */ |
| @Override |
| protected void tearDown() { |
| |
| if (this.request != null) { |
| SlingBindings slingBindings = (SlingBindings) this.request.getAttribute(SlingBindings.class.getName()); |
| if (slingBindings instanceof MockSlingBindings) { |
| ((MockSlingBindings) slingBindings).tearDown(); |
| } |
| } |
| |
| if (this.resourceResolver != null) { |
| |
| // revert potential unsaved changes in resource resolver/JCR session |
| try { |
| this.resourceResolver.revert(); |
| } catch (UnsupportedOperationException ex) { |
| // ignore - this may happen when jcr-mock is used |
| } |
| Session session = this.resourceResolver.adaptTo(Session.class); |
| if (session != null) { |
| try { |
| session.refresh(false); |
| } catch (RepositoryException ex) { |
| // ignore |
| } catch (UnsupportedOperationException ex) { |
| // ignore - this may happen when jcr-mock is used |
| } |
| } |
| |
| // remove unique roots |
| if (this.uniqueRoot != null) { |
| this.uniqueRoot.cleanUp(); |
| } |
| |
| // close resource resolver |
| this.resourceResolver.close(); |
| } |
| |
| this.resourceResolver = null; |
| this.request = null; |
| this.response = null; |
| this.slingScriptHelper = null; |
| this.contentLoader = null; |
| this.contentLoaderAutoCommit = null; |
| this.contentBuilder = null; |
| this.resourceBuilder = null; |
| this.uniqueRoot = null; |
| this.resourceResolverFactory = null; |
| |
| super.tearDown(); |
| |
| MockSling.clearAdapterManagerBundleContext(); |
| } |
| |
| /** |
| * @return Resource resolver type |
| */ |
| public final @NotNull ResourceResolverType resourceResolverType() { |
| return this.resourceResolverType; |
| } |
| |
| /** |
| * Returns the singleton resource resolver bound to this context. |
| * It is automatically closed after the test. |
| * @return Resource resolver |
| */ |
| @SuppressWarnings("deprecation") |
| public final @NotNull ResourceResolver resourceResolver() { |
| if (this.resourceResolver == null) { |
| try { |
| this.resourceResolver = this.resourceResolverFactory().getAdministrativeResourceResolver(null); |
| } catch (LoginException ex) { |
| throw new RuntimeException("Creating resource resolver failed.", ex); |
| } |
| } |
| return this.resourceResolver; |
| } |
| |
| /** |
| * @return Sling request |
| */ |
| public final @NotNull MockSlingHttpServletRequest request() { |
| if (this.request == null) { |
| this.request = new MockSlingHttpServletRequest(this.resourceResolver(), this.bundleContext()); |
| |
| // initialize sling bindings |
| MockSlingBindings bindings = new MockSlingBindings(this); |
| |
| // register as OSGi event handler to get notified on events fired by BindingsValuesProvidersByContextImpl |
| this.registerService( |
| EventHandler.class, |
| bindings, |
| EVENT_TOPIC, |
| "org/apache/sling/scripting/core/BindingsValuesProvider/*"); |
| |
| this.request.setAttribute(SlingBindings.class.getName(), bindings); |
| } |
| return this.request; |
| } |
| |
| /** |
| * Dynamically resolve property request for current request {@link SlingBindings}. |
| * @param property Property key |
| * @param request Context request |
| * @return Resolved object or null if no result found |
| */ |
| protected @Nullable Object resolveSlingBindingProperty( |
| @NotNull String property, @NotNull SlingHttpServletRequest request) { |
| return MockSlingBindings.resolveSlingBindingProperty(this, property); |
| } |
| |
| /** |
| * Dynamically resolve property request for current request {@link SlingBindings}. |
| * @param property Property key |
| * @return Resolved object or null if no result found |
| * @deprecated Please use {@link #resolveSlingBindingProperty(String, SlingHttpServletRequest)} |
| */ |
| @Deprecated |
| protected @Nullable Object resolveSlingBindingProperty(@NotNull String property) { |
| return MockSlingBindings.resolveSlingBindingProperty(this, property); |
| } |
| |
| /** |
| * @return Request path info |
| */ |
| public final @NotNull MockRequestPathInfo requestPathInfo() { |
| return (MockRequestPathInfo) request().getRequestPathInfo(); |
| } |
| |
| /** |
| * @return Sling response |
| */ |
| public final @NotNull MockSlingHttpServletResponse response() { |
| if (this.response == null) { |
| this.response = new MockSlingHttpServletResponse(); |
| } |
| return this.response; |
| } |
| |
| /** |
| * @return Sling script helper |
| */ |
| public final @NotNull SlingScriptHelper slingScriptHelper() { |
| if (this.slingScriptHelper == null) { |
| this.slingScriptHelper = |
| MockSling.newSlingScriptHelper(this.request(), this.response(), this.bundleContext()); |
| } |
| return this.slingScriptHelper; |
| } |
| |
| /** |
| * @return Content loader |
| */ |
| public @NotNull ContentLoader load() { |
| return load(true); |
| } |
| |
| /** |
| * @param autoCommit Automatically commit changes after loading content (default: true) |
| * @return Content loader |
| */ |
| public @NotNull ContentLoader load(boolean autoCommit) { |
| if (autoCommit) { |
| if (this.contentLoaderAutoCommit == null) { |
| this.contentLoaderAutoCommit = |
| new ContentLoader(resourceResolver(), bundleContext(), true, this.resourceResolverType()); |
| } |
| return this.contentLoaderAutoCommit; |
| } else { |
| if (this.contentLoader == null) { |
| this.contentLoader = |
| new ContentLoader(resourceResolver(), bundleContext(), false, this.resourceResolverType()); |
| } |
| return this.contentLoader; |
| } |
| } |
| |
| /** |
| * Creates a {@link ContentBuilder} object for easily creating test content. |
| * This API was part of Sling Mocks since version 1.x. |
| * You can use alternatively the {@link #build()} method and use the {@link ResourceBuilder} API. |
| * @return Content builder for building test content |
| */ |
| public @NotNull ContentBuilder create() { |
| if (this.contentBuilder == null) { |
| this.contentBuilder = new ContentBuilder(resourceResolver()); |
| } |
| return this.contentBuilder; |
| } |
| |
| /** |
| * Creates a {@link ResourceBuilder} object for easily creating test content. |
| * This is a separate API which can be used inside sling mocks or in a running instance. |
| * You can use alternatively the {@link #create()} method to use the {@link ContentBuilder} API. |
| * @return Resource builder for building test content. |
| */ |
| @SuppressWarnings("null") |
| public @NotNull ResourceBuilder build() { |
| if (this.resourceBuilder == null) { |
| this.resourceBuilder = getService(ResourceBuilderFactory.class).forResolver(this.resourceResolver()); |
| } |
| return this.resourceBuilder; |
| } |
| |
| /** |
| * @return Current resource |
| */ |
| public final @Nullable Resource currentResource() { |
| return request().getResource(); |
| } |
| |
| /** |
| * Set current resource in request. |
| * @param resourcePath Resource path |
| * @return Current resource |
| */ |
| public final @Nullable Resource currentResource(@Nullable String resourcePath) { |
| if (resourcePath != null) { |
| Resource resource = resourceResolver().getResource(resourcePath); |
| if (resource == null) { |
| throw new IllegalArgumentException("Resource does not exist: " + resourcePath); |
| } |
| return currentResource(resource); |
| } else { |
| return currentResource((Resource) null); |
| } |
| } |
| |
| /** |
| * Set current resource in request. |
| * @param resource Resource |
| * @return Current resource |
| */ |
| public @Nullable Resource currentResource(@Nullable Resource resource) { |
| request().setResource(resource); |
| return resource; |
| } |
| |
| /** |
| * Search classpath for given java package names (and sub packages) to scan for and |
| * register all classes with @Model annotation. |
| * @param packageName Java package name |
| */ |
| public final void addModelsForPackage(@NotNull String packageName) { |
| ModelAdapterFactoryUtil.addModelsForPackages(bundleContext(), packageName); |
| } |
| |
| /** |
| * Search classpath for given java package names (and sub packages) to scan for and |
| * register all classes with @Model annotation. |
| * @param packageNames Java package names |
| */ |
| public final void addModelsForPackage(@NotNull String @NotNull ... packageNames) { |
| ModelAdapterFactoryUtil.addModelsForPackages(bundleContext(), packageNames); |
| } |
| |
| /** |
| * Search classpath for given class names to scan for and register all classes with @Model annotation. |
| * @param classNames Java class names |
| */ |
| public final void addModelsForClasses(@NotNull String @NotNull ... classNames) { |
| ModelAdapterFactoryUtil.addModelsForClasses(bundleContext(), classNames); |
| } |
| |
| /** |
| * Search classpath for given class names to scan for and register all classes with @Model annotation. |
| * @param classes Java classes |
| */ |
| public final void addModelsForClasses(@NotNull Class @NotNull ... classes) { |
| ModelAdapterFactoryUtil.addModelsForClasses(bundleContext(), classes); |
| } |
| |
| /** |
| * Set current run mode(s). |
| * @param runModes Run mode(s). |
| */ |
| @SuppressWarnings("null") |
| public final void runMode(@NotNull String @NotNull ... runModes) { |
| Set<String> newRunModes = new HashSet<>(Arrays.asList(runModes)); |
| ServiceReference<SlingSettingsService> ref = bundleContext().getServiceReference(SlingSettingsService.class); |
| if (ref != null) { |
| MockSlingSettingService slingSettings = |
| (MockSlingSettingService) bundleContext().getService(ref); |
| slingSettings.setRunModes(newRunModes); |
| } |
| } |
| |
| /** |
| * Create unique root paths for unit tests (and clean them up after the test run automatically). |
| * @return Unique root path helper |
| */ |
| public @NotNull UniqueRoot uniqueRoot() { |
| if (uniqueRoot == null) { |
| uniqueRoot = new UniqueRoot(this); |
| } |
| return uniqueRoot; |
| } |
| |
| /** |
| * Create a Sling AdapterFactory on the fly which can adapt from <code>adaptableClass</code> |
| * to <code>adapterClass</code> and just returns the given value as result. |
| * @param adaptableClass Class to adapt from |
| * @param adapterClass Class to adapt to |
| * @param adapter Object which is always returned for this adaption. |
| * @param <T1> Adaptable type |
| * @param <T2> Adapter type |
| */ |
| public final <T1, T2> void registerAdapter( |
| @NotNull final Class<T1> adaptableClass, @NotNull final Class<T2> adapterClass, @NotNull final T2 adapter) { |
| registerAdapter(adaptableClass, adapterClass, new Function<T1, T2>() { |
| @Override |
| public T2 apply(@Nullable T1 input) { |
| return adapter; |
| } |
| }); |
| } |
| |
| /** |
| * Create a Sling AdapterFactory on the fly which can adapt from <code>adaptableClass</code> |
| * to <code>adapterClass</code> and delegates the adapter mapping to the given <code>adaptHandler</code> function. |
| * @param adaptableClass Class to adapt from |
| * @param adapterClass Class to adapt to |
| * @param adaptHandler Function to handle the adaption |
| * @param <T1> Adaptable type |
| * @param <T2> Adapter type |
| */ |
| public final <T1, T2> void registerAdapter( |
| @NotNull final Class<T1> adaptableClass, |
| @NotNull final Class<T2> adapterClass, |
| @NotNull final Function<T1, T2> adaptHandler) { |
| AdapterFactory adapterFactory = new AdapterFactory() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public <AdapterType> AdapterType getAdapter(@NotNull Object adaptable, @NotNull Class<AdapterType> type) { |
| return (AdapterType) adaptHandler.apply((T1) adaptable); |
| } |
| }; |
| Map<String, Object> props = new HashMap<>(); |
| props.put(AdapterFactory.ADAPTABLE_CLASSES, new String[] {adaptableClass.getName()}); |
| props.put(AdapterFactory.ADAPTER_CLASSES, new String[] {adapterClass.getName()}); |
| // make sure this overlay has higher ranking than other adapter factories |
| // normally we should use Integer.MAX_VALUE for this - but due to SLING-7194 prefers lowest-ranking services |
| // first |
| props.put(Constants.SERVICE_RANKING, Integer.MIN_VALUE); |
| registerService(AdapterFactory.class, adapterFactory, props); |
| } |
| } |