blob: f98facfe208cd0614c617320bf60dee5c40470ff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.testing.mock.sling;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.util.Dictionary;
import java.util.Hashtable;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
import org.apache.sling.jcr.api.SlingRepository;
import org.apache.sling.jcr.resource.internal.helper.jcr.JcrResourceProvider;
import org.apache.sling.resourceresolver.impl.ResourceAccessSecurityTracker;
import org.apache.sling.resourceresolver.impl.ResourceResolverFactoryActivator;
import org.apache.sling.serviceusermapping.ServiceUserMapper;
import org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl;
import org.apache.sling.testing.mock.osgi.MockEventAdmin;
import org.apache.sling.testing.mock.osgi.MockOsgi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Initializes Sling Resource Resolver factories with JCR-resource mapping.
*/
class ResourceResolverFactoryInitializer {
private static final Logger log = LoggerFactory.getLogger(ResourceResolverFactoryInitializer.class);
private ResourceResolverFactoryInitializer() {
// static methods only
}
/**
* Setup resource resolver factory.
* @param slingRepository Sling repository. If null resource resolver factory is setup without any resource provider.
* @param bundleContext Bundle context
*/
@SuppressWarnings("null")
public static @NotNull ResourceResolverFactory setUp(@Nullable SlingRepository slingRepository,
@NotNull BundleContext bundleContext, @NotNull NodeTypeMode nodeTypeMode) {
if (slingRepository != null) {
// register sling repository as OSGi service
registerServiceIfNotPresent(bundleContext, SlingRepository.class, slingRepository);
// register JCR node types found in classpath
registerJcrNodeTypes(slingRepository, nodeTypeMode);
// initialize JCR resource provider
ensureJcrResourceProviderDependencies(bundleContext);
initializeJcrResourceProvider(bundleContext);
}
// initialize resource resolver factory activator
ensureResourceResolverFactoryActivatorDependencies(bundleContext);
initializeResourceResolverFactoryActivator(bundleContext);
ServiceReference<ResourceResolverFactory> factoryRef = bundleContext.getServiceReference(ResourceResolverFactory.class);
if (factoryRef == null) {
throw new IllegalStateException("Unable to get ResourceResolverFactory.");
}
return bundleContext.getService(factoryRef);
}
/**
* Ensure dependencies for JcrResourceProvider are present.
* @param bundleContext Bundle context
*/
@SuppressWarnings("unchecked")
private static void ensureJcrResourceProviderDependencies(@NotNull BundleContext bundleContext) {
if (bundleContext.getServiceReference(DynamicClassLoaderManager.class) == null) {
bundleContext.registerService(DynamicClassLoaderManager.class, new MockDynamicClassLoaderManager(), null);
}
try {
Class pathMapperClass = Class.forName("org.apache.sling.jcr.resource.internal.helper.jcr.PathMapper");
Object pathMapper = pathMapperClass.newInstance();
// eliminate logger in class to suppress deprecation warnings
try {
Field pathMapperLoggerField = pathMapperClass.getDeclaredField("log");
pathMapperLoggerField.setAccessible(true);
pathMapperLoggerField.set(pathMapper, Proxy.newProxyInstance(Logger.class.getClassLoader(),
new Class[] { Logger.class },
(proxy, method, methodArgs) -> { return null; }));
} catch (Exception ex) {
// ignore
}
registerServiceIfNotPresent(bundleContext, pathMapperClass, pathMapper);
}
catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
// ignore - service was removed in org.apache.sling.jcr.resource 3.0.0
}
}
/**
* Initialize JCR resource provider.
* @param bundleContext Bundle context
*/
private static void initializeJcrResourceProvider(@NotNull BundleContext bundleContext) {
Dictionary<String, Object> config = new Hashtable<>();
MockOsgi.registerInjectActivateService(JcrResourceProvider.class, bundleContext, config);
}
/**
* Ensure dependencies for ResourceResolverFactoryActivator are present.
* @param bundleContext Bundle context
*/
private static void ensureResourceResolverFactoryActivatorDependencies(@NotNull BundleContext bundleContext) {
Dictionary<String, Object> config = new Hashtable<>();
config.put("user.mapping", bundleContext.getBundle().getSymbolicName() + "=[admin]");
registerServiceIfNotPresent(bundleContext, ServiceUserMapper.class, ServiceUserMapperImpl.class, config);
registerServiceIfNotPresent(bundleContext, ResourceAccessSecurityTracker.class, ResourceAccessSecurityTracker.class);
registerServiceIfNotPresent(bundleContext, EventAdmin.class, MockEventAdmin.class);
// dependency required since resourceresolver 1.7.0
registerServiceIfNotPresentByName(bundleContext, "org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProvider",
"org.apache.sling.resourceresolver.impl.mapping.StringInterpolationProviderImpl");
}
/**
* Initialize resource resolver factory activator.
* @param bundleContext Bundle context
*/
private static void initializeResourceResolverFactoryActivator(@NotNull BundleContext bundleContext) {
Dictionary<String, Object> config = new Hashtable<>();
// do not required a specific resource provider (otherwise "NONE" will not work)
config.put("resource.resolver.required.providers", "");
config.put("resource.resolver.required.providernames", "");
MockOsgi.registerInjectActivateService(ResourceResolverFactoryActivator.class, bundleContext, config);
}
@SuppressWarnings({ "unchecked", "null" })
private static void registerServiceIfNotPresentByName(@NotNull BundleContext bundleContext,
@NotNull String interfaceClassName, @NotNull String implClassName) {
try {
Class<?> interfaceClass = Class.forName(interfaceClassName);
Class<?> implClass = Class.forName(implClassName);
registerServiceIfNotPresent(bundleContext, (Class)interfaceClass, implClass.newInstance());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
// ignore - probably not the latest sling models impl version
log.debug("registerServiceIfNotPresentByName: Skip registering {} ({}), bundleContext={}", implClassName, interfaceClassName, bundleContext);
}
}
/**
* Registers a service if the service class is found in classpath,
* and if no service with this class is already registered.
* @param className Service class name
* @param serviceClass Service class
* @param instance Service instance
*/
private static <T> void registerServiceIfNotPresent(@NotNull BundleContext bundleContext, @NotNull Class<T> serviceClass,
@NotNull T instance) {
registerServiceIfNotPresent(bundleContext, serviceClass, instance, new Hashtable<>());
}
/**
* Registers a service if the service class is found in classpath,
* and if no service with this class is already registered.
* @param className Service class name
* @param serviceClass Service class
* @param instance Service instance
* @param config OSGi config
*/
private static <T> void registerServiceIfNotPresent(@NotNull BundleContext bundleContext, @NotNull Class<T> serviceClass,
@NotNull T instance, Dictionary<String, Object> config) {
if (bundleContext.getServiceReference(serviceClass.getName()) == null) {
MockOsgi.registerInjectActivateService(instance, bundleContext, config);
}
else if (log.isDebugEnabled()) {
log.debug("registerServiceIfNotPresent: Skip registering {} ({}) because already present, bundleContext={}", instance.getClass(), serviceClass, bundleContext);
}
}
/**
* Registers a service if the service class is found in classpath,
* and if no service with this class is already registered.
* @param className Service class name
* @param serviceClass Service class
* @param implClass Implementation class
*/
private static <T> void registerServiceIfNotPresent(@NotNull BundleContext bundleContext, @NotNull Class<T> serviceClass,
@NotNull Class<?> implClass) {
registerServiceIfNotPresent(bundleContext, serviceClass, implClass, new Hashtable<>());
}
/**
* Registers a service if the service class is found in classpath,
* and if no service with this class is already registered.
* @param className Service class name
* @param serviceClass Service class
* @param implClass Implementation class
* @param config OSGi config
*/
private static <T> void registerServiceIfNotPresent(@NotNull BundleContext bundleContext, @NotNull Class<T> serviceClass,
@NotNull Class<?> implClass, Dictionary<String, Object> config) {
if (bundleContext.getServiceReference(serviceClass.getName()) == null) {
MockOsgi.registerInjectActivateService(implClass, bundleContext, config);
}
}
/**
* Registers all JCR node types found in classpath.
* @param slingRepository Sling repository
*/
@SuppressWarnings("deprecation")
private static void registerJcrNodeTypes(final SlingRepository slingRepository,
final NodeTypeMode nodeTypeMode) {
Session session = null;
try {
session = slingRepository.loginAdministrative(null);
NodeTypeDefinitionScanner.get().register(session, nodeTypeMode);
}
catch (RepositoryException ex) {
throw new RuntimeException("Error registering JCR nodetypes: " + ex.getMessage(), ex);
}
finally {
if (session != null) {
session.logout();
}
}
}
}