blob: 62b2f89ee08d34c6ec980641d80a61a7119d9d8f [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.osgi;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import org.apache.sling.testing.mock.osgi.OsgiMetadataUtil.OsgiMetadata;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.log.LogService;
import static org.apache.sling.testing.mock.osgi.MapMergeUtil.propertiesMergeWithOsgiMetadata;
import static org.apache.sling.testing.mock.osgi.MapUtil.toDictionary;
import static org.apache.sling.testing.mock.osgi.MapUtil.toMap;
/**
* Factory for mock OSGi objects.
*/
public final class MockOsgi {
private MockOsgi() {
// static methods only
}
/**
* @return Mocked {@link BundleContext} instance
*/
public static @NotNull BundleContext newBundleContext() {
return new MockBundleContext();
}
/**
* Simulates a bundle event on the given bundle context (that is forwarded
* to registered bundle listeners).
* @param bundleContext Bundle context
* @param bundleEvent Bundle event
*/
public static void sendBundleEvent(@NotNull BundleContext bundleContext, @NotNull BundleEvent bundleEvent) {
((MockBundleContext) bundleContext).sendBundleEvent(bundleEvent);
}
/**
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext() {
return componentContext().build();
}
/**
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(@Nullable Dictionary<String, Object> properties) {
return componentContext().properties(properties).build();
}
/**
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(@Nullable Map<String, Object> properties) {
return componentContext().properties(properties).build();
}
/**
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(@NotNull Object @NotNull ... properties) {
return componentContext().properties(properties).build();
}
/**
* @param bundleContext Bundle context
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(
@NotNull BundleContext bundleContext, @Nullable Dictionary<String, Object> properties) {
return componentContext()
.bundleContext(bundleContext)
.properties(properties)
.build();
}
/**
* @param bundleContext Bundle context
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(
@NotNull BundleContext bundleContext, @Nullable Map<String, Object> properties) {
return componentContext()
.bundleContext(bundleContext)
.properties(properties)
.build();
}
/**
* @param bundleContext Bundle context
* @param properties Properties
* @return Mocked {@link ComponentContext} instance
*/
public static @NotNull ComponentContext newComponentContext(
@NotNull BundleContext bundleContext, @NotNull Object @NotNull ... properties) {
return componentContext()
.bundleContext(bundleContext)
.properties(properties)
.build();
}
/**
* @return {@link ComponentContextBuilder} to build a mocked {@link ComponentContext}
*/
public static @NotNull ComponentContextBuilder componentContext() {
return new ComponentContextBuilder();
}
/**
* @param loggerContext Context class for logging
* @return Mocked {@link LogService} instance
*/
public static @NotNull LogService newLogService(@NotNull final Class<?> loggerContext) {
return new MockLogService(loggerContext);
}
/**
* Simulates OSGi DS dependency injection. Injects direct references and multiple references.
* If a some references could not be injected no error is thrown.
* @param target Service instance
* @param bundleContext Bundle context from which services are fetched to inject.
* @return true if all dependencies could be injected, false if the service has no dependencies.
*/
public static boolean injectServices(@NotNull Object target, @NotNull BundleContext bundleContext) {
return MockOsgi.injectServices(target, bundleContext, (Map<String, Object>) null);
}
/**
* Simulates OSGi DS dependency injection. Injects direct references and multiple references.
* If a some references could not be injected no error is thrown.
* @param target Service instance
* @param bundleContext Bundle context from which services are fetched to inject.
* @param properties Service properties (used to resolve dynamic reference properties)
* @return true if all dependencies could be injected, false if the service has no dependencies.
*/
public static boolean injectServices(
@NotNull Object target, @NotNull BundleContext bundleContext, @Nullable Map<String, Object> properties) {
return OsgiServiceUtil.injectServices(target, bundleContext, properties);
}
/**
* Simulates OSGi DS dependency injection and activation. Injects direct references and multiple references.
* If a some references could not be injected no error is thrown.
* This method instantiates the service instance and also supports constructor injection.
* @param targetClass Component/service class
* @param bundleContext Bundle context from which services are fetched to inject.
* @param <T> Target class type
* @return Component/service instances with injected services
*/
public static @NotNull <T> T activateInjectServices(
@NotNull Class<T> targetClass, @NotNull BundleContext bundleContext) {
return MockOsgi.activateInjectServices(targetClass, bundleContext, (Map<String, Object>) null);
}
/**
* Simulates OSGi DS dependency injection and activation. Injects direct references and multiple references.
* If a some references could not be injected no error is thrown.
* This method instantiates the service instance and also supports constructor injection.
* @param targetClass Component/service class
* @param bundleContext Bundle context from which services are fetched to inject.
* @param properties Service properties (used to resolve dynamic reference properties)
* @param <T> Target class type
* @return Component/service instances with injected services
*/
public static @NotNull <T> T activateInjectServices(
@NotNull Class<T> targetClass,
@NotNull BundleContext bundleContext,
@Nullable Map<String, Object> properties) {
Map<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(targetClass, getConfigAdmin(bundleContext), properties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
return OsgiServiceUtil.activateInjectServices(targetClass, (MockComponentContext) componentContext);
}
/**
* Simulates OSGi DS dependency injection and activation. Injects direct references and multiple references.
* If a some references could not be injected no error is thrown.
* This method instantiates the service instance and also supports constructor injection.
* @param targetClass Component/service class
* @param bundleContext Bundle context from which services are fetched to inject.
* @param properties Service properties (used to resolve dynamic reference properties)
* @param <T> Target class type
* @return Component/service instances with injected services
*/
public static @NotNull <T> T activateInjectServices(
@NotNull Class<T> targetClass,
@NotNull BundleContext bundleContext,
@NotNull Object @NotNull ... properties) {
return activateInjectServices(targetClass, bundleContext, MapUtil.toMap(properties));
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS Component type
* @param component a DS component instance
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services
*/
public static final @NotNull <T> void registerInjectActivateService(
@NotNull final T component, @NotNull BundleContext bundleContext) {
registerInjectActivateService(component, bundleContext, (Map<String, Object>) null);
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS Component type
* @param component a DS component instance
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services
* @param properties component properties (optional)
*/
public static final @NotNull <T> void registerInjectActivateService(
@NotNull final T component,
@NotNull BundleContext bundleContext,
@Nullable final Map<String, Object> properties) {
Map<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(component.getClass(), getConfigAdmin(bundleContext), properties);
MockOsgi.injectServices(component, bundleContext, mergedProperties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
OsgiServiceUtil.activateDeactivate(component, (MockComponentContext) componentContext, true);
registerDSComponent(component, bundleContext, mergedProperties);
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS Component type
* @param component a DS component instance
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services.
* @param properties component properties (optional)
*/
public static final @NotNull <T> void registerInjectActivateService(
@NotNull final T component,
@NotNull BundleContext bundleContext,
@NotNull final Object @NotNull ... properties) {
registerInjectActivateService(component, bundleContext, MapUtil.toMap(properties));
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS component type
* @param dsComponentClass DS component class
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services
* @return Registered component instance
*/
public static final @NotNull <T> T registerInjectActivateService(
@NotNull final Class<T> dsComponentClass, @NotNull BundleContext bundleContext) {
return registerInjectActivateService(dsComponentClass, bundleContext, (Map<String, Object>) null);
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS component type
* @param dsComponentClass DS component class
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services
* @param properties component properties (optional)
* @return Registered component instance
*/
public static final @NotNull <T> T registerInjectActivateService(
@NotNull Class<T> dsComponentClass,
@NotNull BundleContext bundleContext,
@Nullable final Map<String, Object> properties) {
Map<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(dsComponentClass, getConfigAdmin(bundleContext), properties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
T component = OsgiServiceUtil.activateInjectServices(dsComponentClass, (MockComponentContext) componentContext);
registerDSComponent(component, bundleContext, mergedProperties);
return component;
}
private static <T> void registerDSComponent(
@NotNull T component, @NotNull BundleContext bundleContext, Map<String, Object> mergedProperties) {
OsgiMetadata metadata = Objects.requireNonNull(
OsgiMetadataUtil.getMetadata(component.getClass()), "No metadata found for " + component.getClass());
// convert component properties to service properties
// (http://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-service.properties)
Dictionary<String, Object> serviceProperties = mergedProperties.entrySet().stream()
.filter(e -> e.getKey() != null && !e.getKey().startsWith("."))
.collect(new DictionaryCollector<>(Entry::getKey, Entry::getValue));
// we also register DS Components that aren't services in order for bind/unbind to work - they are registered
// with no service interfaces
bundleContext.registerService(
metadata.getServiceInterfaces().toArray(new String[0]), component, serviceProperties);
}
/**
* Injects dependencies, activates and registers a DS component in the mocked OSGi environment.
* @param <T> DS component type
* @param dsComponentClass DS component class
* @param bundleContext Bundle context from which services are fetched to inject and which is used for registering new services
* @param properties component properties (optional)
* @return Registered component instance
*/
public static final @NotNull <T> T registerInjectActivateService(
@NotNull Class<T> dsComponentClass,
@NotNull BundleContext bundleContext,
@NotNull final Object @NotNull ... properties) {
return registerInjectActivateService(dsComponentClass, bundleContext, MapUtil.toMap(properties));
}
/**
* Simulates activation of a DS component instance. Invokes the @Activate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @return true if activation method was called. False if no activate method is defined.
*/
public static boolean activate(@NotNull Object target, @NotNull BundleContext bundleContext) {
return MockOsgi.activate(target, bundleContext, (Dictionary<String, Object>) null);
}
/**
* Simulates activation of a DS component instance. Invokes the @Activate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if activation method was called. False if no activate method is defined.
*/
public static boolean activate(
@NotNull Object target,
@NotNull BundleContext bundleContext,
@Nullable Dictionary<String, Object> properties) {
Dictionary<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(target.getClass(), getConfigAdmin(bundleContext), properties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
return OsgiServiceUtil.activateDeactivate(target, (MockComponentContext) componentContext, true);
}
/**
* Simulates activation of a DS component instance. Invokes the @Activate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if activation method was called. False if no activate method is defined.
*/
public static boolean activate(
@NotNull Object target, @NotNull BundleContext bundleContext, @Nullable Map<String, Object> properties) {
return activate(target, bundleContext, toDictionary(properties));
}
/**
* Simulates activation of a DS component instance. Invokes the @Activate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if activation method was called. False if no activate method is defined.
*/
public static boolean activate(
@NotNull Object target, @NotNull BundleContext bundleContext, @NotNull Object @NotNull ... properties) {
return activate(target, bundleContext, toDictionary(properties));
}
/**
* Simulates deactivation of a DS component instance. Invokes the @Deactivate annotated method.
* @param target Service instance.
* @param bundleContext Bundle context.
* @return true if deactivation method was called. False if no deactivate method is defined.
*/
public static boolean deactivate(@NotNull Object target, @NotNull BundleContext bundleContext) {
return MockOsgi.deactivate(target, bundleContext, (Dictionary<String, Object>) null);
}
/**
* Simulates deactivation of a DS component instance. Invokes the @Deactivate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if deactivation method was called. False if no deactivate method is defined.
*/
public static boolean deactivate(
@NotNull Object target,
@NotNull BundleContext bundleContext,
@Nullable Dictionary<String, Object> properties) {
Dictionary<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(target.getClass(), getConfigAdmin(bundleContext), properties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
return OsgiServiceUtil.activateDeactivate(target, (MockComponentContext) componentContext, false);
}
/**
* Simulates deactivation of a DS component instance. Invokes the @Deactivate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if deactivation method was called. False if no deactivate method is defined.
*/
public static boolean deactivate(
@NotNull Object target, @NotNull BundleContext bundleContext, @Nullable Map<String, Object> properties) {
return deactivate(target, bundleContext, toDictionary(properties));
}
/**
* Simulates deactivation of a DS component instance. Invokes the @Deactivate annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if deactivation method was called. False if no deactivate method is defined.
*/
public static boolean deactivate(
@NotNull Object target, @NotNull BundleContext bundleContext, @NotNull Object @NotNull ... properties) {
return deactivate(target, bundleContext, toDictionary(properties));
}
/**
* Simulates configuration modification of a DS component instance. Invokes the @Modified annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if modified method was called. False if no modified method is defined.
*/
public static boolean modified(
@NotNull Object target,
@NotNull BundleContext bundleContext,
@Nullable Dictionary<String, Object> properties) {
return modified(target, bundleContext, toMap(properties));
}
/**
* Simulates configuration modification of a DS component instance. Invokes the @Modified annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if modified method was called. False if no modified method is defined.
*/
public static boolean modified(
@NotNull Object target, @NotNull BundleContext bundleContext, @Nullable Map<String, Object> properties) {
Map<String, Object> mergedProperties =
propertiesMergeWithOsgiMetadata(target.getClass(), getConfigAdmin(bundleContext), properties);
ComponentContext componentContext = newComponentContext(bundleContext, mergedProperties);
return OsgiServiceUtil.modified(target, (MockComponentContext) componentContext, mergedProperties);
}
/**
* Simulates configuration modification of a DS component instance. Invokes the @Modified annotated method.
* @param target DS component instance
* @param bundleContext Bundle context
* @param properties Properties
* @return true if modified method was called. False if no modified method is defined.
*/
public static boolean modified(
@NotNull Object target, @NotNull BundleContext bundleContext, @NotNull Object @NotNull ... properties) {
return modified(target, bundleContext, toDictionary(properties));
}
/**
* Set configuration via ConfigurationAdmin service in bundle context for component with given pid.
* @param bundleContext Bundle context
* @param pid PID
* @param properties Configuration properties
*/
public static void setConfigForPid(
@NotNull BundleContext bundleContext, @NotNull String pid, @Nullable Map<String, Object> properties) {
setConfigForPid(bundleContext, pid, toDictionary(properties));
}
/**
* Set configuration via ConfigurationAdmin service in bundle context for component with given pid.
* @param bundleContext Bundle context
* @param pid PID
* @param properties Configuration properties
*/
public static void setConfigForPid(
@NotNull BundleContext bundleContext, @NotNull String pid, @NotNull Object @NotNull ... properties) {
setConfigForPid(bundleContext, pid, toDictionary(properties));
}
private static void setConfigForPid(
@NotNull BundleContext bundleContext,
@NotNull String pid,
@Nullable Dictionary<String, Object> properties) {
ConfigurationAdmin configAdmin = getConfigAdmin(bundleContext);
if (configAdmin == null) {
throw new RuntimeException("ConfigurationAdmin service is not registered in bundle context.");
}
try {
Configuration config = configAdmin.getConfiguration(pid);
config.update(properties);
} catch (IOException ex) {
throw new RuntimeException("Unable to update configuration for pid '" + pid + "'.", ex);
}
}
/**
* Deactivates all bundles registered in the mocked bundle context.
* @param bundleContext Bundle context
*/
public static void shutdown(@NotNull BundleContext bundleContext) {
((MockBundleContext) bundleContext).shutdown();
}
/**
* Gets configuration admin.
* @param bundleContext Bundle context
* @return Configuration admin or null if not registered.
*/
private static @Nullable ConfigurationAdmin getConfigAdmin(@NotNull BundleContext bundleContext) {
ServiceReference<?> ref = bundleContext.getServiceReference(ConfigurationAdmin.class.getName());
if (ref != null) {
return (ConfigurationAdmin) bundleContext.getService(ref);
}
return null;
}
}